UNPKG

@consentry/ui

Version:

Composable UI components for the Consentry consent manager. Built with Emotion and designed for use with @consentry/core or @consentry/next.

745 lines (725 loc) 20.7 kB
// src/CookieBanner.tsx import { X } from "lucide-react"; // src/StyledComponents.tsx import styled from "@emotion/styled"; import { Switch } from "@headlessui/react"; import { motion } from "framer-motion"; var Wrapper = styled.div` position: relative; z-index: 9999; `; var Overlay = styled(motion.div)` position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); padding: 1rem; z-index: 50; display: flex; align-items: center; justify-content: center; `; var ModalContainer = styled(motion.div, { shouldForwardProp: (prop) => prop !== "dark" })` background: ${({ dark }) => dark ? "#111827" : "white"}; color: ${({ dark }) => dark ? "white" : "#111827"}; border-radius: 0.75rem; padding: 1.5rem; width: 100%; max-width: 32rem; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); `; var BannerWrapper = styled(motion.div, { shouldForwardProp: (prop) => !["dark", "mode", "colors", "theme"].includes(prop) })` position: fixed; z-index: 50; padding: 1.5rem; background-color: ${({ colors, theme = "light" }) => colors?.[theme]?.background}; color: ${({ colors, theme = "light" }) => colors?.[theme]?.text}; font-size: 0.875rem; border: 1px solid ${({ colors, theme = "light" }) => colors?.[theme]?.border}; box-shadow: rgba(50, 50, 93, 0.1) 0px 8px 24px, rgba(0, 0, 0, 0.06) 0px 4px 12px; ${({ mode }) => mode === "top" && ` top: 0; left: 0; width: 100vw; border-radius: 0 0 0.75rem 0.75rem; padding: 1.5rem 2.5rem 1.5rem 2rem; `} ${({ mode }) => mode === "bottom" && ` bottom: 0; left: 0; width: 100vw; border-radius: 0.75rem 0.75rem 0 0; padding: 1.5rem 2.5rem 1.5rem 2rem; `} ${({ mode }) => mode === "modal" && ` top: 50%; left: 50%; transform: translate(-50%, -50%); width: 100%; max-width: 42rem; border-radius: 1rem; `} `; var Header = styled.div` display: flex; justify-content: space-between; align-items: flex-start; gap: 1.5rem; `; var Content = styled.div` flex: 1; `; var Title = styled.h2` font-size: ${({ large }) => large ? "1.25rem" : "1.125rem"}; font-weight: 600; margin-bottom: ${({ large }) => large ? "0.5rem" : "0.25rem"}; color: ${({ dark }) => dark ? "white" : "inherit"}; `; var Message = styled.p` font-size: 0.875rem; line-height: 1.5; `; var Section = styled.div` display: flex; flex-direction: column; gap: 1rem; `; var Row = styled.div` display: flex; justify-content: space-between; align-items: flex-start; `; var RowText = styled.div` padding-right: 1rem; `; var RowTitle = styled.p` font-size: 0.875rem; font-weight: 500; color: ${({ dark }) => dark ? "#ffffff" : "#111827"}; `; var RowDescription = styled.p` font-size: 0.75rem; color: ${({ dark }) => dark ? "#9ca3af" : "#6b7280"}; `; var ButtonRow = styled.div` display: flex; justify-content: ${({ align = "end" }) => align === "center" ? "center" : "flex-end"}; gap: 0.75rem; margin-top: 1.5rem; flex-wrap: wrap; `; var ActionButton = styled.button` color: ${({ outlined, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return outlined ? themeColors?.primary ?? "#000000" : themeColors?.primaryText ?? "#ffffff"; }}; background-color: ${({ outlined, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return outlined ? "transparent" : themeColors?.primary ?? "#000000"; }}; border: ${({ outlined, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return outlined ? `2px solid ${themeColors?.primary ?? "#000000"}` : "none"; }}; font-weight: 500; font-size: 0.875rem; padding: 0.625rem 1.25rem; border-radius: 0.5rem; transition: background-color 0.2s ease, border-color 0.2s ease; cursor: pointer; &:hover { background-color: ${({ outlined, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return outlined ? "transparent" : themeColors?.primaryHover ?? "#000000"; }}; border-color: ${({ outlined, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return outlined ? themeColors?.primaryHover ?? "#000000" : "none"; }}; } &:focus { outline: none; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); } `; var SecondaryButton = styled.button` font-size: 0.875rem; color: ${({ dark }) => dark ? "#ffffff" : "#6b7280"}; background: transparent; border: none; cursor: pointer; &:hover { text-decoration: underline; } `; var CloseButton = styled.button` color: #9ca3af; background: transparent; border: none; padding: 0.25rem; cursor: pointer; &:hover { color: #6b7280; } &:focus { outline: none; } `; var StyledSwitch = styled(Switch)` margin-top: 1px; flex-shrink: 0; width: 44px; height: 24px; padding: 2px; display: flex; align-items: center; justify-content: flex-start; border-radius: 9999px; background-color: ${({ checked, colors, theme = "light" }) => { const themeColors = colors?.[theme]; return checked ? themeColors?.primary ?? "#000000" : "#d1d5db"; }}; transition: background-color 0.2s ease; cursor: pointer; border: none; &:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); } `; var SwitchThumb = styled(motion.span)` width: 16px; height: 16px; background-color: white; border-radius: 9999px; `; var FloatingButton = styled.button` position: fixed; bottom: 1rem; left: 1rem; z-index: 40; padding: 0.5rem; background-color: ${({ colors, theme = "light" }) => { const themeColors = colors?.[theme]; return themeColors?.settingsButton ?? "#000000"; }}; color: ${({ colors, theme = "light" }) => { const themeColors = colors?.[theme]; return themeColors?.settingsButtonText ?? "#ffffff"; }}; border-radius: 9999px; border: none; cursor: pointer; transition: background-color 0.2s ease; width: 2.5rem; height: 2.5rem; svg { width: 1.5rem; height: 1.5rem; } &:hover { background-color: ${({ colors, theme = "light" }) => { const themeColors = colors?.[theme]; return themeColors?.settingsButtonHover ?? "#000000"; }}; } `; // src/CookieBanner.tsx import { jsx, jsxs } from "react/jsx-runtime"; var CookieBanner = ({ onClose, onCustomizeClick, onAcceptAll, onRejectAll, mode, dark = false, labels, classNames, colors, theme = "light" }) => { const isModal = mode === "modal"; return /* @__PURE__ */ jsxs( BannerWrapper, { mode, dark, colors, theme, role: "dialog", "aria-modal": "true", "aria-labelledby": "cookie-banner-title", className: classNames?.container, initial: isModal ? { opacity: 0, scale: 0.95, x: "-50%", y: "-50%" } : { opacity: 0, y: mode === "top" ? -100 : 100 }, animate: isModal ? { opacity: 1, scale: 1, x: "-50%", y: "-50%" } : { opacity: 1, y: 0 }, exit: isModal ? { opacity: 0, scale: 0.95, x: "-50%", y: "-50%" } : { opacity: 0, y: mode === "top" ? -100 : 100 }, transition: { duration: 0.3, ease: [0.4, 0, 0.2, 1] }, children: [ /* @__PURE__ */ jsxs(Header, { className: classNames?.header, children: [ /* @__PURE__ */ jsxs(Content, { className: classNames?.content, children: [ /* @__PURE__ */ jsx(Title, { id: "cookie-banner-title", className: classNames?.title, children: labels?.title }), /* @__PURE__ */ jsx(Message, { className: classNames?.message, children: labels?.description }) ] }), /* @__PURE__ */ jsx( CloseButton, { onClick: onClose, "aria-label": "Close banner", className: classNames?.closeButton, children: /* @__PURE__ */ jsx(X, { size: 20 }) } ) ] }), /* @__PURE__ */ jsxs(ButtonRow, { className: classNames?.buttonRow, children: [ /* @__PURE__ */ jsx( SecondaryButton, { style: { marginRight: "0.5rem" }, dark, onClick: onCustomizeClick, className: classNames?.customizeButton, children: labels?.customize } ), /* @__PURE__ */ jsx( ActionButton, { outlined: true, dark, onClick: onRejectAll, className: classNames?.rejectButton, colors, theme, children: labels?.rejectAll } ), /* @__PURE__ */ jsx( ActionButton, { dark, onClick: onAcceptAll, className: classNames?.acceptButton, colors, theme, children: labels?.acceptAll } ) ] }) ] } ); }; // src/CookieSettingsModal.tsx import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; var CookieSettingsModal = ({ localPrefs, setLocalPrefs, categories, onSave, onClose, dark = false, labels, classNames, colors, theme = "light" }) => { const handleToggle = (key) => { setLocalPrefs((prev) => ({ ...prev, [key]: !prev[key] })); }; return /* @__PURE__ */ jsx2( Overlay, { className: classNames?.overlay, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.25 }, children: /* @__PURE__ */ jsxs2( ModalContainer, { dark, initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 20 }, transition: { duration: 0.3, ease: [0.4, 0, 0.2, 1] }, className: classNames?.container, children: [ /* @__PURE__ */ jsx2(Title, { large: true, dark, className: classNames?.title, children: labels?.title }), /* @__PURE__ */ jsx2(Section, { className: classNames?.section, children: categories.map(({ key, title, description, mandatory }) => /* @__PURE__ */ jsxs2(Row, { className: classNames?.row, children: [ /* @__PURE__ */ jsxs2(RowText, { className: classNames?.rowText, children: [ /* @__PURE__ */ jsx2(RowTitle, { dark, className: classNames?.rowTitle, children: title }), /* @__PURE__ */ jsx2(RowDescription, { dark, className: classNames?.rowDescription, children: description }) ] }), /* @__PURE__ */ jsx2( StyledSwitch, { checked: mandatory ? true : localPrefs[key], onChange: () => mandatory ? null : handleToggle(key), className: classNames?.toggleSwitch, colors, theme, children: /* @__PURE__ */ jsx2( SwitchThumb, { animate: { x: localPrefs[key] ? 20 : 4 }, transition: { type: "spring", stiffness: 400, damping: 30 }, className: classNames?.toggleThumb } ) } ) ] }, key)) }), /* @__PURE__ */ jsxs2(ButtonRow, { className: classNames?.buttonRow, children: [ /* @__PURE__ */ jsx2( SecondaryButton, { style: { marginRight: "0.5rem" }, onClick: onClose, className: classNames?.cancelButton, children: labels?.cancel } ), /* @__PURE__ */ jsx2( ActionButton, { dark, onClick: onSave, className: classNames?.saveButton, colors, theme, children: labels?.save } ) ] }) ] } ) } ); }; // src/SettingsButton.tsx import { Cookie } from "lucide-react"; import { jsx as jsx3 } from "react/jsx-runtime"; var SettingsButton = ({ setVisible, open, className, icon, colors, theme = "light" }) => { if (!open) return null; return /* @__PURE__ */ jsx3( FloatingButton, { onClick: () => setVisible(true), "aria-label": "Reopen preferences", className, colors, theme, children: icon ?? /* @__PURE__ */ jsx3(Cookie, { "aria-hidden": "true", focusable: "false" }) } ); }; // src/consentUI.ts var opener = null; function setConsentOpener(fn) { opener = fn; } function openConsentSettings() { if (opener) { opener(); } else { console.warn("[consentry] ConsentManager is not mounted yet."); } } // src/ConsentManager.tsx import { useEffect, useState } from "react"; import merge from "lodash-es/merge"; import { useConsentManager } from "@consentry/next"; // src/defaultClassNames.ts var defaultClassNames = { wrapper: "consent-wrapper", banner: { container: "cookie-banner", header: "cookie-banner-header", title: "cookie-banner-title", message: "cookie-banner-message", closeButton: "cookie-banner-close-button", buttonRow: "cookie-banner-button-row", acceptButton: "cookie-banner-accept-button", rejectButton: "cookie-banner-reject-button", customizeButton: "cookie-banner-customize-button", content: "cookie-banner-content" }, modal: { overlay: "cookie-modal-overlay", container: "cookie-modal-container", title: "cookie-modal-title", section: "cookie-modal-section", row: "cookie-modal-row", rowText: "cookie-modal-row-text", rowTitle: "cookie-modal-row-title", rowDescription: "cookie-modal-row-description", toggleSwitch: "cookie-modal-switch", toggleThumb: "cookie-modal-switch-thumb", buttonRow: "cookie-modal-button-row", saveButton: "cookie-modal-save-button", cancelButton: "cookie-modal-cancel-button" }, settingsButton: "cookie-settings-button" }; // src/defaultColors.ts var defaultColors = { light: { primary: "#1d4ed8", primaryHover: "#1e40af", primaryText: "#ffffff", settingsButton: "#1d4ed8", settingsButtonHover: "#1e40af", settingsButtonText: "#ffffff", background: "#ffffff", text: "#111827", border: "#e5e7eb" }, dark: { primary: "#3b82f6", primaryHover: "#2563eb", primaryText: "#ffffff", settingsButton: "#3b82f6", settingsButtonHover: "#2563eb", settingsButtonText: "#ffffff", background: "#111827", text: "#ffffff", border: "#374151" } }; // src/ConsentManager.tsx import { jsx as jsx4, jsxs as jsxs3 } from "@emotion/react/jsx-runtime"; var DEFAULT_CATEGORIES = [ { key: "functional", title: "Essential Cookies", description: "These cookies are necessary for the website to function and cannot be disabled. They ensure core features like security, accessibility, and network management work properly.", mandatory: true }, { key: "performance", title: "Performance Cookies", description: "These cookies help us understand how visitors interact with the website by collecting and reporting information anonymously. This allows us to improve performance and user experience." }, { key: "advertising", title: "Advertising Cookies", description: "These cookies are used to deliver relevant advertisements and measure the effectiveness of our marketing campaigns across platforms." }, { key: "social", title: "Social Media Cookies", description: "These cookies enable you to share content through social media platforms and allow us to integrate social features like comment sections and media embeds." } ]; var DEFAULT_LABELS = { banner: { title: "Manage your cookie preferences", description: "We use cookies to enhance site navigation, analyze usage, and assist in our marketing efforts. You can choose which types of cookies to allow. Essential cookies are always active to ensure core functionality.", acceptAll: "Accept all", rejectAll: "Reject all", customize: "Customize" }, modal: { title: "Customize Cookie Preferences", save: "Save Preferences", cancel: "Cancel" } }; var ConsentManager = ({ mode, dark = false, hideSettingsButton = false, categories, labels, classNames, settingsButtonIcon, colors, theme = "light" }) => { const { cookiePreferences, setCookiePreferences, isConsentKnown, showConsentBanner } = useConsentManager(); const mergedLabels = merge({}, DEFAULT_LABELS, labels ?? {}); const mergedClassNames = merge({}, defaultClassNames, classNames ?? {}); const mergedCategories = categories ?? DEFAULT_CATEGORIES; const mergedColors = merge({}, defaultColors, colors ?? {}); useEffect(() => { setConsentOpener(() => { setLocalPrefs({ ...cookiePreferences }); setShowSettings(true); setShowBanner(false); }); return () => { setConsentOpener(() => { console.warn("[consentry] ConsentManager was unmounted."); }); }; }, [cookiePreferences]); const [localPrefs, setLocalPrefs] = useState({ ...cookiePreferences }); const [showSettings, setShowSettings] = useState(false); const [showBanner, setShowBanner] = useState(false); useEffect(() => { if (isConsentKnown && showConsentBanner) { setShowBanner(true); } else { setShowBanner(false); } }, [isConsentKnown, showConsentBanner]); const handleSaveSettings = () => { setCookiePreferences(localPrefs); setShowSettings(false); setShowBanner(false); }; const handleRejectAll = () => { setCookiePreferences({ functional: true, performance: false, advertising: false, social: false }); setShowBanner(false); }; const handleAcceptAll = () => { setCookiePreferences({ functional: true, performance: true, advertising: true, social: true }); setShowBanner(false); }; const handleOpenSettings = () => { setLocalPrefs({ ...cookiePreferences }); setShowSettings(true); setShowBanner(false); }; const handleCloseSettings = () => { if (showConsentBanner) { setShowBanner(true); } setShowSettings(false); }; return /* @__PURE__ */ jsxs3(Wrapper, { className: mergedClassNames.wrapper, children: [ showBanner && /* @__PURE__ */ jsx4( CookieBanner, { onClose: () => setShowBanner(false), onCustomizeClick: () => { setShowBanner(false); setShowSettings(true); }, onAcceptAll: () => { const newPrefs = Object.fromEntries( mergedCategories.map(({ key, mandatory }) => [key, mandatory ?? true]) ); setCookiePreferences(newPrefs); setShowBanner(false); }, onRejectAll: () => { const newPrefs = Object.fromEntries( mergedCategories.map(({ key, mandatory }) => [key, mandatory ?? false]) ); setCookiePreferences(newPrefs); setShowBanner(false); }, mode, dark, labels: mergedLabels.banner, classNames: mergedClassNames.banner, colors: mergedColors, theme } ), showSettings && /* @__PURE__ */ jsx4( CookieSettingsModal, { localPrefs, setLocalPrefs, categories: mergedCategories, onSave: handleSaveSettings, onClose: () => setShowSettings(false), dark, labels: mergedLabels.modal, classNames: mergedClassNames.modal, colors: mergedColors, theme } ), !hideSettingsButton && /* @__PURE__ */ jsx4( SettingsButton, { setVisible: setShowSettings, open: !showSettings && !showBanner, className: mergedClassNames.settingsButton, icon: settingsButtonIcon, colors: mergedColors, theme } ) ] }); }; export { ActionButton, BannerWrapper, ButtonRow, CloseButton, Content, CookieBanner, CookieSettingsModal, FloatingButton, Header, Message, ModalContainer, Overlay, Row, RowDescription, RowText, RowTitle, SecondaryButton, Section, SettingsButton, StyledSwitch, SwitchThumb, Title, Wrapper, ConsentManager as default, openConsentSettings, setConsentOpener }; //# sourceMappingURL=index.mjs.map