aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
383 lines (380 loc) • 13.4 kB
JavaScript
'use client';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { forwardRef, useState, useMemo, useRef, useEffect } from 'react';
import { cn } from '../../lib/utilsComprehensive.js';
import styles from './GlobalCookieConsent.module.css.js';
import { useReducedMotion } from '../../hooks/useReducedMotion.js';
import { useAnimationContext } from '../../contexts/AnimationContext.js';
import { Box } from '../layout/Box.js';
import { GlassButton } from '../button/GlassButton.js';
import '../button/GlassFab.js';
import '../button/GlassMagneticButton.js';
import { Typography } from '../data-display/Typography.js';
import { GlassModal } from '../modal/GlassModal.js';
import { GlassCheckbox } from '../input/GlassCheckbox.js';
import { useGalileoStateSpring } from '../../hooks/useGalileoStateSpring.js';
import { SpringPresets } from '../../animations/physics/springPhysics.js';
// Cookie management utilities
const setCookie = (name, value, days) => {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${value};${expires};path=/`;
};
const getCookie = name => {
const nameEQ = `${name}=`;
const ca = document.cookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === " ") c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
};
const POSITION_CLASS_MAP = {
bottom: styles.positionCenterBottom,
top: styles.positionCenterTop,
"bottom-left": styles.positionBottomLeft,
"bottom-right": styles.positionBottomRight,
"top-left": styles.positionTopLeft,
"top-right": styles.positionTopRight
};
/**
* Global Cookie Consent component for comprehensive cookie consent management
*/
const GlobalCookieConsent = /*#__PURE__*/forwardRef(({
title = "Manage Cookie Preferences",
message = "We use cookies to enhance your browsing experience, analyze site traffic, and personalize content.",
position = "bottom",
acceptButtonText = "Accept All",
declineButtonText = "Decline All",
settingsButtonText = "Save Preferences",
onAccept,
onDecline,
onSave,
onCategoryChange,
enableSettings = true,
glassIntensity = 0.8,
privacyPolicyUrl,
privacyPolicyText = "Privacy Policy",
className,
animate = true,
delay = 700,
timeout = 0,
onTimeout,
dismissible = true,
cookieExpiration = 365,
style,
cookieCategories = [],
customContent,
initiallyExpanded = false,
useModalForDetails = false,
defaultSelectedCategories = [],
...rest
}, ref) => {
const [visible, setVisible] = useState(false);
const [expanded, setExpanded] = useState(initiallyExpanded);
const [showDetailsModal, setShowDetailsModal] = useState(false);
const [selectedCategories, setSelectedCategories] = useState([]);
const prefersReducedMotion = useReducedMotion();
const {
defaultSpring
} = useAnimationContext();
const shouldAnimate = animate && !prefersReducedMotion;
// Memoize initial categories to prevent infinite loops
const initialCategories = useMemo(() => {
const categories = [...defaultSelectedCategories];
// Always include required categories
cookieCategories.filter(category => category.required).forEach(category => {
if (!categories.includes(category.id)) {
categories.push(category.id);
}
});
return categories;
}, [cookieCategories, defaultSelectedCategories]);
// Track if categories have been initialized to prevent loops
const categoriesInitialized = useRef(false);
// Set initial selected categories only once
useEffect(() => {
if (!categoriesInitialized.current) {
setSelectedCategories(initialCategories);
categoriesInitialized.current = true;
}
}, [initialCategories]);
// Check if consent was previously given
useEffect(() => {
const consentValue = getCookie("cookie-consent");
if (!consentValue) {
const timer = setTimeout(() => {
setVisible(true);
}, delay);
return () => clearTimeout(timer);
}
}, [delay]);
// Handle timeout
useEffect(() => {
if (visible && timeout > 0) {
const timer = setTimeout(() => {
setVisible(false);
if (onTimeout) {
onTimeout();
}
}, timeout);
return () => clearTimeout(timer);
}
}, [visible, timeout, onTimeout]);
const handleToggleCategory = (categoryId, required = false) => {
if (required) return; // Can't toggle required categories
setSelectedCategories(prevSelected => {
const newSelected = prevSelected.includes(categoryId) ? prevSelected.filter(id => id !== categoryId) : [...prevSelected, categoryId];
if (onCategoryChange) {
onCategoryChange(newSelected);
}
return newSelected;
});
};
const handleAcceptAll = () => {
const allCategoryIds = cookieCategories.map(category => category.id);
setCookie("cookie-consent", "accepted", cookieExpiration);
setCookie("cookie-categories", JSON.stringify(allCategoryIds), cookieExpiration);
setVisible(false);
if (onAccept) {
onAccept();
}
};
const handleDeclineAll = () => {
// Only include required categories when declining all
const requiredCategoryIds = cookieCategories.filter(category => category.required).map(category => category.id);
setCookie("cookie-consent", "declined", cookieExpiration);
setCookie("cookie-categories", JSON.stringify(requiredCategoryIds), cookieExpiration);
setVisible(false);
if (onDecline) {
onDecline();
}
};
const handleSavePreferences = () => {
setCookie("cookie-consent", "customized", cookieExpiration);
setCookie("cookie-categories", JSON.stringify(selectedCategories), cookieExpiration);
setVisible(false);
if (onSave) {
onSave(selectedCategories);
}
};
const handleShowDetails = () => {
if (useModalForDetails) {
setShowDetailsModal(true);
} else {
setExpanded(true);
}
};
// Calculate final spring config
const finalSpringConfig = useMemo(() => {
const baseConfig = SpringPresets.default;
let contextConfig = {};
if (typeof defaultSpring === "string" && defaultSpring in SpringPresets) {
contextConfig = SpringPresets[defaultSpring];
} else if (typeof defaultSpring === "object") {
contextConfig = defaultSpring ?? {};
}
return {
...baseConfig,
...contextConfig
};
}, [defaultSpring]);
const positionClass = useMemo(() => {
const key = position ?? "bottom";
return POSITION_CLASS_MAP[key] ?? styles.positionCenterBottom;
}, [position]);
const containerStyleVars = useMemo(() => {
const depth = Math.max(0.5, Math.min(2, glassIntensity));
const shadowDepth = (32 * depth).toFixed(2);
return {
"--cookie-blur-scale": depth,
"--cookie-box-shadow": `0 12px ${shadowDepth}px rgba(15, 23, 42, 0.18)`
};
}, [glassIntensity]);
const isTop = position?.startsWith("top");
const exitY = isTop ? -30 : 30; // Use 30px like original CSS
// Spring for Opacity
const {
value: animatedOpacity
} = useGalileoStateSpring(visible ? 1 : 0, {
...finalSpringConfig,
immediate: !shouldAnimate
});
// Spring for TranslateY
const {
value: animatedTranslateY
} = useGalileoStateSpring(visible ? 0 : exitY, {
...finalSpringConfig,
immediate: !shouldAnimate
});
// Calculate transform
const isCentered = position === "top" || position === "bottom";
const animatedStyle = {
opacity: animatedOpacity,
transform: `translateY(${animatedTranslateY}px)${isCentered ? " translateX(-50%)" : ""}`
};
if (!visible) {
return jsx("div", {
ref: ref,
className: cn(styles.container, positionClass, className),
style: {
...containerStyleVars,
display: "none",
...style
},
"aria-hidden": true,
...rest
});
}
// Create the categories section
const renderCategories = () => jsx("div", {
className: styles.categoryContainer,
children: cookieCategories.map(category => jsxs("div", {
className: styles.categoryItem,
children: [jsxs("div", {
className: styles.categoryHeader,
children: [jsx(GlassCheckbox, {
checked: selectedCategories.includes(category.id),
onCheckedChange: () => handleToggleCategory(category.id, category.required),
disabled: category.required
}), jsxs(Typography, {
variant: "span",
className: 'font-semibold',
children: [category.name, " ", category.required && jsx("em", {
children: "(Required)"
})]
})]
}), jsx("div", {
className: styles.categoryDescription,
children: jsx(Typography, {
variant: "p",
children: category.description
})
}), category.cookies && category.cookies.length > 0 && jsx(Fragment, {
children: jsx("button", {
type: "button",
className: styles.detailsToggle,
"aria-expanded": expanded,
"aria-controls": "cookie-details",
onClick: e => {
// Logic to show cookie details could be expanded here
},
children: "Show cookie details"
})
})]
}, category.id))
});
return jsxs(Fragment, {
children: [jsx("div", {
ref: ref,
className: cn(styles.container, positionClass, className),
style: {
...containerStyleVars,
...animatedStyle,
...style
},
"aria-hidden": !visible,
...rest,
children: jsxs(Box, {
children: [jsx(Typography, {
variant: "h6",
className: 'mb-2 font-semibold',
children: title
}), jsxs(Typography, {
variant: "p",
children: [message, privacyPolicyUrl && jsxs(Fragment, {
children: [" ", jsx("a", {
href: privacyPolicyUrl,
target: "_blank",
rel: "noopener noreferrer",
className: "glass-focus glass-touch-target glass-contrast-guard",
children: privacyPolicyText
})]
})]
}), customContent && jsx(Box, {
className: "glass-mt-4",
children: customContent
}), !expanded && cookieCategories.length > 0 && jsx(GlassButton, {
variant: "ghost",
onClick: handleShowDetails,
size: "sm",
className: "glass-focus glass-touch-target",
children: "Customize settings"
}), (expanded || initiallyExpanded) && cookieCategories.length > 0 && jsx("div", {
id: "cookie-details",
children: renderCategories()
}), jsxs("div", {
className: styles.buttonContainer,
children: [dismissible && jsx(GlassButton, {
variant: "outline",
onClick: handleDeclineAll,
size: "sm",
className: "glass-focus glass-touch-target",
children: declineButtonText
}), expanded && enableSettings && jsx(GlassButton, {
variant: "outline",
onClick: handleSavePreferences,
size: "sm",
className: "glass-focus glass-touch-target",
children: settingsButtonText
}), jsx(GlassButton, {
variant: "primary",
onClick: handleAcceptAll,
size: "sm",
className: "glass-focus glass-touch-target",
children: acceptButtonText
})]
})]
})
}), useModalForDetails && jsx(GlassModal, {
open: showDetailsModal,
onClose: () => setShowDetailsModal(false),
children: jsxs("div", {
className: 'dialog-container',
children: [jsxs("div", {
className: 'dialog-header',
children: [jsx(Typography, {
variant: "h6",
children: "Cookie Settings"
}), jsx(GlassButton, {
variant: "ghost",
onClick: e => setShowDetailsModal(false),
className: "glass-focus glass-touch-target",
children: "\u00D7"
})]
}), jsx("div", {
className: 'dialog-content',
children: renderCategories()
}), jsxs("div", {
className: 'dialog-actions',
children: [jsx(GlassButton, {
variant: "outline",
onClick: e => setShowDetailsModal(false),
className: "glass-focus glass-touch-target",
children: "Cancel"
}), jsx(GlassButton, {
variant: "primary",
onClick: e => {
handleSavePreferences();
setShowDetailsModal(false);
},
className: "glass-focus glass-touch-target",
children: "Save Preferences"
})]
})]
})
})]
});
});
GlobalCookieConsent.displayName = "GlobalCookieConsent";
// Glass version of the GlobalCookieConsent
const GlassGlobalCookieConsent = /*#__PURE__*/forwardRef((props, ref) => jsx(GlobalCookieConsent, {
ref: ref,
glassIntensity: 0.9,
...props
}));
GlassGlobalCookieConsent.displayName = "GlassGlobalCookieConsent";
export { GlassGlobalCookieConsent, GlobalCookieConsent };
//# sourceMappingURL=GlobalCookieConsent.js.map