crumbie 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -408,7 +408,7 @@ function CookieBanner({
|
|
|
408
408
|
"h2",
|
|
409
409
|
{
|
|
410
410
|
id: "crumbie-banner-title",
|
|
411
|
-
className: "text-base font-semibold
|
|
411
|
+
className: "text-base font-semibold text-foreground",
|
|
412
412
|
children: title
|
|
413
413
|
}
|
|
414
414
|
),
|
|
@@ -513,10 +513,7 @@ var DialogTitle = React3.forwardRef((_a, ref) => {
|
|
|
513
513
|
DialogPrimitive.Title,
|
|
514
514
|
__spreadValues({
|
|
515
515
|
ref,
|
|
516
|
-
className: cn(
|
|
517
|
-
"text-sm font-semibold tracking-tight text-foreground",
|
|
518
|
-
className
|
|
519
|
-
)
|
|
516
|
+
className: cn("text-sm font-semibold text-foreground", className)
|
|
520
517
|
}, props)
|
|
521
518
|
);
|
|
522
519
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../components/crumbie/provider.tsx","../types/consent.ts","../lib/storage.ts","../components/crumbie/cookie-banner.tsx","../components/ui/button.tsx","../lib/utils.ts","../hooks/use-cookie-consent.ts","../components/crumbie/cookie-settings.tsx","../components/ui/dialog.tsx","../components/ui/switch.tsx","../components/crumbie/cookie-settings-button.tsx","../hooks/use-consent-category.ts","../components/crumbie/consent-gate.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n acceptAllPreferences,\n clearConsent,\n declineAllPreferences,\n readConsent,\n writeConsent,\n} from \"@/lib/storage\";\nimport {\n DEFAULT_PREFERENCES,\n type ConsentConfig,\n type ConsentPreferences,\n type ConsentRecord,\n type CookieCategory,\n} from \"@/types/consent\";\n\nexport type CookieConsentContextValue = {\n consent: ConsentRecord | null;\n preferences: ConsentPreferences;\n hasConsent: boolean;\n isSettingsOpen: boolean;\n isHydrated: boolean;\n acceptAll: () => void;\n declineAll: () => void;\n updatePreferences: (preferences: Partial<ConsentPreferences>) => void;\n resetConsent: () => void;\n openSettings: () => void;\n closeSettings: () => void;\n setSettingsOpen: (open: boolean) => void;\n isAllowed: (category: CookieCategory) => boolean;\n};\n\nconst CookieConsentContext =\n React.createContext<CookieConsentContextValue | null>(null);\n\nexport type CrumbieProviderProps = {\n children: React.ReactNode;\n config?: ConsentConfig;\n};\n\nexport function CrumbieProvider({\n children,\n config,\n}: CrumbieProviderProps) {\n const [consent, setConsent] = React.useState<ConsentRecord | null>(null);\n const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);\n const [isHydrated, setIsHydrated] = React.useState(false);\n\n React.useEffect(() => {\n setConsent(readConsent(config));\n setIsHydrated(true);\n // config is expected to be stable; re-read only on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const preferences = consent?.preferences ?? {\n ...DEFAULT_PREFERENCES,\n ...config?.defaultPreferences,\n necessary: true as const,\n };\n\n const hasConsent = consent !== null;\n\n const persist = React.useCallback(\n (next: ConsentPreferences) => {\n const record = writeConsent(\n { ...next, necessary: true },\n config,\n );\n setConsent(record);\n },\n [config],\n );\n\n const acceptAll = React.useCallback(() => {\n persist(acceptAllPreferences());\n setIsSettingsOpen(false);\n }, [persist]);\n\n const declineAll = React.useCallback(() => {\n persist(declineAllPreferences());\n setIsSettingsOpen(false);\n }, [persist]);\n\n const updatePreferences = React.useCallback(\n (partial: Partial<ConsentPreferences>) => {\n const next: ConsentPreferences = {\n ...preferences,\n ...partial,\n necessary: true,\n };\n persist(next);\n setIsSettingsOpen(false);\n },\n [persist, preferences],\n );\n\n const resetConsent = React.useCallback(() => {\n clearConsent(config);\n setConsent(null);\n }, [config]);\n\n const openSettings = React.useCallback(() => {\n setIsSettingsOpen(true);\n }, []);\n\n const closeSettings = React.useCallback(() => {\n setIsSettingsOpen(false);\n }, []);\n\n const setSettingsOpen = React.useCallback((open: boolean) => {\n setIsSettingsOpen(open);\n }, []);\n\n const isAllowed = React.useCallback(\n (category: CookieCategory) => {\n if (category === \"necessary\") return true;\n if (!consent) return false;\n return consent.preferences[category] === true;\n },\n [consent],\n );\n\n const value = React.useMemo<CookieConsentContextValue>(\n () => ({\n consent,\n preferences,\n hasConsent,\n isSettingsOpen,\n isHydrated,\n acceptAll,\n declineAll,\n updatePreferences,\n resetConsent,\n openSettings,\n closeSettings,\n setSettingsOpen,\n isAllowed,\n }),\n [\n consent,\n preferences,\n hasConsent,\n isSettingsOpen,\n isHydrated,\n acceptAll,\n declineAll,\n updatePreferences,\n resetConsent,\n openSettings,\n closeSettings,\n setSettingsOpen,\n isAllowed,\n ],\n );\n\n return (\n <CookieConsentContext.Provider value={value}>\n {children}\n </CookieConsentContext.Provider>\n );\n}\n\nexport function useCookieConsentContext(): CookieConsentContextValue {\n const context = React.useContext(CookieConsentContext);\n if (!context) {\n throw new Error(\n \"useCookieConsent must be used within a CrumbieProvider\",\n );\n }\n return context;\n}\n","export type CookieCategory =\n | \"necessary\"\n | \"preferences\"\n | \"analytics\"\n | \"marketing\";\n\nexport type ConsentPreferences = {\n necessary: true;\n preferences: boolean;\n analytics: boolean;\n marketing: boolean;\n};\n\nexport type ConsentRecord = {\n version: number;\n acceptedAt: string;\n expiresAt: string;\n preferences: ConsentPreferences;\n};\n\nexport type ConsentConfig = {\n version?: number;\n storageKey?: string;\n expirationDays?: number;\n defaultPreferences?: Partial<\n Omit<ConsentPreferences, \"necessary\">\n >;\n};\n\nexport type CookieCategoryMeta = {\n id: CookieCategory;\n title: string;\n description: string;\n required: boolean;\n};\n\nexport const COOKIE_CATEGORIES: CookieCategoryMeta[] = [\n {\n id: \"necessary\",\n title: \"Necessary\",\n description:\n \"Essential cookies required for the website to function. These cannot be disabled.\",\n required: true,\n },\n {\n id: \"preferences\",\n title: \"Preferences\",\n description:\n \"Cookies that remember your settings and preferences for a more personalized experience.\",\n required: false,\n },\n {\n id: \"analytics\",\n title: \"Analytics\",\n description:\n \"Cookies that help us understand how visitors interact with our website.\",\n required: false,\n },\n {\n id: \"marketing\",\n title: \"Marketing\",\n description:\n \"Cookies used to deliver relevant ads and measure campaign performance.\",\n required: false,\n },\n];\n\nexport const DEFAULT_PREFERENCES: ConsentPreferences = {\n necessary: true,\n preferences: false,\n analytics: false,\n marketing: false,\n};\n\nexport const CONSENT_VERSION = 1;\nexport const STORAGE_KEY = \"crumbie-consent\";\nexport const EXPIRATION_DAYS = 365;\n","import {\n CONSENT_VERSION,\n DEFAULT_PREFERENCES,\n EXPIRATION_DAYS,\n STORAGE_KEY,\n type ConsentConfig,\n type ConsentPreferences,\n type ConsentRecord,\n} from \"@/types/consent\";\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof localStorage !== \"undefined\";\n}\n\nfunction resolveConfig(config?: ConsentConfig) {\n return {\n version: config?.version ?? CONSENT_VERSION,\n storageKey: config?.storageKey ?? STORAGE_KEY,\n expirationDays: config?.expirationDays ?? EXPIRATION_DAYS,\n defaultPreferences: {\n ...DEFAULT_PREFERENCES,\n ...config?.defaultPreferences,\n necessary: true as const,\n },\n };\n}\n\nfunction isValidPreferences(\n value: unknown,\n): value is ConsentPreferences {\n if (!value || typeof value !== \"object\") return false;\n const prefs = value as Record<string, unknown>;\n return (\n prefs.necessary === true &&\n typeof prefs.preferences === \"boolean\" &&\n typeof prefs.analytics === \"boolean\" &&\n typeof prefs.marketing === \"boolean\"\n );\n}\n\nfunction isValidRecord(value: unknown): value is ConsentRecord {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return (\n typeof record.version === \"number\" &&\n typeof record.acceptedAt === \"string\" &&\n typeof record.expiresAt === \"string\" &&\n isValidPreferences(record.preferences)\n );\n}\n\nfunction isExpired(record: ConsentRecord): boolean {\n const expires = Date.parse(record.expiresAt);\n if (Number.isNaN(expires)) return true;\n return expires <= Date.now();\n}\n\nexport function createConsentRecord(\n preferences: ConsentPreferences,\n config?: ConsentConfig,\n): ConsentRecord {\n const { version, expirationDays } = resolveConfig(config);\n const acceptedAt = new Date();\n const expiresAt = new Date(\n acceptedAt.getTime() + expirationDays * 24 * 60 * 60 * 1000,\n );\n\n return {\n version,\n acceptedAt: acceptedAt.toISOString(),\n expiresAt: expiresAt.toISOString(),\n preferences: {\n ...preferences,\n necessary: true,\n },\n };\n}\n\nexport function readConsent(\n config?: ConsentConfig,\n): ConsentRecord | null {\n if (!isBrowser()) return null;\n\n const { version, storageKey } = resolveConfig(config);\n\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed: unknown = JSON.parse(raw);\n if (!isValidRecord(parsed)) {\n localStorage.removeItem(storageKey);\n return null;\n }\n\n if (parsed.version !== version || isExpired(parsed)) {\n localStorage.removeItem(storageKey);\n return null;\n }\n\n return {\n ...parsed,\n preferences: {\n ...parsed.preferences,\n necessary: true,\n },\n };\n } catch {\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // ignore cleanup failures\n }\n return null;\n }\n}\n\nexport function writeConsent(\n preferences: ConsentPreferences,\n config?: ConsentConfig,\n): ConsentRecord {\n const record = createConsentRecord(preferences, config);\n\n if (isBrowser()) {\n const { storageKey } = resolveConfig(config);\n try {\n localStorage.setItem(storageKey, JSON.stringify(record));\n } catch {\n // storage may be unavailable (private mode quotas)\n }\n }\n\n return record;\n}\n\nexport function clearConsent(config?: ConsentConfig): void {\n if (!isBrowser()) return;\n const { storageKey } = resolveConfig(config);\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n}\n\nexport function acceptAllPreferences(): ConsentPreferences {\n return {\n necessary: true,\n preferences: true,\n analytics: true,\n marketing: true,\n };\n}\n\nexport function declineAllPreferences(): ConsentPreferences {\n return {\n necessary: true,\n preferences: false,\n analytics: false,\n marketing: false,\n };\n}\n","\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport Image from \"next/image\";\nimport { Button } from \"@/components/ui/button\";\nimport cookieImage from \"@/components/crumbie/assets/cookie.png\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CookieBannerProps = {\n title?: string;\n description?: string;\n className?: string;\n position?:\n \"left\" | \"center\" | \"right\" | \"bottom\" | \"bottom-left\" | \"bottom-right\";\n};\n\nexport function CookieBanner({\n title = \"We value your privacy\",\n description = \"We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking Accept All, you consent to our use of cookies.\",\n className,\n position = \"left\",\n}: CookieBannerProps) {\n const { hasConsent, isHydrated, acceptAll, declineAll, openSettings } =\n useCookieConsent();\n const prefersReducedMotion = useReducedMotion();\n\n const show = isHydrated && !hasConsent;\n\n const positionClass =\n position === \"left\" || position === \"bottom-left\"\n ? \"left-3 right-3 sm:left-4 sm:right-auto sm:max-w-md\"\n : position === \"right\" || position === \"bottom-right\"\n ? \"left-3 right-3 sm:right-4 sm:left-auto sm:max-w-md\"\n : \"left-3 right-3 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 sm:max-w-lg\";\n\n return (\n <AnimatePresence>\n {show ? (\n <motion.div\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby=\"crumbie-banner-title\"\n aria-describedby=\"crumbie-banner-description\"\n initial={\n prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 16 }\n }\n animate={prefersReducedMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}\n exit={prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 12 }}\n transition={{ duration: 0.24, ease: [0.22, 1, 0.36, 1] }}\n className={cn(\n \"fixed bottom-3 z-50 sm:bottom-4\",\n positionClass,\n className,\n )}\n >\n <div className=\"rounded-2xl border border-border bg-background p-5 shadow-[0_6px_24px_rgb(0,0,0,0.06)] sm:p-6\">\n <div className=\"flex flex-col items-start gap-3\">\n <div\n className=\"flex h-10 w-10 shrink-0 items-center justify-center\"\n aria-hidden\n >\n <Image\n src={cookieImage}\n alt=\"\"\n width={36}\n height={36}\n className=\"h-9 w-9 object-contain\"\n />\n </div>\n <div className=\"space-y-0.5\">\n <h2\n id=\"crumbie-banner-title\"\n className=\"text-base font-semibold tracking-tight text-foreground\"\n >\n {title}\n </h2>\n <p\n id=\"crumbie-banner-description\"\n className=\"text-sm leading-relaxed text-muted-foreground\"\n >\n {description}\n </p>\n </div>\n </div>\n\n <div className=\"mt-5 flex flex-wrap items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={openSettings}>\n Cookie Settings\n </Button>\n <Button variant=\"secondary\" size=\"sm\" onClick={declineAll}>\n Decline\n </Button>\n <Button size=\"sm\" onClick={acceptAll}>\n Accept All\n </Button>\n </div>\n </div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n );\n}\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 cursor-pointer\",\n {\n variants: {\n variant: {\n default: \"bg-foreground text-background hover:bg-foreground/90\",\n secondary: \"bg-muted text-foreground hover:bg-muted/80\",\n outline:\n \"border border-border bg-transparent text-foreground hover:bg-muted/60\",\n ghost: \"hover:bg-muted/60 text-foreground\",\n link: \"text-foreground underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-5\",\n sm: \"h-9 px-4 text-sm\",\n lg: \"h-11 px-6 text-base\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { useCookieConsentContext } from \"@/components/crumbie/provider\";\n\nexport function useCookieConsent() {\n return useCookieConsentContext();\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { COOKIE_CATEGORIES, type ConsentPreferences } from \"@/types/consent\";\nimport { cn } from \"@/lib/utils\";\nimport styles from \"./cookie-settings.module.css\";\n\nexport type CookieSettingsProps = {\n title?: string;\n description?: string;\n className?: string;\n};\n\nexport function CookieSettings({\n title = \"Cookie Settings\",\n description = \"Manage your cookie preferences. Necessary cookies are always enabled to keep the site working.\",\n className,\n}: CookieSettingsProps) {\n const {\n isSettingsOpen,\n setSettingsOpen,\n preferences,\n acceptAll,\n declineAll,\n updatePreferences,\n } = useCookieConsent();\n\n const [draft, setDraft] = React.useState<ConsentPreferences>(preferences);\n\n React.useEffect(() => {\n if (isSettingsOpen) {\n setDraft(preferences);\n }\n }, [isSettingsOpen, preferences]);\n\n const handleToggle = (key: keyof ConsentPreferences, value: boolean) => {\n if (key === \"necessary\") return;\n setDraft((prev) => ({ ...prev, [key]: value, necessary: true }));\n };\n\n const handleSave = () => {\n updatePreferences(draft);\n };\n\n return (\n <Dialog open={isSettingsOpen} onOpenChange={setSettingsOpen}>\n <DialogContent\n className={cn(\"max-h-[min(90vh,520px)] overflow-hidden\", className)}\n >\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <div\n className={cn(\n \"max-h-[min(48vh,300px)] space-y-0.5 overflow-y-auto px-4 py-2.5\",\n styles.scrollArea,\n )}\n >\n {COOKIE_CATEGORIES.map((category) => {\n const checked = draft[category.id];\n const disabled = category.required;\n\n return (\n <div\n key={category.id}\n className=\"flex items-start justify-between gap-3 py-2.5\"\n >\n <div className=\"min-w-0 space-y-0.5 pr-1.5\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-xs font-medium text-foreground\">\n {category.title}\n </p>\n {category.required ? (\n <span className=\"rounded-lg bg-muted px-1.5 py-px text-[9px] font-medium uppercase tracking-wide text-muted-foreground\">\n Always on\n </span>\n ) : null}\n </div>\n <p className=\"text-[11px] leading-relaxed text-muted-foreground\">\n {category.description}\n </p>\n </div>\n <Switch\n checked={checked}\n disabled={disabled}\n onCheckedChange={(value) =>\n handleToggle(category.id, value)\n }\n aria-label={`${category.title} cookies`}\n className=\"mt-0.5\"\n />\n </div>\n );\n })}\n </div>\n\n <DialogFooter>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full sm:w-auto\"\n onClick={declineAll}\n >\n Decline Optional\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full sm:w-auto\"\n onClick={handleSave}\n >\n Save Preferences\n </Button>\n <Button size=\"sm\" className=\"w-full sm:w-auto\" onClick={acceptAll}>\n Accept All\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\"fixed inset-0 z-50 bg-black/40\", className)}\n {...props}\n />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-1.5rem)] max-w-md -translate-x-1/2 -translate-y-1/2 gap-0 rounded-lg border border-border bg-background p-0 shadow-lg outline-none\",\n className,\n )}\n {...props}\n >\n {children}\n <DialogPrimitive.Close className=\"absolute right-3 top-3 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\">\n <X className=\"h-3.5 w-3.5\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nfunction DialogHeader({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\"flex flex-col gap-1 px-4 pt-4 pr-10\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\n \"flex flex-col-reverse gap-1.5 border-t border-border px-4 py-3 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\n \"text-sm font-semibold tracking-tight text-foreground\",\n className,\n )}\n {...props}\n />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"text-xs leading-relaxed text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\nimport { cn } from \"@/lib/utils\";\n\nconst Switch = React.forwardRef<\n React.ComponentRef<typeof SwitchPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n <SwitchPrimitives.Root\n className={cn(\n \"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=unchecked]:bg-muted\",\n className,\n )}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n \"pointer-events-none block h-3.5 w-3.5 rounded-full bg-background shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5\",\n )}\n />\n </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n","\"use client\";\n\nimport { Settings2 } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { Button } from \"@/components/ui/button\";\nimport cookieImage from \"@/components/crumbie/assets/cookie.png\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CookieSettingsButtonProps = {\n className?: string;\n label?: string;\n variant?: \"floating\" | \"inline\";\n};\n\nexport function CookieSettingsButton({\n className,\n label = \"Cookie settings\",\n variant = \"floating\",\n}: CookieSettingsButtonProps) {\n const { hasConsent, isHydrated, openSettings } = useCookieConsent();\n\n if (!isHydrated || !hasConsent) return null;\n\n if (variant === \"inline\") {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={openSettings}\n className={className}\n aria-label={label}\n >\n <Settings2 className=\"h-3.5 w-3.5\" />\n {label}\n </Button>\n );\n }\n\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={openSettings}\n aria-label={label}\n className={cn(\n \"fixed bottom-3 left-3 z-40 h-9 w-9 rounded-full border-border bg-background shadow-[0_3px_14px_rgb(0,0,0,0.08)] hover:bg-muted sm:bottom-4 sm:left-4\",\n className,\n )}\n >\n <Image\n src={cookieImage}\n alt=\"\"\n width={28}\n height={28}\n className=\"h-7 w-7 object-contain\"\n />\n </Button>\n );\n}\n","\"use client\";\n\nimport { useCookieConsentContext } from \"@/components/crumbie/provider\";\nimport type { CookieCategory } from \"@/types/consent\";\n\nexport function useConsentCategory(category: CookieCategory): boolean {\n const { isAllowed, isHydrated } = useCookieConsentContext();\n if (!isHydrated) return category === \"necessary\";\n return isAllowed(category);\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { useConsentCategory } from \"@/hooks/use-consent-category\";\nimport type { CookieCategory } from \"@/types/consent\";\n\nexport type ConsentGateProps = {\n category: CookieCategory;\n children: ReactNode;\n fallback?: ReactNode;\n};\n\nexport function ConsentGate({\n category,\n children,\n fallback = null,\n}: ConsentGateProps) {\n const allowed = useConsentCategory(category);\n if (!allowed) return <>{fallback}</>;\n return <>{children}</>;\n}\n"],"mappings":"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","names":["e","React","jsx","jsx","React","React","jsx","jsxs","jsx","jsxs","React","jsx","jsx","jsxs","Image","jsx","jsxs","Image","jsx"]}
|
|
1
|
+
{"version":3,"sources":["../components/crumbie/provider.tsx","../types/consent.ts","../lib/storage.ts","../components/crumbie/cookie-banner.tsx","../components/ui/button.tsx","../lib/utils.ts","../hooks/use-cookie-consent.ts","../components/crumbie/cookie-settings.tsx","../components/ui/dialog.tsx","../components/ui/switch.tsx","../components/crumbie/cookie-settings-button.tsx","../hooks/use-consent-category.ts","../components/crumbie/consent-gate.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n acceptAllPreferences,\n clearConsent,\n declineAllPreferences,\n readConsent,\n writeConsent,\n} from \"@/lib/storage\";\nimport {\n DEFAULT_PREFERENCES,\n type ConsentConfig,\n type ConsentPreferences,\n type ConsentRecord,\n type CookieCategory,\n} from \"@/types/consent\";\n\nexport type CookieConsentContextValue = {\n consent: ConsentRecord | null;\n preferences: ConsentPreferences;\n hasConsent: boolean;\n isSettingsOpen: boolean;\n isHydrated: boolean;\n acceptAll: () => void;\n declineAll: () => void;\n updatePreferences: (preferences: Partial<ConsentPreferences>) => void;\n resetConsent: () => void;\n openSettings: () => void;\n closeSettings: () => void;\n setSettingsOpen: (open: boolean) => void;\n isAllowed: (category: CookieCategory) => boolean;\n};\n\nconst CookieConsentContext =\n React.createContext<CookieConsentContextValue | null>(null);\n\nexport type CrumbieProviderProps = {\n children: React.ReactNode;\n config?: ConsentConfig;\n};\n\nexport function CrumbieProvider({\n children,\n config,\n}: CrumbieProviderProps) {\n const [consent, setConsent] = React.useState<ConsentRecord | null>(null);\n const [isSettingsOpen, setIsSettingsOpen] = React.useState(false);\n const [isHydrated, setIsHydrated] = React.useState(false);\n\n React.useEffect(() => {\n setConsent(readConsent(config));\n setIsHydrated(true);\n // config is expected to be stable; re-read only on mount\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const preferences = consent?.preferences ?? {\n ...DEFAULT_PREFERENCES,\n ...config?.defaultPreferences,\n necessary: true as const,\n };\n\n const hasConsent = consent !== null;\n\n const persist = React.useCallback(\n (next: ConsentPreferences) => {\n const record = writeConsent(\n { ...next, necessary: true },\n config,\n );\n setConsent(record);\n },\n [config],\n );\n\n const acceptAll = React.useCallback(() => {\n persist(acceptAllPreferences());\n setIsSettingsOpen(false);\n }, [persist]);\n\n const declineAll = React.useCallback(() => {\n persist(declineAllPreferences());\n setIsSettingsOpen(false);\n }, [persist]);\n\n const updatePreferences = React.useCallback(\n (partial: Partial<ConsentPreferences>) => {\n const next: ConsentPreferences = {\n ...preferences,\n ...partial,\n necessary: true,\n };\n persist(next);\n setIsSettingsOpen(false);\n },\n [persist, preferences],\n );\n\n const resetConsent = React.useCallback(() => {\n clearConsent(config);\n setConsent(null);\n }, [config]);\n\n const openSettings = React.useCallback(() => {\n setIsSettingsOpen(true);\n }, []);\n\n const closeSettings = React.useCallback(() => {\n setIsSettingsOpen(false);\n }, []);\n\n const setSettingsOpen = React.useCallback((open: boolean) => {\n setIsSettingsOpen(open);\n }, []);\n\n const isAllowed = React.useCallback(\n (category: CookieCategory) => {\n if (category === \"necessary\") return true;\n if (!consent) return false;\n return consent.preferences[category] === true;\n },\n [consent],\n );\n\n const value = React.useMemo<CookieConsentContextValue>(\n () => ({\n consent,\n preferences,\n hasConsent,\n isSettingsOpen,\n isHydrated,\n acceptAll,\n declineAll,\n updatePreferences,\n resetConsent,\n openSettings,\n closeSettings,\n setSettingsOpen,\n isAllowed,\n }),\n [\n consent,\n preferences,\n hasConsent,\n isSettingsOpen,\n isHydrated,\n acceptAll,\n declineAll,\n updatePreferences,\n resetConsent,\n openSettings,\n closeSettings,\n setSettingsOpen,\n isAllowed,\n ],\n );\n\n return (\n <CookieConsentContext.Provider value={value}>\n {children}\n </CookieConsentContext.Provider>\n );\n}\n\nexport function useCookieConsentContext(): CookieConsentContextValue {\n const context = React.useContext(CookieConsentContext);\n if (!context) {\n throw new Error(\n \"useCookieConsent must be used within a CrumbieProvider\",\n );\n }\n return context;\n}\n","export type CookieCategory =\n | \"necessary\"\n | \"preferences\"\n | \"analytics\"\n | \"marketing\";\n\nexport type ConsentPreferences = {\n necessary: true;\n preferences: boolean;\n analytics: boolean;\n marketing: boolean;\n};\n\nexport type ConsentRecord = {\n version: number;\n acceptedAt: string;\n expiresAt: string;\n preferences: ConsentPreferences;\n};\n\nexport type ConsentConfig = {\n version?: number;\n storageKey?: string;\n expirationDays?: number;\n defaultPreferences?: Partial<\n Omit<ConsentPreferences, \"necessary\">\n >;\n};\n\nexport type CookieCategoryMeta = {\n id: CookieCategory;\n title: string;\n description: string;\n required: boolean;\n};\n\nexport const COOKIE_CATEGORIES: CookieCategoryMeta[] = [\n {\n id: \"necessary\",\n title: \"Necessary\",\n description:\n \"Essential cookies required for the website to function. These cannot be disabled.\",\n required: true,\n },\n {\n id: \"preferences\",\n title: \"Preferences\",\n description:\n \"Cookies that remember your settings and preferences for a more personalized experience.\",\n required: false,\n },\n {\n id: \"analytics\",\n title: \"Analytics\",\n description:\n \"Cookies that help us understand how visitors interact with our website.\",\n required: false,\n },\n {\n id: \"marketing\",\n title: \"Marketing\",\n description:\n \"Cookies used to deliver relevant ads and measure campaign performance.\",\n required: false,\n },\n];\n\nexport const DEFAULT_PREFERENCES: ConsentPreferences = {\n necessary: true,\n preferences: false,\n analytics: false,\n marketing: false,\n};\n\nexport const CONSENT_VERSION = 1;\nexport const STORAGE_KEY = \"crumbie-consent\";\nexport const EXPIRATION_DAYS = 365;\n","import {\n CONSENT_VERSION,\n DEFAULT_PREFERENCES,\n EXPIRATION_DAYS,\n STORAGE_KEY,\n type ConsentConfig,\n type ConsentPreferences,\n type ConsentRecord,\n} from \"@/types/consent\";\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\" && typeof localStorage !== \"undefined\";\n}\n\nfunction resolveConfig(config?: ConsentConfig) {\n return {\n version: config?.version ?? CONSENT_VERSION,\n storageKey: config?.storageKey ?? STORAGE_KEY,\n expirationDays: config?.expirationDays ?? EXPIRATION_DAYS,\n defaultPreferences: {\n ...DEFAULT_PREFERENCES,\n ...config?.defaultPreferences,\n necessary: true as const,\n },\n };\n}\n\nfunction isValidPreferences(\n value: unknown,\n): value is ConsentPreferences {\n if (!value || typeof value !== \"object\") return false;\n const prefs = value as Record<string, unknown>;\n return (\n prefs.necessary === true &&\n typeof prefs.preferences === \"boolean\" &&\n typeof prefs.analytics === \"boolean\" &&\n typeof prefs.marketing === \"boolean\"\n );\n}\n\nfunction isValidRecord(value: unknown): value is ConsentRecord {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return (\n typeof record.version === \"number\" &&\n typeof record.acceptedAt === \"string\" &&\n typeof record.expiresAt === \"string\" &&\n isValidPreferences(record.preferences)\n );\n}\n\nfunction isExpired(record: ConsentRecord): boolean {\n const expires = Date.parse(record.expiresAt);\n if (Number.isNaN(expires)) return true;\n return expires <= Date.now();\n}\n\nexport function createConsentRecord(\n preferences: ConsentPreferences,\n config?: ConsentConfig,\n): ConsentRecord {\n const { version, expirationDays } = resolveConfig(config);\n const acceptedAt = new Date();\n const expiresAt = new Date(\n acceptedAt.getTime() + expirationDays * 24 * 60 * 60 * 1000,\n );\n\n return {\n version,\n acceptedAt: acceptedAt.toISOString(),\n expiresAt: expiresAt.toISOString(),\n preferences: {\n ...preferences,\n necessary: true,\n },\n };\n}\n\nexport function readConsent(\n config?: ConsentConfig,\n): ConsentRecord | null {\n if (!isBrowser()) return null;\n\n const { version, storageKey } = resolveConfig(config);\n\n try {\n const raw = localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed: unknown = JSON.parse(raw);\n if (!isValidRecord(parsed)) {\n localStorage.removeItem(storageKey);\n return null;\n }\n\n if (parsed.version !== version || isExpired(parsed)) {\n localStorage.removeItem(storageKey);\n return null;\n }\n\n return {\n ...parsed,\n preferences: {\n ...parsed.preferences,\n necessary: true,\n },\n };\n } catch {\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // ignore cleanup failures\n }\n return null;\n }\n}\n\nexport function writeConsent(\n preferences: ConsentPreferences,\n config?: ConsentConfig,\n): ConsentRecord {\n const record = createConsentRecord(preferences, config);\n\n if (isBrowser()) {\n const { storageKey } = resolveConfig(config);\n try {\n localStorage.setItem(storageKey, JSON.stringify(record));\n } catch {\n // storage may be unavailable (private mode quotas)\n }\n }\n\n return record;\n}\n\nexport function clearConsent(config?: ConsentConfig): void {\n if (!isBrowser()) return;\n const { storageKey } = resolveConfig(config);\n try {\n localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n}\n\nexport function acceptAllPreferences(): ConsentPreferences {\n return {\n necessary: true,\n preferences: true,\n analytics: true,\n marketing: true,\n };\n}\n\nexport function declineAllPreferences(): ConsentPreferences {\n return {\n necessary: true,\n preferences: false,\n analytics: false,\n marketing: false,\n };\n}\n","\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion } from \"framer-motion\";\nimport Image from \"next/image\";\nimport { Button } from \"@/components/ui/button\";\nimport cookieImage from \"@/components/crumbie/assets/cookie.png\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CookieBannerProps = {\n title?: string;\n description?: string;\n className?: string;\n position?:\n \"left\" | \"center\" | \"right\" | \"bottom\" | \"bottom-left\" | \"bottom-right\";\n};\n\nexport function CookieBanner({\n title = \"We value your privacy\",\n description = \"We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking Accept All, you consent to our use of cookies.\",\n className,\n position = \"left\",\n}: CookieBannerProps) {\n const { hasConsent, isHydrated, acceptAll, declineAll, openSettings } =\n useCookieConsent();\n const prefersReducedMotion = useReducedMotion();\n\n const show = isHydrated && !hasConsent;\n\n const positionClass =\n position === \"left\" || position === \"bottom-left\"\n ? \"left-3 right-3 sm:left-4 sm:right-auto sm:max-w-md\"\n : position === \"right\" || position === \"bottom-right\"\n ? \"left-3 right-3 sm:right-4 sm:left-auto sm:max-w-md\"\n : \"left-3 right-3 sm:left-1/2 sm:right-auto sm:-translate-x-1/2 sm:max-w-lg\";\n\n return (\n <AnimatePresence>\n {show ? (\n <motion.div\n role=\"dialog\"\n aria-modal=\"false\"\n aria-labelledby=\"crumbie-banner-title\"\n aria-describedby=\"crumbie-banner-description\"\n initial={\n prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 16 }\n }\n animate={prefersReducedMotion ? { opacity: 1 } : { opacity: 1, y: 0 }}\n exit={prefersReducedMotion ? { opacity: 0 } : { opacity: 0, y: 12 }}\n transition={{ duration: 0.24, ease: [0.22, 1, 0.36, 1] }}\n className={cn(\n \"fixed bottom-3 z-50 sm:bottom-4\",\n positionClass,\n className,\n )}\n >\n <div className=\"rounded-2xl border border-border bg-background p-5 shadow-[0_6px_24px_rgb(0,0,0,0.06)] sm:p-6\">\n <div className=\"flex flex-col items-start gap-3\">\n <div\n className=\"flex h-10 w-10 shrink-0 items-center justify-center\"\n aria-hidden\n >\n <Image\n src={cookieImage}\n alt=\"\"\n width={36}\n height={36}\n className=\"h-9 w-9 object-contain\"\n />\n </div>\n <div className=\"space-y-0.5\">\n <h2\n id=\"crumbie-banner-title\"\n className=\"text-base font-semibold text-foreground\"\n >\n {title}\n </h2>\n <p\n id=\"crumbie-banner-description\"\n className=\"text-sm leading-relaxed text-muted-foreground\"\n >\n {description}\n </p>\n </div>\n </div>\n\n <div className=\"mt-5 flex flex-wrap items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={openSettings}>\n Cookie Settings\n </Button>\n <Button variant=\"secondary\" size=\"sm\" onClick={declineAll}>\n Decline\n </Button>\n <Button size=\"sm\" onClick={acceptAll}>\n Accept All\n </Button>\n </div>\n </div>\n </motion.div>\n ) : null}\n </AnimatePresence>\n );\n}\n","import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-full text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 cursor-pointer\",\n {\n variants: {\n variant: {\n default: \"bg-foreground text-background hover:bg-foreground/90\",\n secondary: \"bg-muted text-foreground hover:bg-muted/80\",\n outline:\n \"border border-border bg-transparent text-foreground hover:bg-muted/60\",\n ghost: \"hover:bg-muted/60 text-foreground\",\n link: \"text-foreground underline-offset-4 hover:underline\",\n },\n size: {\n default: \"h-10 px-5\",\n sm: \"h-9 px-4 text-sm\",\n lg: \"h-11 px-6 text-base\",\n icon: \"h-10 w-10\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nexport interface ButtonProps\n extends\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof buttonVariants> {\n asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant, size, asChild = false, ...props }, ref) => {\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n className={cn(buttonVariants({ variant, size, className }))}\n ref={ref}\n {...props}\n />\n );\n },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n","import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport { useCookieConsentContext } from \"@/components/crumbie/provider\";\n\nexport function useCookieConsent() {\n return useCookieConsentContext();\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@/components/ui/dialog\";\nimport { Button } from \"@/components/ui/button\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { COOKIE_CATEGORIES, type ConsentPreferences } from \"@/types/consent\";\nimport { cn } from \"@/lib/utils\";\nimport styles from \"./cookie-settings.module.css\";\n\nexport type CookieSettingsProps = {\n title?: string;\n description?: string;\n className?: string;\n};\n\nexport function CookieSettings({\n title = \"Cookie Settings\",\n description = \"Manage your cookie preferences. Necessary cookies are always enabled to keep the site working.\",\n className,\n}: CookieSettingsProps) {\n const {\n isSettingsOpen,\n setSettingsOpen,\n preferences,\n acceptAll,\n declineAll,\n updatePreferences,\n } = useCookieConsent();\n\n const [draft, setDraft] = React.useState<ConsentPreferences>(preferences);\n\n React.useEffect(() => {\n if (isSettingsOpen) {\n setDraft(preferences);\n }\n }, [isSettingsOpen, preferences]);\n\n const handleToggle = (key: keyof ConsentPreferences, value: boolean) => {\n if (key === \"necessary\") return;\n setDraft((prev) => ({ ...prev, [key]: value, necessary: true }));\n };\n\n const handleSave = () => {\n updatePreferences(draft);\n };\n\n return (\n <Dialog open={isSettingsOpen} onOpenChange={setSettingsOpen}>\n <DialogContent\n className={cn(\"max-h-[min(90vh,520px)] overflow-hidden\", className)}\n >\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n\n <div\n className={cn(\n \"max-h-[min(48vh,300px)] space-y-0.5 overflow-y-auto px-4 py-2.5\",\n styles.scrollArea,\n )}\n >\n {COOKIE_CATEGORIES.map((category) => {\n const checked = draft[category.id];\n const disabled = category.required;\n\n return (\n <div\n key={category.id}\n className=\"flex items-start justify-between gap-3 py-2.5\"\n >\n <div className=\"min-w-0 space-y-0.5 pr-1.5\">\n <div className=\"flex items-center gap-1.5\">\n <p className=\"text-xs font-medium text-foreground\">\n {category.title}\n </p>\n {category.required ? (\n <span className=\"rounded-lg bg-muted px-1.5 py-px text-[9px] font-medium uppercase tracking-wide text-muted-foreground\">\n Always on\n </span>\n ) : null}\n </div>\n <p className=\"text-[11px] leading-relaxed text-muted-foreground\">\n {category.description}\n </p>\n </div>\n <Switch\n checked={checked}\n disabled={disabled}\n onCheckedChange={(value) =>\n handleToggle(category.id, value)\n }\n aria-label={`${category.title} cookies`}\n className=\"mt-0.5\"\n />\n </div>\n );\n })}\n </div>\n\n <DialogFooter>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full sm:w-auto\"\n onClick={declineAll}\n >\n Decline Optional\n </Button>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full sm:w-auto\"\n onClick={handleSave}\n >\n Save Preferences\n </Button>\n <Button size=\"sm\" className=\"w-full sm:w-auto\" onClick={acceptAll}>\n Accept All\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\nconst Dialog = DialogPrimitive.Root;\nconst DialogTrigger = DialogPrimitive.Trigger;\nconst DialogPortal = DialogPrimitive.Portal;\nconst DialogClose = DialogPrimitive.Close;\n\nconst DialogOverlay = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Overlay>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Overlay\n ref={ref}\n className={cn(\"fixed inset-0 z-50 bg-black/40\", className)}\n {...props}\n />\n));\nDialogOverlay.displayName = DialogPrimitive.Overlay.displayName;\n\nconst DialogContent = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>\n>(({ className, children, ...props }, ref) => (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Content\n ref={ref}\n className={cn(\n \"fixed left-1/2 top-1/2 z-50 grid w-[calc(100%-1.5rem)] max-w-md -translate-x-1/2 -translate-y-1/2 gap-0 rounded-lg border border-border bg-background p-0 shadow-lg outline-none\",\n className,\n )}\n {...props}\n >\n {children}\n <DialogPrimitive.Close className=\"absolute right-3 top-3 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\">\n <X className=\"h-3.5 w-3.5\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n </DialogPrimitive.Content>\n </DialogPortal>\n));\nDialogContent.displayName = DialogPrimitive.Content.displayName;\n\nfunction DialogHeader({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\"flex flex-col gap-1 px-4 pt-4 pr-10\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n return (\n <div\n className={cn(\n \"flex flex-col-reverse gap-1.5 border-t border-border px-4 py-3 sm:flex-row sm:justify-end\",\n className,\n )}\n {...props}\n />\n );\n}\n\nconst DialogTitle = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Title>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Title\n ref={ref}\n className={cn(\"text-sm font-semibold text-foreground\", className)}\n {...props}\n />\n));\nDialogTitle.displayName = DialogPrimitive.Title.displayName;\n\nconst DialogDescription = React.forwardRef<\n React.ComponentRef<typeof DialogPrimitive.Description>,\n React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n <DialogPrimitive.Description\n ref={ref}\n className={cn(\"text-xs leading-relaxed text-muted-foreground\", className)}\n {...props}\n />\n));\nDialogDescription.displayName = DialogPrimitive.Description.displayName;\n\nexport {\n Dialog,\n DialogPortal,\n DialogOverlay,\n DialogClose,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport * as SwitchPrimitives from \"@radix-ui/react-switch\";\nimport { cn } from \"@/lib/utils\";\n\nconst Switch = React.forwardRef<\n React.ComponentRef<typeof SwitchPrimitives.Root>,\n React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>\n>(({ className, ...props }, ref) => (\n <SwitchPrimitives.Root\n className={cn(\n \"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-foreground data-[state=unchecked]:bg-muted\",\n className,\n )}\n {...props}\n ref={ref}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n \"pointer-events-none block h-3.5 w-3.5 rounded-full bg-background shadow-sm ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0.5\",\n )}\n />\n </SwitchPrimitives.Root>\n));\nSwitch.displayName = SwitchPrimitives.Root.displayName;\n\nexport { Switch };\n","\"use client\";\n\nimport { Settings2 } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { Button } from \"@/components/ui/button\";\nimport cookieImage from \"@/components/crumbie/assets/cookie.png\";\nimport { useCookieConsent } from \"@/hooks/use-cookie-consent\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CookieSettingsButtonProps = {\n className?: string;\n label?: string;\n variant?: \"floating\" | \"inline\";\n};\n\nexport function CookieSettingsButton({\n className,\n label = \"Cookie settings\",\n variant = \"floating\",\n}: CookieSettingsButtonProps) {\n const { hasConsent, isHydrated, openSettings } = useCookieConsent();\n\n if (!isHydrated || !hasConsent) return null;\n\n if (variant === \"inline\") {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={openSettings}\n className={className}\n aria-label={label}\n >\n <Settings2 className=\"h-3.5 w-3.5\" />\n {label}\n </Button>\n );\n }\n\n return (\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={openSettings}\n aria-label={label}\n className={cn(\n \"fixed bottom-3 left-3 z-40 h-9 w-9 rounded-full border-border bg-background shadow-[0_3px_14px_rgb(0,0,0,0.08)] hover:bg-muted sm:bottom-4 sm:left-4\",\n className,\n )}\n >\n <Image\n src={cookieImage}\n alt=\"\"\n width={28}\n height={28}\n className=\"h-7 w-7 object-contain\"\n />\n </Button>\n );\n}\n","\"use client\";\n\nimport { useCookieConsentContext } from \"@/components/crumbie/provider\";\nimport type { CookieCategory } from \"@/types/consent\";\n\nexport function useConsentCategory(category: CookieCategory): boolean {\n const { isAllowed, isHydrated } = useCookieConsentContext();\n if (!isHydrated) return category === \"necessary\";\n return isAllowed(category);\n}\n","\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { useConsentCategory } from \"@/hooks/use-consent-category\";\nimport type { CookieCategory } from \"@/types/consent\";\n\nexport type ConsentGateProps = {\n category: CookieCategory;\n children: ReactNode;\n fallback?: ReactNode;\n};\n\nexport function ConsentGate({\n category,\n children,\n fallback = null,\n}: ConsentGateProps) {\n const allowed = useConsentCategory(category);\n if (!allowed) return <>{fallback}</>;\n return <>{children}</>;\n}\n"],"mappings":"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","names":["e","React","jsx","jsx","React","React","jsx","jsxs","jsx","jsxs","React","jsx","jsx","jsxs","Image","jsx","jsxs","Image","jsx"]}
|