Features — Numbered Grid
blockmarketingSix-cell feature grid drawn with one-pixel dividers instead of cards, each cell led by a monospace ordinal, an icon, a title, and two lines of supporting copy.
Installation
$pnpm dlx uipkge-ng@latest add features-numbered-grid$npx uipkge-ng@latest add features-numbered-grid$yarn dlx uipkge-ng@latest add features-numbered-grid$bunx uipkge-ng@latest add features-numbered-gridManifest:
https://uipkge.dev/r/angular/features-numbered-grid.jsonInstalls to:src/app/components/blocks/features-numbered-grid/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
NumberedFeatureinterface NumberedFeature {
icon: LucideIconData
title: string
body: string
}npm dependencies
Files installed (2)
src/app/components/blocks/features-numbered-grid/features-numbered-grid.component.ts4.1 kB
// Six-cell feature grid drawn with one-pixel dividers instead of cards, each cell led // by a monospace ordinal, an icon, a title, and two lines of supporting copy. // Port of the React block 1:1 — ordinals derive from index, so order is the source. import { ChangeDetectionStrategy, Component, Input } from '@angular/core' import { ArrowRight, Boxes, Clock, GitBranch, Lock, LucideAngularModule, Receipt, Signal, type LucideIconData, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' export interface NumberedFeature { icon: LucideIconData title: string body: string } const defaultFeatures: NumberedFeature[] = [ { icon: GitBranch, title: 'Definitions under review', body: 'Metrics live in the repo. Changing one opens a pull request with a diff, an owner, and a revert path.', }, { icon: Lock, title: 'Access at query time', body: 'Scope resolves from your identity provider on every query, so a shared link cannot outrun permissions.', }, { icon: Clock, title: 'Freshness you choose', body: 'Materialise the hot aggregates on a schedule; let everything else fall through to the warehouse.', }, { icon: Receipt, title: 'Cost ceilings per team', body: 'Query budgets are enforced where the query runs, not reconciled from an invoice a month later.', }, { icon: Signal, title: 'Drift alerting', body: 'Threshold and anomaly alerts fire against certified metrics, so nobody debates the trigger.', }, { icon: Boxes, title: 'Embedding without forks', body: 'Signed, scoped URLs render the same dashboards inside your product with the viewer’s own permissions.', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-features-numbered-grid, [ui-features-numbered-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], template: ` <section data-slot="features-numbered-grid" [class]="rootClass"> <div class="mx-auto max-w-6xl px-6 py-20 lg:py-28"> <div class="flex flex-wrap items-end justify-between gap-6"> <div class="max-w-2xl"> <span ui-badge variant="secondary">How it holds up</span> <h2 class="mt-4 text-3xl font-semibold tracking-tight sm:text-4xl">Six decisions, in order of impact</h2> </div> <button ui-button variant="outline"> Read the architecture notes <lucide-icon [img]="ArrowRight" class="ml-2 size-4" aria-hidden="true" /> </button> </div> <!-- gap-px over a border-coloured ground draws hairline rules between cells, so the grid reads as one table rather than six floating cards. --> <div class="bg-border border-border mt-10 grid gap-px overflow-hidden rounded-xl border sm:grid-cols-2 lg:grid-cols-3" > @for (feature of features; track feature.title; let i = $index) { <article class="bg-background p-6"> <div class="flex items-center gap-3"> <span class="text-muted-foreground/70 font-mono text-xs">{{ ordinal(i) }}</span> <lucide-icon [img]="feature.icon" class="text-muted-foreground size-4" aria-hidden="true" /> </div> <h3 class="mt-4 text-base font-semibold">{{ feature.title }}</h3> <p class="text-muted-foreground mt-2 text-sm leading-relaxed">{{ feature.body }}</p> </article> } </div> </div> </section> `, }) export class UiFeaturesNumberedGridComponent { protected readonly ArrowRight = ArrowRight @Input() features: NumberedFeature[] = defaultFeatures.map((f) => ({ ...f })) @Input('class') className?: string get rootClass(): string { return cn('bg-background', this.className) } ordinal(index: number): string { return String(index + 1).padStart(2, '0') } }src/app/components/blocks/features-numbered-grid/index.ts0.1 kB
export { UiFeaturesNumberedGridComponent, type NumberedFeature } from './features-numbered-grid.component'
Raw manifest:https://uipkge.dev/r/angular/features-numbered-grid.json