Menu

Change language

GitHub repo

Bento — Asymmetric Grid

blockmarketing

Asymmetric bento where one tall tile anchors the composition and three shorter tiles carry a headline metric, a budget breakdown, and a customer quote.

Installation

$npx uipkge-ng@latest add bento-asymmetric-grid
Manifest:https://uipkge.dev/r/angular/bento-asymmetric-grid.jsonInstalls to:src/app/components/blocks/bento-asymmetric-grid/

Variants

Loading interactive previews…

Files installed (2)

  • src/app/components/blocks/bento-asymmetric-grid/bento-asymmetric-grid.component.ts5.8 kB
    // Five-tile asymmetric bento anchored by one tall tile, the rest carrying a metric,
    // a budget breakdown, and a customer quote. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import { ArrowUpRight, LucideAngularModule, Quote } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    import { UiProgressComponent } from '@/ui/progress/progress.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    const capabilities = ['Versioned definitions', 'Row-level access', 'Query budgets', 'Drift alerting', 'Audit export']
    
    const budgets = [
      { team: 'Finance', used: 62 },
      { team: 'RevOps', used: 38 },
      { team: 'Product', used: 81 },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-bento-asymmetric-grid, [ui-bento-asymmetric-grid]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiProgressComponent,
        UiSeparatorComponent,
      ],
      template: `
        <section data-slot="bento-asymmetric-grid" [class]="rootClass">
          <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28">
            <div class="max-w-2xl">
              <span ui-badge variant="secondary">The shape of it</span>
              <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">One layer, five jobs</h2>
            </div>
    
            <!-- Spans are explicit rather than auto-flowed, so rearranging the
                 composition is a class change and not a reshuffle of the markup. -->
            <div class="mt-10 grid gap-3 sm:grid-cols-3 lg:grid-rows-2">
              <div ui-card class="sm:row-span-2">
                <div ui-card-content class="flex h-full flex-col p-6">
                  <p class="text-muted-foreground font-mono text-xs tracking-[0.14em] uppercase">Everything included</p>
                  <h3 class="mt-3 text-xl font-semibold tracking-tight text-balance">No feature gates between plans</h3>
                  <p class="text-muted-foreground mt-3 text-sm leading-relaxed">
                    Limits differ; capabilities do not. The list below is on every plan from the first day of a trial.
                  </p>
    
                  <ui-separator class="my-5" />
    
                  <ul class="space-y-2.5">
                    @for (capability of capabilities; track capability) {
                      <li class="flex items-center gap-2.5 text-sm">
                        <span class="bg-primary size-1.5 shrink-0 rounded-full" aria-hidden="true"></span>
                        <span>{{ capability }}</span>
                      </li>
                    }
                  </ul>
    
                  <button ui-button variant="outline" size="sm" class="mt-auto self-start pt-0 [&]:mt-6">
                    Compare plans
                  </button>
                </div>
              </div>
    
              <div ui-card class="sm:col-span-2">
                <div ui-card-content class="flex h-full flex-wrap items-center justify-between gap-4 p-6">
                  <div>
                    <p class="font-display text-4xl font-bold tracking-tight">4.2 days</p>
                    <p class="text-muted-foreground mt-1 text-sm">median time to close, down from 9.4</p>
                  </div>
                  <span ui-badge variant="secondary" class="shrink-0">240 rollouts</span>
                </div>
              </div>
    
              <div ui-card>
                <div ui-card-content class="p-6">
                  <p class="text-muted-foreground text-xs tracking-wide uppercase">Query budgets</p>
                  <div class="mt-4 space-y-3">
                    @for (budget of budgetRows; track budget.team) {
                      <div>
                        <div class="flex items-baseline justify-between gap-3">
                          <span class="text-xs font-medium">{{ budget.team }}</span>
                          <span class="text-muted-foreground font-mono text-xs">{{ budget.used }}%</span>
                        </div>
                        <ui-progress
                          [value]="budget.used"
                          class="mt-1.5 h-1.5"
                          [attr.aria-label]="budget.team + ' budget used'"
                        />
                      </div>
                    }
                  </div>
                </div>
              </div>
    
              <div ui-card class="group">
                <div ui-card-content class="flex h-full flex-col p-6">
                  <lucide-icon [img]="Quote" class="text-muted-foreground/60 size-4" aria-hidden="true" />
                  <blockquote class="mt-3 text-sm leading-relaxed">
                    “We stopped arguing about whose number was right.”
                  </blockquote>
                  <p class="text-muted-foreground mt-auto pt-4 text-xs">Erin Walsh · VP Finance, Northwind</p>
                  <button ui-button variant="ghost" size="sm" class="mt-2 h-auto justify-start px-0 text-xs">
                    Read the story
                    <lucide-icon
                      [img]="ArrowUpRight"
                      class="ml-1 size-3 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
                      aria-hidden="true"
                    />
                  </button>
                </div>
              </div>
            </div>
          </div>
        </section>
      `,
    })
    export class UiBentoAsymmetricGridComponent {
      protected readonly ArrowUpRight = ArrowUpRight
      protected readonly Quote = Quote
    
      protected readonly capabilities = capabilities
      protected readonly budgetRows = budgets
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    }
    
  • src/app/components/blocks/bento-asymmetric-grid/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/bento-asymmetric-grid.json