tempest-react-sdk 0.66.0 → 0.68.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/README.md +97 -93
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +41 -40
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js +35 -40
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Card/Card.module.cjs.map +1 -1
- package/dist/components/Card/Card.module.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js +96 -74
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Combobox/Combobox.module.cjs +1 -1
- package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.module.js +4 -3
- package/dist/components/Combobox/Combobox.module.js.map +1 -1
- package/dist/components/Command/Command.cjs +1 -1
- package/dist/components/Command/Command.js +4 -4
- package/dist/components/ContextMenu/ContextMenu.cjs +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js +80 -83
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +138 -127
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs +1 -1
- package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
- package/dist/components/DataTable/use-dev-warnings.js +5 -3
- package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js +46 -46
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js +129 -105
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.module.js +12 -11
- package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
- package/dist/components/FilterPanel/FilterPanel.cjs +2 -0
- package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.js +75 -0
- package/dist/components/FilterPanel/FilterPanel.js.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs +3 -0
- package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -0
- package/dist/components/FilterPanel/FilterPanel.module.js +12 -0
- package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -0
- package/dist/components/HoverCard/HoverCard.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js +54 -29
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs +1 -1
- package/dist/components/HoverCard/HoverCard.module.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.module.js +4 -3
- package/dist/components/HoverCard/HoverCard.module.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js +84 -87
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js +86 -65
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Menubar/Menubar.module.cjs +1 -1
- package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.module.js +4 -3
- package/dist/components/Menubar/Menubar.module.js.map +1 -1
- package/dist/components/Modal/Modal.cjs +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js +47 -51
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js +113 -96
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.module.js +4 -3
- package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +83 -61
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.module.js +4 -3
- package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js +31 -30
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/Popover/Popover.cjs +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js +57 -36
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Popover/Popover.module.cjs +1 -1
- package/dist/components/Popover/Popover.module.cjs.map +1 -1
- package/dist/components/Popover/Popover.module.js +7 -6
- package/dist/components/Popover/Popover.module.js.map +1 -1
- package/dist/components/Portal/anchor-position.cjs +2 -0
- package/dist/components/Portal/anchor-position.cjs.map +1 -0
- package/dist/components/Portal/anchor-position.js +99 -0
- package/dist/components/Portal/anchor-position.js.map +1 -0
- package/dist/components/Portal/escape-layer.cjs +2 -0
- package/dist/components/Portal/escape-layer.cjs.map +1 -0
- package/dist/components/Portal/escape-layer.js +30 -0
- package/dist/components/Portal/escape-layer.js.map +1 -0
- package/dist/components/Portal/tab-bridge.cjs +2 -0
- package/dist/components/Portal/tab-bridge.cjs.map +1 -0
- package/dist/components/Portal/tab-bridge.js +37 -0
- package/dist/components/Portal/tab-bridge.js.map +1 -0
- package/dist/components/Select/CaretIcon.cjs +2 -0
- package/dist/components/Select/CaretIcon.cjs.map +1 -0
- package/dist/components/Select/CaretIcon.js +21 -0
- package/dist/components/Select/CaretIcon.js.map +1 -0
- package/dist/components/Select/Select.cjs +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js +33 -47
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Select/Select.module.cjs +1 -1
- package/dist/components/Select/Select.module.cjs.map +1 -1
- package/dist/components/Select/Select.module.js +4 -3
- package/dist/components/Select/Select.module.js.map +1 -1
- package/dist/components/Select/options.cjs +2 -0
- package/dist/components/Select/options.cjs.map +1 -0
- package/dist/components/Select/options.js +25 -0
- package/dist/components/Select/options.js.map +1 -0
- package/dist/components/Sidebar/Sidebar.cjs +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js +49 -43
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/active-nav-key.cjs +2 -0
- package/dist/components/Sidebar/active-nav-key.cjs.map +1 -0
- package/dist/components/Sidebar/active-nav-key.js +22 -0
- package/dist/components/Sidebar/active-nav-key.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js +43 -29
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs +1 -1
- package/dist/components/Tooltip/Tooltip.module.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.module.js +4 -3
- package/dist/components/Tooltip/Tooltip.module.js.map +1 -1
- package/dist/hooks/tab-sequence.cjs +2 -0
- package/dist/hooks/tab-sequence.cjs.map +1 -0
- package/dist/hooks/tab-sequence.js +53 -0
- package/dist/hooks/tab-sequence.js.map +1 -0
- package/dist/hooks/use-deep-memo.cjs +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.js +1 -1
- package/dist/hooks/use-deep-memo.js.map +1 -1
- package/dist/hooks/use-draft-filters.cjs +2 -0
- package/dist/hooks/use-draft-filters.cjs.map +1 -0
- package/dist/hooks/use-draft-filters.js +49 -0
- package/dist/hooks/use-draft-filters.js.map +1 -0
- package/dist/hooks/use-focus-trap.cjs +1 -1
- package/dist/hooks/use-focus-trap.cjs.map +1 -1
- package/dist/hooks/use-focus-trap.js +31 -34
- package/dist/hooks/use-focus-trap.js.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +3 -3
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +92 -80
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +5 -3
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/csrf.cjs +2 -0
- package/dist/http/csrf.cjs.map +1 -0
- package/dist/http/csrf.js +46 -0
- package/dist/http/csrf.js.map +1 -0
- package/dist/http/describe-api-error.cjs +1 -1
- package/dist/http/describe-api-error.cjs.map +1 -1
- package/dist/http/describe-api-error.js +4 -2
- package/dist/http/describe-api-error.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +131 -123
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +41 -33
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/query/use-paginated-query.cjs +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/query/use-paginated-query.js +47 -30
- package/dist/query/use-paginated-query.js.map +1 -1
- package/dist/styles/FilterPanel.css +2 -0
- package/dist/styles/actions.css +3 -0
- package/dist/styles/advanced.css +1 -0
- package/dist/styles/component/Card.css +1 -1
- package/dist/styles/component/Combobox.css +5 -3
- package/dist/styles/component/DropdownMenu.css +1 -0
- package/dist/styles/component/FilterPanel.css +5 -0
- package/dist/styles/component/HoverCard.css +1 -0
- package/dist/styles/component/Menubar.css +1 -0
- package/dist/styles/component/MultiSelect.css +1 -0
- package/dist/styles/component/NavigationMenu.css +1 -0
- package/dist/styles/component/Popover.css +1 -0
- package/dist/styles/component/Select.css +13 -6
- package/dist/styles/component/Tooltip.css +1 -0
- package/dist/styles/core.css +4 -4
- package/dist/styles/data.css +4 -0
- package/dist/styles/forms.css +19 -9
- package/dist/styles/identity.css +1 -1
- package/dist/styles/manifest.json +6 -0
- package/dist/styles/navigation.css +2 -0
- package/dist/styles/tokens.css +4 -4
- package/dist/styles.css +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +805 -35
- package/dist/tempest-react-sdk.js +347 -342
- package/dist/vite/tempest-csp.cjs +3 -0
- package/dist/vite/tempest-csp.cjs.map +1 -0
- package/dist/vite/tempest-csp.js +189 -0
- package/dist/vite/tempest-csp.js.map +1 -0
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +145 -0
- package/dist/vite.js +2 -1
- package/package.json +2 -2
- package/template/vite.config.ts +8 -1
- package/template-pwa/vite.config.ts +6 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.cjs","names":[],"sources":["../../../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./HoverCard.module.css\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface HoverCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Element the user hovers/focuses to reveal the card. */\n trigger: ReactNode;\n /** Card content shown while hovering/focusing the trigger or the card itself. */\n children: ReactNode;\n /** Delay in ms before opening on mouseenter/focus. Default 300. */\n openDelay?: number;\n /** Delay in ms before closing on mouseleave/blur. Default 150. */\n closeDelay?: number;\n /** Where the card is anchored relative to the trigger. Default \"bottom\". */\n placement?: HoverCardPlacement;\n}\n\n/**\n * Content preview shown when the trigger is hovered or focused.\n *\n * - Opens after `openDelay` on `mouseenter`/`focus`.\n * - Closes after `closeDelay` on `mouseleave`/`blur`.\n * - Positioned relative to the trigger via `placement
|
|
1
|
+
{"version":3,"file":"HoverCard.cjs","names":[],"sources":["../../../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./HoverCard.module.css\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface HoverCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Element the user hovers/focuses to reveal the card. */\n trigger: ReactNode;\n /** Card content shown while hovering/focusing the trigger or the card itself. */\n children: ReactNode;\n /** Delay in ms before opening on mouseenter/focus. Default 300. */\n openDelay?: number;\n /** Delay in ms before closing on mouseleave/blur. Default 150. */\n closeDelay?: number;\n /** Where the card is anchored relative to the trigger. Default \"bottom\". */\n placement?: HoverCardPlacement;\n /**\n * Render the card in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the card — a\n * user handle in the last row of a `DataTable` showed 17% of it (measured in\n * Chrome at 1440 px). In a portal it escapes the clip and flips to the\n * opposite side at the viewport edge.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and card, in px — the in-flow CSS uses the same 8 px. */\nconst CARD_OFFSET = 8;\n\n/**\n * Content preview shown when the trigger is hovered or focused.\n *\n * - Opens after `openDelay` on `mouseenter`/`focus`.\n * - Closes after `closeDelay` on `mouseleave`/`blur`.\n * - Positioned relative to the trigger via `placement`, in a portal by default.\n * - Moving the pointer from the trigger into the card keeps it open: React\n * dispatches `mouseenter`/`mouseleave` along the component tree, and the\n * portalled card is still inside the trigger's wrapper there.\n * - A link or button in the card is the next `Tab` stop after the trigger, as it\n * was in flow.\n * - The card is rendered as a labelled `role=\"dialog\"` region; the trigger stays keyboard focusable.\n *\n * @param props - The hover card props.\n * @returns The trigger wrapper and the anchored card when open.\n */\nexport function HoverCard({\n trigger,\n children,\n openDelay = 300,\n closeDelay = 150,\n placement = \"bottom\",\n portal = true,\n className,\n style,\n ...rest\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [rootNode, setRootNode] = useState<HTMLSpanElement | null>(null);\n const [cardNode, setCardNode] = useState<HTMLDivElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: rootNode,\n floating: cardNode,\n side: placement,\n align: \"center\",\n offset: CARD_OFFSET,\n enabled: portal && open,\n });\n usePortalTabOrder({ enabled: portal && open, anchor: rootNode, panel: cardNode });\n const id = useId();\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearTimer = useCallback((): void => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const scheduleOpen = useCallback((): void => {\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }, [clearTimer, openDelay]);\n\n const scheduleClose = useCallback((): void => {\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(false), closeDelay);\n }, [clearTimer, closeDelay]);\n\n useEffect(() => clearTimer, [clearTimer]);\n\n const card = (\n <div\n ref={setCardNode}\n id={id}\n role=\"dialog\"\n className={cn(styles.card, styles[placement], portal && styles.portalled, className)}\n style={{ ...style, ...floatingStyle }}\n {...rest}\n >\n {children}\n </div>\n );\n\n return (\n <span\n ref={setRootNode}\n className={styles.root}\n onMouseEnter={scheduleOpen}\n onMouseLeave={scheduleClose}\n onFocus={scheduleOpen}\n onBlur={scheduleClose}\n >\n <span aria-describedby={open ? id : undefined} className={styles.trigger}>\n {trigger}\n </span>\n {open && (portal ? <Portal>{card}</Portal> : card)}\n </span>\n );\n}\n"],"mappings":"mPAiCA,IAAM,EAAc,EAkBpB,SAAgB,EAAU,CACtB,UACA,WACA,YAAY,IACZ,aAAa,IACb,YAAY,SACZ,SAAS,GACT,YACA,QACA,GAAG,GACY,CACf,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAS,EAAK,EAChC,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAAiC,IAAI,EAC/D,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAAgC,IAAI,EAC9D,EAAgB,EAAA,kBAAkB,CACpC,OAAQ,EACR,SAAU,EACV,KAAM,EACN,MAAO,SACP,OAAQ,EACR,QAAS,GAAU,CACvB,CAAC,EACD,EAAA,kBAAkB,CAAE,QAAS,GAAU,EAAM,OAAQ,EAAU,MAAO,CAAS,CAAC,EAChF,IAAM,GAAA,EAAK,EAAA,MAAA,CAAM,EACX,GAAA,EAAW,EAAA,OAAA,CAA6C,IAAI,EAE5D,GAAA,EAAa,EAAA,YAAA,KAAwB,CACnC,EAAS,UAAY,OACrB,aAAa,EAAS,OAAO,EAC7B,EAAS,QAAU,KAE3B,EAAG,CAAC,CAAC,EAEC,GAAA,EAAe,EAAA,YAAA,KAAwB,CACzC,EAAW,EACX,EAAS,QAAU,eAAiB,EAAQ,EAAI,EAAG,CAAS,CAChE,EAAG,CAAC,EAAY,CAAS,CAAC,EAEpB,GAAA,EAAgB,EAAA,YAAA,KAAwB,CAC1C,EAAW,EACX,EAAS,QAAU,eAAiB,EAAQ,EAAK,EAAG,CAAU,CAClE,EAAG,CAAC,EAAY,CAAU,CAAC,GAE3B,EAAA,EAAA,UAAA,KAAgB,EAAY,CAAC,CAAU,CAAC,EAExC,IAAM,GACF,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACD,KACJ,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,EAAA,QAAO,GAAY,GAAU,EAAA,QAAO,UAAW,CAAS,EACnF,MAAO,CAAE,GAAG,EAAO,GAAG,CAAc,EACpC,GAAI,EAEH,UACA,CAAA,EAGT,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CACI,IAAK,EACL,UAAW,EAAA,QAAO,KAClB,aAAc,EACd,aAAc,EACd,QAAS,EACT,OAAQ,EANZ,SAAA,EAQI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,mBAAkB,EAAO,EAAK,IAAA,GAAW,UAAW,EAAA,QAAO,QAC5D,SAAA,CACC,CAAA,EACL,IAAS,GAAS,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CAAA,SAAS,CAAa,CAAA,EAAI,EAC3C,GAEd"}
|
|
@@ -1,36 +1,61 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import t from "
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { Portal as t } from "../Portal/Portal.js";
|
|
3
|
+
import { useAnchorPosition as n } from "../Portal/anchor-position.js";
|
|
4
|
+
import { usePortalTabOrder as r } from "../Portal/tab-bridge.js";
|
|
5
|
+
import i from "./HoverCard.module.js";
|
|
6
|
+
import { useCallback as a, useEffect as o, useId as s, useRef as c, useState as l } from "react";
|
|
7
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
5
8
|
//#region src/components/HoverCard/HoverCard.tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
var f = 8;
|
|
10
|
+
function p({ trigger: p, children: m, openDelay: h = 300, closeDelay: g = 150, placement: _ = "bottom", portal: v = !0, className: y, style: b, ...x }) {
|
|
11
|
+
let [S, C] = l(!1), [w, T] = l(null), [E, D] = l(null), O = n({
|
|
12
|
+
anchor: w,
|
|
13
|
+
floating: E,
|
|
14
|
+
side: _,
|
|
15
|
+
align: "center",
|
|
16
|
+
offset: f,
|
|
17
|
+
enabled: v && S
|
|
18
|
+
});
|
|
19
|
+
r({
|
|
20
|
+
enabled: v && S,
|
|
21
|
+
anchor: w,
|
|
22
|
+
panel: E
|
|
23
|
+
});
|
|
24
|
+
let k = s(), A = c(null), j = a(() => {
|
|
25
|
+
A.current !== null && (clearTimeout(A.current), A.current = null);
|
|
26
|
+
}, []), M = a(() => {
|
|
27
|
+
j(), A.current = setTimeout(() => C(!0), h);
|
|
28
|
+
}, [j, h]), N = a(() => {
|
|
29
|
+
j(), A.current = setTimeout(() => C(!1), g);
|
|
30
|
+
}, [j, g]);
|
|
31
|
+
o(() => j, [j]);
|
|
32
|
+
let P = /* @__PURE__ */ u("div", {
|
|
33
|
+
ref: D,
|
|
34
|
+
id: k,
|
|
35
|
+
role: "dialog",
|
|
36
|
+
className: e(i.card, i[_], v && i.portalled, y),
|
|
37
|
+
style: {
|
|
38
|
+
...b,
|
|
39
|
+
...O
|
|
40
|
+
},
|
|
41
|
+
...x,
|
|
42
|
+
children: m
|
|
43
|
+
});
|
|
44
|
+
return /* @__PURE__ */ d("span", {
|
|
45
|
+
ref: T,
|
|
46
|
+
className: i.root,
|
|
47
|
+
onMouseEnter: M,
|
|
48
|
+
onMouseLeave: N,
|
|
49
|
+
onFocus: M,
|
|
50
|
+
onBlur: N,
|
|
51
|
+
children: [/* @__PURE__ */ u("span", {
|
|
52
|
+
"aria-describedby": S ? k : void 0,
|
|
53
|
+
className: i.trigger,
|
|
54
|
+
children: p
|
|
55
|
+
}), S && (v ? /* @__PURE__ */ u(t, { children: P }) : P)]
|
|
31
56
|
});
|
|
32
57
|
}
|
|
33
58
|
//#endregion
|
|
34
|
-
export {
|
|
59
|
+
export { p as HoverCard };
|
|
35
60
|
|
|
36
61
|
//# sourceMappingURL=HoverCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.js","names":[],"sources":["../../../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./HoverCard.module.css\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface HoverCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Element the user hovers/focuses to reveal the card. */\n trigger: ReactNode;\n /** Card content shown while hovering/focusing the trigger or the card itself. */\n children: ReactNode;\n /** Delay in ms before opening on mouseenter/focus. Default 300. */\n openDelay?: number;\n /** Delay in ms before closing on mouseleave/blur. Default 150. */\n closeDelay?: number;\n /** Where the card is anchored relative to the trigger. Default \"bottom\". */\n placement?: HoverCardPlacement;\n}\n\n/**\n * Content preview shown when the trigger is hovered or focused.\n *\n * - Opens after `openDelay` on `mouseenter`/`focus`.\n * - Closes after `closeDelay` on `mouseleave`/`blur`.\n * - Positioned relative to the trigger via `placement
|
|
1
|
+
{"version":3,"file":"HoverCard.js","names":[],"sources":["../../../src/components/HoverCard/HoverCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { Portal } from \"@/components/Portal\";\nimport { useAnchorPosition } from \"@/components/Portal/anchor-position\";\nimport { usePortalTabOrder } from \"@/components/Portal/tab-bridge\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./HoverCard.module.css\";\n\nexport type HoverCardPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface HoverCardProps extends HTMLAttributes<HTMLDivElement> {\n /** Element the user hovers/focuses to reveal the card. */\n trigger: ReactNode;\n /** Card content shown while hovering/focusing the trigger or the card itself. */\n children: ReactNode;\n /** Delay in ms before opening on mouseenter/focus. Default 300. */\n openDelay?: number;\n /** Delay in ms before closing on mouseleave/blur. Default 150. */\n closeDelay?: number;\n /** Where the card is anchored relative to the trigger. Default \"bottom\". */\n placement?: HoverCardPlacement;\n /**\n * Render the card in a portal, positioned against the trigger. Default `true`.\n *\n * In flow, an ancestor with `overflow` other than `visible` clips the card — a\n * user handle in the last row of a `DataTable` showed 17% of it (measured in\n * Chrome at 1440 px). In a portal it escapes the clip and flips to the\n * opposite side at the viewport edge.\n */\n portal?: boolean;\n}\n\n/** Gap between trigger and card, in px — the in-flow CSS uses the same 8 px. */\nconst CARD_OFFSET = 8;\n\n/**\n * Content preview shown when the trigger is hovered or focused.\n *\n * - Opens after `openDelay` on `mouseenter`/`focus`.\n * - Closes after `closeDelay` on `mouseleave`/`blur`.\n * - Positioned relative to the trigger via `placement`, in a portal by default.\n * - Moving the pointer from the trigger into the card keeps it open: React\n * dispatches `mouseenter`/`mouseleave` along the component tree, and the\n * portalled card is still inside the trigger's wrapper there.\n * - A link or button in the card is the next `Tab` stop after the trigger, as it\n * was in flow.\n * - The card is rendered as a labelled `role=\"dialog\"` region; the trigger stays keyboard focusable.\n *\n * @param props - The hover card props.\n * @returns The trigger wrapper and the anchored card when open.\n */\nexport function HoverCard({\n trigger,\n children,\n openDelay = 300,\n closeDelay = 150,\n placement = \"bottom\",\n portal = true,\n className,\n style,\n ...rest\n}: HoverCardProps) {\n const [open, setOpen] = useState(false);\n const [rootNode, setRootNode] = useState<HTMLSpanElement | null>(null);\n const [cardNode, setCardNode] = useState<HTMLDivElement | null>(null);\n const floatingStyle = useAnchorPosition({\n anchor: rootNode,\n floating: cardNode,\n side: placement,\n align: \"center\",\n offset: CARD_OFFSET,\n enabled: portal && open,\n });\n usePortalTabOrder({ enabled: portal && open, anchor: rootNode, panel: cardNode });\n const id = useId();\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearTimer = useCallback((): void => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n }, []);\n\n const scheduleOpen = useCallback((): void => {\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(true), openDelay);\n }, [clearTimer, openDelay]);\n\n const scheduleClose = useCallback((): void => {\n clearTimer();\n timerRef.current = setTimeout(() => setOpen(false), closeDelay);\n }, [clearTimer, closeDelay]);\n\n useEffect(() => clearTimer, [clearTimer]);\n\n const card = (\n <div\n ref={setCardNode}\n id={id}\n role=\"dialog\"\n className={cn(styles.card, styles[placement], portal && styles.portalled, className)}\n style={{ ...style, ...floatingStyle }}\n {...rest}\n >\n {children}\n </div>\n );\n\n return (\n <span\n ref={setRootNode}\n className={styles.root}\n onMouseEnter={scheduleOpen}\n onMouseLeave={scheduleClose}\n onFocus={scheduleOpen}\n onBlur={scheduleClose}\n >\n <span aria-describedby={open ? id : undefined} className={styles.trigger}>\n {trigger}\n </span>\n {open && (portal ? <Portal>{card}</Portal> : card)}\n </span>\n );\n}\n"],"mappings":";;;;;;;;AAiCA,IAAM,IAAc;AAkBpB,SAAgB,EAAU,EACtB,YACA,aACA,eAAY,KACZ,gBAAa,KACb,eAAY,UACZ,YAAS,IACT,cACA,UACA,GAAG,KACY;CACf,IAAM,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,CAAC,GAAU,KAAe,EAAiC,IAAI,GAC/D,CAAC,GAAU,KAAe,EAAgC,IAAI,GAC9D,IAAgB,EAAkB;EACpC,QAAQ;EACR,UAAU;EACV,MAAM;EACN,OAAO;EACP,QAAQ;EACR,SAAS,KAAU;CACvB,CAAC;CACD,EAAkB;EAAE,SAAS,KAAU;EAAM,QAAQ;EAAU,OAAO;CAAS,CAAC;CAChF,IAAM,IAAK,EAAM,GACX,IAAW,EAA6C,IAAI,GAE5D,IAAa,QAAwB;EACvC,AAAI,EAAS,YAAY,SACrB,aAAa,EAAS,OAAO,GAC7B,EAAS,UAAU;CAE3B,GAAG,CAAC,CAAC,GAEC,IAAe,QAAwB;EAEzC,AADA,EAAW,GACX,EAAS,UAAU,iBAAiB,EAAQ,EAAI,GAAG,CAAS;CAChE,GAAG,CAAC,GAAY,CAAS,CAAC,GAEpB,IAAgB,QAAwB;EAE1C,AADA,EAAW,GACX,EAAS,UAAU,iBAAiB,EAAQ,EAAK,GAAG,CAAU;CAClE,GAAG,CAAC,GAAY,CAAU,CAAC;CAE3B,QAAgB,GAAY,CAAC,CAAU,CAAC;CAExC,IAAM,IACF,kBAAC,OAAD;EACI,KAAK;EACD;EACJ,MAAK;EACL,WAAW,EAAG,EAAO,MAAM,EAAO,IAAY,KAAU,EAAO,WAAW,CAAS;EACnF,OAAO;GAAE,GAAG;GAAO,GAAG;EAAc;EACpC,GAAI;EAEH;CACA,CAAA;CAGT,OACI,kBAAC,QAAD;EACI,KAAK;EACL,WAAW,EAAO;EAClB,cAAc;EACd,cAAc;EACd,SAAS;EACT,QAAQ;EANZ,UAAA,CAQI,kBAAC,QAAD;GAAM,oBAAkB,IAAO,IAAK,KAAA;GAAW,WAAW,EAAO;GAC5D,UAAA;EACC,CAAA,GACL,MAAS,IAAS,kBAAC,GAAD,EAAA,UAAS,EAAa,CAAA,IAAI,EAC3C;;AAEd"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
require("../../styles/component/HoverCard.css");
|
|
2
|
-
var e=`tempest_root_2KoZA`,t=`tempest_trigger_OXsnr`,n=`tempest_card_CoGx4`,r=`tempest_tempest-hover-card-in_jD85J`,i=`tempest_top_AVcQ0`,a=`tempest_bottom_2rLAA`,o=`tempest_left_tqGPt`,s=`tempest_right_9Fe6S`,c={root:e,trigger:t,card:n,tempestHoverCardIn:r,top:i,bottom:a,left:o,right:s};exports.bottom=a,exports.card=n,exports.default=
|
|
2
|
+
var e=`tempest_root_2KoZA`,t=`tempest_trigger_OXsnr`,n=`tempest_card_CoGx4`,r=`tempest_tempest-hover-card-in_jD85J`,i=`tempest_top_AVcQ0`,a=`tempest_bottom_2rLAA`,o=`tempest_left_tqGPt`,s=`tempest_right_9Fe6S`,c=`tempest_portalled_GMcNw`,l={root:e,trigger:t,card:n,tempestHoverCardIn:r,top:i,bottom:a,left:o,right:s,portalled:c};exports.bottom=a,exports.card=n,exports.default=l,exports.left=o,exports.portalled=c,exports.right=s,exports.root=e,exports.tempestHoverCardIn=r,exports.top=i,exports.trigger=t;
|
|
3
3
|
//# sourceMappingURL=HoverCard.module.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.module.cjs","names":[],"sources":["../../../src/components/HoverCard/HoverCard.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.trigger {\n display: inline-flex;\n}\n\n.card {\n position: absolute;\n z-index: var(--tempest-z-popover);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-width: 200px;\n max-width: 320px;\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n animation: tempest-hover-card-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.card:focus {\n outline: none;\n}\n\n/* Placements (center-aligned across the trigger). */\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-hover-card-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n animation: none;\n }\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"HoverCard.module.cjs","names":[],"sources":["../../../src/components/HoverCard/HoverCard.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.trigger {\n display: inline-flex;\n}\n\n.card {\n position: absolute;\n z-index: var(--tempest-z-popover);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-width: 200px;\n max-width: 320px;\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n animation: tempest-hover-card-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.card:focus {\n outline: none;\n}\n\n/* Placements (center-aligned across the trigger). */\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-hover-card-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n animation: none;\n }\n}\n"],"mappings":";"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../../styles/component/HoverCard.css";
|
|
2
2
|
//#region src/components/HoverCard/HoverCard.module.css
|
|
3
|
-
var e = "tempest_root_2KoZA", t = "tempest_trigger_OXsnr", n = "tempest_card_CoGx4", r = "tempest_tempest-hover-card-in_jD85J", i = "tempest_top_AVcQ0", a = "tempest_bottom_2rLAA", o = "tempest_left_tqGPt", s = "tempest_right_9Fe6S", c = {
|
|
3
|
+
var e = "tempest_root_2KoZA", t = "tempest_trigger_OXsnr", n = "tempest_card_CoGx4", r = "tempest_tempest-hover-card-in_jD85J", i = "tempest_top_AVcQ0", a = "tempest_bottom_2rLAA", o = "tempest_left_tqGPt", s = "tempest_right_9Fe6S", c = "tempest_portalled_GMcNw", l = {
|
|
4
4
|
root: e,
|
|
5
5
|
trigger: t,
|
|
6
6
|
card: n,
|
|
@@ -8,9 +8,10 @@ var e = "tempest_root_2KoZA", t = "tempest_trigger_OXsnr", n = "tempest_card_CoG
|
|
|
8
8
|
top: i,
|
|
9
9
|
bottom: a,
|
|
10
10
|
left: o,
|
|
11
|
-
right: s
|
|
11
|
+
right: s,
|
|
12
|
+
portalled: c
|
|
12
13
|
};
|
|
13
14
|
//#endregion
|
|
14
|
-
export { a as bottom, n as card,
|
|
15
|
+
export { a as bottom, n as card, l as default, o as left, c as portalled, s as right, e as root, r as tempestHoverCardIn, i as top, t as trigger };
|
|
15
16
|
|
|
16
17
|
//# sourceMappingURL=HoverCard.module.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverCard.module.js","names":[],"sources":["../../../src/components/HoverCard/HoverCard.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.trigger {\n display: inline-flex;\n}\n\n.card {\n position: absolute;\n z-index: var(--tempest-z-popover);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-width: 200px;\n max-width: 320px;\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n animation: tempest-hover-card-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.card:focus {\n outline: none;\n}\n\n/* Placements (center-aligned across the trigger). */\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n@keyframes tempest-hover-card-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n animation: none;\n }\n}\n"],"mappings":";"}
|
|
1
|
+
{"version":3,"file":"HoverCard.module.js","names":[],"sources":["../../../src/components/HoverCard/HoverCard.module.css"],"sourcesContent":[".root {\n position: relative;\n display: inline-flex;\n}\n\n.trigger {\n display: inline-flex;\n}\n\n.card {\n position: absolute;\n z-index: var(--tempest-z-popover);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n border: 1px solid var(--tempest-border);\n border-radius: var(--tempest-radius-lg);\n box-shadow: var(--tempest-shadow-lg);\n padding: var(--tempest-space-3) var(--tempest-space-4);\n min-width: 200px;\n max-width: 320px;\n font-family: var(--tempest-font-sans);\n font-size: var(--tempest-text-base);\n line-height: var(--tempest-leading-snug);\n animation: tempest-hover-card-in var(--tempest-duration-fast) var(--tempest-ease-out);\n}\n\n.card:focus {\n outline: none;\n}\n\n/* Placements (center-aligned across the trigger). */\n.top {\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.bottom {\n top: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n}\n.left {\n right: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n.right {\n left: calc(100% + 8px);\n top: 50%;\n transform: translateY(-50%);\n}\n\n.portalled {\n right: auto;\n bottom: auto;\n transform: none;\n}\n\n@keyframes tempest-hover-card-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .card {\n animation: none;\n }\n}\n"],"mappings":";"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../../hooks/use-focus-trap.cjs"),r=require("../Portal/Portal.cjs"),i=require("../../hooks/use-scroll-lock.cjs"),a=require("./Lightbox.module.cjs");let o=require("react"),s=require("react/jsx-runtime"),c=require("lucide-react");function l({items:l,open:u,index:d=0,onIndexChange:f,onClose:p,showThumbnails:m,showCounter:h=!0,loop:g=!0,closeLabel:_=`Close`,previousLabel:v=`Previous image`,nextLabel:y=`Next image`,className:b}){let x=(0,o.useRef)(null),[S,C]=(0,o.useState)(d),w=f!==void 0,T=w?d:S;n.useFocusTrap(x,u),i.useScrollLock(u);let E=(0,o.useCallback)(e=>{if(l.length===0)return;let t=g?(e+l.length)%l.length:Math.max(0,Math.min(e,l.length-1));w||C(t),f?.(t)},[w,l.length,g,f]);if((0,o.useEffect)(()=>{w||C(d)},[d,w]),t.useEscapeLayer(u,p),(0,o.useEffect)(()=>{if(!u)return;function e(e){switch(e.key){case`ArrowRight`:E(T+1);break;case`ArrowLeft`:E(T-1);break;case`Home`:E(0);break;case`End`:E(l.length-1);break;default:return}e.preventDefault()}return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[T,l.length,E,u]),(0,o.useEffect)(()=>{if(!(!u||typeof Image>`u`))for(let e of[-1,1]){let t=l[(T+e+l.length)%l.length];if(!t)continue;let n=new Image;n.src=t.src}},[T,l,u]),!u||l.length===0)return null;let D=l[Math.max(0,Math.min(T,l.length-1))],O=m??l.length>1,k=l.length>1;return(0,s.jsx)(r.Portal,{children:(0,s.jsxs)(`div`,{ref:x,role:`dialog`,"aria-modal":`true`,"aria-label":D.alt,className:e.cn(a.default.overlay,b),children:[(0,s.jsxs)(`div`,{className:a.default.topBar,children:[h&&k?(0,s.jsxs)(`span`,{className:a.default.counter,children:[T+1,` / `,l.length]}):(0,s.jsx)(`span`,{}),(0,s.jsx)(`button`,{type:`button`,className:a.default.iconButton,"aria-label":_,onClick:p,children:(0,s.jsx)(c.X,{size:20})})]}),(0,s.jsxs)(`div`,{className:a.default.stage,children:[k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":v,disabled:!g&&T===0,onClick:()=>E(T-1),children:(0,s.jsx)(c.ChevronLeft,{size:24})}):null,(0,s.jsxs)(`figure`,{className:a.default.figure,children:[(0,s.jsx)(`img`,{src:D.src,alt:D.alt,className:a.default.image}),D.caption?(0,s.jsx)(`figcaption`,{className:a.default.caption,children:D.caption}):null]}),k?(0,s.jsx)(`button`,{type:`button`,className:e.cn(a.default.iconButton,a.default.nav),"aria-label":y,disabled:!g&&T===l.length-1,onClick:()=>E(T+1),children:(0,s.jsx)(c.ChevronRight,{size:24})}):null]}),O&&k?(0,s.jsx)(`div`,{className:a.default.thumbnails,role:`tablist`,"aria-label":D.alt,children:l.map((t,n)=>(0,s.jsx)(`button`,{type:`button`,role:`tab`,"aria-selected":n===T,"aria-label":t.alt,className:e.cn(a.default.thumbnail,n===T&&a.default.thumbnailActive),onClick:()=>E(n),children:(0,s.jsx)(`img`,{src:t.thumbnail??t.src,alt:``,className:a.default.thumbnailImage})},`${t.src}-${n}`))}):null]})})}exports.Lightbox=l;
|
|
2
2
|
//# sourceMappingURL=Lightbox.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lightbox.cjs","names":[],"sources":["../../../src/components/Lightbox/Lightbox.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — closeLabel,\n * previousLabel and nextLabel are three separate strings an app has to translate,\n * showThumbnails/showCounter/loop are the chrome, and items/index/\n * onIndexChange/open/onClose are the controlled contract. The body is the focus\n * trap, the key handling and the swipe, which all move the same index.\n */\nimport { ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { useScrollLock } from \"@/hooks/use-scroll-lock\";\nimport { cn } from \"@/utils/cn\";\nimport { Portal } from \"../Portal\";\nimport styles from \"./Lightbox.module.css\";\n\n/** One item of the gallery. */\nexport interface LightboxItem {\n /** Full-size image URL. */\n src: string;\n /** Alt text. Required: a gallery of unlabeled images is unusable with a screen reader. */\n alt: string;\n /** Caption rendered under the image. */\n caption?: string;\n /** Thumbnail URL for the strip. Falls back to `src`. */\n thumbnail?: string;\n}\n\nexport interface LightboxProps {\n items: LightboxItem[];\n /** Whether the overlay is open. */\n open: boolean;\n /** Index shown while open. Default `0`. */\n index?: number;\n onIndexChange?: (index: number) => void;\n onClose: () => void;\n /** Renders the thumbnail strip. Default `true` when there is more than one item. */\n showThumbnails?: boolean;\n /** Renders the `3 / 12` counter. Default `true`. */\n showCounter?: boolean;\n /**\n * Wrap around at the ends. Default `true` — in a photo viewer, hitting a dead\n * end at the last image reads as a bug more often than as a boundary.\n */\n loop?: boolean;\n closeLabel?: string;\n previousLabel?: string;\n nextLabel?: string;\n className?: string;\n}\n\n/**\n * Full-screen image viewer with keyboard navigation.\n *\n * `Esc` closes, `←`/`→` walk the gallery, `Home`/`End` jump to the ends. Focus is\n * trapped inside while open and the page behind is scroll-locked, so the overlay\n * behaves like the dialog it is instead of a fancy `<div>` the keyboard can escape.\n *\n * Only the current image is mounted; neighbours are **preloaded** via `Image()` so\n * pressing `→` does not flash an empty frame.\n *\n * @example\n * ```tsx\n * const [open, setOpen] = useState(false);\n * const [index, setIndex] = useState(0);\n *\n * <Lightbox\n * open={open}\n * items={photos.map((p) => ({ src: p.url, alt: p.description }))}\n * index={index}\n * onIndexChange={setIndex}\n * onClose={() => setOpen(false)}\n * />\n * ```\n */\nexport function Lightbox({\n items,\n open,\n index = 0,\n onIndexChange,\n onClose,\n showThumbnails,\n showCounter = true,\n loop = true,\n closeLabel = \"Close\",\n previousLabel = \"Previous image\",\n nextLabel = \"Next image\",\n className,\n}: LightboxProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [internalIndex, setInternalIndex] = useState(index);\n const isControlled = onIndexChange !== undefined;\n const current = isControlled ? index : internalIndex;\n\n useFocusTrap(containerRef, open);\n useScrollLock(open);\n\n const move = useCallback(\n (next: number): void => {\n if (items.length === 0) return;\n const target = loop\n ? (next + items.length) % items.length\n : Math.max(0, Math.min(next, items.length - 1));\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [isControlled, items.length, loop, onIndexChange],\n );\n\n useEffect(() => {\n if (!isControlled) setInternalIndex(index);\n }, [index, isControlled]);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"Escape\":\n onClose();\n break;\n case \"ArrowRight\":\n move(current + 1);\n break;\n case \"ArrowLeft\":\n move(current - 1);\n break;\n case \"Home\":\n move(0);\n break;\n case \"End\":\n move(items.length - 1);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [current, items.length, move, onClose, open]);\n\n useEffect(() => {\n if (!open || typeof Image === \"undefined\") return;\n for (const offset of [-1, 1]) {\n const neighbour = items[(current + offset + items.length) % items.length];\n if (!neighbour) continue;\n const preload = new Image();\n preload.src = neighbour.src;\n }\n }, [current, items, open]);\n\n if (!open || items.length === 0) return null;\n\n const item = items[Math.max(0, Math.min(current, items.length - 1))];\n const withThumbnails = showThumbnails ?? items.length > 1;\n const multiple = items.length > 1;\n\n return (\n <Portal>\n <div\n ref={containerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={item.alt}\n className={cn(styles.overlay, className)}\n >\n <div className={styles.topBar}>\n {showCounter && multiple ? (\n <span className={styles.counter}>\n {current + 1} / {items.length}\n </span>\n ) : (\n <span />\n )}\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={closeLabel}\n onClick={onClose}\n >\n <X size={20} />\n </button>\n </div>\n\n <div className={styles.stage}>\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={previousLabel}\n disabled={!loop && current === 0}\n onClick={() => move(current - 1)}\n >\n <ChevronLeft size={24} />\n </button>\n ) : null}\n\n <figure className={styles.figure}>\n <img src={item.src} alt={item.alt} className={styles.image} />\n {item.caption ? (\n <figcaption className={styles.caption}>{item.caption}</figcaption>\n ) : null}\n </figure>\n\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={nextLabel}\n disabled={!loop && current === items.length - 1}\n onClick={() => move(current + 1)}\n >\n <ChevronRight size={24} />\n </button>\n ) : null}\n </div>\n\n {withThumbnails && multiple ? (\n <div className={styles.thumbnails} role=\"tablist\" aria-label={item.alt}>\n {items.map((entry, entryIndex) => (\n <button\n key={`${entry.src}-${entryIndex}`}\n type=\"button\"\n role=\"tab\"\n aria-selected={entryIndex === current}\n aria-label={entry.alt}\n className={cn(\n styles.thumbnail,\n entryIndex === current && styles.thumbnailActive,\n )}\n onClick={() => move(entryIndex)}\n >\n <img\n src={entry.thumbnail ?? entry.src}\n alt=\"\"\n className={styles.thumbnailImage}\n />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </Portal>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Lightbox.cjs","names":[],"sources":["../../../src/components/Lightbox/Lightbox.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — closeLabel,\n * previousLabel and nextLabel are three separate strings an app has to translate,\n * showThumbnails/showCounter/loop are the chrome, and items/index/\n * onIndexChange/open/onClose are the controlled contract. The body is the focus\n * trap, the key handling and the swipe, which all move the same index.\n */\nimport { ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { useScrollLock } from \"@/hooks/use-scroll-lock\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { Portal } from \"../Portal\";\nimport styles from \"./Lightbox.module.css\";\n\n/** One item of the gallery. */\nexport interface LightboxItem {\n /** Full-size image URL. */\n src: string;\n /** Alt text. Required: a gallery of unlabeled images is unusable with a screen reader. */\n alt: string;\n /** Caption rendered under the image. */\n caption?: string;\n /** Thumbnail URL for the strip. Falls back to `src`. */\n thumbnail?: string;\n}\n\nexport interface LightboxProps {\n items: LightboxItem[];\n /** Whether the overlay is open. */\n open: boolean;\n /** Index shown while open. Default `0`. */\n index?: number;\n onIndexChange?: (index: number) => void;\n onClose: () => void;\n /** Renders the thumbnail strip. Default `true` when there is more than one item. */\n showThumbnails?: boolean;\n /** Renders the `3 / 12` counter. Default `true`. */\n showCounter?: boolean;\n /**\n * Wrap around at the ends. Default `true` — in a photo viewer, hitting a dead\n * end at the last image reads as a bug more often than as a boundary.\n */\n loop?: boolean;\n closeLabel?: string;\n previousLabel?: string;\n nextLabel?: string;\n className?: string;\n}\n\n/**\n * Full-screen image viewer with keyboard navigation.\n *\n * `Esc` closes, `←`/`→` walk the gallery, `Home`/`End` jump to the ends. Focus is\n * trapped inside while open and the page behind is scroll-locked, so the overlay\n * behaves like the dialog it is instead of a fancy `<div>` the keyboard can escape.\n *\n * Only the current image is mounted; neighbours are **preloaded** via `Image()` so\n * pressing `→` does not flash an empty frame.\n *\n * @example\n * ```tsx\n * const [open, setOpen] = useState(false);\n * const [index, setIndex] = useState(0);\n *\n * <Lightbox\n * open={open}\n * items={photos.map((p) => ({ src: p.url, alt: p.description }))}\n * index={index}\n * onIndexChange={setIndex}\n * onClose={() => setOpen(false)}\n * />\n * ```\n */\nexport function Lightbox({\n items,\n open,\n index = 0,\n onIndexChange,\n onClose,\n showThumbnails,\n showCounter = true,\n loop = true,\n closeLabel = \"Close\",\n previousLabel = \"Previous image\",\n nextLabel = \"Next image\",\n className,\n}: LightboxProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [internalIndex, setInternalIndex] = useState(index);\n const isControlled = onIndexChange !== undefined;\n const current = isControlled ? index : internalIndex;\n\n useFocusTrap(containerRef, open);\n useScrollLock(open);\n\n const move = useCallback(\n (next: number): void => {\n if (items.length === 0) return;\n const target = loop\n ? (next + items.length) % items.length\n : Math.max(0, Math.min(next, items.length - 1));\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [isControlled, items.length, loop, onIndexChange],\n );\n\n useEffect(() => {\n if (!isControlled) setInternalIndex(index);\n }, [index, isControlled]);\n\n useEscapeLayer(open, onClose);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowRight\":\n move(current + 1);\n break;\n case \"ArrowLeft\":\n move(current - 1);\n break;\n case \"Home\":\n move(0);\n break;\n case \"End\":\n move(items.length - 1);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [current, items.length, move, open]);\n\n useEffect(() => {\n if (!open || typeof Image === \"undefined\") return;\n for (const offset of [-1, 1]) {\n const neighbour = items[(current + offset + items.length) % items.length];\n if (!neighbour) continue;\n const preload = new Image();\n preload.src = neighbour.src;\n }\n }, [current, items, open]);\n\n if (!open || items.length === 0) return null;\n\n const item = items[Math.max(0, Math.min(current, items.length - 1))];\n const withThumbnails = showThumbnails ?? items.length > 1;\n const multiple = items.length > 1;\n\n return (\n <Portal>\n <div\n ref={containerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={item.alt}\n className={cn(styles.overlay, className)}\n >\n <div className={styles.topBar}>\n {showCounter && multiple ? (\n <span className={styles.counter}>\n {current + 1} / {items.length}\n </span>\n ) : (\n <span />\n )}\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={closeLabel}\n onClick={onClose}\n >\n <X size={20} />\n </button>\n </div>\n\n <div className={styles.stage}>\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={previousLabel}\n disabled={!loop && current === 0}\n onClick={() => move(current - 1)}\n >\n <ChevronLeft size={24} />\n </button>\n ) : null}\n\n <figure className={styles.figure}>\n <img src={item.src} alt={item.alt} className={styles.image} />\n {item.caption ? (\n <figcaption className={styles.caption}>{item.caption}</figcaption>\n ) : null}\n </figure>\n\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={nextLabel}\n disabled={!loop && current === items.length - 1}\n onClick={() => move(current + 1)}\n >\n <ChevronRight size={24} />\n </button>\n ) : null}\n </div>\n\n {withThumbnails && multiple ? (\n <div className={styles.thumbnails} role=\"tablist\" aria-label={item.alt}>\n {items.map((entry, entryIndex) => (\n <button\n key={`${entry.src}-${entryIndex}`}\n type=\"button\"\n role=\"tab\"\n aria-selected={entryIndex === current}\n aria-label={entry.alt}\n className={cn(\n styles.thumbnail,\n entryIndex === current && styles.thumbnailActive,\n )}\n onClick={() => move(entryIndex)}\n >\n <img\n src={entry.thumbnail ?? entry.src}\n alt=\"\"\n className={styles.thumbnailImage}\n />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </Portal>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,45 +1,43 @@
|
|
|
1
1
|
import { cn as e } from "../../utils/cn.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useEscapeLayer as t } from "../Portal/escape-layer.js";
|
|
3
3
|
import { useFocusTrap as n } from "../../hooks/use-focus-trap.js";
|
|
4
4
|
import { Portal as r } from "../Portal/Portal.js";
|
|
5
|
-
import i from "
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { useScrollLock as i } from "../../hooks/use-scroll-lock.js";
|
|
6
|
+
import a from "./Lightbox.module.js";
|
|
7
|
+
import { useCallback as o, useEffect as s, useRef as c, useState as l } from "react";
|
|
8
|
+
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
9
|
+
import { ChevronLeft as f, ChevronRight as p, X as m } from "lucide-react";
|
|
9
10
|
//#region src/components/Lightbox/Lightbox.tsx
|
|
10
|
-
function
|
|
11
|
-
let
|
|
12
|
-
n(
|
|
13
|
-
let
|
|
14
|
-
if (
|
|
15
|
-
let t =
|
|
16
|
-
|
|
11
|
+
function h({ items: h, open: g, index: _ = 0, onIndexChange: v, onClose: y, showThumbnails: b, showCounter: x = !0, loop: S = !0, closeLabel: C = "Close", previousLabel: w = "Previous image", nextLabel: T = "Next image", className: E }) {
|
|
12
|
+
let D = c(null), [O, k] = l(_), A = v !== void 0, j = A ? _ : O;
|
|
13
|
+
n(D, g), i(g);
|
|
14
|
+
let M = o((e) => {
|
|
15
|
+
if (h.length === 0) return;
|
|
16
|
+
let t = S ? (e + h.length) % h.length : Math.max(0, Math.min(e, h.length - 1));
|
|
17
|
+
A || k(t), v?.(t);
|
|
17
18
|
}, [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
A,
|
|
20
|
+
h.length,
|
|
21
|
+
S,
|
|
22
|
+
v
|
|
22
23
|
]);
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
}, [
|
|
26
|
-
if (!
|
|
24
|
+
if (s(() => {
|
|
25
|
+
A || k(_);
|
|
26
|
+
}, [_, A]), t(g, y), s(() => {
|
|
27
|
+
if (!g) return;
|
|
27
28
|
function e(e) {
|
|
28
29
|
switch (e.key) {
|
|
29
|
-
case "Escape":
|
|
30
|
-
v();
|
|
31
|
-
break;
|
|
32
30
|
case "ArrowRight":
|
|
33
|
-
j
|
|
31
|
+
M(j + 1);
|
|
34
32
|
break;
|
|
35
33
|
case "ArrowLeft":
|
|
36
|
-
j
|
|
34
|
+
M(j - 1);
|
|
37
35
|
break;
|
|
38
36
|
case "Home":
|
|
39
|
-
|
|
37
|
+
M(0);
|
|
40
38
|
break;
|
|
41
39
|
case "End":
|
|
42
|
-
|
|
40
|
+
M(h.length - 1);
|
|
43
41
|
break;
|
|
44
42
|
default: return;
|
|
45
43
|
}
|
|
@@ -47,95 +45,94 @@ function m({ items: m, open: h, index: g = 0, onIndexChange: _, onClose: v, show
|
|
|
47
45
|
}
|
|
48
46
|
return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
|
|
49
47
|
}, [
|
|
50
|
-
A,
|
|
51
|
-
m.length,
|
|
52
48
|
j,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
h.length,
|
|
50
|
+
M,
|
|
51
|
+
g
|
|
52
|
+
]), s(() => {
|
|
53
|
+
if (!(!g || typeof Image > "u")) for (let e of [-1, 1]) {
|
|
54
|
+
let t = h[(j + e + h.length) % h.length];
|
|
58
55
|
if (!t) continue;
|
|
59
56
|
let n = new Image();
|
|
60
57
|
n.src = t.src;
|
|
61
58
|
}
|
|
62
59
|
}, [
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]), !
|
|
67
|
-
let
|
|
68
|
-
return /* @__PURE__ */
|
|
69
|
-
ref:
|
|
60
|
+
j,
|
|
61
|
+
h,
|
|
62
|
+
g
|
|
63
|
+
]), !g || h.length === 0) return null;
|
|
64
|
+
let N = h[Math.max(0, Math.min(j, h.length - 1))], P = b ?? h.length > 1, F = h.length > 1;
|
|
65
|
+
return /* @__PURE__ */ u(r, { children: /* @__PURE__ */ d("div", {
|
|
66
|
+
ref: D,
|
|
70
67
|
role: "dialog",
|
|
71
68
|
"aria-modal": "true",
|
|
72
|
-
"aria-label":
|
|
73
|
-
className: e(
|
|
69
|
+
"aria-label": N.alt,
|
|
70
|
+
className: e(a.overlay, E),
|
|
74
71
|
children: [
|
|
75
|
-
/* @__PURE__ */
|
|
76
|
-
className:
|
|
77
|
-
children: [
|
|
78
|
-
className:
|
|
72
|
+
/* @__PURE__ */ d("div", {
|
|
73
|
+
className: a.topBar,
|
|
74
|
+
children: [x && F ? /* @__PURE__ */ d("span", {
|
|
75
|
+
className: a.counter,
|
|
79
76
|
children: [
|
|
80
|
-
|
|
77
|
+
j + 1,
|
|
81
78
|
" / ",
|
|
82
|
-
|
|
79
|
+
h.length
|
|
83
80
|
]
|
|
84
|
-
}) : /* @__PURE__ */
|
|
81
|
+
}) : /* @__PURE__ */ u("span", {}), /* @__PURE__ */ u("button", {
|
|
85
82
|
type: "button",
|
|
86
|
-
className:
|
|
87
|
-
"aria-label":
|
|
88
|
-
onClick:
|
|
89
|
-
children: /* @__PURE__ */
|
|
83
|
+
className: a.iconButton,
|
|
84
|
+
"aria-label": C,
|
|
85
|
+
onClick: y,
|
|
86
|
+
children: /* @__PURE__ */ u(m, { size: 20 })
|
|
90
87
|
})]
|
|
91
88
|
}),
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
className:
|
|
89
|
+
/* @__PURE__ */ d("div", {
|
|
90
|
+
className: a.stage,
|
|
94
91
|
children: [
|
|
95
|
-
|
|
92
|
+
F ? /* @__PURE__ */ u("button", {
|
|
96
93
|
type: "button",
|
|
97
|
-
className: e(
|
|
98
|
-
"aria-label":
|
|
99
|
-
disabled: !
|
|
100
|
-
onClick: () => j
|
|
101
|
-
children: /* @__PURE__ */
|
|
94
|
+
className: e(a.iconButton, a.nav),
|
|
95
|
+
"aria-label": w,
|
|
96
|
+
disabled: !S && j === 0,
|
|
97
|
+
onClick: () => M(j - 1),
|
|
98
|
+
children: /* @__PURE__ */ u(f, { size: 24 })
|
|
102
99
|
}) : null,
|
|
103
|
-
/* @__PURE__ */
|
|
104
|
-
className:
|
|
105
|
-
children: [/* @__PURE__ */
|
|
106
|
-
src:
|
|
107
|
-
alt:
|
|
108
|
-
className:
|
|
109
|
-
}),
|
|
110
|
-
className:
|
|
111
|
-
children:
|
|
100
|
+
/* @__PURE__ */ d("figure", {
|
|
101
|
+
className: a.figure,
|
|
102
|
+
children: [/* @__PURE__ */ u("img", {
|
|
103
|
+
src: N.src,
|
|
104
|
+
alt: N.alt,
|
|
105
|
+
className: a.image
|
|
106
|
+
}), N.caption ? /* @__PURE__ */ u("figcaption", {
|
|
107
|
+
className: a.caption,
|
|
108
|
+
children: N.caption
|
|
112
109
|
}) : null]
|
|
113
110
|
}),
|
|
114
|
-
|
|
111
|
+
F ? /* @__PURE__ */ u("button", {
|
|
115
112
|
type: "button",
|
|
116
|
-
className: e(
|
|
117
|
-
"aria-label":
|
|
118
|
-
disabled: !
|
|
119
|
-
onClick: () => j
|
|
120
|
-
children: /* @__PURE__ */
|
|
113
|
+
className: e(a.iconButton, a.nav),
|
|
114
|
+
"aria-label": T,
|
|
115
|
+
disabled: !S && j === h.length - 1,
|
|
116
|
+
onClick: () => M(j + 1),
|
|
117
|
+
children: /* @__PURE__ */ u(p, { size: 24 })
|
|
121
118
|
}) : null
|
|
122
119
|
]
|
|
123
120
|
}),
|
|
124
|
-
|
|
125
|
-
className:
|
|
121
|
+
P && F ? /* @__PURE__ */ u("div", {
|
|
122
|
+
className: a.thumbnails,
|
|
126
123
|
role: "tablist",
|
|
127
|
-
"aria-label":
|
|
128
|
-
children:
|
|
124
|
+
"aria-label": N.alt,
|
|
125
|
+
children: h.map((t, n) => /* @__PURE__ */ u("button", {
|
|
129
126
|
type: "button",
|
|
130
127
|
role: "tab",
|
|
131
|
-
"aria-selected": n ===
|
|
128
|
+
"aria-selected": n === j,
|
|
132
129
|
"aria-label": t.alt,
|
|
133
|
-
className: e(
|
|
134
|
-
onClick: () =>
|
|
135
|
-
children: /* @__PURE__ */
|
|
130
|
+
className: e(a.thumbnail, n === j && a.thumbnailActive),
|
|
131
|
+
onClick: () => M(n),
|
|
132
|
+
children: /* @__PURE__ */ u("img", {
|
|
136
133
|
src: t.thumbnail ?? t.src,
|
|
137
134
|
alt: "",
|
|
138
|
-
className:
|
|
135
|
+
className: a.thumbnailImage
|
|
139
136
|
})
|
|
140
137
|
}, `${t.src}-${n}`))
|
|
141
138
|
}) : null
|
|
@@ -143,6 +140,6 @@ function m({ items: m, open: h, index: g = 0, onIndexChange: _, onClose: v, show
|
|
|
143
140
|
}) });
|
|
144
141
|
}
|
|
145
142
|
//#endregion
|
|
146
|
-
export {
|
|
143
|
+
export { h as Lightbox };
|
|
147
144
|
|
|
148
145
|
//# sourceMappingURL=Lightbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Lightbox.js","names":[],"sources":["../../../src/components/Lightbox/Lightbox.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — closeLabel,\n * previousLabel and nextLabel are three separate strings an app has to translate,\n * showThumbnails/showCounter/loop are the chrome, and items/index/\n * onIndexChange/open/onClose are the controlled contract. The body is the focus\n * trap, the key handling and the swipe, which all move the same index.\n */\nimport { ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { useScrollLock } from \"@/hooks/use-scroll-lock\";\nimport { cn } from \"@/utils/cn\";\nimport { Portal } from \"../Portal\";\nimport styles from \"./Lightbox.module.css\";\n\n/** One item of the gallery. */\nexport interface LightboxItem {\n /** Full-size image URL. */\n src: string;\n /** Alt text. Required: a gallery of unlabeled images is unusable with a screen reader. */\n alt: string;\n /** Caption rendered under the image. */\n caption?: string;\n /** Thumbnail URL for the strip. Falls back to `src`. */\n thumbnail?: string;\n}\n\nexport interface LightboxProps {\n items: LightboxItem[];\n /** Whether the overlay is open. */\n open: boolean;\n /** Index shown while open. Default `0`. */\n index?: number;\n onIndexChange?: (index: number) => void;\n onClose: () => void;\n /** Renders the thumbnail strip. Default `true` when there is more than one item. */\n showThumbnails?: boolean;\n /** Renders the `3 / 12` counter. Default `true`. */\n showCounter?: boolean;\n /**\n * Wrap around at the ends. Default `true` — in a photo viewer, hitting a dead\n * end at the last image reads as a bug more often than as a boundary.\n */\n loop?: boolean;\n closeLabel?: string;\n previousLabel?: string;\n nextLabel?: string;\n className?: string;\n}\n\n/**\n * Full-screen image viewer with keyboard navigation.\n *\n * `Esc` closes, `←`/`→` walk the gallery, `Home`/`End` jump to the ends. Focus is\n * trapped inside while open and the page behind is scroll-locked, so the overlay\n * behaves like the dialog it is instead of a fancy `<div>` the keyboard can escape.\n *\n * Only the current image is mounted; neighbours are **preloaded** via `Image()` so\n * pressing `→` does not flash an empty frame.\n *\n * @example\n * ```tsx\n * const [open, setOpen] = useState(false);\n * const [index, setIndex] = useState(0);\n *\n * <Lightbox\n * open={open}\n * items={photos.map((p) => ({ src: p.url, alt: p.description }))}\n * index={index}\n * onIndexChange={setIndex}\n * onClose={() => setOpen(false)}\n * />\n * ```\n */\nexport function Lightbox({\n items,\n open,\n index = 0,\n onIndexChange,\n onClose,\n showThumbnails,\n showCounter = true,\n loop = true,\n closeLabel = \"Close\",\n previousLabel = \"Previous image\",\n nextLabel = \"Next image\",\n className,\n}: LightboxProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [internalIndex, setInternalIndex] = useState(index);\n const isControlled = onIndexChange !== undefined;\n const current = isControlled ? index : internalIndex;\n\n useFocusTrap(containerRef, open);\n useScrollLock(open);\n\n const move = useCallback(\n (next: number): void => {\n if (items.length === 0) return;\n const target = loop\n ? (next + items.length) % items.length\n : Math.max(0, Math.min(next, items.length - 1));\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [isControlled, items.length, loop, onIndexChange],\n );\n\n useEffect(() => {\n if (!isControlled) setInternalIndex(index);\n }, [index, isControlled]);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"Escape\":\n onClose();\n break;\n case \"ArrowRight\":\n move(current + 1);\n break;\n case \"ArrowLeft\":\n move(current - 1);\n break;\n case \"Home\":\n move(0);\n break;\n case \"End\":\n move(items.length - 1);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [current, items.length, move, onClose, open]);\n\n useEffect(() => {\n if (!open || typeof Image === \"undefined\") return;\n for (const offset of [-1, 1]) {\n const neighbour = items[(current + offset + items.length) % items.length];\n if (!neighbour) continue;\n const preload = new Image();\n preload.src = neighbour.src;\n }\n }, [current, items, open]);\n\n if (!open || items.length === 0) return null;\n\n const item = items[Math.max(0, Math.min(current, items.length - 1))];\n const withThumbnails = showThumbnails ?? items.length > 1;\n const multiple = items.length > 1;\n\n return (\n <Portal>\n <div\n ref={containerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={item.alt}\n className={cn(styles.overlay, className)}\n >\n <div className={styles.topBar}>\n {showCounter && multiple ? (\n <span className={styles.counter}>\n {current + 1} / {items.length}\n </span>\n ) : (\n <span />\n )}\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={closeLabel}\n onClick={onClose}\n >\n <X size={20} />\n </button>\n </div>\n\n <div className={styles.stage}>\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={previousLabel}\n disabled={!loop && current === 0}\n onClick={() => move(current - 1)}\n >\n <ChevronLeft size={24} />\n </button>\n ) : null}\n\n <figure className={styles.figure}>\n <img src={item.src} alt={item.alt} className={styles.image} />\n {item.caption ? (\n <figcaption className={styles.caption}>{item.caption}</figcaption>\n ) : null}\n </figure>\n\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={nextLabel}\n disabled={!loop && current === items.length - 1}\n onClick={() => move(current + 1)}\n >\n <ChevronRight size={24} />\n </button>\n ) : null}\n </div>\n\n {withThumbnails && multiple ? (\n <div className={styles.thumbnails} role=\"tablist\" aria-label={item.alt}>\n {items.map((entry, entryIndex) => (\n <button\n key={`${entry.src}-${entryIndex}`}\n type=\"button\"\n role=\"tab\"\n aria-selected={entryIndex === current}\n aria-label={entry.alt}\n className={cn(\n styles.thumbnail,\n entryIndex === current && styles.thumbnailActive,\n )}\n onClick={() => move(entryIndex)}\n >\n <img\n src={entry.thumbnail ?? entry.src}\n alt=\"\"\n className={styles.thumbnailImage}\n />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </Portal>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Lightbox.js","names":[],"sources":["../../../src/components/Lightbox/Lightbox.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — closeLabel,\n * previousLabel and nextLabel are three separate strings an app has to translate,\n * showThumbnails/showCounter/loop are the chrome, and items/index/\n * onIndexChange/open/onClose are the controlled contract. The body is the focus\n * trap, the key handling and the swipe, which all move the same index.\n */\nimport { ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { useScrollLock } from \"@/hooks/use-scroll-lock\";\nimport { useEscapeLayer } from \"@/components/Portal/escape-layer\";\nimport { cn } from \"@/utils/cn\";\nimport { Portal } from \"../Portal\";\nimport styles from \"./Lightbox.module.css\";\n\n/** One item of the gallery. */\nexport interface LightboxItem {\n /** Full-size image URL. */\n src: string;\n /** Alt text. Required: a gallery of unlabeled images is unusable with a screen reader. */\n alt: string;\n /** Caption rendered under the image. */\n caption?: string;\n /** Thumbnail URL for the strip. Falls back to `src`. */\n thumbnail?: string;\n}\n\nexport interface LightboxProps {\n items: LightboxItem[];\n /** Whether the overlay is open. */\n open: boolean;\n /** Index shown while open. Default `0`. */\n index?: number;\n onIndexChange?: (index: number) => void;\n onClose: () => void;\n /** Renders the thumbnail strip. Default `true` when there is more than one item. */\n showThumbnails?: boolean;\n /** Renders the `3 / 12` counter. Default `true`. */\n showCounter?: boolean;\n /**\n * Wrap around at the ends. Default `true` — in a photo viewer, hitting a dead\n * end at the last image reads as a bug more often than as a boundary.\n */\n loop?: boolean;\n closeLabel?: string;\n previousLabel?: string;\n nextLabel?: string;\n className?: string;\n}\n\n/**\n * Full-screen image viewer with keyboard navigation.\n *\n * `Esc` closes, `←`/`→` walk the gallery, `Home`/`End` jump to the ends. Focus is\n * trapped inside while open and the page behind is scroll-locked, so the overlay\n * behaves like the dialog it is instead of a fancy `<div>` the keyboard can escape.\n *\n * Only the current image is mounted; neighbours are **preloaded** via `Image()` so\n * pressing `→` does not flash an empty frame.\n *\n * @example\n * ```tsx\n * const [open, setOpen] = useState(false);\n * const [index, setIndex] = useState(0);\n *\n * <Lightbox\n * open={open}\n * items={photos.map((p) => ({ src: p.url, alt: p.description }))}\n * index={index}\n * onIndexChange={setIndex}\n * onClose={() => setOpen(false)}\n * />\n * ```\n */\nexport function Lightbox({\n items,\n open,\n index = 0,\n onIndexChange,\n onClose,\n showThumbnails,\n showCounter = true,\n loop = true,\n closeLabel = \"Close\",\n previousLabel = \"Previous image\",\n nextLabel = \"Next image\",\n className,\n}: LightboxProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [internalIndex, setInternalIndex] = useState(index);\n const isControlled = onIndexChange !== undefined;\n const current = isControlled ? index : internalIndex;\n\n useFocusTrap(containerRef, open);\n useScrollLock(open);\n\n const move = useCallback(\n (next: number): void => {\n if (items.length === 0) return;\n const target = loop\n ? (next + items.length) % items.length\n : Math.max(0, Math.min(next, items.length - 1));\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [isControlled, items.length, loop, onIndexChange],\n );\n\n useEffect(() => {\n if (!isControlled) setInternalIndex(index);\n }, [index, isControlled]);\n\n useEscapeLayer(open, onClose);\n\n useEffect(() => {\n if (!open) return;\n function onKeyDown(event: KeyboardEvent): void {\n switch (event.key) {\n case \"ArrowRight\":\n move(current + 1);\n break;\n case \"ArrowLeft\":\n move(current - 1);\n break;\n case \"Home\":\n move(0);\n break;\n case \"End\":\n move(items.length - 1);\n break;\n default:\n return;\n }\n event.preventDefault();\n }\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [current, items.length, move, open]);\n\n useEffect(() => {\n if (!open || typeof Image === \"undefined\") return;\n for (const offset of [-1, 1]) {\n const neighbour = items[(current + offset + items.length) % items.length];\n if (!neighbour) continue;\n const preload = new Image();\n preload.src = neighbour.src;\n }\n }, [current, items, open]);\n\n if (!open || items.length === 0) return null;\n\n const item = items[Math.max(0, Math.min(current, items.length - 1))];\n const withThumbnails = showThumbnails ?? items.length > 1;\n const multiple = items.length > 1;\n\n return (\n <Portal>\n <div\n ref={containerRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={item.alt}\n className={cn(styles.overlay, className)}\n >\n <div className={styles.topBar}>\n {showCounter && multiple ? (\n <span className={styles.counter}>\n {current + 1} / {items.length}\n </span>\n ) : (\n <span />\n )}\n <button\n type=\"button\"\n className={styles.iconButton}\n aria-label={closeLabel}\n onClick={onClose}\n >\n <X size={20} />\n </button>\n </div>\n\n <div className={styles.stage}>\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={previousLabel}\n disabled={!loop && current === 0}\n onClick={() => move(current - 1)}\n >\n <ChevronLeft size={24} />\n </button>\n ) : null}\n\n <figure className={styles.figure}>\n <img src={item.src} alt={item.alt} className={styles.image} />\n {item.caption ? (\n <figcaption className={styles.caption}>{item.caption}</figcaption>\n ) : null}\n </figure>\n\n {multiple ? (\n <button\n type=\"button\"\n className={cn(styles.iconButton, styles.nav)}\n aria-label={nextLabel}\n disabled={!loop && current === items.length - 1}\n onClick={() => move(current + 1)}\n >\n <ChevronRight size={24} />\n </button>\n ) : null}\n </div>\n\n {withThumbnails && multiple ? (\n <div className={styles.thumbnails} role=\"tablist\" aria-label={item.alt}>\n {items.map((entry, entryIndex) => (\n <button\n key={`${entry.src}-${entryIndex}`}\n type=\"button\"\n role=\"tab\"\n aria-selected={entryIndex === current}\n aria-label={entry.alt}\n className={cn(\n styles.thumbnail,\n entryIndex === current && styles.thumbnailActive,\n )}\n onClick={() => move(entryIndex)}\n >\n <img\n src={entry.thumbnail ?? entry.src}\n alt=\"\"\n className={styles.thumbnailImage}\n />\n </button>\n ))}\n </div>\n ) : null}\n </div>\n </Portal>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../utils/cn.cjs"),t=require("./Menubar.module.cjs");let
|
|
1
|
+
const e=require("../../utils/cn.cjs"),t=require("../Portal/escape-layer.cjs"),n=require("../Portal/Portal.cjs"),r=require("../Portal/anchor-position.cjs"),i=require("../Portal/tab-bridge.cjs"),a=require("./Menubar.module.cjs");let o=require("react"),s=require("react/jsx-runtime");var c=4;function l(e){return`separator`in e}function u({menus:u,portal:d=!0,className:f,...p}){let[m,h]=(0,o.useState)(-1),g=(0,o.useId)(),_=(0,o.useRef)(null),v=(0,o.useRef)([]),y=(0,o.useCallback)(()=>{h(-1)},[]),[b,x]=(0,o.useState)(null),[S,C]=(0,o.useState)(null),w=d&&m!==-1,T=r.useAnchorPosition({anchor:b,floating:S,side:`bottom`,align:`start`,offset:c,enabled:w});i.usePortalTabOrder({enabled:w,anchor:b,panel:S}),t.useEscapeLayer(m!==-1,()=>{y(),v.current[m]?.focus()}),(0,o.useEffect)(()=>{if(m===-1)return;let e=e=>{if(e.key===`ArrowRight`){e.preventDefault();let t=(m+1)%u.length;h(t),v.current[t]?.focus()}if(e.key===`ArrowLeft`){e.preventDefault();let t=(m-1+u.length)%u.length;h(t),v.current[t]?.focus()}},t=e=>{let t=e.target;_.current?.contains(t)||S?.contains(t)||y()};return window.addEventListener(`keydown`,e),window.addEventListener(`mousedown`,t),()=>{window.removeEventListener(`keydown`,e),window.removeEventListener(`mousedown`,t)}},[m,u.length,y,S]);let E=(t,n)=>(0,s.jsx)(`ul`,{ref:C,id:n,role:`menu`,className:e.cn(a.default.panel,d&&a.default.portalled),style:T,children:t.items.map((e,t)=>l(e)?(0,s.jsx)(`li`,{role:`separator`,className:a.default.separator,"aria-hidden":!0},t):(0,s.jsx)(`li`,{role:`none`,children:(0,s.jsxs)(`button`,{type:`button`,role:`menuitem`,className:a.default.item,disabled:e.disabled,onClick:()=>{e.onSelect?.(),y()},children:[(0,s.jsx)(`span`,{className:a.default.itemLabel,children:e.label}),e.shortcut&&(0,s.jsx)(`span`,{className:a.default.shortcut,children:e.shortcut})]})},t))});return(0,s.jsx)(`div`,{ref:_,role:`menubar`,className:e.cn(a.default.root,f),...p,children:u.map((t,r)=>{let i=m===r,o=`${g}-panel-${r}`;return(0,s.jsxs)(`div`,{ref:i?x:void 0,className:a.default.menu,children:[(0,s.jsx)(`button`,{ref:e=>{v.current[r]=e},type:`button`,role:`menuitem`,"aria-haspopup":`menu`,"aria-expanded":i,"aria-controls":i?o:void 0,className:e.cn(a.default.trigger,i&&a.default.triggerOpen),onClick:()=>h(e=>e===r?-1:r),children:t.label}),i&&(d?(0,s.jsx)(n.Portal,{children:E(t,o)}):E(t,o))]},r)})})}exports.Menubar=u;
|
|
2
2
|
//# sourceMappingURL=Menubar.cjs.map
|