@raydenui/ui 0.9.7 → 0.10.0
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/Table-DCNSxua1.d.cts +31 -0
- package/dist/Table-DCNSxua1.d.ts +31 -0
- package/dist/blocks/ApplicationShellBlock.cjs +157 -0
- package/dist/blocks/ApplicationShellBlock.js +154 -0
- package/dist/blocks/CheckoutReviewBlock.cjs +16 -0
- package/dist/blocks/CheckoutReviewBlock.js +13 -0
- package/dist/blocks/CommandPaletteBlock.cjs +334 -0
- package/dist/blocks/CommandPaletteBlock.js +331 -0
- package/dist/blocks/CreateAccountBlock.cjs +147 -0
- package/dist/blocks/CreateAccountBlock.js +143 -0
- package/dist/blocks/EmptyStateBlock.cjs +19 -0
- package/dist/blocks/EmptyStateBlock.js +16 -0
- package/dist/blocks/FeatureOverviewBlock.cjs +54 -0
- package/dist/blocks/FeatureOverviewBlock.js +51 -0
- package/dist/blocks/HeaderBlock.cjs +230 -0
- package/dist/blocks/HeaderBlock.js +227 -0
- package/dist/blocks/KpiOverviewBlock.cjs +63 -0
- package/dist/blocks/KpiOverviewBlock.js +60 -0
- package/dist/blocks/LoginBlock.cjs +66 -0
- package/dist/blocks/LoginBlock.js +63 -0
- package/dist/blocks/NotificationsBlock.cjs +17 -0
- package/dist/blocks/NotificationsBlock.js +14 -0
- package/dist/blocks/PageHeaderBlock.cjs +121 -0
- package/dist/blocks/PageHeaderBlock.js +118 -0
- package/dist/blocks/PricingPlansBlock.cjs +146 -0
- package/dist/blocks/PricingPlansBlock.js +143 -0
- package/dist/blocks/ProductCollectionBlock.cjs +37 -0
- package/dist/blocks/ProductCollectionBlock.js +34 -0
- package/dist/blocks/ProductDetailBlock.cjs +30 -0
- package/dist/blocks/ProductDetailBlock.js +27 -0
- package/dist/blocks/ProductHeroBlock.cjs +83 -0
- package/dist/blocks/ProductHeroBlock.js +80 -0
- package/dist/blocks/ProfileSettingsBlock.cjs +130 -0
- package/dist/blocks/ProfileSettingsBlock.js +126 -0
- package/dist/blocks/QuickSendBlock.cjs +11 -0
- package/dist/blocks/QuickSendBlock.js +8 -0
- package/dist/blocks/RecentTransactionsBlock.cjs +13 -0
- package/dist/blocks/RecentTransactionsBlock.js +10 -0
- package/dist/blocks/SearchableTableBlock.cjs +91 -0
- package/dist/blocks/SearchableTableBlock.js +88 -0
- package/dist/blocks/ShoppingCartBlock.cjs +16 -0
- package/dist/blocks/ShoppingCartBlock.js +13 -0
- package/dist/blocks/SiteFooterBlock.cjs +69 -0
- package/dist/blocks/SiteFooterBlock.js +66 -0
- package/dist/blocks/TableBlock.cjs +20 -0
- package/dist/blocks/TableBlock.js +17 -0
- package/dist/blocks/TaskListBlock.cjs +198 -0
- package/dist/blocks/TaskListBlock.js +195 -0
- package/dist/blocks/WorkspaceSwitcherBlock.cjs +291 -0
- package/dist/blocks/WorkspaceSwitcherBlock.js +288 -0
- package/dist/blocks/commerce.cjs +44 -0
- package/dist/blocks/commerce.js +35 -0
- package/dist/blocks/index.cjs +53 -0
- package/dist/blocks/index.js +24 -0
- package/dist/blocks.d.cts +1820 -0
- package/dist/blocks.d.ts +1820 -0
- package/dist/chart.cjs +13 -0
- package/dist/chart.d.cts +59 -0
- package/dist/chart.d.ts +59 -0
- package/dist/chart.js +5 -0
- package/dist/components/Accordion/Accordion.cjs +94 -0
- package/dist/components/Accordion/Accordion.js +91 -0
- package/dist/components/Accordion/index.cjs +8 -0
- package/dist/components/Accordion/index.js +1 -0
- package/dist/components/ActivityFeed/ActivityContent.cjs +40 -0
- package/dist/components/ActivityFeed/ActivityContent.js +37 -0
- package/dist/components/ActivityFeed/ActivityItem.cjs +19 -0
- package/dist/components/ActivityFeed/ActivityItem.js +16 -0
- package/dist/components/ActivityFeed/index.cjs +7 -0
- package/dist/components/ActivityFeed/index.js +2 -0
- package/dist/components/Alert/Alert.cjs +42 -0
- package/dist/components/Alert/Alert.js +39 -0
- package/dist/components/Alert/index.cjs +5 -0
- package/dist/components/Alert/index.js +1 -0
- package/dist/components/Avatar/Avatar.cjs +101 -0
- package/dist/components/Avatar/Avatar.js +98 -0
- package/dist/components/Avatar/index.cjs +6 -0
- package/dist/components/Avatar/index.js +1 -0
- package/dist/components/Badge/Badge.cjs +53 -0
- package/dist/components/Badge/Badge.js +50 -0
- package/dist/components/Badge/index.cjs +5 -0
- package/dist/components/Badge/index.js +1 -0
- package/dist/components/Banner/Banner.cjs +83 -0
- package/dist/components/Banner/Banner.js +80 -0
- package/dist/components/Banner/index.cjs +5 -0
- package/dist/components/Banner/index.js +1 -0
- package/dist/components/Breadcrumb/Breadcrumb.cjs +31 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +28 -0
- package/dist/components/Breadcrumb/index.cjs +5 -0
- package/dist/components/Breadcrumb/index.js +1 -0
- package/dist/components/Button/Button.cjs +88 -0
- package/dist/components/Button/Button.js +85 -0
- package/dist/components/Button/index.cjs +5 -0
- package/dist/components/Button/index.js +1 -0
- package/dist/components/ButtonGroup/ButtonGroup.cjs +8 -0
- package/dist/components/ButtonGroup/ButtonGroup.js +5 -0
- package/dist/components/ButtonGroup/ButtonGroupItem.cjs +15 -0
- package/dist/components/ButtonGroup/ButtonGroupItem.js +12 -0
- package/dist/components/ButtonGroup/index.cjs +7 -0
- package/dist/components/ButtonGroup/index.js +2 -0
- package/dist/components/Card/Card.cjs +94 -0
- package/dist/components/Card/Card.js +91 -0
- package/dist/components/Card/index.cjs +9 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Chart/Chart.cjs +160 -0
- package/dist/components/Chart/Chart.js +153 -0
- package/dist/components/Chart/index.cjs +10 -0
- package/dist/components/Chart/index.js +2 -0
- package/dist/components/Chart/theme.cjs +67 -0
- package/dist/components/Chart/theme.js +62 -0
- package/dist/components/Chip/Chip.cjs +18 -0
- package/dist/components/Chip/Chip.js +15 -0
- package/dist/components/Chip/index.cjs +5 -0
- package/dist/components/Chip/index.js +1 -0
- package/dist/components/Counter/Counter.cjs +74 -0
- package/dist/components/Counter/Counter.js +71 -0
- package/dist/components/Counter/index.cjs +6 -0
- package/dist/components/Counter/index.js +1 -0
- package/dist/components/DatePicker/DatePicker.cjs +343 -0
- package/dist/components/DatePicker/DatePicker.js +340 -0
- package/dist/components/DatePicker/index.cjs +5 -0
- package/dist/components/DatePicker/index.js +1 -0
- package/dist/components/Divider/Divider.cjs +31 -0
- package/dist/components/Divider/Divider.js +28 -0
- package/dist/components/Divider/index.cjs +5 -0
- package/dist/components/Divider/index.js +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.cjs +108 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +105 -0
- package/dist/components/DropdownMenu/index.cjs +11 -0
- package/dist/components/DropdownMenu/index.js +1 -0
- package/dist/components/EmptyStateIllustration/EmptyStateIllustration.cjs +42 -0
- package/dist/components/EmptyStateIllustration/EmptyStateIllustration.js +39 -0
- package/dist/components/EmptyStateIllustration/illustrations.cjs +3322 -0
- package/dist/components/EmptyStateIllustration/illustrations.js +3319 -0
- package/dist/components/EmptyStateIllustration/index.cjs +7 -0
- package/dist/components/EmptyStateIllustration/index.js +2 -0
- package/dist/components/FileUpload/FileUpload.cjs +116 -0
- package/dist/components/FileUpload/FileUpload.js +113 -0
- package/dist/components/FileUpload/FileUploadDropZone.cjs +58 -0
- package/dist/components/FileUpload/FileUploadDropZone.js +55 -0
- package/dist/components/FileUpload/FileUploadIcons.cjs +55 -0
- package/dist/components/FileUpload/FileUploadIcons.js +50 -0
- package/dist/components/FileUpload/FileUploadItem.cjs +34 -0
- package/dist/components/FileUpload/FileUploadItem.js +31 -0
- package/dist/components/FileUpload/index.cjs +9 -0
- package/dist/components/FileUpload/index.js +3 -0
- package/dist/components/FormControl/Checkbox.cjs +27 -0
- package/dist/components/FormControl/Checkbox.js +24 -0
- package/dist/components/FormControl/Radio.cjs +15 -0
- package/dist/components/FormControl/Radio.js +12 -0
- package/dist/components/FormControl/Toggle.cjs +15 -0
- package/dist/components/FormControl/Toggle.js +12 -0
- package/dist/components/FormControl/index.cjs +9 -0
- package/dist/components/FormControl/index.js +3 -0
- package/dist/components/Icon/Icon.cjs +70 -0
- package/dist/components/Icon/Icon.js +34 -0
- package/dist/components/Icon/catalog.cjs +1672 -0
- package/dist/components/Icon/catalog.js +1669 -0
- package/dist/components/Icon/icons.cjs +5621 -0
- package/dist/components/Icon/icons.js +5610 -0
- package/dist/components/Icon/index.cjs +8 -0
- package/dist/components/Icon/index.js +2 -0
- package/dist/components/Input/Input.cjs +64 -0
- package/dist/components/Input/Input.js +61 -0
- package/dist/components/Input/index.cjs +5 -0
- package/dist/components/Input/index.js +1 -0
- package/dist/components/MetricsCard/MetricsCard.cjs +107 -0
- package/dist/components/MetricsCard/MetricsCard.js +104 -0
- package/dist/components/MetricsCard/index.cjs +5 -0
- package/dist/components/MetricsCard/index.js +1 -0
- package/dist/components/Modal/Modal.cjs +126 -0
- package/dist/components/Modal/Modal.js +123 -0
- package/dist/components/Modal/index.cjs +5 -0
- package/dist/components/Modal/index.js +1 -0
- package/dist/components/Pagination/Pagination.cjs +42 -0
- package/dist/components/Pagination/Pagination.js +39 -0
- package/dist/components/Pagination/index.cjs +5 -0
- package/dist/components/Pagination/index.js +1 -0
- package/dist/components/ProgressBar/ProgressBar.cjs +34 -0
- package/dist/components/ProgressBar/ProgressBar.js +31 -0
- package/dist/components/ProgressBar/index.cjs +5 -0
- package/dist/components/ProgressBar/index.js +1 -0
- package/dist/components/ProgressCircle/ProgressCircle.cjs +34 -0
- package/dist/components/ProgressCircle/ProgressCircle.js +31 -0
- package/dist/components/ProgressCircle/index.cjs +5 -0
- package/dist/components/ProgressCircle/index.js +1 -0
- package/dist/components/Select/Select.cjs +84 -0
- package/dist/components/Select/Select.js +80 -0
- package/dist/components/Select/SelectOption.cjs +26 -0
- package/dist/components/Select/SelectOption.js +23 -0
- package/dist/components/Select/index.cjs +7 -0
- package/dist/components/Select/index.js +2 -0
- package/dist/components/SidebarMenu/SidebarMenu.cjs +85 -0
- package/dist/components/SidebarMenu/SidebarMenu.js +81 -0
- package/dist/components/SidebarMenu/SidebarMenuItem.cjs +126 -0
- package/dist/components/SidebarMenu/SidebarMenuItem.js +122 -0
- package/dist/components/SidebarMenu/SidebarMenuSection.cjs +14 -0
- package/dist/components/SidebarMenu/SidebarMenuSection.js +11 -0
- package/dist/components/SidebarMenu/index.cjs +12 -0
- package/dist/components/SidebarMenu/index.js +4 -0
- package/dist/components/Slider/Slider.cjs +161 -0
- package/dist/components/Slider/Slider.js +158 -0
- package/dist/components/Slider/index.cjs +6 -0
- package/dist/components/Slider/index.js +1 -0
- package/dist/components/Spinner/Spinner.cjs +93 -0
- package/dist/components/Spinner/Spinner.js +90 -0
- package/dist/components/Spinner/index.cjs +5 -0
- package/dist/components/Spinner/index.js +1 -0
- package/dist/components/Stepper/Stepper.cjs +82 -0
- package/dist/components/Stepper/Stepper.js +79 -0
- package/dist/components/Stepper/index.cjs +7 -0
- package/dist/components/Stepper/index.js +1 -0
- package/dist/components/Table/Table.cjs +25 -0
- package/dist/components/Table/Table.js +22 -0
- package/dist/components/Table/index.cjs +10 -0
- package/dist/components/Table/index.js +1 -0
- package/dist/components/Table/useTableSelection.cjs +60 -0
- package/dist/components/Table/useTableSelection.js +57 -0
- package/dist/components/Tabs/Tab.cjs +70 -0
- package/dist/components/Tabs/Tab.js +67 -0
- package/dist/components/Tabs/Tabs.cjs +133 -0
- package/dist/components/Tabs/Tabs.js +129 -0
- package/dist/components/Tabs/index.cjs +7 -0
- package/dist/components/Tabs/index.js +2 -0
- package/dist/components/Tooltip/Tooltip.cjs +101 -0
- package/dist/components/Tooltip/Tooltip.js +98 -0
- package/dist/components/Tooltip/index.cjs +5 -0
- package/dist/components/Tooltip/index.js +1 -0
- package/dist/context/ThemeContext.cjs +97 -0
- package/dist/context/ThemeContext.js +92 -0
- package/dist/fonts/README.md +13 -0
- package/dist/fonts/hanken-grotesk-cyrillic-ext-italic.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-cyrillic-ext-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-ext-italic.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-ext-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-italic.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-latin-normal.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-vietnamese-italic.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-vietnamese-normal.woff2 +0 -0
- package/dist/fonts/hankengrotesk-LICENSE.txt +94 -0
- package/dist/fonts/manrope-LICENSE.txt +93 -0
- package/dist/fonts/manrope-cyrillic-ext-normal.woff2 +0 -0
- package/dist/fonts/manrope-cyrillic-normal.woff2 +0 -0
- package/dist/fonts/manrope-greek-normal.woff2 +0 -0
- package/dist/fonts/manrope-latin-ext-normal.woff2 +0 -0
- package/dist/fonts/manrope-latin-normal.woff2 +0 -0
- package/dist/fonts/manrope-vietnamese-normal.woff2 +0 -0
- package/dist/fonts.css +148 -0
- package/dist/hooks/form/index.cjs +11 -0
- package/dist/hooks/form/index.js +3 -0
- package/dist/hooks/form/useRaydenFormControl.cjs +81 -0
- package/dist/hooks/form/useRaydenFormControl.js +76 -0
- package/dist/hooks/form/useRaydenInput.cjs +38 -0
- package/dist/hooks/form/useRaydenInput.js +35 -0
- package/dist/hooks/form/useRaydenSelect.cjs +45 -0
- package/dist/hooks/form/useRaydenSelect.js +42 -0
- package/dist/hooks/index.cjs +17 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useBodyScrollLock.cjs +24 -0
- package/dist/hooks/useBodyScrollLock.js +21 -0
- package/dist/hooks/useCollisionAwareSide.cjs +72 -0
- package/dist/hooks/useCollisionAwareSide.js +69 -0
- package/dist/hooks/useControllableValue.cjs +16 -0
- package/dist/hooks/useControllableValue.js +13 -0
- package/dist/hooks/useDismissableLayer.cjs +100 -0
- package/dist/hooks/useDismissableLayer.js +97 -0
- package/dist/hooks/usePopupList.cjs +101 -0
- package/dist/hooks/usePopupList.js +98 -0
- package/dist/icons-8yD7I5jO.d.cts +430 -0
- package/dist/icons-8yD7I5jO.d.ts +430 -0
- package/dist/icons.cjs +2111 -418
- package/dist/icons.d.cts +1256 -429
- package/dist/icons.d.ts +1256 -429
- package/dist/icons.js +2089 -1
- package/dist/index.cjs +128 -15046
- package/dist/index.d.cts +697 -662
- package/dist/index.d.ts +697 -662
- package/dist/index.js +43 -14962
- package/dist/motion/Collapse.cjs +28 -0
- package/dist/motion/Collapse.js +25 -0
- package/dist/motion/MotionProvider.cjs +43 -0
- package/dist/motion/MotionProvider.js +38 -0
- package/dist/motion/Pressable.cjs +46 -0
- package/dist/motion/Pressable.js +43 -0
- package/dist/motion/Reveal.cjs +19 -0
- package/dist/motion/Reveal.js +16 -0
- package/dist/motion/SharedLayout.cjs +46 -0
- package/dist/motion/SharedLayout.js +43 -0
- package/dist/motion/index.cjs +19 -0
- package/dist/motion/index.js +6 -0
- package/dist/motion/presence.cjs +58 -0
- package/dist/motion/presence.js +55 -0
- package/dist/motion/presets.cjs +88 -0
- package/dist/motion/presets.js +84 -0
- package/dist/motion.d.cts +51 -0
- package/dist/motion.d.ts +51 -0
- package/dist/preset.cjs +356 -195
- package/dist/preset.d.cts +111 -32
- package/dist/preset.d.ts +111 -32
- package/dist/preset.js +353 -187
- package/dist/presets-Ba5QndHg.d.cts +144 -0
- package/dist/presets-Ba5QndHg.d.ts +144 -0
- package/dist/styles.css +1 -1
- package/dist/utils/cn.cjs +38 -0
- package/dist/utils/cn.js +35 -0
- package/dist/utils/resolveIcon.cjs +18 -0
- package/dist/utils/resolveIcon.js +15 -0
- package/package.json +79 -21
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PageHeaderBlock = PageHeaderBlock;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const cn_1 = require("../utils/cn.cjs");
|
|
7
|
+
const Icon_1 = require("../components/Icon/index.cjs");
|
|
8
|
+
const Spinner_1 = require("../components/Spinner/index.cjs");
|
|
9
|
+
/* ─── Recipes ────────────────────────────────────────────────────────── */
|
|
10
|
+
const FOCUS_RING = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text";
|
|
11
|
+
/** Minimum height, never a fixed width: a translated label must be able to grow (audit B13). */
|
|
12
|
+
const ACTION_BASE = (0, cn_1.cn)("inline-flex min-h-11 cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-body-sm font-semibold break-words transition-colors", FOCUS_RING);
|
|
13
|
+
const ACTION_PRIMARY = "bg-action-primary text-white hover:bg-action-primary-hover";
|
|
14
|
+
const ACTION_SECONDARY = "border border-surface-border bg-surface text-grey-700 hover:bg-surface-muted hover:text-grey-900";
|
|
15
|
+
const STATUS_TONE = {
|
|
16
|
+
neutral: "bg-grey-100 text-grey-700",
|
|
17
|
+
success: "bg-success-50 text-success-700",
|
|
18
|
+
warning: "bg-warning-50 text-warning-700",
|
|
19
|
+
danger: "bg-error-50 text-error-700",
|
|
20
|
+
};
|
|
21
|
+
/* ─── Component ──────────────────────────────────────────────────────── */
|
|
22
|
+
function PageHeaderBlock({ title, headingLevel = "h1", eyebrow, description, breadcrumbs = [], breadcrumbLabel = "Breadcrumb", maxVisibleBreadcrumbs = 4, breadcrumbExpandLabel = (count) => count === 1 ? "Show 1 hidden level" : `Show ${count} hidden levels`, breadcrumbCollapseLabel = (count) => (count === 1 ? "Hide 1 level" : `Hide ${count} levels`), status, meta = [], metaLabel = "Page details", collapseMetaOnSmallScreens = true, variant = "surface", backAction, actions = [], actionsLabel = "Page actions", maxInlineActions = 3, overflowLabel = "More actions", state = "default", errorTitle = "This page could not be loaded", errorDescription, onRetry, loadingMessage = "Loading page details", children, className, }) {
|
|
23
|
+
const uid = (0, react_1.useId)();
|
|
24
|
+
const Heading = headingLevel;
|
|
25
|
+
const overflowId = `${uid}-overflow`;
|
|
26
|
+
const statusDescriptionId = `${uid}-status-description`;
|
|
27
|
+
const [metaExpanded, setMetaExpanded] = (0, react_1.useState)(false);
|
|
28
|
+
const metaId = `${uid}-meta`;
|
|
29
|
+
const surfaceClass = (0, cn_1.cn)("flex w-full flex-col gap-4 border-b border-surface-border py-5", variant === "surface" && "bg-surface px-4 @min-[640px]:px-6");
|
|
30
|
+
const [crumbsExpanded, setCrumbsExpanded] = (0, react_1.useState)(false);
|
|
31
|
+
const [overflowOpen, setOverflowOpen] = (0, react_1.useState)(false);
|
|
32
|
+
const overflowToggleRef = (0, react_1.useRef)(null);
|
|
33
|
+
/* ── Action partitioning ─────────────────────────────────────────── */
|
|
34
|
+
const primaryActions = actions.filter((action) => action.priority === "primary");
|
|
35
|
+
const explicitOverflow = actions.filter((action) => action.priority === "overflow");
|
|
36
|
+
const secondaryActions = actions.filter((action) => action.priority !== "primary" && action.priority !== "overflow");
|
|
37
|
+
const inlineSecondaryBudget = Math.max(0, maxInlineActions - primaryActions.length);
|
|
38
|
+
const inlineSecondary = secondaryActions.slice(0, inlineSecondaryBudget);
|
|
39
|
+
const overflowActions = [...secondaryActions.slice(inlineSecondaryBudget), ...explicitOverflow];
|
|
40
|
+
function closeOverflow() {
|
|
41
|
+
setOverflowOpen(false);
|
|
42
|
+
}
|
|
43
|
+
function handleKeyDown(event) {
|
|
44
|
+
if (event.key !== "Escape" || !overflowOpen)
|
|
45
|
+
return;
|
|
46
|
+
setOverflowOpen(false);
|
|
47
|
+
overflowToggleRef.current?.focus();
|
|
48
|
+
}
|
|
49
|
+
/* ── Action rendering ────────────────────────────────────────────── */
|
|
50
|
+
function renderAction(action, tone) {
|
|
51
|
+
const unavailable = "unavailableReason" in action ? action.unavailableReason : undefined;
|
|
52
|
+
const reasonId = `${uid}-${action.id}-reason`;
|
|
53
|
+
const classes = (0, cn_1.cn)(ACTION_BASE, tone === "primary" && ACTION_PRIMARY, tone === "secondary" && ACTION_SECONDARY, tone === "stacked" && "w-full justify-start text-grey-700 hover:bg-surface-muted");
|
|
54
|
+
const inner = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [action.icon && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: action.icon, size: "sm", className: "shrink-0" }), (0, jsx_runtime_1.jsx)("span", { className: "break-words", children: action.label }), action.description && (0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: action.description })] }));
|
|
55
|
+
if (unavailable) {
|
|
56
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", disabled: true, "aria-describedby": reasonId, className: (0, cn_1.cn)(classes, "cursor-not-allowed border-surface-border bg-grey-100 text-grey-500"), children: inner }), (0, jsx_runtime_1.jsx)("p", { id: reasonId, className: "max-w-[28ch] text-body-xs break-words text-grey-500", children: unavailable })] }, action.id));
|
|
57
|
+
}
|
|
58
|
+
if (action.href) {
|
|
59
|
+
return ((0, jsx_runtime_1.jsx)("a", { href: action.href, onClick: () => {
|
|
60
|
+
action.onClick?.();
|
|
61
|
+
if (tone === "stacked")
|
|
62
|
+
closeOverflow();
|
|
63
|
+
}, ...(action.external ? { target: "_blank", rel: "noreferrer" } : {}), className: classes, children: inner }, action.id));
|
|
64
|
+
}
|
|
65
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => {
|
|
66
|
+
action.onClick?.();
|
|
67
|
+
if (tone === "stacked")
|
|
68
|
+
closeOverflow();
|
|
69
|
+
}, className: classes, children: inner }, action.id));
|
|
70
|
+
}
|
|
71
|
+
/* ── Breadcrumbs ─────────────────────────────────────────────────── */
|
|
72
|
+
function renderCrumbContent(crumb, isLast) {
|
|
73
|
+
const label = ((0, jsx_runtime_1.jsxs)("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [crumb.icon && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: crumb.icon, size: "sm", className: "shrink-0" }), (0, jsx_runtime_1.jsx)("span", { className: "break-words", children: crumb.label })] }));
|
|
74
|
+
if (isLast) {
|
|
75
|
+
return ((0, jsx_runtime_1.jsx)("span", { "aria-current": "page", className: "font-semibold text-grey-900", children: label }));
|
|
76
|
+
}
|
|
77
|
+
if (crumb.href) {
|
|
78
|
+
return ((0, jsx_runtime_1.jsx)("a", { href: crumb.href, onClick: crumb.onClick, className: (0, cn_1.cn)("rounded text-grey-500 underline-offset-4 hover:text-grey-900 hover:underline", FOCUS_RING), children: label }));
|
|
79
|
+
}
|
|
80
|
+
if (crumb.onClick) {
|
|
81
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: crumb.onClick, className: (0, cn_1.cn)("cursor-pointer rounded text-grey-500 underline-offset-4 hover:text-grey-900 hover:underline", FOCUS_RING), children: label }));
|
|
82
|
+
}
|
|
83
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "text-grey-500", children: label });
|
|
84
|
+
}
|
|
85
|
+
function renderBreadcrumbs() {
|
|
86
|
+
if (breadcrumbs.length === 0)
|
|
87
|
+
return null;
|
|
88
|
+
const lastIndex = breadcrumbs.length - 1;
|
|
89
|
+
// A long trail keeps its root and its final two levels. Everything between
|
|
90
|
+
// stays mounted and hidden, so the toggle's `aria-controls` always resolves
|
|
91
|
+
// to real elements and the trail can be collapsed again after expanding.
|
|
92
|
+
const collapses = maxVisibleBreadcrumbs >= 3 && breadcrumbs.length > maxVisibleBreadcrumbs;
|
|
93
|
+
const hiddenFrom = 1;
|
|
94
|
+
const hiddenTo = collapses ? lastIndex - 1 : 1;
|
|
95
|
+
const hiddenCount = Math.max(0, hiddenTo - hiddenFrom);
|
|
96
|
+
const hiddenIds = collapses
|
|
97
|
+
? breadcrumbs.slice(hiddenFrom, hiddenTo).map((crumb) => `${uid}-crumb-${crumb.id}`)
|
|
98
|
+
: [];
|
|
99
|
+
const rows = [];
|
|
100
|
+
breadcrumbs.forEach((crumb, index) => {
|
|
101
|
+
const isLast = index === lastIndex;
|
|
102
|
+
const isCollapsible = collapses && index >= hiddenFrom && index < hiddenTo;
|
|
103
|
+
if (collapses && index === hiddenFrom) {
|
|
104
|
+
rows.push((0, jsx_runtime_1.jsxs)("li", { className: "flex min-w-0 items-center gap-1", children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "chevron-right", size: "sm", className: "shrink-0 text-grey-400" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", "aria-expanded": crumbsExpanded, "aria-controls": hiddenIds.join(" "), onClick: () => setCrumbsExpanded((open) => !open), className: (0, cn_1.cn)("inline-flex min-h-8 min-w-8 cursor-pointer items-center justify-center rounded px-1.5 text-grey-500 hover:bg-surface-muted hover:text-grey-900", FOCUS_RING), children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: "\u2026" }), (0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: crumbsExpanded
|
|
105
|
+
? breadcrumbCollapseLabel(hiddenCount)
|
|
106
|
+
: breadcrumbExpandLabel(hiddenCount) })] })] }, "__collapse"));
|
|
107
|
+
}
|
|
108
|
+
rows.push((0, jsx_runtime_1.jsxs)("li", { id: `${uid}-crumb-${crumb.id}`, className: (0, cn_1.cn)("flex min-w-0 items-center gap-1", isCollapsible && !crumbsExpanded && "hidden"), children: [index > 0 && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "chevron-right", size: "sm", className: "shrink-0 text-grey-400" }), renderCrumbContent(crumb, isLast)] }, crumb.id));
|
|
109
|
+
});
|
|
110
|
+
return ((0, jsx_runtime_1.jsx)("nav", { "aria-label": breadcrumbLabel, className: "min-w-0", children: (0, jsx_runtime_1.jsx)("ol", { className: "flex list-none flex-wrap items-center gap-x-1 gap-y-1 text-body-sm", children: rows }) }));
|
|
111
|
+
}
|
|
112
|
+
/* ── States ──────────────────────────────────────────────────────── */
|
|
113
|
+
if (state === "loading") {
|
|
114
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("@container relative w-full", className), children: (0, jsx_runtime_1.jsxs)("div", { "aria-busy": "true", className: surfaceClass, children: [(0, jsx_runtime_1.jsxs)("div", { "aria-hidden": "true", className: "flex flex-col gap-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-4 w-40 max-w-full rounded bg-grey-100" }), (0, jsx_runtime_1.jsx)("div", { className: "h-8 w-72 max-w-full rounded bg-grey-100" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-full max-w-[52ch] rounded bg-grey-100" })] }), (0, jsx_runtime_1.jsxs)("p", { role: "status", className: "flex items-center gap-2 text-body-sm text-grey-500", children: [(0, jsx_runtime_1.jsx)(Spinner_1.Spinner, { size: "sm" }), (0, jsx_runtime_1.jsx)("span", { children: loadingMessage })] })] }) }));
|
|
115
|
+
}
|
|
116
|
+
return ((0, jsx_runtime_1.jsx)("div", { onKeyDown: handleKeyDown,
|
|
117
|
+
// `@container` on the outer element, outside the padded surface, so the
|
|
118
|
+
// `@min-[…]` padding inside resolves against the header's real width.
|
|
119
|
+
className: (0, cn_1.cn)("@container relative w-full", className), children: (0, jsx_runtime_1.jsxs)("div", { className: surfaceClass, children: [backAction &&
|
|
120
|
+
(backAction.href ? ((0, jsx_runtime_1.jsxs)("a", { href: backAction.href, onClick: backAction.onClick, className: (0, cn_1.cn)("inline-flex min-h-8 w-fit items-center gap-1.5 rounded text-body-sm font-medium text-grey-500 hover:text-grey-900", FOCUS_RING), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "arrow-left", size: "sm" }), (0, jsx_runtime_1.jsx)("span", { className: "break-words", children: backAction.label })] })) : backAction.onClick ? ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: backAction.onClick, className: (0, cn_1.cn)("inline-flex min-h-8 w-fit cursor-pointer items-center gap-1.5 rounded text-body-sm font-medium text-grey-500 hover:text-grey-900", FOCUS_RING), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "arrow-left", size: "sm" }), (0, jsx_runtime_1.jsx)("span", { className: "break-words", children: backAction.label })] })) : null), renderBreadcrumbs(), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-4 @min-[720px]:flex-row @min-[720px]:items-start @min-[720px]:justify-between @min-[720px]:gap-6", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [eyebrow && ((0, jsx_runtime_1.jsx)("p", { className: "text-caption-sm font-semibold break-words text-action-primary-text uppercase", children: eyebrow })), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [(0, jsx_runtime_1.jsx)(Heading, { className: "min-w-0 text-h6 font-semibold break-words text-grey-900 @min-[640px]:text-h5", children: title }), status && ((0, jsx_runtime_1.jsx)("span", { "aria-describedby": status.description ? statusDescriptionId : undefined, className: (0, cn_1.cn)("inline-flex shrink-0 items-center rounded-full px-2.5 py-1 text-body-xs font-semibold break-words", STATUS_TONE[status.tone ?? "neutral"]), children: status.label }))] }), status?.description && ((0, jsx_runtime_1.jsx)("p", { id: statusDescriptionId, className: "text-body-sm break-words text-grey-500", children: status.description })), description && ((0, jsx_runtime_1.jsx)("p", { className: "max-w-[68ch] text-body-sm break-words text-grey-500", children: description }))] }), actions.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 shrink-0 flex-col gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { role: "group", "aria-label": actionsLabel, className: "flex flex-wrap items-start gap-2 @min-[720px]:justify-end", children: [inlineSecondary.map((action) => renderAction(action, "secondary")), primaryActions.map((action) => renderAction(action, "primary")), overflowActions.length > 0 && ((0, jsx_runtime_1.jsxs)("button", { ref: overflowToggleRef, type: "button", "aria-expanded": overflowOpen, "aria-controls": overflowId, onClick: () => setOverflowOpen((open) => !open), className: (0, cn_1.cn)(ACTION_BASE, ACTION_SECONDARY), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "dots-h", size: "sm" }), (0, jsx_runtime_1.jsx)("span", { className: "break-words", children: overflowLabel })] }))] }), (0, jsx_runtime_1.jsx)("div", { id: overflowId, className: (0, cn_1.cn)("flex w-full flex-col gap-1 rounded-lg border border-surface-border bg-surface p-1 @min-[720px]:min-w-[220px]", !overflowOpen && "hidden"), children: overflowActions.map((action) => renderAction(action, "stacked")) })] }))] }), meta.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { children: [collapseMetaOnSmallScreens && ((0, jsx_runtime_1.jsxs)("button", { type: "button", "aria-expanded": metaExpanded, "aria-controls": metaId, onClick: () => setMetaExpanded((value) => !value), className: (0, cn_1.cn)("inline-flex min-h-11 cursor-pointer items-center gap-2 rounded text-body-sm font-medium text-grey-600 @min-[640px]:hidden", FOCUS_RING), children: [metaLabel, (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: metaExpanded ? "chevron-up" : "chevron-down", size: "sm" })] })), (0, jsx_runtime_1.jsx)("dl", { id: metaId, "aria-label": metaLabel, className: (0, cn_1.cn)("flex flex-wrap gap-x-6 gap-y-3 border-t border-surface-border pt-4", collapseMetaOnSmallScreens && !metaExpanded && "hidden @min-[640px]:flex"), children: meta.map((item) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-0.5", children: [(0, jsx_runtime_1.jsx)("dt", { className: "text-body-xs font-medium break-words text-grey-500", children: item.label }), (0, jsx_runtime_1.jsxs)("dd", { className: "flex min-w-0 items-center gap-1.5 text-body-sm font-medium break-words text-grey-900", children: [item.icon && ((0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: item.icon, size: "sm", className: "shrink-0 text-grey-500" })), (0, jsx_runtime_1.jsx)("span", { className: "min-w-0 break-words", children: item.value })] })] }, item.id))) })] })), state === "error" && ((0, jsx_runtime_1.jsxs)("div", { role: "alert", className: "flex flex-col gap-2 rounded-lg border border-error-200 bg-error-50 px-4 py-3 text-error-700", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-body-sm font-semibold break-words", children: errorTitle }), errorDescription && (0, jsx_runtime_1.jsx)("p", { className: "text-body-sm break-words", children: errorDescription }), onRetry && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: onRetry, className: (0, cn_1.cn)("inline-flex min-h-10 w-fit cursor-pointer items-center gap-2 rounded-lg border-[1.5px] border-current px-4 py-2 text-body-sm font-semibold", FOCUS_RING), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "refresh", size: "sm" }), "Try again"] }))] })), children] }) }));
|
|
121
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useRef, useState, } from "react";
|
|
3
|
+
import { cn } from "../utils/cn.js";
|
|
4
|
+
import { Icon } from "../components/Icon/index.js";
|
|
5
|
+
import { Spinner } from "../components/Spinner/index.js";
|
|
6
|
+
/* ─── Recipes ────────────────────────────────────────────────────────── */
|
|
7
|
+
const FOCUS_RING = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text";
|
|
8
|
+
/** Minimum height, never a fixed width: a translated label must be able to grow (audit B13). */
|
|
9
|
+
const ACTION_BASE = cn("inline-flex min-h-11 cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-body-sm font-semibold break-words transition-colors", FOCUS_RING);
|
|
10
|
+
const ACTION_PRIMARY = "bg-action-primary text-white hover:bg-action-primary-hover";
|
|
11
|
+
const ACTION_SECONDARY = "border border-surface-border bg-surface text-grey-700 hover:bg-surface-muted hover:text-grey-900";
|
|
12
|
+
const STATUS_TONE = {
|
|
13
|
+
neutral: "bg-grey-100 text-grey-700",
|
|
14
|
+
success: "bg-success-50 text-success-700",
|
|
15
|
+
warning: "bg-warning-50 text-warning-700",
|
|
16
|
+
danger: "bg-error-50 text-error-700",
|
|
17
|
+
};
|
|
18
|
+
/* ─── Component ──────────────────────────────────────────────────────── */
|
|
19
|
+
export function PageHeaderBlock({ title, headingLevel = "h1", eyebrow, description, breadcrumbs = [], breadcrumbLabel = "Breadcrumb", maxVisibleBreadcrumbs = 4, breadcrumbExpandLabel = (count) => count === 1 ? "Show 1 hidden level" : `Show ${count} hidden levels`, breadcrumbCollapseLabel = (count) => (count === 1 ? "Hide 1 level" : `Hide ${count} levels`), status, meta = [], metaLabel = "Page details", collapseMetaOnSmallScreens = true, variant = "surface", backAction, actions = [], actionsLabel = "Page actions", maxInlineActions = 3, overflowLabel = "More actions", state = "default", errorTitle = "This page could not be loaded", errorDescription, onRetry, loadingMessage = "Loading page details", children, className, }) {
|
|
20
|
+
const uid = useId();
|
|
21
|
+
const Heading = headingLevel;
|
|
22
|
+
const overflowId = `${uid}-overflow`;
|
|
23
|
+
const statusDescriptionId = `${uid}-status-description`;
|
|
24
|
+
const [metaExpanded, setMetaExpanded] = useState(false);
|
|
25
|
+
const metaId = `${uid}-meta`;
|
|
26
|
+
const surfaceClass = cn("flex w-full flex-col gap-4 border-b border-surface-border py-5", variant === "surface" && "bg-surface px-4 @min-[640px]:px-6");
|
|
27
|
+
const [crumbsExpanded, setCrumbsExpanded] = useState(false);
|
|
28
|
+
const [overflowOpen, setOverflowOpen] = useState(false);
|
|
29
|
+
const overflowToggleRef = useRef(null);
|
|
30
|
+
/* ── Action partitioning ─────────────────────────────────────────── */
|
|
31
|
+
const primaryActions = actions.filter((action) => action.priority === "primary");
|
|
32
|
+
const explicitOverflow = actions.filter((action) => action.priority === "overflow");
|
|
33
|
+
const secondaryActions = actions.filter((action) => action.priority !== "primary" && action.priority !== "overflow");
|
|
34
|
+
const inlineSecondaryBudget = Math.max(0, maxInlineActions - primaryActions.length);
|
|
35
|
+
const inlineSecondary = secondaryActions.slice(0, inlineSecondaryBudget);
|
|
36
|
+
const overflowActions = [...secondaryActions.slice(inlineSecondaryBudget), ...explicitOverflow];
|
|
37
|
+
function closeOverflow() {
|
|
38
|
+
setOverflowOpen(false);
|
|
39
|
+
}
|
|
40
|
+
function handleKeyDown(event) {
|
|
41
|
+
if (event.key !== "Escape" || !overflowOpen)
|
|
42
|
+
return;
|
|
43
|
+
setOverflowOpen(false);
|
|
44
|
+
overflowToggleRef.current?.focus();
|
|
45
|
+
}
|
|
46
|
+
/* ── Action rendering ────────────────────────────────────────────── */
|
|
47
|
+
function renderAction(action, tone) {
|
|
48
|
+
const unavailable = "unavailableReason" in action ? action.unavailableReason : undefined;
|
|
49
|
+
const reasonId = `${uid}-${action.id}-reason`;
|
|
50
|
+
const classes = cn(ACTION_BASE, tone === "primary" && ACTION_PRIMARY, tone === "secondary" && ACTION_SECONDARY, tone === "stacked" && "w-full justify-start text-grey-700 hover:bg-surface-muted");
|
|
51
|
+
const inner = (_jsxs(_Fragment, { children: [action.icon && _jsx(Icon, { name: action.icon, size: "sm", className: "shrink-0" }), _jsx("span", { className: "break-words", children: action.label }), action.description && _jsx("span", { className: "sr-only", children: action.description })] }));
|
|
52
|
+
if (unavailable) {
|
|
53
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("button", { type: "button", disabled: true, "aria-describedby": reasonId, className: cn(classes, "cursor-not-allowed border-surface-border bg-grey-100 text-grey-500"), children: inner }), _jsx("p", { id: reasonId, className: "max-w-[28ch] text-body-xs break-words text-grey-500", children: unavailable })] }, action.id));
|
|
54
|
+
}
|
|
55
|
+
if (action.href) {
|
|
56
|
+
return (_jsx("a", { href: action.href, onClick: () => {
|
|
57
|
+
action.onClick?.();
|
|
58
|
+
if (tone === "stacked")
|
|
59
|
+
closeOverflow();
|
|
60
|
+
}, ...(action.external ? { target: "_blank", rel: "noreferrer" } : {}), className: classes, children: inner }, action.id));
|
|
61
|
+
}
|
|
62
|
+
return (_jsx("button", { type: "button", onClick: () => {
|
|
63
|
+
action.onClick?.();
|
|
64
|
+
if (tone === "stacked")
|
|
65
|
+
closeOverflow();
|
|
66
|
+
}, className: classes, children: inner }, action.id));
|
|
67
|
+
}
|
|
68
|
+
/* ── Breadcrumbs ─────────────────────────────────────────────────── */
|
|
69
|
+
function renderCrumbContent(crumb, isLast) {
|
|
70
|
+
const label = (_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [crumb.icon && _jsx(Icon, { name: crumb.icon, size: "sm", className: "shrink-0" }), _jsx("span", { className: "break-words", children: crumb.label })] }));
|
|
71
|
+
if (isLast) {
|
|
72
|
+
return (_jsx("span", { "aria-current": "page", className: "font-semibold text-grey-900", children: label }));
|
|
73
|
+
}
|
|
74
|
+
if (crumb.href) {
|
|
75
|
+
return (_jsx("a", { href: crumb.href, onClick: crumb.onClick, className: cn("rounded text-grey-500 underline-offset-4 hover:text-grey-900 hover:underline", FOCUS_RING), children: label }));
|
|
76
|
+
}
|
|
77
|
+
if (crumb.onClick) {
|
|
78
|
+
return (_jsx("button", { type: "button", onClick: crumb.onClick, className: cn("cursor-pointer rounded text-grey-500 underline-offset-4 hover:text-grey-900 hover:underline", FOCUS_RING), children: label }));
|
|
79
|
+
}
|
|
80
|
+
return _jsx("span", { className: "text-grey-500", children: label });
|
|
81
|
+
}
|
|
82
|
+
function renderBreadcrumbs() {
|
|
83
|
+
if (breadcrumbs.length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
const lastIndex = breadcrumbs.length - 1;
|
|
86
|
+
// A long trail keeps its root and its final two levels. Everything between
|
|
87
|
+
// stays mounted and hidden, so the toggle's `aria-controls` always resolves
|
|
88
|
+
// to real elements and the trail can be collapsed again after expanding.
|
|
89
|
+
const collapses = maxVisibleBreadcrumbs >= 3 && breadcrumbs.length > maxVisibleBreadcrumbs;
|
|
90
|
+
const hiddenFrom = 1;
|
|
91
|
+
const hiddenTo = collapses ? lastIndex - 1 : 1;
|
|
92
|
+
const hiddenCount = Math.max(0, hiddenTo - hiddenFrom);
|
|
93
|
+
const hiddenIds = collapses
|
|
94
|
+
? breadcrumbs.slice(hiddenFrom, hiddenTo).map((crumb) => `${uid}-crumb-${crumb.id}`)
|
|
95
|
+
: [];
|
|
96
|
+
const rows = [];
|
|
97
|
+
breadcrumbs.forEach((crumb, index) => {
|
|
98
|
+
const isLast = index === lastIndex;
|
|
99
|
+
const isCollapsible = collapses && index >= hiddenFrom && index < hiddenTo;
|
|
100
|
+
if (collapses && index === hiddenFrom) {
|
|
101
|
+
rows.push(_jsxs("li", { className: "flex min-w-0 items-center gap-1", children: [_jsx(Icon, { name: "chevron-right", size: "sm", className: "shrink-0 text-grey-400" }), _jsxs("button", { type: "button", "aria-expanded": crumbsExpanded, "aria-controls": hiddenIds.join(" "), onClick: () => setCrumbsExpanded((open) => !open), className: cn("inline-flex min-h-8 min-w-8 cursor-pointer items-center justify-center rounded px-1.5 text-grey-500 hover:bg-surface-muted hover:text-grey-900", FOCUS_RING), children: [_jsx("span", { "aria-hidden": "true", children: "\u2026" }), _jsx("span", { className: "sr-only", children: crumbsExpanded
|
|
102
|
+
? breadcrumbCollapseLabel(hiddenCount)
|
|
103
|
+
: breadcrumbExpandLabel(hiddenCount) })] })] }, "__collapse"));
|
|
104
|
+
}
|
|
105
|
+
rows.push(_jsxs("li", { id: `${uid}-crumb-${crumb.id}`, className: cn("flex min-w-0 items-center gap-1", isCollapsible && !crumbsExpanded && "hidden"), children: [index > 0 && _jsx(Icon, { name: "chevron-right", size: "sm", className: "shrink-0 text-grey-400" }), renderCrumbContent(crumb, isLast)] }, crumb.id));
|
|
106
|
+
});
|
|
107
|
+
return (_jsx("nav", { "aria-label": breadcrumbLabel, className: "min-w-0", children: _jsx("ol", { className: "flex list-none flex-wrap items-center gap-x-1 gap-y-1 text-body-sm", children: rows }) }));
|
|
108
|
+
}
|
|
109
|
+
/* ── States ──────────────────────────────────────────────────────── */
|
|
110
|
+
if (state === "loading") {
|
|
111
|
+
return (_jsx("div", { className: cn("@container relative w-full", className), children: _jsxs("div", { "aria-busy": "true", className: surfaceClass, children: [_jsxs("div", { "aria-hidden": "true", className: "flex flex-col gap-3", children: [_jsx("div", { className: "h-4 w-40 max-w-full rounded bg-grey-100" }), _jsx("div", { className: "h-8 w-72 max-w-full rounded bg-grey-100" }), _jsx("div", { className: "h-4 w-full max-w-[52ch] rounded bg-grey-100" })] }), _jsxs("p", { role: "status", className: "flex items-center gap-2 text-body-sm text-grey-500", children: [_jsx(Spinner, { size: "sm" }), _jsx("span", { children: loadingMessage })] })] }) }));
|
|
112
|
+
}
|
|
113
|
+
return (_jsx("div", { onKeyDown: handleKeyDown,
|
|
114
|
+
// `@container` on the outer element, outside the padded surface, so the
|
|
115
|
+
// `@min-[…]` padding inside resolves against the header's real width.
|
|
116
|
+
className: cn("@container relative w-full", className), children: _jsxs("div", { className: surfaceClass, children: [backAction &&
|
|
117
|
+
(backAction.href ? (_jsxs("a", { href: backAction.href, onClick: backAction.onClick, className: cn("inline-flex min-h-8 w-fit items-center gap-1.5 rounded text-body-sm font-medium text-grey-500 hover:text-grey-900", FOCUS_RING), children: [_jsx(Icon, { name: "arrow-left", size: "sm" }), _jsx("span", { className: "break-words", children: backAction.label })] })) : backAction.onClick ? (_jsxs("button", { type: "button", onClick: backAction.onClick, className: cn("inline-flex min-h-8 w-fit cursor-pointer items-center gap-1.5 rounded text-body-sm font-medium text-grey-500 hover:text-grey-900", FOCUS_RING), children: [_jsx(Icon, { name: "arrow-left", size: "sm" }), _jsx("span", { className: "break-words", children: backAction.label })] })) : null), renderBreadcrumbs(), _jsxs("div", { className: "flex flex-col gap-4 @min-[720px]:flex-row @min-[720px]:items-start @min-[720px]:justify-between @min-[720px]:gap-6", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [eyebrow && (_jsx("p", { className: "text-caption-sm font-semibold break-words text-action-primary-text uppercase", children: eyebrow })), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2", children: [_jsx(Heading, { className: "min-w-0 text-h6 font-semibold break-words text-grey-900 @min-[640px]:text-h5", children: title }), status && (_jsx("span", { "aria-describedby": status.description ? statusDescriptionId : undefined, className: cn("inline-flex shrink-0 items-center rounded-full px-2.5 py-1 text-body-xs font-semibold break-words", STATUS_TONE[status.tone ?? "neutral"]), children: status.label }))] }), status?.description && (_jsx("p", { id: statusDescriptionId, className: "text-body-sm break-words text-grey-500", children: status.description })), description && (_jsx("p", { className: "max-w-[68ch] text-body-sm break-words text-grey-500", children: description }))] }), actions.length > 0 && (_jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-2", children: [_jsxs("div", { role: "group", "aria-label": actionsLabel, className: "flex flex-wrap items-start gap-2 @min-[720px]:justify-end", children: [inlineSecondary.map((action) => renderAction(action, "secondary")), primaryActions.map((action) => renderAction(action, "primary")), overflowActions.length > 0 && (_jsxs("button", { ref: overflowToggleRef, type: "button", "aria-expanded": overflowOpen, "aria-controls": overflowId, onClick: () => setOverflowOpen((open) => !open), className: cn(ACTION_BASE, ACTION_SECONDARY), children: [_jsx(Icon, { name: "dots-h", size: "sm" }), _jsx("span", { className: "break-words", children: overflowLabel })] }))] }), _jsx("div", { id: overflowId, className: cn("flex w-full flex-col gap-1 rounded-lg border border-surface-border bg-surface p-1 @min-[720px]:min-w-[220px]", !overflowOpen && "hidden"), children: overflowActions.map((action) => renderAction(action, "stacked")) })] }))] }), meta.length > 0 && (_jsxs("div", { children: [collapseMetaOnSmallScreens && (_jsxs("button", { type: "button", "aria-expanded": metaExpanded, "aria-controls": metaId, onClick: () => setMetaExpanded((value) => !value), className: cn("inline-flex min-h-11 cursor-pointer items-center gap-2 rounded text-body-sm font-medium text-grey-600 @min-[640px]:hidden", FOCUS_RING), children: [metaLabel, _jsx(Icon, { name: metaExpanded ? "chevron-up" : "chevron-down", size: "sm" })] })), _jsx("dl", { id: metaId, "aria-label": metaLabel, className: cn("flex flex-wrap gap-x-6 gap-y-3 border-t border-surface-border pt-4", collapseMetaOnSmallScreens && !metaExpanded && "hidden @min-[640px]:flex"), children: meta.map((item) => (_jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [_jsx("dt", { className: "text-body-xs font-medium break-words text-grey-500", children: item.label }), _jsxs("dd", { className: "flex min-w-0 items-center gap-1.5 text-body-sm font-medium break-words text-grey-900", children: [item.icon && (_jsx(Icon, { name: item.icon, size: "sm", className: "shrink-0 text-grey-500" })), _jsx("span", { className: "min-w-0 break-words", children: item.value })] })] }, item.id))) })] })), state === "error" && (_jsxs("div", { role: "alert", className: "flex flex-col gap-2 rounded-lg border border-error-200 bg-error-50 px-4 py-3 text-error-700", children: [_jsx("p", { className: "text-body-sm font-semibold break-words", children: errorTitle }), errorDescription && _jsx("p", { className: "text-body-sm break-words", children: errorDescription }), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, className: cn("inline-flex min-h-10 w-fit cursor-pointer items-center gap-2 rounded-lg border-[1.5px] border-current px-4 py-2 text-body-sm font-semibold", FOCUS_RING), children: [_jsx(Icon, { name: "refresh", size: "sm" }), "Try again"] }))] })), children] }) }));
|
|
118
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PricingPlansBlock = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const cn_1 = require("../utils/cn.cjs");
|
|
7
|
+
const Badge_1 = require("../components/Badge/index.cjs");
|
|
8
|
+
const Icon_1 = require("../components/Icon/index.cjs");
|
|
9
|
+
const HEADING_ORDER = ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
10
|
+
function nextHeadingLevel(level) {
|
|
11
|
+
const index = HEADING_ORDER.indexOf(level);
|
|
12
|
+
return HEADING_ORDER[Math.min(index + 1, HEADING_ORDER.length - 1)];
|
|
13
|
+
}
|
|
14
|
+
function isLinkCta(cta) {
|
|
15
|
+
return typeof cta.href === "string";
|
|
16
|
+
}
|
|
17
|
+
/* ─── Formatting ───────────────────────────────────────────────────────── */
|
|
18
|
+
function formatCurrency(amount, currency, locale, options) {
|
|
19
|
+
const whole = Number.isInteger(amount);
|
|
20
|
+
const base = currency
|
|
21
|
+
? {
|
|
22
|
+
style: "currency",
|
|
23
|
+
currency,
|
|
24
|
+
minimumFractionDigits: whole ? 0 : 2,
|
|
25
|
+
maximumFractionDigits: whole ? 0 : 2,
|
|
26
|
+
}
|
|
27
|
+
: { maximumFractionDigits: whole ? 0 : 2 };
|
|
28
|
+
try {
|
|
29
|
+
return new Intl.NumberFormat(locale, { ...base, ...options }).format(amount);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// An unknown locale or currency code must not break the page.
|
|
33
|
+
return currency ? `${currency} ${amount}` : String(amount);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/* ─── Appearance ───────────────────────────────────────────────────────── */
|
|
37
|
+
const PLAN_COLUMN_CLASS = {
|
|
38
|
+
1: "@min-[600px]:grid-cols-1 @min-[1136px]:grid-cols-1",
|
|
39
|
+
2: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-2",
|
|
40
|
+
3: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-3",
|
|
41
|
+
4: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-4",
|
|
42
|
+
};
|
|
43
|
+
const CTA_BASE = "inline-flex w-full max-w-full items-center justify-center gap-2 min-h-11 rounded-lg px-4 py-2.5 text-sm font-semibold leading-[1.45] text-center break-words no-underline transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text";
|
|
44
|
+
const CTA_TONE = {
|
|
45
|
+
primary: "bg-action-primary text-white hover:bg-action-primary-hover cursor-pointer",
|
|
46
|
+
secondary: "bg-transparent text-action-primary-text border-[1.5px] border-current hover:bg-primary-50 cursor-pointer",
|
|
47
|
+
};
|
|
48
|
+
const NOTICE_TONE = {
|
|
49
|
+
success: {
|
|
50
|
+
border: "border-success-300",
|
|
51
|
+
icon: "check-circle",
|
|
52
|
+
text: "text-action-success",
|
|
53
|
+
},
|
|
54
|
+
info: { border: "border-secondary-300", icon: "info-circle", text: "text-action-info" },
|
|
55
|
+
warning: { border: "border-warning-300", icon: "info-triangle", text: "text-action-warning" },
|
|
56
|
+
error: { border: "border-error-300", icon: "info-triangle", text: "text-action-danger-text" },
|
|
57
|
+
};
|
|
58
|
+
function PlanCta({ cta, tone, disabled = false, describedBy }) {
|
|
59
|
+
const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [cta.icon && (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: cta.icon, size: "sm", className: "shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { className: "min-w-0", children: cta.label })] }));
|
|
60
|
+
// A control that cannot be used is rendered as an explicitly disabled button,
|
|
61
|
+
// never as an active link that goes nowhere.
|
|
62
|
+
if (disabled) {
|
|
63
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", disabled: true, "aria-describedby": describedBy, className: (0, cn_1.cn)(CTA_BASE, "cursor-not-allowed border-[1.5px] border-surface-border-strong bg-grey-100 text-grey-600"), children: content }));
|
|
64
|
+
}
|
|
65
|
+
if (isLinkCta(cta)) {
|
|
66
|
+
const externalAttributes = cta.external
|
|
67
|
+
? { target: "_blank", rel: "noreferrer" }
|
|
68
|
+
: {};
|
|
69
|
+
return ((0, jsx_runtime_1.jsxs)("a", { href: cta.href, onClick: cta.onClick, "aria-describedby": describedBy, className: (0, cn_1.cn)(CTA_BASE, CTA_TONE[tone]), ...externalAttributes, children: [content, cta.external && (0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: "(opens in a new tab)" })] }));
|
|
70
|
+
}
|
|
71
|
+
return ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: cta.onClick, "aria-describedby": describedBy, className: (0, cn_1.cn)(CTA_BASE, CTA_TONE[tone]), children: content }));
|
|
72
|
+
}
|
|
73
|
+
function PlanCard({ plan, periodId, locale, priceFormatOptions, formatPrice, limitsLabel, featuresVisibleLimit, defaultUnavailableReason, headingElement, }) {
|
|
74
|
+
const [expanded, setExpanded] = (0, react_1.useState)(false);
|
|
75
|
+
const reasonId = (0, react_1.useId)();
|
|
76
|
+
const PlanHeading = headingElement;
|
|
77
|
+
// Unavailability is conveyed by the disabled control and its associated reason,
|
|
78
|
+
// never by dimming the card: reducing opacity drops every label below AA.
|
|
79
|
+
const availability = plan.availability ?? "available";
|
|
80
|
+
const unavailable = availability === "unavailable";
|
|
81
|
+
const isCurrent = availability === "current";
|
|
82
|
+
const price = plan.prices[periodId];
|
|
83
|
+
const features = plan.features ?? [];
|
|
84
|
+
const limit = featuresVisibleLimit && featuresVisibleLimit > 0 ? featuresVisibleLimit : undefined;
|
|
85
|
+
const collapsible = limit !== undefined && features.length > limit;
|
|
86
|
+
const visibleFeatures = collapsible && !expanded ? features.slice(0, limit) : features;
|
|
87
|
+
const planNameText = typeof plan.name === "string" ? plan.name : undefined;
|
|
88
|
+
const reason = unavailable ? (plan.unavailableReason ?? defaultUnavailableReason) : undefined;
|
|
89
|
+
let priceBody = null;
|
|
90
|
+
if (!price) {
|
|
91
|
+
priceBody = (0, jsx_runtime_1.jsx)("span", { className: "text-base text-grey-600", children: "Price unavailable" });
|
|
92
|
+
}
|
|
93
|
+
else if (formatPrice) {
|
|
94
|
+
priceBody = formatPrice(price, { locale, periodId });
|
|
95
|
+
}
|
|
96
|
+
else if (price.custom !== undefined) {
|
|
97
|
+
priceBody = ((0, jsx_runtime_1.jsx)("span", { className: "text-h5 font-semibold break-words text-grey-900 @min-[600px]:text-h4", children: price.custom }));
|
|
98
|
+
}
|
|
99
|
+
else if (price.amount !== undefined) {
|
|
100
|
+
priceBody = ((0, jsx_runtime_1.jsxs)("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1", children: [price.originalAmount !== undefined && ((0, jsx_runtime_1.jsxs)("span", { className: "text-base text-grey-600 line-through tabular-nums", children: [(0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: "Was " }), formatCurrency(price.originalAmount, price.currency, locale, priceFormatOptions)] })), (0, jsx_runtime_1.jsx)("span", { className: "text-h4 font-semibold break-words tabular-nums text-grey-900 @min-[600px]:text-h3", children: formatCurrency(price.amount, price.currency, locale, priceFormatOptions) }), price.unit && ((0, jsx_runtime_1.jsx)("span", { className: "min-w-0 text-sm break-words text-grey-600", children: price.unit }))] }));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
priceBody = (0, jsx_runtime_1.jsx)("span", { className: "text-base text-grey-600", children: "Price unavailable" });
|
|
104
|
+
}
|
|
105
|
+
return ((0, jsx_runtime_1.jsxs)("li", { className: (0, cn_1.cn)("flex min-w-0 flex-col gap-5 rounded-xl border bg-surface p-5 @min-[600px]:p-6", plan.highlighted ? "border-primary-400 shadow-soft-sm @min-[600px]:border-2" : "border-surface-border"), children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [(0, jsx_runtime_1.jsx)(PlanHeading, { className: "min-w-0 text-body-lg font-semibold break-words text-grey-900", children: plan.name }), plan.badge && ((0, jsx_runtime_1.jsx)(Badge_1.Badge, { color: "orange", type: "accent", size: "md", children: plan.badge })), isCurrent && ((0, jsx_runtime_1.jsx)(Badge_1.Badge, { color: "success", type: "accent", size: "md", children: plan.currentLabel ?? "Current plan" }))] }), plan.description && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm break-words text-grey-600", children: plan.description }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [priceBody, price?.note && (0, jsx_runtime_1.jsx)("p", { className: "text-sm break-words text-grey-600", children: price.note })] }), reason && ((0, jsx_runtime_1.jsx)("p", { id: reasonId, className: "text-sm break-words text-grey-600", children: reason })), plan.cta ? ((0, jsx_runtime_1.jsx)(PlanCta, { cta: plan.cta, tone: plan.highlighted ? "primary" : "secondary", disabled: unavailable, describedBy: reason ? reasonId : undefined })) : (isCurrent && ((0, jsx_runtime_1.jsx)("p", { className: "rounded-lg border border-surface-border bg-surface-muted px-4 py-2.5 text-center text-sm font-semibold break-words text-grey-700", children: plan.currentLabel ?? "Current plan" }))), plan.limits && plan.limits.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-2 border-t border-surface-border pt-4", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-caption-sm font-semibold tracking-[0.08em] text-grey-600 uppercase", children: limitsLabel }), (0, jsx_runtime_1.jsx)("dl", { className: "m-0 flex min-w-0 flex-col gap-1.5", children: plan.limits.map((entry) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-wrap justify-between gap-x-3 gap-y-0.5", children: [(0, jsx_runtime_1.jsx)("dt", { className: "min-w-0 text-sm break-words text-grey-600", children: entry.label }), (0, jsx_runtime_1.jsx)("dd", { className: "m-0 min-w-0 text-sm font-semibold break-words tabular-nums text-grey-900", children: entry.value })] }, entry.id))) })] })), features.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-3 border-t border-surface-border pt-4", children: [(0, jsx_runtime_1.jsx)("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: visibleFeatures.map((feature) => {
|
|
106
|
+
const included = feature.included !== false;
|
|
107
|
+
return ((0, jsx_runtime_1.jsxs)("li", { className: "flex min-w-0 items-start gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: included ? "check-circle" : "minus-circle", size: "sm", className: (0, cn_1.cn)("mt-0.5 shrink-0", included ? "text-action-success" : "text-grey-400"), "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { className: (0, cn_1.cn)("min-w-0 break-words", included ? "text-grey-700" : "text-grey-600"), children: [(0, jsx_runtime_1.jsx)("span", { className: "sr-only", children: included ? "Included: " : "Not included: " }), feature.label, feature.note && ((0, jsx_runtime_1.jsxs)("span", { className: "text-grey-600", children: [" (", feature.note, ")"] }))] })] }, feature.id));
|
|
108
|
+
}) }), collapsible && ((0, jsx_runtime_1.jsxs)("button", { type: "button", "aria-expanded": expanded, onClick: () => setExpanded((open) => !open), className: "inline-flex max-w-full items-center gap-1.5 self-start rounded text-sm font-semibold break-words text-action-primary-text cursor-pointer hover:underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text", children: [(0, jsx_runtime_1.jsx)("span", { className: "min-w-0", children: expanded
|
|
109
|
+
? `Show fewer features${planNameText ? ` in ${planNameText}` : ""}`
|
|
110
|
+
: `Show all ${features.length} features${planNameText ? ` in ${planNameText}` : ""}` }), (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: expanded ? "chevron-up" : "chevron-down", size: "sm", className: "shrink-0", "aria-hidden": "true" })] }))] }))] }));
|
|
111
|
+
}
|
|
112
|
+
/* ─── Block ────────────────────────────────────────────────────────────── */
|
|
113
|
+
/**
|
|
114
|
+
* Pricing Plans — compare plans, switch billing period, and understand included
|
|
115
|
+
* usage and limits.
|
|
116
|
+
*
|
|
117
|
+
* States: `default`, `loading`, `empty`, `error`, plus per-plan `available`,
|
|
118
|
+
* `current`, and `unavailable`. Choosing a plan calls `cta.onClick` or follows
|
|
119
|
+
* `cta.href`; the block performs no purchase and never claims one succeeded.
|
|
120
|
+
* Pass `notice` to show an outcome the consuming application has confirmed.
|
|
121
|
+
*/
|
|
122
|
+
exports.PricingPlansBlock = (0, react_1.forwardRef)(({ eyebrow, title, description, headingLevel = "h2", periods, period: controlledPeriod, defaultPeriod, onPeriodChange, periodLegend = "Billing period", plans, locale = "en-US", priceFormatOptions, formatPrice, limitsLabel = "Included usage", featuresVisibleLimit, defaultUnavailableReason = "This plan is not available for your account.", notice, footnote, state, loadingPlanCount = 3, loadingLabel = "Loading pricing plans", emptyTitle = "No plans are available right now", emptyDescription, errorTitle = "We could not load pricing", errorDescription, onRetry, retryLabel = "Try again", className, ...rest }, ref) => {
|
|
123
|
+
const headingId = (0, react_1.useId)();
|
|
124
|
+
const radioName = (0, react_1.useId)();
|
|
125
|
+
const Heading = headingLevel;
|
|
126
|
+
const PlanHeading = nextHeadingLevel(headingLevel);
|
|
127
|
+
const periodList = (0, react_1.useMemo)(() => periods ?? [], [periods]);
|
|
128
|
+
const fallbackPeriodId = periodList[0]?.id ?? "default";
|
|
129
|
+
const [internalPeriod, setInternalPeriod] = (0, react_1.useState)(defaultPeriod ?? fallbackPeriodId);
|
|
130
|
+
const activePeriod = controlledPeriod ?? internalPeriod;
|
|
131
|
+
const selectPeriod = (0, react_1.useCallback)((id) => {
|
|
132
|
+
if (controlledPeriod === undefined)
|
|
133
|
+
setInternalPeriod(id);
|
|
134
|
+
onPeriodChange?.(id);
|
|
135
|
+
}, [controlledPeriod, onPeriodChange]);
|
|
136
|
+
const resolvedState = state ?? (plans.length === 0 ? "empty" : "default");
|
|
137
|
+
const showSwitcher = resolvedState === "default" && periodList.length > 1;
|
|
138
|
+
const columnClass = PLAN_COLUMN_CLASS[Math.min(Math.max(plans.length, 1), 4)];
|
|
139
|
+
return ((0, jsx_runtime_1.jsx)("section", { ref: ref, "aria-labelledby": headingId, "aria-busy": resolvedState === "loading" || undefined, className: (0, cn_1.cn)("@container relative w-full", className), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "mx-auto flex w-full max-w-[1136px] flex-col gap-8 px-4 py-12 @min-[600px]:gap-10 @min-[600px]:px-8 @min-[600px]:py-16", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 max-w-[68ch] flex-col gap-3", children: [eyebrow && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold break-words text-action-primary-text", children: eyebrow })), (0, jsx_runtime_1.jsx)(Heading, { id: headingId, className: "text-h5 font-semibold break-words text-balance text-grey-900 @min-[600px]:text-h3", children: title }), description && ((0, jsx_runtime_1.jsx)("p", { className: "text-base break-words text-grey-600 @min-[600px]:text-body-lg", children: description }))] }), showSwitcher && ((0, jsx_runtime_1.jsxs)("fieldset", { className: "m-0 min-w-0 border-0 p-0", children: [(0, jsx_runtime_1.jsx)("legend", { className: "sr-only", children: periodLegend }), (0, jsx_runtime_1.jsx)("div", { className: "inline-flex max-w-full flex-wrap gap-1 rounded-xl border border-surface-border bg-surface-muted p-1", children: periodList.map((entry) => {
|
|
140
|
+
const checked = entry.id === activePeriod;
|
|
141
|
+
return ((0, jsx_runtime_1.jsxs)("label", { className: (0, cn_1.cn)("relative inline-flex max-w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-semibold break-words transition-colors", "has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-action-primary-text", checked
|
|
142
|
+
? "bg-surface text-grey-900 shadow-soft-xxs"
|
|
143
|
+
: "text-grey-600 hover:text-grey-900"), children: [(0, jsx_runtime_1.jsx)("input", { type: "radio", name: radioName, value: entry.id, checked: checked, onChange: () => selectPeriod(entry.id), className: "sr-only" }), (0, jsx_runtime_1.jsx)("span", { className: "min-w-0", children: entry.label }), entry.badge && ((0, jsx_runtime_1.jsx)(Badge_1.Badge, { color: "success", type: "accent", size: "sm", children: entry.badge }))] }, entry.id));
|
|
144
|
+
}) })] }))] }), notice && ((0, jsx_runtime_1.jsxs)("div", { role: "status", className: (0, cn_1.cn)("flex min-w-0 items-start gap-3 rounded-xl border bg-surface p-4", NOTICE_TONE[notice.tone].border), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: NOTICE_TONE[notice.tone].icon, size: "md", className: (0, cn_1.cn)("mt-0.5 shrink-0", NOTICE_TONE[notice.tone].text), "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-1", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-sm font-semibold break-words text-grey-900", children: notice.title }), notice.description && ((0, jsx_runtime_1.jsx)("p", { className: "text-sm break-words text-grey-600", children: notice.description }))] })] })), resolvedState === "loading" && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("p", { role: "status", className: "sr-only", children: loadingLabel }), (0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("grid grid-cols-1 gap-5 @min-[600px]:gap-6", PLAN_COLUMN_CLASS[3]), "aria-hidden": "true", children: Array.from({ length: Math.max(1, loadingPlanCount) }).map((_, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col gap-4 rounded-xl border border-surface-border bg-surface p-6", children: [(0, jsx_runtime_1.jsx)("div", { className: "h-5 w-28 rounded bg-grey-200" }), (0, jsx_runtime_1.jsx)("div", { className: "h-9 w-40 max-w-full rounded bg-grey-200" }), (0, jsx_runtime_1.jsx)("div", { className: "h-11 w-full rounded-lg bg-grey-100" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-full rounded bg-grey-100" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-5/6 rounded bg-grey-100" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-4/6 rounded bg-grey-100" })] }, `plan-skeleton-${index}`))) })] })), resolvedState === "error" && ((0, jsx_runtime_1.jsxs)("div", { role: "alert", className: "flex min-w-0 flex-col items-start gap-3 rounded-xl border border-surface-border bg-surface p-5 @min-[600px]:p-6", children: [(0, jsx_runtime_1.jsx)("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-error-50 text-action-danger-text", children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "info-triangle", size: "lg", "aria-hidden": "true" }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-base font-semibold break-words text-grey-900", children: errorTitle }), errorDescription && ((0, jsx_runtime_1.jsx)("p", { className: "max-w-[60ch] text-sm break-words text-grey-600", children: errorDescription })), onRetry && ((0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: onRetry, className: (0, cn_1.cn)(CTA_BASE, CTA_TONE.secondary, "w-auto"), children: [(0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "refresh", size: "sm", className: "shrink-0", "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("span", { className: "min-w-0", children: retryLabel })] }))] })), resolvedState === "empty" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex min-w-0 flex-col items-start gap-3 rounded-xl border border-dashed border-surface-border-strong bg-surface p-6 @min-[600px]:p-8", children: [(0, jsx_runtime_1.jsx)("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-grey-100 text-grey-600", children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "receipt", size: "lg", "aria-hidden": "true" }) }), (0, jsx_runtime_1.jsx)("p", { className: "text-base font-semibold break-words text-grey-900", children: emptyTitle }), emptyDescription && ((0, jsx_runtime_1.jsx)("p", { className: "max-w-[60ch] text-sm break-words text-grey-600", children: emptyDescription }))] })), resolvedState === "default" && plans.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: (0, cn_1.cn)("grid list-none grid-cols-1 items-start gap-5 p-0 @min-[600px]:gap-6", columnClass), children: plans.map((plan) => ((0, jsx_runtime_1.jsx)(PlanCard, { plan: plan, periodId: activePeriod, locale: locale, priceFormatOptions: priceFormatOptions, formatPrice: formatPrice, limitsLabel: limitsLabel, featuresVisibleLimit: featuresVisibleLimit, defaultUnavailableReason: defaultUnavailableReason, headingElement: PlanHeading }, plan.id))) })), footnote && (0, jsx_runtime_1.jsx)("p", { className: "text-sm break-words text-grey-600", children: footnote })] }) }));
|
|
145
|
+
});
|
|
146
|
+
exports.PricingPlansBlock.displayName = "PricingPlansBlock";
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useCallback, useId, useMemo, useState, } from "react";
|
|
3
|
+
import { cn } from "../utils/cn.js";
|
|
4
|
+
import { Badge } from "../components/Badge/index.js";
|
|
5
|
+
import { Icon } from "../components/Icon/index.js";
|
|
6
|
+
const HEADING_ORDER = ["h1", "h2", "h3", "h4", "h5", "h6"];
|
|
7
|
+
function nextHeadingLevel(level) {
|
|
8
|
+
const index = HEADING_ORDER.indexOf(level);
|
|
9
|
+
return HEADING_ORDER[Math.min(index + 1, HEADING_ORDER.length - 1)];
|
|
10
|
+
}
|
|
11
|
+
function isLinkCta(cta) {
|
|
12
|
+
return typeof cta.href === "string";
|
|
13
|
+
}
|
|
14
|
+
/* ─── Formatting ───────────────────────────────────────────────────────── */
|
|
15
|
+
function formatCurrency(amount, currency, locale, options) {
|
|
16
|
+
const whole = Number.isInteger(amount);
|
|
17
|
+
const base = currency
|
|
18
|
+
? {
|
|
19
|
+
style: "currency",
|
|
20
|
+
currency,
|
|
21
|
+
minimumFractionDigits: whole ? 0 : 2,
|
|
22
|
+
maximumFractionDigits: whole ? 0 : 2,
|
|
23
|
+
}
|
|
24
|
+
: { maximumFractionDigits: whole ? 0 : 2 };
|
|
25
|
+
try {
|
|
26
|
+
return new Intl.NumberFormat(locale, { ...base, ...options }).format(amount);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// An unknown locale or currency code must not break the page.
|
|
30
|
+
return currency ? `${currency} ${amount}` : String(amount);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/* ─── Appearance ───────────────────────────────────────────────────────── */
|
|
34
|
+
const PLAN_COLUMN_CLASS = {
|
|
35
|
+
1: "@min-[600px]:grid-cols-1 @min-[1136px]:grid-cols-1",
|
|
36
|
+
2: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-2",
|
|
37
|
+
3: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-3",
|
|
38
|
+
4: "@min-[600px]:grid-cols-2 @min-[1136px]:grid-cols-4",
|
|
39
|
+
};
|
|
40
|
+
const CTA_BASE = "inline-flex w-full max-w-full items-center justify-center gap-2 min-h-11 rounded-lg px-4 py-2.5 text-sm font-semibold leading-[1.45] text-center break-words no-underline transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text";
|
|
41
|
+
const CTA_TONE = {
|
|
42
|
+
primary: "bg-action-primary text-white hover:bg-action-primary-hover cursor-pointer",
|
|
43
|
+
secondary: "bg-transparent text-action-primary-text border-[1.5px] border-current hover:bg-primary-50 cursor-pointer",
|
|
44
|
+
};
|
|
45
|
+
const NOTICE_TONE = {
|
|
46
|
+
success: {
|
|
47
|
+
border: "border-success-300",
|
|
48
|
+
icon: "check-circle",
|
|
49
|
+
text: "text-action-success",
|
|
50
|
+
},
|
|
51
|
+
info: { border: "border-secondary-300", icon: "info-circle", text: "text-action-info" },
|
|
52
|
+
warning: { border: "border-warning-300", icon: "info-triangle", text: "text-action-warning" },
|
|
53
|
+
error: { border: "border-error-300", icon: "info-triangle", text: "text-action-danger-text" },
|
|
54
|
+
};
|
|
55
|
+
function PlanCta({ cta, tone, disabled = false, describedBy }) {
|
|
56
|
+
const content = (_jsxs(_Fragment, { children: [cta.icon && _jsx(Icon, { name: cta.icon, size: "sm", className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "min-w-0", children: cta.label })] }));
|
|
57
|
+
// A control that cannot be used is rendered as an explicitly disabled button,
|
|
58
|
+
// never as an active link that goes nowhere.
|
|
59
|
+
if (disabled) {
|
|
60
|
+
return (_jsx("button", { type: "button", disabled: true, "aria-describedby": describedBy, className: cn(CTA_BASE, "cursor-not-allowed border-[1.5px] border-surface-border-strong bg-grey-100 text-grey-600"), children: content }));
|
|
61
|
+
}
|
|
62
|
+
if (isLinkCta(cta)) {
|
|
63
|
+
const externalAttributes = cta.external
|
|
64
|
+
? { target: "_blank", rel: "noreferrer" }
|
|
65
|
+
: {};
|
|
66
|
+
return (_jsxs("a", { href: cta.href, onClick: cta.onClick, "aria-describedby": describedBy, className: cn(CTA_BASE, CTA_TONE[tone]), ...externalAttributes, children: [content, cta.external && _jsx("span", { className: "sr-only", children: "(opens in a new tab)" })] }));
|
|
67
|
+
}
|
|
68
|
+
return (_jsx("button", { type: "button", onClick: cta.onClick, "aria-describedby": describedBy, className: cn(CTA_BASE, CTA_TONE[tone]), children: content }));
|
|
69
|
+
}
|
|
70
|
+
function PlanCard({ plan, periodId, locale, priceFormatOptions, formatPrice, limitsLabel, featuresVisibleLimit, defaultUnavailableReason, headingElement, }) {
|
|
71
|
+
const [expanded, setExpanded] = useState(false);
|
|
72
|
+
const reasonId = useId();
|
|
73
|
+
const PlanHeading = headingElement;
|
|
74
|
+
// Unavailability is conveyed by the disabled control and its associated reason,
|
|
75
|
+
// never by dimming the card: reducing opacity drops every label below AA.
|
|
76
|
+
const availability = plan.availability ?? "available";
|
|
77
|
+
const unavailable = availability === "unavailable";
|
|
78
|
+
const isCurrent = availability === "current";
|
|
79
|
+
const price = plan.prices[periodId];
|
|
80
|
+
const features = plan.features ?? [];
|
|
81
|
+
const limit = featuresVisibleLimit && featuresVisibleLimit > 0 ? featuresVisibleLimit : undefined;
|
|
82
|
+
const collapsible = limit !== undefined && features.length > limit;
|
|
83
|
+
const visibleFeatures = collapsible && !expanded ? features.slice(0, limit) : features;
|
|
84
|
+
const planNameText = typeof plan.name === "string" ? plan.name : undefined;
|
|
85
|
+
const reason = unavailable ? (plan.unavailableReason ?? defaultUnavailableReason) : undefined;
|
|
86
|
+
let priceBody = null;
|
|
87
|
+
if (!price) {
|
|
88
|
+
priceBody = _jsx("span", { className: "text-base text-grey-600", children: "Price unavailable" });
|
|
89
|
+
}
|
|
90
|
+
else if (formatPrice) {
|
|
91
|
+
priceBody = formatPrice(price, { locale, periodId });
|
|
92
|
+
}
|
|
93
|
+
else if (price.custom !== undefined) {
|
|
94
|
+
priceBody = (_jsx("span", { className: "text-h5 font-semibold break-words text-grey-900 @min-[600px]:text-h4", children: price.custom }));
|
|
95
|
+
}
|
|
96
|
+
else if (price.amount !== undefined) {
|
|
97
|
+
priceBody = (_jsxs("span", { className: "flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1", children: [price.originalAmount !== undefined && (_jsxs("span", { className: "text-base text-grey-600 line-through tabular-nums", children: [_jsx("span", { className: "sr-only", children: "Was " }), formatCurrency(price.originalAmount, price.currency, locale, priceFormatOptions)] })), _jsx("span", { className: "text-h4 font-semibold break-words tabular-nums text-grey-900 @min-[600px]:text-h3", children: formatCurrency(price.amount, price.currency, locale, priceFormatOptions) }), price.unit && (_jsx("span", { className: "min-w-0 text-sm break-words text-grey-600", children: price.unit }))] }));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
priceBody = _jsx("span", { className: "text-base text-grey-600", children: "Price unavailable" });
|
|
101
|
+
}
|
|
102
|
+
return (_jsxs("li", { className: cn("flex min-w-0 flex-col gap-5 rounded-xl border bg-surface p-5 @min-[600px]:p-6", plan.highlighted ? "border-primary-400 shadow-soft-sm @min-[600px]:border-2" : "border-surface-border"), children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [_jsx(PlanHeading, { className: "min-w-0 text-body-lg font-semibold break-words text-grey-900", children: plan.name }), plan.badge && (_jsx(Badge, { color: "orange", type: "accent", size: "md", children: plan.badge })), isCurrent && (_jsx(Badge, { color: "success", type: "accent", size: "md", children: plan.currentLabel ?? "Current plan" }))] }), plan.description && (_jsx("p", { className: "text-sm break-words text-grey-600", children: plan.description }))] }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [priceBody, price?.note && _jsx("p", { className: "text-sm break-words text-grey-600", children: price.note })] }), reason && (_jsx("p", { id: reasonId, className: "text-sm break-words text-grey-600", children: reason })), plan.cta ? (_jsx(PlanCta, { cta: plan.cta, tone: plan.highlighted ? "primary" : "secondary", disabled: unavailable, describedBy: reason ? reasonId : undefined })) : (isCurrent && (_jsx("p", { className: "rounded-lg border border-surface-border bg-surface-muted px-4 py-2.5 text-center text-sm font-semibold break-words text-grey-700", children: plan.currentLabel ?? "Current plan" }))), plan.limits && plan.limits.length > 0 && (_jsxs("div", { className: "flex min-w-0 flex-col gap-2 border-t border-surface-border pt-4", children: [_jsx("p", { className: "text-caption-sm font-semibold tracking-[0.08em] text-grey-600 uppercase", children: limitsLabel }), _jsx("dl", { className: "m-0 flex min-w-0 flex-col gap-1.5", children: plan.limits.map((entry) => (_jsxs("div", { className: "flex min-w-0 flex-wrap justify-between gap-x-3 gap-y-0.5", children: [_jsx("dt", { className: "min-w-0 text-sm break-words text-grey-600", children: entry.label }), _jsx("dd", { className: "m-0 min-w-0 text-sm font-semibold break-words tabular-nums text-grey-900", children: entry.value })] }, entry.id))) })] })), features.length > 0 && (_jsxs("div", { className: "flex min-w-0 flex-col gap-3 border-t border-surface-border pt-4", children: [_jsx("ul", { className: "m-0 flex list-none flex-col gap-2 p-0", children: visibleFeatures.map((feature) => {
|
|
103
|
+
const included = feature.included !== false;
|
|
104
|
+
return (_jsxs("li", { className: "flex min-w-0 items-start gap-2 text-sm", children: [_jsx(Icon, { name: included ? "check-circle" : "minus-circle", size: "sm", className: cn("mt-0.5 shrink-0", included ? "text-action-success" : "text-grey-400"), "aria-hidden": "true" }), _jsxs("span", { className: cn("min-w-0 break-words", included ? "text-grey-700" : "text-grey-600"), children: [_jsx("span", { className: "sr-only", children: included ? "Included: " : "Not included: " }), feature.label, feature.note && (_jsxs("span", { className: "text-grey-600", children: [" (", feature.note, ")"] }))] })] }, feature.id));
|
|
105
|
+
}) }), collapsible && (_jsxs("button", { type: "button", "aria-expanded": expanded, onClick: () => setExpanded((open) => !open), className: "inline-flex max-w-full items-center gap-1.5 self-start rounded text-sm font-semibold break-words text-action-primary-text cursor-pointer hover:underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-action-primary-text", children: [_jsx("span", { className: "min-w-0", children: expanded
|
|
106
|
+
? `Show fewer features${planNameText ? ` in ${planNameText}` : ""}`
|
|
107
|
+
: `Show all ${features.length} features${planNameText ? ` in ${planNameText}` : ""}` }), _jsx(Icon, { name: expanded ? "chevron-up" : "chevron-down", size: "sm", className: "shrink-0", "aria-hidden": "true" })] }))] }))] }));
|
|
108
|
+
}
|
|
109
|
+
/* ─── Block ────────────────────────────────────────────────────────────── */
|
|
110
|
+
/**
|
|
111
|
+
* Pricing Plans — compare plans, switch billing period, and understand included
|
|
112
|
+
* usage and limits.
|
|
113
|
+
*
|
|
114
|
+
* States: `default`, `loading`, `empty`, `error`, plus per-plan `available`,
|
|
115
|
+
* `current`, and `unavailable`. Choosing a plan calls `cta.onClick` or follows
|
|
116
|
+
* `cta.href`; the block performs no purchase and never claims one succeeded.
|
|
117
|
+
* Pass `notice` to show an outcome the consuming application has confirmed.
|
|
118
|
+
*/
|
|
119
|
+
export const PricingPlansBlock = forwardRef(({ eyebrow, title, description, headingLevel = "h2", periods, period: controlledPeriod, defaultPeriod, onPeriodChange, periodLegend = "Billing period", plans, locale = "en-US", priceFormatOptions, formatPrice, limitsLabel = "Included usage", featuresVisibleLimit, defaultUnavailableReason = "This plan is not available for your account.", notice, footnote, state, loadingPlanCount = 3, loadingLabel = "Loading pricing plans", emptyTitle = "No plans are available right now", emptyDescription, errorTitle = "We could not load pricing", errorDescription, onRetry, retryLabel = "Try again", className, ...rest }, ref) => {
|
|
120
|
+
const headingId = useId();
|
|
121
|
+
const radioName = useId();
|
|
122
|
+
const Heading = headingLevel;
|
|
123
|
+
const PlanHeading = nextHeadingLevel(headingLevel);
|
|
124
|
+
const periodList = useMemo(() => periods ?? [], [periods]);
|
|
125
|
+
const fallbackPeriodId = periodList[0]?.id ?? "default";
|
|
126
|
+
const [internalPeriod, setInternalPeriod] = useState(defaultPeriod ?? fallbackPeriodId);
|
|
127
|
+
const activePeriod = controlledPeriod ?? internalPeriod;
|
|
128
|
+
const selectPeriod = useCallback((id) => {
|
|
129
|
+
if (controlledPeriod === undefined)
|
|
130
|
+
setInternalPeriod(id);
|
|
131
|
+
onPeriodChange?.(id);
|
|
132
|
+
}, [controlledPeriod, onPeriodChange]);
|
|
133
|
+
const resolvedState = state ?? (plans.length === 0 ? "empty" : "default");
|
|
134
|
+
const showSwitcher = resolvedState === "default" && periodList.length > 1;
|
|
135
|
+
const columnClass = PLAN_COLUMN_CLASS[Math.min(Math.max(plans.length, 1), 4)];
|
|
136
|
+
return (_jsx("section", { ref: ref, "aria-labelledby": headingId, "aria-busy": resolvedState === "loading" || undefined, className: cn("@container relative w-full", className), ...rest, children: _jsxs("div", { className: "mx-auto flex w-full max-w-[1136px] flex-col gap-8 px-4 py-12 @min-[600px]:gap-10 @min-[600px]:px-8 @min-[600px]:py-16", children: [_jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [_jsxs("div", { className: "flex min-w-0 max-w-[68ch] flex-col gap-3", children: [eyebrow && (_jsx("p", { className: "text-sm font-semibold break-words text-action-primary-text", children: eyebrow })), _jsx(Heading, { id: headingId, className: "text-h5 font-semibold break-words text-balance text-grey-900 @min-[600px]:text-h3", children: title }), description && (_jsx("p", { className: "text-base break-words text-grey-600 @min-[600px]:text-body-lg", children: description }))] }), showSwitcher && (_jsxs("fieldset", { className: "m-0 min-w-0 border-0 p-0", children: [_jsx("legend", { className: "sr-only", children: periodLegend }), _jsx("div", { className: "inline-flex max-w-full flex-wrap gap-1 rounded-xl border border-surface-border bg-surface-muted p-1", children: periodList.map((entry) => {
|
|
137
|
+
const checked = entry.id === activePeriod;
|
|
138
|
+
return (_jsxs("label", { className: cn("relative inline-flex max-w-full cursor-pointer items-center justify-center gap-2 rounded-lg px-3 py-2 text-sm font-semibold break-words transition-colors", "has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-action-primary-text", checked
|
|
139
|
+
? "bg-surface text-grey-900 shadow-soft-xxs"
|
|
140
|
+
: "text-grey-600 hover:text-grey-900"), children: [_jsx("input", { type: "radio", name: radioName, value: entry.id, checked: checked, onChange: () => selectPeriod(entry.id), className: "sr-only" }), _jsx("span", { className: "min-w-0", children: entry.label }), entry.badge && (_jsx(Badge, { color: "success", type: "accent", size: "sm", children: entry.badge }))] }, entry.id));
|
|
141
|
+
}) })] }))] }), notice && (_jsxs("div", { role: "status", className: cn("flex min-w-0 items-start gap-3 rounded-xl border bg-surface p-4", NOTICE_TONE[notice.tone].border), children: [_jsx(Icon, { name: NOTICE_TONE[notice.tone].icon, size: "md", className: cn("mt-0.5 shrink-0", NOTICE_TONE[notice.tone].text), "aria-hidden": "true" }), _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("p", { className: "text-sm font-semibold break-words text-grey-900", children: notice.title }), notice.description && (_jsx("p", { className: "text-sm break-words text-grey-600", children: notice.description }))] })] })), resolvedState === "loading" && (_jsxs(_Fragment, { children: [_jsx("p", { role: "status", className: "sr-only", children: loadingLabel }), _jsx("div", { className: cn("grid grid-cols-1 gap-5 @min-[600px]:gap-6", PLAN_COLUMN_CLASS[3]), "aria-hidden": "true", children: Array.from({ length: Math.max(1, loadingPlanCount) }).map((_, index) => (_jsxs("div", { className: "flex min-w-0 flex-col gap-4 rounded-xl border border-surface-border bg-surface p-6", children: [_jsx("div", { className: "h-5 w-28 rounded bg-grey-200" }), _jsx("div", { className: "h-9 w-40 max-w-full rounded bg-grey-200" }), _jsx("div", { className: "h-11 w-full rounded-lg bg-grey-100" }), _jsx("div", { className: "h-4 w-full rounded bg-grey-100" }), _jsx("div", { className: "h-4 w-5/6 rounded bg-grey-100" }), _jsx("div", { className: "h-4 w-4/6 rounded bg-grey-100" })] }, `plan-skeleton-${index}`))) })] })), resolvedState === "error" && (_jsxs("div", { role: "alert", className: "flex min-w-0 flex-col items-start gap-3 rounded-xl border border-surface-border bg-surface p-5 @min-[600px]:p-6", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-error-50 text-action-danger-text", children: _jsx(Icon, { name: "info-triangle", size: "lg", "aria-hidden": "true" }) }), _jsx("p", { className: "text-base font-semibold break-words text-grey-900", children: errorTitle }), errorDescription && (_jsx("p", { className: "max-w-[60ch] text-sm break-words text-grey-600", children: errorDescription })), onRetry && (_jsxs("button", { type: "button", onClick: onRetry, className: cn(CTA_BASE, CTA_TONE.secondary, "w-auto"), children: [_jsx(Icon, { name: "refresh", size: "sm", className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: "min-w-0", children: retryLabel })] }))] })), resolvedState === "empty" && (_jsxs("div", { className: "flex min-w-0 flex-col items-start gap-3 rounded-xl border border-dashed border-surface-border-strong bg-surface p-6 @min-[600px]:p-8", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-full bg-grey-100 text-grey-600", children: _jsx(Icon, { name: "receipt", size: "lg", "aria-hidden": "true" }) }), _jsx("p", { className: "text-base font-semibold break-words text-grey-900", children: emptyTitle }), emptyDescription && (_jsx("p", { className: "max-w-[60ch] text-sm break-words text-grey-600", children: emptyDescription }))] })), resolvedState === "default" && plans.length > 0 && (_jsx("ul", { className: cn("grid list-none grid-cols-1 items-start gap-5 p-0 @min-[600px]:gap-6", columnClass), children: plans.map((plan) => (_jsx(PlanCard, { plan: plan, periodId: activePeriod, locale: locale, priceFormatOptions: priceFormatOptions, formatPrice: formatPrice, limitsLabel: limitsLabel, featuresVisibleLimit: featuresVisibleLimit, defaultUnavailableReason: defaultUnavailableReason, headingElement: PlanHeading }, plan.id))) })), footnote && _jsx("p", { className: "text-sm break-words text-grey-600", children: footnote })] }) }));
|
|
142
|
+
});
|
|
143
|
+
PricingPlansBlock.displayName = "PricingPlansBlock";
|