@consentry/ui
Version:
Composable UI components for the Consentry consent manager. Built with Emotion and designed for use with @consentry/core or @consentry/next.
806 lines (784 loc) • 24.5 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
ActionButton: () => ActionButton,
BannerWrapper: () => BannerWrapper,
ButtonRow: () => ButtonRow,
CloseButton: () => CloseButton,
Content: () => Content,
CookieBanner: () => CookieBanner,
CookieSettingsModal: () => CookieSettingsModal,
FloatingButton: () => FloatingButton,
Header: () => Header,
Message: () => Message,
ModalContainer: () => ModalContainer,
Overlay: () => Overlay,
Row: () => Row,
RowDescription: () => RowDescription,
RowText: () => RowText,
RowTitle: () => RowTitle,
SecondaryButton: () => SecondaryButton,
Section: () => Section,
SettingsButton: () => SettingsButton,
StyledSwitch: () => StyledSwitch,
SwitchThumb: () => SwitchThumb,
Title: () => Title,
Wrapper: () => Wrapper,
default: () => ConsentManager,
openConsentSettings: () => openConsentSettings,
setConsentOpener: () => setConsentOpener
});
module.exports = __toCommonJS(index_exports);
// src/CookieBanner.tsx
var import_lucide_react = require("lucide-react");
// src/StyledComponents.tsx
var import_styled = __toESM(require("@emotion/styled"));
var import_react = require("@headlessui/react");
var import_framer_motion = require("framer-motion");
var Wrapper = import_styled.default.div`
position: relative;
z-index: 9999;
`;
var Overlay = (0, import_styled.default)(import_framer_motion.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 = (0, import_styled.default)(import_framer_motion.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 = (0, import_styled.default)(import_framer_motion.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 = import_styled.default.div`
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1.5rem;
`;
var Content = import_styled.default.div`
flex: 1;
`;
var Title = import_styled.default.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 = import_styled.default.p`
font-size: 0.875rem;
line-height: 1.5;
`;
var Section = import_styled.default.div`
display: flex;
flex-direction: column;
gap: 1rem;
`;
var Row = import_styled.default.div`
display: flex;
justify-content: space-between;
align-items: flex-start;
`;
var RowText = import_styled.default.div`
padding-right: 1rem;
`;
var RowTitle = import_styled.default.p`
font-size: 0.875rem;
font-weight: 500;
color: ${({ dark }) => dark ? "#ffffff" : "#111827"};
`;
var RowDescription = import_styled.default.p`
font-size: 0.75rem;
color: ${({ dark }) => dark ? "#9ca3af" : "#6b7280"};
`;
var ButtonRow = import_styled.default.div`
display: flex;
justify-content: ${({ align = "end" }) => align === "center" ? "center" : "flex-end"};
gap: 0.75rem;
margin-top: 1.5rem;
flex-wrap: wrap;
`;
var ActionButton = import_styled.default.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 = import_styled.default.button`
font-size: 0.875rem;
color: ${({ dark }) => dark ? "#ffffff" : "#6b7280"};
background: transparent;
border: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
`;
var CloseButton = import_styled.default.button`
color: #9ca3af;
background: transparent;
border: none;
padding: 0.25rem;
cursor: pointer;
&:hover {
color: #6b7280;
}
&:focus {
outline: none;
}
`;
var StyledSwitch = (0, import_styled.default)(import_react.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 = (0, import_styled.default)(import_framer_motion.motion.span)`
width: 16px;
height: 16px;
background-color: white;
border-radius: 9999px;
`;
var FloatingButton = import_styled.default.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
var import_jsx_runtime = require("react/jsx-runtime");
var CookieBanner = ({
onClose,
onCustomizeClick,
onAcceptAll,
onRejectAll,
mode,
dark = false,
labels,
classNames,
colors,
theme = "light"
}) => {
const isModal = mode === "modal";
return /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)(Header, { className: classNames?.header, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, { className: classNames?.content, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, { id: "cookie-banner-title", className: classNames?.title, children: labels?.title }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Message, { className: classNames?.message, children: labels?.description })
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
CloseButton,
{
onClick: onClose,
"aria-label": "Close banner",
className: classNames?.closeButton,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { size: 20 })
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ButtonRow, { className: classNames?.buttonRow, children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
SecondaryButton,
{
style: { marginRight: "0.5rem" },
dark,
onClick: onCustomizeClick,
className: classNames?.customizeButton,
children: labels?.customize
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
ActionButton,
{
outlined: true,
dark,
onClick: onRejectAll,
className: classNames?.rejectButton,
colors,
theme,
children: labels?.rejectAll
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
ActionButton,
{
dark,
onClick: onAcceptAll,
className: classNames?.acceptButton,
colors,
theme,
children: labels?.acceptAll
}
)
] })
]
}
);
};
// src/CookieSettingsModal.tsx
var import_jsx_runtime2 = require("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__ */ (0, import_jsx_runtime2.jsx)(
Overlay,
{
className: classNames?.overlay,
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0 },
transition: { duration: 0.25 },
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
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__ */ (0, import_jsx_runtime2.jsx)(Title, { large: true, dark, className: classNames?.title, children: labels?.title }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Section, { className: classNames?.section, children: categories.map(({ key, title, description, mandatory }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Row, { className: classNames?.row, children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(RowText, { className: classNames?.rowText, children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RowTitle, { dark, className: classNames?.rowTitle, children: title }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RowDescription, { dark, className: classNames?.rowDescription, children: description })
] }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
StyledSwitch,
{
checked: mandatory ? true : localPrefs[key],
onChange: () => mandatory ? null : handleToggle(key),
className: classNames?.toggleSwitch,
colors,
theme,
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
SwitchThumb,
{
animate: { x: localPrefs[key] ? 20 : 4 },
transition: { type: "spring", stiffness: 400, damping: 30 },
className: classNames?.toggleThumb
}
)
}
)
] }, key)) }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(ButtonRow, { className: classNames?.buttonRow, children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
SecondaryButton,
{
style: { marginRight: "0.5rem" },
onClick: onClose,
className: classNames?.cancelButton,
children: labels?.cancel
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
ActionButton,
{
dark,
onClick: onSave,
className: classNames?.saveButton,
colors,
theme,
children: labels?.save
}
)
] })
]
}
)
}
);
};
// src/SettingsButton.tsx
var import_lucide_react2 = require("lucide-react");
var import_jsx_runtime3 = require("react/jsx-runtime");
var SettingsButton = ({
setVisible,
open,
className,
icon,
colors,
theme = "light"
}) => {
if (!open) return null;
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
FloatingButton,
{
onClick: () => setVisible(true),
"aria-label": "Reopen preferences",
className,
colors,
theme,
children: icon ?? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.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
var import_react2 = require("react");
var import_merge = __toESM(require("lodash-es/merge"));
var import_next = require("@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
var import_jsx_runtime4 = require("@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 } = (0, import_next.useConsentManager)();
const mergedLabels = (0, import_merge.default)({}, DEFAULT_LABELS, labels ?? {});
const mergedClassNames = (0, import_merge.default)({}, defaultClassNames, classNames ?? {});
const mergedCategories = categories ?? DEFAULT_CATEGORIES;
const mergedColors = (0, import_merge.default)({}, defaultColors, colors ?? {});
(0, import_react2.useEffect)(() => {
setConsentOpener(() => {
setLocalPrefs({ ...cookiePreferences });
setShowSettings(true);
setShowBanner(false);
});
return () => {
setConsentOpener(() => {
console.warn("[consentry] ConsentManager was unmounted.");
});
};
}, [cookiePreferences]);
const [localPrefs, setLocalPrefs] = (0, import_react2.useState)({ ...cookiePreferences });
const [showSettings, setShowSettings] = (0, import_react2.useState)(false);
const [showBanner, setShowBanner] = (0, import_react2.useState)(false);
(0, import_react2.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__ */ (0, import_jsx_runtime4.jsxs)(Wrapper, { className: mergedClassNames.wrapper, children: [
showBanner && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
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__ */ (0, import_jsx_runtime4.jsx)(
CookieSettingsModal,
{
localPrefs,
setLocalPrefs,
categories: mergedCategories,
onSave: handleSaveSettings,
onClose: () => setShowSettings(false),
dark,
labels: mergedLabels.modal,
classNames: mergedClassNames.modal,
colors: mergedColors,
theme
}
),
!hideSettingsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
SettingsButton,
{
setVisible: setShowSettings,
open: !showSettings && !showBanner,
className: mergedClassNames.settingsButton,
icon: settingsButtonIcon,
colors: mergedColors,
theme
}
)
] });
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ActionButton,
BannerWrapper,
ButtonRow,
CloseButton,
Content,
CookieBanner,
CookieSettingsModal,
FloatingButton,
Header,
Message,
ModalContainer,
Overlay,
Row,
RowDescription,
RowText,
RowTitle,
SecondaryButton,
Section,
SettingsButton,
StyledSwitch,
SwitchThumb,
Title,
Wrapper,
openConsentSettings,
setConsentOpener
});
//# sourceMappingURL=index.js.map