Seat Pricing Matrix
blockmarketingComprehensive enterprise pricing workbench with monthly/annual billing cycle toggle, currency selector (USD/EUR/GBP), team seat simulator slider, dynamic add-on calculators, and an expandable side-by-side feature comparison matrix.
Installation
$pnpm dlx uipkge-ng@latest add pricing-01$npx uipkge-ng@latest add pricing-01$yarn dlx uipkge-ng@latest add pricing-01$bunx uipkge-ng@latest add pricing-01Manifest:
https://uipkge.dev/r/angular/pricing-01.jsonInstalls to:src/app/components/blocks/pricing-01/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.
Pricing01FeatureRowinterface Pricing01FeatureRow {
name: string
starter: string
team: string
enterprise: string
}Pricing01ComparisonGroupinterface Pricing01ComparisonGroup {
category: string
features: Pricing01FeatureRow[]
}npm dependencies
Used by
Files installed (2)
src/app/components/blocks/pricing-01/pricing-01.component.ts27.9 kB
// Seat pricing matrix: monthly/annual billing toggle, currency selector (USD/EUR/GBP), // team seat simulator slider, dynamic add-on calculators, and an expandable side-by-side // feature comparison matrix. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { ArrowRight, Check, ChevronDown, ChevronUp, LucideAngularModule, ShieldCheck, Users, Zap } 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, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSliderComponent } from '@/ui/slider/slider.component' import { UiSwitchComponent } from '@/ui/switch/switch.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' import { UiToggleGroupComponent, UiToggleGroupItemComponent } from '@/ui/toggle-group/toggle-group.component' export type Pricing01BillingCycle = 'monthly' | 'yearly' export type Pricing01Currency = 'USD' | 'EUR' | 'GBP' const currencySymbols: Record<Pricing01Currency, string> = { USD: '$', EUR: '€', GBP: '£', } const currencyRates: Record<Pricing01Currency, number> = { USD: 1.0, EUR: 0.92, GBP: 0.79, } export interface Pricing01FeatureRow { name: string starter: string team: string enterprise: string } export interface Pricing01ComparisonGroup { category: string features: Pricing01FeatureRow[] } const comparisonMatrix: Pricing01ComparisonGroup[] = [ { category: 'Platform & Compute Core', features: [ { name: 'Active Team Members', starter: 'Up to 10', team: 'Unlimited', enterprise: 'Unlimited + Org Units' }, { name: 'Monthly API Requests', starter: '100,000 / mo', team: '2,500,000 / mo', enterprise: 'Custom Unlimited' }, { name: 'Workflow Automations', starter: '10 active', team: '100 active', enterprise: 'Unlimited real-time' }, { name: 'Data Retention History', starter: '30 days', team: '365 days', enterprise: '7 years immutable' }, ], }, { category: 'Security & Enterprise Governance', features: [ { name: 'SOC 2 Type II & ISO 27001', starter: 'Standard', team: 'Included', enterprise: 'Included + Auditor Portal', }, { name: 'SAML SSO & SCIM Provisioning', starter: '—', team: 'Google / Okta', enterprise: 'Custom IdP + SCIM v2' }, { name: 'Role-Based Access (RBAC)', starter: '3 predefined roles', team: 'Granular permissions', enterprise: 'Custom policy engine', }, { name: 'Immutable Audit Trail Logs', starter: '—', team: '90 days exportable', enterprise: 'Real-time SIEM streaming', }, ], }, { category: 'Support & Success SLAs', features: [ { name: 'Support Channel', starter: 'Community & Email', team: 'Priority Email + Chat', enterprise: 'Dedicated Private Slack', }, { name: 'First-Response SLA', starter: '24 business hours', team: '4 business hours', enterprise: '< 15 mins (24/7/365)', }, { name: 'Uptime SLA Guarantee', starter: '99.9%', team: '99.95%', enterprise: '99.99% financially backed' }, { name: 'Dedicated Solutions Architect', starter: '—', team: '—', enterprise: 'Assigned Principal Engineer' }, ], }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-pricing-01, [ui-pricing-01]', 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, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiSeparatorComponent, UiSliderComponent, UiSwitchComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, UiToggleGroupComponent, UiToggleGroupItemComponent, ], template: ` <section data-slot="pricing-01" [class]="rootClass"> <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <!-- Section Header --> <div class="mx-auto max-w-3xl space-y-4 text-center"> <div class="border-primary/20 bg-primary/5 text-primary inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium shadow-2xs" > <lucide-icon [img]="ShieldCheck" class="size-3.5" /> <span>Predictable Enterprise Pricing</span> </div> <h2 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl lg:text-5xl"> Scale effortlessly without seat tax surprises </h2> <p class="text-muted-foreground text-base sm:text-lg"> Zero setup fees, transparent volume discounts, and instant self-serve provisioning. Switch or cancel plans anytime. </p> <!-- Billing Cycle & Currency Switcher Toolbar --> <div class="mt-8 flex flex-wrap items-center justify-center gap-4 pt-2"> <!-- Monthly vs Yearly --> <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ui-toggle-group type="single" [value]="billingCycle()" (valueChange)="setBillingCycle($event)"> <button ui-toggle-group-item value="monthly" class="px-3.5 py-1.5 text-xs font-medium">Monthly</button> <button ui-toggle-group-item value="yearly" class="flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium" > <span>Annual</span> <span ui-badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 px-1.5 py-0 text-xs" > Save 20% </span> </button> </ui-toggle-group> </div> <!-- Currency Selector --> <div class="border-border bg-card inline-flex rounded-lg border p-1 shadow-2xs"> <ui-toggle-group type="single" [value]="currency()" (valueChange)="setCurrency($event)"> <button ui-toggle-group-item value="USD" class="px-2.5 py-1 font-mono text-xs font-medium"> USD ($) </button> <button ui-toggle-group-item value="EUR" class="px-2.5 py-1 font-mono text-xs font-medium"> EUR (€) </button> <button ui-toggle-group-item value="GBP" class="px-2.5 py-1 font-mono text-xs font-medium"> GBP (£) </button> </ui-toggle-group> </div> </div> <!-- Interactive Team Seat Simulator Bar --> <div class="border-border bg-card mx-auto mt-6 max-w-xl rounded-xl border p-4 shadow-2xs"> <div class="flex flex-wrap items-center justify-between gap-2 text-xs"> <div class="text-foreground flex items-center gap-2 font-medium"> <lucide-icon [img]="Users" class="text-primary size-4" /> <span>Simulate Team Size:</span> </div> <div class="flex items-center gap-2"> <span class="bg-muted text-foreground rounded px-2 py-0.5 font-mono text-xs font-semibold"> {{ currentSeats() }} {{ currentSeats() === 1 ? 'seat' : 'seats' }} </span> @if (billingCycle() === 'yearly' && annualSavingsTeam() > 0) { <span class="text-success text-xs font-medium"> (Saves ~{{ sym() }}{{ annualSavingsTeam().toLocaleString() }}/yr on Team plan) </span> } </div> </div> <div class="mt-3"> <ui-slider [value]="[currentSeats()]" [min]="1" [max]="50" [step]="1" (valueChange)="onSeatsChange($event)" class="cursor-pointer" /> </div> <div class="text-muted-foreground mt-2 flex justify-between font-mono text-xs"> <span>1 seat</span> <span>25 seats</span> <span>50+ seats</span> </div> </div> </div> <!-- Plan Cards Grid --> <div class="mt-12 grid grid-cols-1 gap-8 lg:grid-cols-3 lg:items-stretch"> <!-- Tier 1: Starter --> <div ui-card class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md" > <div> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <span ui-badge variant="outline" class="font-mono text-xs">Bootstrap & Indie</span> </div> <h3 ui-card-title class="text-foreground text-xl font-bold tracking-tight">Starter Studio</h3> <p ui-card-description class="text-muted-foreground text-xs"> For agile squads and early-stage product teams validating core workflows. </p> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {{ sym() }}{{ starterUnit() }} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-muted-foreground font-mono text-xs"> Est. {{ sym() }}{{ starterMonthlyTotal().toLocaleString() }}/mo for {{ currentSeats() }} {{ currentSeats() === 1 ? 'seat' : 'seats' }} </div> </div> </div> <div ui-card-content class="space-y-4 pt-2"> <div ui-separator></div> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Included features</p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Up to 10 team seats & unlimited guests</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>100,000 monthly API event calls</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>30-day continuous audit & revision history</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Standard Community & Email support (24h)</span> </li> <li class="text-muted-foreground flex items-start gap-2"> <lucide-icon [img]="Check" class="text-muted-foreground/50 mt-0.5 size-4 shrink-0" /> <span>Community Discord access & quickstarts</span> </li> </ul> </div> </div> </div> <div ui-card-footer class="border-border mt-auto border-t pt-4"> <button ui-button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold"> <span>Start Free 14-Day Trial</span> <lucide-icon [img]="ArrowRight" class="size-3.5" /> </button> </div> </div> <!-- Tier 2: Team Pro (Highlighted Flagship) --> <div ui-card class="border-primary bg-card ring-primary/20 relative flex flex-col justify-between shadow-lg ring-2 transition-colors duration-200 hover:shadow-xl lg:-translate-y-2" > <div class="absolute -top-3.5 left-1/2 -translate-x-1/2"> <span ui-badge class="bg-primary text-primary-foreground gap-1.5 px-3 py-0.5 text-xs font-semibold shadow-sm" > <lucide-icon [img]="Zap" class="size-3 fill-current" /> <span>Most Popular Choice</span> </span> </div> <div> <div ui-card-header class="pt-7 pb-4"> <div class="flex items-center justify-between"> <span ui-badge variant="secondary" class="bg-primary/10 text-primary border-primary/20 font-mono text-xs" > Scale & High-Growth </span> </div> <h3 ui-card-title class="text-foreground text-2xl font-bold tracking-tight">Team Scale</h3> <p ui-card-description class="text-muted-foreground text-xs"> For fast-scaling engineering and product organizations requiring deep automation. </p> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {{ sym() }}{{ teamUnit() }} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-primary font-mono text-xs font-medium"> Est. {{ sym() }}{{ teamMonthlyTotal().toLocaleString() }}/mo for {{ currentSeats() }} {{ currentSeats() === 1 ? 'seat' : 'seats' }} </div> </div> </div> <div ui-card-content class="space-y-4 pt-2"> <div ui-separator></div> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Starter, plus </p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span class="font-medium">Unlimited team seats & custom workspace roles</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>2.5M monthly API calls & priority queue</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Google & Okta SAML SSO integration</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>365-day immutable compliance logs</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Priority ticketing with 4h guaranteed SLA</span> </li> </ul> </div> <!-- Addons Selector for Team --> <div class="border-border bg-muted/30 space-y-2.5 rounded-lg border p-3"> <p class="text-foreground text-xs font-semibold">Optional Power Add-ons</p> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground"> 24/7 Dedicated SLA (+{{ sym() }}{{ slaAddonUnit() }}/mo) </span> <button ui-switch aria-label="Toggle dedicated SLA add-on" [checked]="addDedicatedSla()" (checkedChange)="addDedicatedSla.set($event)" ></button> </div> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground"> SOC 2 Vault Streaming (+{{ sym() }}{{ vaultAddonUnit() }}/mo) </span> <button ui-switch aria-label="Toggle audit vault add-on" [checked]="addAuditVault()" (checkedChange)="addAuditVault.set($event)" ></button> </div> </div> </div> </div> <div ui-card-footer class="border-border mt-auto border-t pt-4"> <button ui-button class="h-10 w-full gap-1.5 text-xs font-semibold shadow-sm"> <span>Deploy Team Workspace</span> <lucide-icon [img]="ArrowRight" class="size-3.5" /> </button> </div> </div> <!-- Tier 3: Enterprise Platform --> <div ui-card class="border-border bg-card hover:border-primary/40 relative flex flex-col justify-between shadow-xs transition-colors duration-200 hover:shadow-md" > <div> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <span ui-badge variant="outline" class="font-mono text-xs">Enterprise & Security</span> </div> <h3 ui-card-title class="text-foreground text-xl font-bold tracking-tight">Enterprise Suite</h3> <p ui-card-description class="text-muted-foreground text-xs"> Dedicated infrastructure, custom security controls, and bespoke compliance SLAs. </p> <div class="mt-6 space-y-1"> <div class="flex items-baseline gap-1.5"> <span class="text-foreground text-4xl font-bold tracking-tight"> {{ sym() }}{{ enterpriseUnit() }} </span> <span class="text-muted-foreground text-xs font-medium">/ seat / month</span> </div> <div class="text-muted-foreground font-mono text-xs"> Custom volume licensing available for 100+ seats </div> </div> </div> <div ui-card-content class="space-y-4 pt-2"> <div ui-separator></div> <div class="space-y-2.5"> <p class="text-muted-foreground text-xs font-semibold tracking-wider uppercase"> Everything in Team, plus </p> <ul class="text-foreground space-y-2.5 text-xs"> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Custom SCIM v2 user provisioning & directory sync</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Dedicated VPC peering & custom data residency</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>99.99% uptime SLA with financial penalty backing</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Assigned Principal Solutions Engineer & private Slack</span> </li> <li class="flex items-start gap-2"> <lucide-icon [img]="Check" class="text-success mt-0.5 size-4 shrink-0" /> <span>Custom Master Service Agreement (MSA) & DPA</span> </li> </ul> </div> </div> </div> <div ui-card-footer class="border-border mt-auto border-t pt-4"> <button ui-button variant="outline" class="h-10 w-full gap-1.5 text-xs font-semibold"> <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" /> <span>Contact Solutions Team</span> </button> </div> </div> </div> <!-- Collapsible Feature Matrix Trigger --> <div class="mt-12 text-center"> <button ui-button variant="ghost" size="sm" class="text-foreground hover:bg-muted gap-2 text-xs font-semibold" (click)="toggleComparison()" > <span>{{ showComparisonTable() ? 'Hide Detailed Feature Comparison' : 'Compare All Features & Enterprise Limits' }}</span> @if (showComparisonTable()) { <lucide-icon [img]="ChevronUp" class="size-4" /> } @else { <lucide-icon [img]="ChevronDown" class="size-4" /> } </button> </div> <!-- Feature Comparison Matrix Table --> @if (showComparisonTable()) { <div class="border-border bg-card mt-8 overflow-hidden rounded-xl border shadow-xs"> <div class="border-border bg-muted/20 flex flex-wrap items-center justify-between gap-4 border-b p-4 sm:p-6" > <div> <h3 class="text-foreground text-base font-bold">Detailed Specification & Limits</h3> <p class="text-muted-foreground text-xs">Comprehensive side-by-side breakdown across every tier.</p> </div> <span ui-badge variant="outline" class="font-mono text-xs" >All plans include SSL & automated backups</span > </div> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row class="bg-muted/40"> <th ui-table-head class="text-foreground w-[34%] text-xs font-semibold">Capabilities</th> <th ui-table-head class="text-foreground w-[22%] text-xs font-semibold">Starter Studio</th> <th ui-table-head class="text-primary w-[22%] text-xs font-bold font-semibold">Team Scale</th> <th ui-table-head class="text-foreground w-[22%] text-xs font-semibold">Enterprise Suite</th> </tr> </thead> <tbody ui-table-body> @for (group of comparisonGroups; track group.category) { <tr ui-table-row class="bg-muted/60 text-muted-foreground text-xs font-semibold"> <td ui-table-cell colspan="4" class="py-2.5 font-mono text-xs tracking-wider uppercase"> {{ group.category }} </td> </tr> @for (feat of group.features; track feat.name) { <tr ui-table-row class="text-xs"> <td ui-table-cell class="text-foreground py-3 font-medium">{{ feat.name }}</td> <td ui-table-cell class="text-muted-foreground py-3">{{ feat.starter }}</td> <td ui-table-cell class="text-foreground bg-primary/5 py-3 font-medium">{{ feat.team }}</td> <td ui-table-cell class="text-foreground py-3">{{ feat.enterprise }}</td> </tr> } } </tbody> </table> </div> </div> } </div> </section> `, }) export class UiPricing01Component { protected readonly ArrowRight = ArrowRight protected readonly Check = Check protected readonly ChevronDown = ChevronDown protected readonly ChevronUp = ChevronUp protected readonly ShieldCheck = ShieldCheck protected readonly Users = Users protected readonly Zap = Zap protected readonly comparisonGroups = comparisonMatrix @Input() set initialBillingCycle(v: Pricing01BillingCycle) { if (v) this.billingCycle.set(v) } @Input() set initialCurrency(v: Pricing01Currency) { if (v) this.currency.set(v) } @Input() set initialShowComparison(v: boolean) { this.showComparisonTable.set(!!v) } @Input('class') className?: string readonly billingCycle = signal<Pricing01BillingCycle>('yearly') readonly currency = signal<Pricing01Currency>('USD') readonly teamSeats = signal<number[]>([12]) readonly addDedicatedSla = signal(false) readonly addAuditVault = signal(false) readonly showComparisonTable = signal(false) readonly rates = computed(() => currencyRates[this.currency()]) readonly sym = computed(() => currencySymbols[this.currency()]) readonly starterBase = computed(() => (this.billingCycle() === 'yearly' ? 12 : 15)) readonly teamBase = computed(() => (this.billingCycle() === 'yearly' ? 28 : 35)) readonly enterpriseBase = computed(() => (this.billingCycle() === 'yearly' ? 68 : 85)) readonly slaCost = computed(() => (this.addDedicatedSla() ? 99 : 0)) readonly vaultCost = computed(() => (this.addAuditVault() ? 49 : 0)) readonly currentSeats = computed(() => this.teamSeats()[0] ?? 1) readonly starterUnit = computed(() => Math.round(this.starterBase() * this.rates())) readonly teamUnit = computed(() => Math.round(this.teamBase() * this.rates())) readonly enterpriseUnit = computed(() => Math.round(this.enterpriseBase() * this.rates())) readonly slaAddonUnit = computed(() => Math.round(99 * this.rates())) readonly vaultAddonUnit = computed(() => Math.round(49 * this.rates())) readonly starterMonthlyTotal = computed(() => { const base = this.starterBase() * this.currentSeats() return Math.round((base + (this.addAuditVault() ? 29 : 0)) * this.rates()) }) readonly teamMonthlyTotal = computed(() => { const base = this.teamBase() * this.currentSeats() return Math.round((base + this.slaCost() + this.vaultCost()) * this.rates()) }) readonly enterpriseMonthlyTotal = computed(() => { const base = this.enterpriseBase() * this.currentSeats() return Math.round((base + this.slaCost() + this.vaultCost()) * this.rates()) }) readonly annualSavingsTeam = computed(() => { const monthlyCost = 35 * this.currentSeats() * 12 const yearlyCost = 28 * this.currentSeats() * 12 return Math.round((monthlyCost - yearlyCost) * this.rates()) }) get rootClass(): string { return cn('bg-background w-full py-16 sm:py-24', this.className) } setBillingCycle(value: string): void { if (value === 'monthly' || value === 'yearly') this.billingCycle.set(value) } setCurrency(value: string): void { if (value === 'USD' || value === 'EUR' || value === 'GBP') this.currency.set(value) } onSeatsChange(value: number[]): void { if (value.length > 0) this.teamSeats.set(value) } toggleComparison(): void { this.showComparisonTable.update((v) => !v) } }src/app/components/blocks/pricing-01/index.ts0.2 kB
export { UiPricing01Component, type Pricing01BillingCycle, type Pricing01Currency, type Pricing01ComparisonGroup, type Pricing01FeatureRow, } from './pricing-01.component'
Raw manifest:https://uipkge.dev/r/angular/pricing-01.json