Menu

Change language

GitHub repo

Features — Full-Bleed Rows

blockmarketing

Alternating full-bleed feature bands on contrasting surfaces, each pairing a headline and supporting detail with an edge-to-edge visual that runs past the container.

Installation

$npx uipkge-ng@latest add feature-full-bleed-rows
Manifest:https://uipkge.dev/r/angular/feature-full-bleed-rows.jsonInstalls to:src/app/components/blocks/feature-full-bleed-rows/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

FullBleedRowDetail
interface FullBleedRowDetail {
  label: string
  value: string
}
FullBleedRow
interface FullBleedRow {
  eyebrow: string
  title: string
  body: string
  detail: FullBleedRowDetail[]
  reverse: boolean
}

Files installed (2)

  • src/app/components/blocks/feature-full-bleed-rows/feature-full-bleed-rows.component.ts5.2 kB
    // Alternating full-bleed feature bands on contrasting surfaces: each row pairs a headline and
    // supporting detail with an edge-to-edge card that runs past the container edge. Bands alternate
    // surface as well as side, so the eye has two cues that a new section started. Port of the React
    // block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import { ArrowRight, LucideAngularModule } 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 { UiSeparatorComponent } from '@/ui/separator/separator.component'
    
    export interface FullBleedRowDetail {
      label: string
      value: string
    }
    
    export interface FullBleedRow {
      eyebrow: string
      title: string
      body: string
      detail: FullBleedRowDetail[]
      reverse: boolean
    }
    
    export const FULL_BLEED_ROWS: FullBleedRow[] = [
      {
        eyebrow: 'Modelling',
        title: 'The definition is the product',
        body: 'Dashboards are an output. What your company argues about is the formula behind the number, so that is the thing that gets an owner, a review, and a history.',
        detail: [
          { label: 'Authored in', value: 'Your repository' },
          { label: 'Reviewed by', value: 'The named owner' },
          { label: 'Reverted in', value: 'One click' },
        ],
        reverse: false,
      },
      {
        eyebrow: 'Governance',
        title: 'Permissions that survive a share',
        body: 'Scope resolves per query from your identity provider, so forwarding a dashboard link never widens what the recipient can see. Every evaluation is logged.',
        detail: [
          { label: 'Resolved at', value: 'Query time' },
          { label: 'Sourced from', value: 'SCIM groups' },
          { label: 'Retained for', value: '7 years' },
        ],
        reverse: true,
      },
      {
        eyebrow: 'Operations',
        title: 'Cost you can see before the invoice',
        body: 'Query budgets are enforced by the planner before execution, and each dashboard reports what it costs to open. Spend stops being a monthly surprise.',
        detail: [
          { label: 'Enforced at', value: 'Plan time' },
          { label: 'Scoped per', value: 'Team' },
          { label: 'Reported per', value: 'Dashboard' },
        ],
        reverse: false,
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-feature-full-bleed-rows, [ui-feature-full-bleed-rows]',
      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,
        UiSeparatorComponent,
      ],
      template: `
        <section data-slot="feature-full-bleed-rows" [class]="rootClass">
          <!-- Bands alternate surface as well as side, so the eye has two cues that a
               new section started rather than relying on whitespace alone. -->
          @for (row of rows; track row.title; let index = $index) {
            <div class="border-border border-b" [class]="index % 2 === 1 ? 'bg-muted/40' : 'bg-background'">
              <div class="mx-auto grid max-w-6xl items-center gap-10 px-6 py-16 lg:grid-cols-2 lg:gap-16 lg:py-20">
                <div [class]="row.reverse ? 'lg:order-2' : ''">
                  <span ui-badge variant="secondary">{{ row.eyebrow }}</span>
                  <h2 class="mt-4 text-2xl font-semibold tracking-tight text-balance sm:text-3xl">{{ row.title }}</h2>
                  <p class="text-muted-foreground mt-3 max-w-prose leading-relaxed">{{ row.body }}</p>
                  <button ui-button variant="link" class="mt-4 h-auto p-0">
                    Read the detail
                    <lucide-icon [img]="ArrowRight" class="ml-1.5 size-3.5" />
                  </button>
                </div>
    
                <div
                  ui-card
                  [class]="
                    row.reverse
                      ? 'lg:order-1 lg:-ml-[calc((100vw-100%)/2)] lg:rounded-l-none lg:border-l-0'
                      : 'lg:-mr-[calc((100vw-100%)/2)] lg:rounded-r-none lg:border-r-0'
                  "
                >
                  <div ui-card-content class="p-0">
                    <div class="border-border text-muted-foreground border-b px-4 py-2.5 font-mono text-xs">
                      {{ row.eyebrow.toLowerCase() }}
                    </div>
                    <dl>
                      @for (entry of row.detail; track entry.label; let entryIndex = $index) {
                        <div class="flex items-center justify-between gap-4 px-4 py-3">
                          <dt class="text-muted-foreground text-sm">{{ entry.label }}</dt>
                          <dd class="text-sm font-medium">{{ entry.value }}</dd>
                        </div>
                        @if (entryIndex < row.detail.length - 1) {
                          <ui-separator />
                        }
                      }
                    </dl>
                  </div>
                </div>
              </div>
            </div>
          }
        </section>
      `,
    })
    export class UiFeatureFullBleedRowsComponent {
      protected readonly ArrowRight = ArrowRight
      protected readonly rows = FULL_BLEED_ROWS
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background', this.className)
      }
    }
    
  • src/app/components/blocks/feature-full-bleed-rows/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/feature-full-bleed-rows.json