Menu

Change language

GitHub repo

Cookie Consent — Preferences

blockmarketing

Granular consent dialog listing each cookie category with its purpose, retention, and a switch, with strictly-necessary shown as locked rather than hidden.

Installation

$npx uipkge-ng@latest add cookie-consent-preferences
Manifest:https://uipkge.dev/r/angular/cookie-consent-preferences.jsonInstalls to:src/app/components/blocks/cookie-consent-preferences/

Variants

Loading interactive previews…

Schema

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

CookieConsentCategory
interface 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

Raw manifest:https://uipkge.dev/r/angular/cookie-consent-preferences.json