Cookie Consent — Preferences
blockmarketingGranular consent dialog listing each cookie category with its purpose, retention, and a switch, with strictly-necessary shown as locked rather than hidden.
Installation
$pnpm dlx uipkge-ng@latest add cookie-consent-preferences$npx uipkge-ng@latest add cookie-consent-preferences$yarn dlx uipkge-ng@latest add cookie-consent-preferences$bunx uipkge-ng@latest add cookie-consent-preferencesManifest:
https://uipkge.dev/r/angular/cookie-consent-preferences.jsonInstalls to:src/app/components/blocks/cookie-consent-preferences/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.
CookieConsentCategoryinterface CookieConsentCategory {
id: string
label: string
purpose: string
retention: string
locked: boolean
}Files installed (2)
src/app/components/blocks/cookie-consent-preferences/cookie-consent-preferences.component.ts5.1 kB
// Granular cookie-consent dialog: each category lists its purpose and retention // with a switch, and strictly-necessary stays visible as a locked row rather than // hidden. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, signal } from '@angular/core' import { Lock, LucideAngularModule } from 'lucide-angular' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogFooterComponent, UiDialogHeaderComponent, UiDialogTitleComponent, } from '@/ui/dialog/dialog.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSwitchComponent } from '@/ui/switch/switch.component' export interface CookieConsentCategory { id: string label: string purpose: string retention: string locked: boolean } export const cookieConsentCategories: CookieConsentCategory[] = [ { id: 'necessary', label: 'Strictly necessary', purpose: 'Session and sign-in state. Without these the app cannot keep you logged in.', retention: 'Session', locked: true, }, { id: 'analytics', label: 'Product analytics', purpose: 'Which pages are read and where people give up. Aggregated, never sold.', retention: '13 months', locked: false, }, { id: 'support', label: 'Support chat', purpose: 'Keeps a conversation open across page loads so you do not repeat yourself.', retention: '30 days', locked: false, }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-cookie-consent-preferences, [ui-cookie-consent-preferences]', standalone: true, // React renders the root <div> itself: the host stays out of layout. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiDialogComponent, UiDialogContentComponent, UiDialogDescriptionComponent, UiDialogFooterComponent, UiDialogHeaderComponent, UiDialogTitleComponent, UiSeparatorComponent, UiSwitchComponent, ], template: ` <div data-slot="cookie-consent-preferences"> <ui-dialog [open]="open()" (openChange)="open.set($event)"> <ui-dialog-content class="sm:max-w-lg"> <div ui-dialog-header> <span ui-badge variant="secondary" class="w-fit">Privacy</span> <h2 ui-dialog-title class="mt-3 text-xl">Choose what we store</h2> <p ui-dialog-description> Each category says what it is for and how long it is kept. Nothing here is advertising. </p> </div> <div ui-separator></div> <ul class="space-y-4"> @for (category of categories; track category.id) { <li class="flex items-start gap-4"> <div class="min-w-0 flex-1"> <div class="flex flex-wrap items-center gap-2"> <p class="text-sm font-medium">{{ category.label }}</p> @if (category.locked) { <span ui-badge variant="outline" class="gap-1"> <lucide-icon [img]="Lock" class="size-2.5" aria-hidden="true" /> Required </span> } </div> <p class="text-muted-foreground mt-1 text-xs leading-relaxed">{{ category.purpose }}</p> <p class="text-muted-foreground/80 mt-1 font-mono text-xs">Kept {{ category.retention }}</p> </div> <button ui-switch [checked]="choices()[category.id] ?? false" [disabled]="category.locked" [attr.aria-label]="'Allow ' + category.label" class="mt-1 shrink-0" (checkedChange)="setChoice(category.id, $event)" ></button> </li> } </ul> <div ui-dialog-footer class="sm:justify-start"> <button ui-button (click)="saveChoices()">Save choices</button> <button ui-button variant="outline" (click)="rejectOptional()">Reject optional</button> </div> </ui-dialog-content> </ui-dialog> </div> `, }) export class UiCookieConsentPreferencesComponent { protected readonly Lock = Lock protected readonly categories = cookieConsentCategories @Input() set initialOpen(v: boolean) { this.open.set(v ?? true) } @Input('class') className?: string readonly open = signal(true) readonly choices = signal<Record<string, boolean>>({ necessary: true, analytics: false, support: false }) setChoice(id: string, value: boolean): void { this.choices.update((prev) => ({ ...prev, [id]: value })) } saveChoices(): void { this.open.set(false) } rejectOptional(): void { this.choices.update((prev) => { const next: Record<string, boolean> = {} for (const category of cookieConsentCategories) next[category.id] = category.locked ? prev[category.id]! : false return next }) this.open.set(false) } }src/app/components/blocks/cookie-consent-preferences/index.ts0.1 kB
export { UiCookieConsentPreferencesComponent, cookieConsentCategories, type CookieConsentCategory, } from './cookie-consent-preferences.component'
Raw manifest:https://uipkge.dev/r/angular/cookie-consent-preferences.json