@seekalfred/primitives 0.5.0 → 0.6.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 +664 -9
- package/dist/accordion/accordion.d.ts +5 -5
- package/dist/accordion/accordion.js +22 -11
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/action-button/action-button.d.ts +50 -0
- package/dist/action-button/action-button.js +159 -0
- package/dist/action-button/action-button.js.map +1 -0
- package/dist/action-button/index.d.ts +2 -0
- package/dist/action-button/index.js +2 -0
- package/dist/alert/alert.d.ts +16 -2
- package/dist/alert/alert.js +118 -20
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/alert-dialog/alert-dialog.js +4 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -1
- package/dist/avatar/avatar.js +51 -18
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +10 -3
- package/dist/badge/badge.js +37 -5
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +3 -30
- package/dist/breadcrumb/breadcrumb.js +25 -8
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +36 -23
- package/dist/button/button.js +178 -33
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.d.ts +76 -0
- package/dist/button-group/button-group.js +403 -0
- package/dist/button-group/button-group.js.map +1 -0
- package/dist/button-group/index.d.ts +2 -0
- package/dist/button-group/index.js +2 -0
- package/dist/calendar/calendar.d.ts +3 -3
- package/dist/calendar/calendar.js +263 -34
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +14 -7
- package/dist/card/card.js +32 -14
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +2 -9
- package/dist/checkbox/checkbox.js +28 -29
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/confirm-morph/confirm-morph.d.ts +85 -0
- package/dist/confirm-morph/confirm-morph.js +652 -0
- package/dist/confirm-morph/confirm-morph.js.map +1 -0
- package/dist/confirm-morph/index.d.ts +2 -0
- package/dist/confirm-morph/index.js +2 -0
- package/dist/copy-button/copy-button.d.ts +32 -0
- package/dist/copy-button/copy-button.js +134 -0
- package/dist/copy-button/copy-button.js.map +1 -0
- package/dist/copy-button/index.d.ts +2 -0
- package/dist/copy-button/index.js +2 -0
- package/dist/date-picker/date-picker.d.ts +51 -0
- package/dist/date-picker/date-picker.js +241 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/date-picker/date-range-picker.d.ts +78 -0
- package/dist/date-picker/date-range-picker.js +1015 -0
- package/dist/date-picker/date-range-picker.js.map +1 -0
- package/dist/date-picker/index.d.ts +4 -0
- package/dist/date-picker/index.js +4 -0
- package/dist/date-picker/picker-parts.d.ts +46 -0
- package/dist/date-picker/picker-parts.js +135 -0
- package/dist/date-picker/picker-parts.js.map +1 -0
- package/dist/dialog/dialog.d.ts +12 -16
- package/dist/dialog/dialog.js +45 -14
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/empty-state/empty-state.d.ts +44 -0
- package/dist/empty-state/empty-state.js +83 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/empty-state/index.d.ts +2 -0
- package/dist/empty-state/index.js +2 -0
- package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
- package/dist/expanding-button-group/expanding-button-group.js +350 -0
- package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
- package/dist/expanding-button-group/index.d.ts +2 -0
- package/dist/expanding-button-group/index.js +2 -0
- package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
- package/dist/file-drop-zone/file-drop-zone.js +116 -0
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
- package/dist/file-drop-zone/index.d.ts +2 -0
- package/dist/file-drop-zone/index.js +2 -0
- package/dist/filter-bar/filter-bar.d.ts +121 -0
- package/dist/filter-bar/filter-bar.js +300 -0
- package/dist/filter-bar/filter-bar.js.map +1 -0
- package/dist/filter-bar/index.d.ts +2 -0
- package/dist/filter-bar/index.js +2 -0
- package/dist/floating-button-group/floating-button-group.d.ts +61 -0
- package/dist/floating-button-group/floating-button-group.js +340 -0
- package/dist/floating-button-group/floating-button-group.js.map +1 -0
- package/dist/floating-button-group/index.d.ts +2 -0
- package/dist/floating-button-group/index.js +2 -0
- package/dist/icon-morph/icon-morph.d.ts +55 -0
- package/dist/icon-morph/icon-morph.js +331 -0
- package/dist/icon-morph/icon-morph.js.map +1 -0
- package/dist/icon-morph/index.d.ts +2 -0
- package/dist/icon-morph/index.js +2 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +29 -7
- package/dist/input/input.d.ts +23 -1
- package/dist/input/input.js +37 -13
- package/dist/input/input.js.map +1 -1
- package/dist/label/label.d.ts +1 -1
- package/dist/label/label.js +4 -2
- package/dist/label/label.js.map +1 -1
- package/dist/lib/count-to.js +113 -0
- package/dist/lib/count-to.js.map +1 -0
- package/dist/lib/eye-morph.js +62 -0
- package/dist/lib/eye-morph.js.map +1 -0
- package/dist/lib/field-message.js +336 -0
- package/dist/lib/field-message.js.map +1 -0
- package/dist/lib/field-shell.js +81 -0
- package/dist/lib/field-shell.js.map +1 -0
- package/dist/lib/field-surface.js +4 -2
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flip.js +80 -0
- package/dist/lib/flip.js.map +1 -0
- package/dist/lib/height-follow.js +59 -0
- package/dist/lib/height-follow.js.map +1 -0
- package/dist/lib/icon-swap.js +47 -0
- package/dist/lib/icon-swap.js.map +1 -0
- package/dist/lib/leaving-list.js +57 -0
- package/dist/lib/leaving-list.js.map +1 -0
- package/dist/lib/merge-refs.js +22 -0
- package/dist/lib/merge-refs.js.map +1 -0
- package/dist/lib/morph-text.js +128 -0
- package/dist/lib/morph-text.js.map +1 -0
- package/dist/lib/motion.js +162 -0
- package/dist/lib/motion.js.map +1 -0
- package/dist/lib/pausable-timeout.js +67 -0
- package/dist/lib/pausable-timeout.js.map +1 -0
- package/dist/lib/rolling-value.js +31 -0
- package/dist/lib/rolling-value.js.map +1 -0
- package/dist/lib/spring-progress.js +114 -0
- package/dist/lib/spring-progress.js.map +1 -0
- package/dist/lib/svg-id.js +10 -0
- package/dist/lib/svg-id.js.map +1 -0
- package/dist/lib/swap-text.js +77 -0
- package/dist/lib/swap-text.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +36 -0
- package/dist/lib/use-copy-feedback.js.map +1 -0
- package/dist/menu/menu.d.ts +5 -5
- package/dist/menu/menu.js +10 -6
- package/dist/menu/menu.js.map +1 -1
- package/dist/page-header/index.d.ts +2 -0
- package/dist/page-header/index.js +2 -0
- package/dist/page-header/page-header.d.ts +75 -0
- package/dist/page-header/page-header.js +83 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/password-field/index.d.ts +2 -0
- package/dist/password-field/index.js +2 -0
- package/dist/password-field/password-field.d.ts +37 -0
- package/dist/password-field/password-field.js +101 -0
- package/dist/password-field/password-field.js.map +1 -0
- package/dist/password-strength/index.d.ts +2 -0
- package/dist/password-strength/index.js +2 -0
- package/dist/password-strength/password-strength.d.ts +127 -0
- package/dist/password-strength/password-strength.js +455 -0
- package/dist/password-strength/password-strength.js.map +1 -0
- package/dist/popover/popover.d.ts +10 -2
- package/dist/popover/popover.js +6 -4
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +2 -2
- package/dist/progress/progress.js +50 -7
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.d.ts +3 -3
- package/dist/radio-group/radio-group.js +14 -10
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +17 -6
- package/dist/scroll-area/scroll-area.js +30 -6
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/index.d.ts +2 -0
- package/dist/search-field/index.js +2 -0
- package/dist/search-field/search-field.d.ts +26 -0
- package/dist/search-field/search-field.js +155 -0
- package/dist/search-field/search-field.js.map +1 -0
- package/dist/select/select.d.ts +3 -3
- package/dist/select/select.js +85 -24
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/separator/separator.js +4 -2
- package/dist/separator/separator.js.map +1 -1
- package/dist/sheet/sheet.d.ts +1 -1
- package/dist/sheet/sheet.js +6 -3
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/side-nav/index.d.ts +2 -0
- package/dist/side-nav/index.js +2 -0
- package/dist/side-nav/side-nav.d.ts +72 -0
- package/dist/side-nav/side-nav.js +142 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +8 -2
- package/dist/skeleton/skeleton.js +9 -3
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.d.ts +2 -2
- package/dist/slider/slider.js +64 -8
- package/dist/slider/slider.js.map +1 -1
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +4 -2
- package/dist/spinner/spinner.js.map +1 -1
- package/dist/split-button/index.d.ts +2 -0
- package/dist/split-button/index.js +2 -0
- package/dist/split-button/split-button.d.ts +46 -0
- package/dist/split-button/split-button.js +122 -0
- package/dist/split-button/split-button.js.map +1 -0
- package/dist/stat-card/index.d.ts +2 -0
- package/dist/stat-card/index.js +2 -0
- package/dist/stat-card/stat-card.d.ts +81 -0
- package/dist/stat-card/stat-card.js +152 -0
- package/dist/stat-card/stat-card.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/switch/switch.d.ts +2 -2
- package/dist/switch/switch.js +11 -8
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +38 -7
- package/dist/table/table.js +114 -12
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.d.ts +4 -4
- package/dist/tabs/tabs.js +153 -12
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +24 -1
- package/dist/textarea/textarea.js +27 -3
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/theme-switch/index.d.ts +2 -0
- package/dist/theme-switch/index.js +2 -0
- package/dist/theme-switch/theme-switch.d.ts +37 -0
- package/dist/theme-switch/theme-switch.js +123 -0
- package/dist/theme-switch/theme-switch.js.map +1 -0
- package/dist/time-picker/index.d.ts +2 -0
- package/dist/time-picker/index.js +2 -0
- package/dist/time-picker/time-picker.d.ts +54 -0
- package/dist/time-picker/time-picker.js +201 -0
- package/dist/time-picker/time-picker.js.map +1 -0
- package/dist/toast/toast.d.ts +2 -2
- package/dist/toast/toast.js +61 -39
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +3 -3
- package/dist/tooltip/tooltip.js +66 -15
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +83 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confirm-morph.js","names":[],"sources":["../../src/confirm-morph/confirm-morph.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport {\n prefersReducedMotion,\n useExitBackstop,\n useIsomorphicLayoutEffect,\n useLeavingItems,\n} from \"../lib/motion.js\";\nimport { usePausableTimeout } from \"../lib/pausable-timeout.js\";\n\n/** Where the control is in its life: resting, asking, working, finished, or failed. */\nexport type ConfirmMorphState = \"idle\" | \"confirming\" | \"pending\" | \"done\" | \"error\";\n\n/** How the question is answered: a press, a press held until the fill completes, or a press followed by a short countdown. */\nexport type ConfirmMorphMode = \"press\" | \"hold\" | \"countdown\";\n\n/**\n * A button for destructive or important actions that asks in place. Pressing it\n * morphs the same pill into an inline question with Cancel and Confirm, then into\n * a spinner, then into a result with Undo. The width springs to each face while\n * the faces crossfade at their natural width, so text never squashes and nothing\n * around the control jumps. Escape, an outside press, or the timeout all return\n * it to rest.\n *\n * Use it where a modal dialog would be heavy: deleting a project, revoking\n * access, discarding a draft.\n */\nexport interface ConfirmMorphProps {\n /** The resting label, such as \"Delete project\". */\n label: ReactNode;\n /** A plain icon before the resting label. Mark it `aria-hidden`. */\n icon?: ReactNode;\n /** The question shown while confirming, such as \"Delete 3 files?\". Defaults to the label with a question mark. */\n prompt?: ReactNode;\n confirmLabel?: string;\n cancelLabel?: string;\n /** Shown beside the spinner while `onConfirm` resolves. */\n pendingLabel?: string;\n doneLabel?: string;\n errorLabel?: string;\n retryLabel?: string;\n undoLabel?: string;\n /** Shown beside the spinner while `onUndo` resolves. */\n undoingLabel?: string;\n /**\n * `danger` (default) gives the confirm button the destructive colour, for\n * actions that destroy something. `neutral` gives it the primary colour, for\n * important actions that can be reversed. The resting button is neutral either way.\n */\n tone?: \"danger\" | \"neutral\";\n /** `press` confirms on a press. `hold` asks for a press held for `holdDuration`. `countdown` confirms, then counts down `countdown` seconds that Cancel or Escape can still stop. */\n confirmMode?: ConfirmMorphMode;\n /** Milliseconds the confirm button must be held in `hold` mode. */\n holdDuration?: number;\n /** Seconds counted down before the action runs in `countdown` mode. */\n countdown?: number;\n /** Text before the seconds in `countdown` mode, such as \"Deleting in\". Defaults to the pending label followed by \"in\". */\n countdownLabel?: string;\n /** Runs on confirm. Return a promise to show the pending face; a rejection shows the error face with Retry. */\n onConfirm?: () => void | Promise<unknown>;\n /** Offering it adds Undo to the result. Return a promise to show a pending face while it runs. */\n onUndo?: () => void | Promise<unknown>;\n onCancel?: () => void;\n /** Controlled state. Pair it with `onStateChange`. */\n state?: ConfirmMorphState;\n /** Starting state when uncontrolled. */\n defaultState?: ConfirmMorphState;\n onStateChange?: (state: ConfirmMorphState) => void;\n /** Milliseconds before an unanswered question returns to rest. Resting the pointer on the control pauses it. 0 turns it off. */\n confirmTimeout?: number;\n /** Milliseconds a result stays before returning to rest. Resting the pointer on the control pauses it. 0 turns it off. */\n resultTimeout?: number;\n /** A press outside the control cancels an open question. Defaults to true. */\n cancelOnOutsidePress?: boolean;\n isDisabled?: boolean;\n /** Extra classes for the root. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\n/** A press this soon after the question appears is the tail of the press that opened it, not an answer. */\nconst ARM_MS = 280;\n\ntype FaceKey = ConfirmMorphState | \"counting\";\n\n/** Whether the parts of the current face arrive with motion: not on first render, only on a change of face. */\nconst PartsAnimate = createContext(false);\n\n/**\n * One piece of a face. The parts of a new face arrive in reading order, 35ms\n * apart after a 50ms beat, each clearing a small blur (--part-delay).\n */\nfunction Part({\n index,\n className,\n children,\n}: {\n index: number;\n className?: string;\n children: ReactNode;\n}) {\n const animate = useContext(PartsAnimate);\n return (\n <span\n style={{ \"--part-delay\": 50 + index * 35 + \"ms\" } as CSSProperties}\n className={cn(\n \"inline-flex h-full items-center \" +\n (animate\n ? \"motion-safe:animate-part-in motion-safe:[animation-delay:var(--part-delay)] \"\n : \"\") +\n (className ?? \"\"),\n )}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Faces never move and never scale: each one sits at its natural width,\n * against the pill's leading edge, so the baseline stays put and the pill's clip\n * reveals it as the width springs. The present face reports its width so the\n * pill can spring to it.\n */\nfunction Face({\n id,\n onSize,\n labelledBy,\n animated,\n children,\n}: {\n id: FaceKey;\n onSize: (width: number) => void;\n labelledBy?: string;\n animated: boolean;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n useIsomorphicLayoutEffect(() => {\n const node = ref.current;\n if (!node) return;\n // Fractional widths, so the spring lands exactly where the pill's natural width is and the hand back to auto is seamless.\n const report = () => onSize(node.getBoundingClientRect().width);\n report();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(report);\n observer.observe(node);\n return () => observer.disconnect();\n }, [id, onSize]);\n return (\n <div\n ref={ref}\n data-face={id}\n role={labelledBy ? \"group\" : undefined}\n aria-labelledby={labelledBy}\n className={cn(\n \"flex h-full flex-none items-center gap-0.5 px-1 whitespace-nowrap data-[face=idle]:px-0\" +\n (animated ? \" motion-reduce:animate-face-in-soft\" : \"\"),\n )}\n >\n <PartsAnimate.Provider value={animated}>{children}</PartsAnimate.Provider>\n </div>\n );\n}\n\n/** A leaving face steps out of the flow, exactly where it was, and fades while the pill springs to the next one. */\nfunction LeavingFace({\n id,\n onDone,\n children,\n}: {\n id: number;\n onDone: (id: number) => void;\n children: ReactNode;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <div\n aria-hidden=\"true\"\n ref={(node) => node?.setAttribute(\"inert\", \"\")}\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n className=\"pointer-events-none absolute start-0 top-0 flex h-full flex-none items-center gap-0.5 px-1 whitespace-nowrap motion-safe:animate-face-out motion-reduce:animate-fade-out\"\n >\n <PartsAnimate.Provider value={false}>{children}</PartsAnimate.Provider>\n </div>\n );\n}\n\n/** A success disc that pops in with a tick drawing across it, the moment the action lands. */\nfunction Check() {\n return (\n <svg className=\"size-4.5 flex-none\" viewBox=\"0 0 18 18\" fill=\"none\" aria-hidden=\"true\">\n <circle\n cx=\"9\"\n cy=\"9\"\n r=\"8\"\n className=\"fill-success-strong [transform-box:fill-box] [transform-origin:center] motion-safe:animate-disc-in\"\n />\n <path\n d=\"M5.6 9.3 7.8 11.4 12.4 6.7\"\n pathLength={1}\n strokeWidth=\"1.9\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"stroke-background [stroke-dasharray:1] motion-safe:animate-tick-draw\"\n />\n </svg>\n );\n}\n\n/** A ring that drains with the countdown. */\nfunction Ring({ seconds, tone }: { seconds: number; tone: \"danger\" | \"neutral\" }) {\n return (\n <svg className=\"size-4 flex-none\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\">\n <circle cx=\"8\" cy=\"8\" r=\"6.25\" strokeWidth=\"1.75\" className=\"stroke-border\" />\n <circle\n cx=\"8\"\n cy=\"8\"\n r=\"6.25\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n pathLength={1}\n style={{ animationDuration: seconds + \"s\" }}\n className={cn(\n \"[stroke-dasharray:1] [transform-box:fill-box] [transform-origin:center] -rotate-90 motion-safe:animate-ring-drain \" +\n (tone === \"neutral\" ? \"stroke-primary\" : \"stroke-destructive\"),\n )}\n />\n </svg>\n );\n}\n\n/** Seconds that swap in place: the old digit fades up and out, the new one rises in, on tabular figures so the width holds. */\nfunction Seconds({ value }: { value: number }) {\n const ref = useRef<HTMLSpanElement>(null);\n const { leaving, changed, remove } = useLeavingItems(String(value), value, () => ref.current);\n return (\n <span className=\"inline-grid min-w-2 text-foreground tabular-nums\">\n {leaving.map((item) => (\n <LeavingDigit key={item.id} id={item.id} onDone={remove}>\n {item.value}\n </LeavingDigit>\n ))}\n <span\n key={value}\n ref={ref}\n className={cn(\n \"col-start-1 row-start-1\" +\n (changed ? \" motion-safe:animate-digit-in motion-reduce:animate-fade-in\" : \"\"),\n )}\n >\n {value}\n </span>\n </span>\n );\n}\n\nfunction LeavingDigit({\n id,\n onDone,\n children,\n}: {\n id: number;\n onDone: (id: number) => void;\n children: ReactNode;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n return (\n <span\n aria-hidden=\"true\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n className=\"col-start-1 row-start-1 motion-safe:animate-digit-out motion-reduce:animate-fade-out\"\n >\n {children}\n </span>\n );\n}\n\nfunction Spinner() {\n return (\n <svg\n className=\"size-4 flex-none motion-safe:animate-spin-fast motion-reduce:[animation:spin_1.6s_linear_infinite]\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M21 12a9 9 0 1 1-6.219-8.56\" />\n </svg>\n );\n}\n\nfunction AlertIcon() {\n return (\n <svg\n className=\"size-4 flex-none text-destructive-strong\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n}\n\n/*\n * Class strings are static literals: Tailwind scans source text. Focus reads as\n * a ring on every button here, and as a fill: a tinted fill alone is too faint\n * for WCAG 2.4.7.\n */\nconst focusRing =\n \"outline-none focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset) focus-visible:outline-(--color-ring)\";\n\nconst trigger = cn(\n \"inline-flex h-full cursor-pointer items-center gap-2 rounded-pill border-0 bg-transparent py-0 ps-3.5 pe-4 font-sans font-medium text-foreground \" +\n \"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle \" +\n \"hover:not-active:not-disabled:bg-foreground/5 active:not-disabled:bg-foreground/10 active:not-disabled:duration-0 focus-visible:bg-foreground/6 \" +\n \"disabled:cursor-not-allowed disabled:text-muted-foreground \" +\n focusRing,\n);\n\nconst secondary =\n \"relative inline-flex confirm-button-h flex-none cursor-pointer touch-manipulation items-center justify-center rounded-pill border-0 bg-transparent px-3 font-sans font-medium text-muted-foreground \" +\n \"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle \" +\n \"hover:not-active:bg-foreground/7 hover:not-active:text-foreground active:bg-foreground/10 active:text-foreground active:duration-0 focus-visible:bg-foreground/8 focus-visible:text-foreground \" +\n focusRing;\n\nconst confirmDanger =\n \"bg-destructive-subtle text-destructive-subtle-foreground hover:not-active:bg-[color-mix(in_oklab,var(--color-destructive)_18%,transparent)] active:bg-[color-mix(in_oklab,var(--color-destructive)_22%,transparent)] focus-visible:bg-[color-mix(in_oklab,var(--color-destructive)_20%,transparent)]\";\nconst confirmNeutral =\n \"bg-primary text-primary-foreground hover:not-active:bg-primary-hover active:bg-primary-pressed focus-visible:bg-primary-hover\";\n\nconst confirmBase =\n \"relative inline-flex confirm-button-h flex-none cursor-pointer touch-manipulation items-center justify-center overflow-clip rounded-pill border-0 px-3 font-sans font-medium \" +\n \"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle active:duration-0 \" +\n focusRing;\n\n/**\n * A button for an action that deserves a second thought, without a dialog. It\n * asks in place: the pill morphs into a question with Cancel and Confirm, then\n * a spinner, then a result with Undo, its width springing to each face while the\n * faces crossfade at their natural width.\n *\n * Escape, a press outside, or the timeout answer the question with no. Focus\n * follows the control between faces, but only when it was already inside, so a\n * timeout never steals it. A live region announces each step.\n *\n * @example\n * <ConfirmMorph label=\"Delete project\" onConfirm={() => deleteProject()} onUndo={() => restore()} />\n * <ConfirmMorph label=\"Delete project\" confirmMode=\"hold\" onConfirm={deleteProject} />\n */\nexport const ConfirmMorph = forwardRef<HTMLDivElement, ConfirmMorphProps>(function ConfirmMorph(\n {\n label,\n icon,\n prompt,\n confirmLabel = \"Delete\",\n cancelLabel = \"Cancel\",\n pendingLabel = \"Deleting\",\n doneLabel = \"Deleted\",\n errorLabel = \"Couldn’t finish\",\n retryLabel = \"Retry\",\n undoLabel = \"Undo\",\n undoingLabel = \"Restoring\",\n tone = \"danger\",\n confirmMode = \"press\",\n holdDuration = 1000,\n countdown = 3,\n countdownLabel,\n onConfirm,\n onUndo,\n onCancel,\n state: stateProp,\n defaultState = \"idle\",\n onStateChange,\n confirmTimeout = 6000,\n resultTimeout = 5000,\n cancelOnOutsidePress = true,\n isDisabled = false,\n className,\n },\n ref,\n) {\n const uid = useId();\n const promptId = uid + \"-prompt\";\n const holdHintId = uid + \"-hold\";\n const rootRef = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => rootRef.current as HTMLDivElement, []);\n\n const [inner, setInner] = useState<ConfirmMorphState>(defaultState);\n const state = stateProp ?? inner;\n const [working, setWorking] = useState<\"confirm\" | \"undo\">(\"confirm\");\n const [counting, setCounting] = useState(false);\n const [announcement, setAnnouncement] = useState(\"\");\n const faceKey: FaceKey = state === \"confirming\" && counting ? \"counting\" : state;\n\n const live = useRef({ state, onStateChange, controlled: stateProp !== undefined });\n useIsomorphicLayoutEffect(() => {\n live.current = { state, onStateChange, controlled: stateProp !== undefined };\n });\n const pendingFocus = useRef(false);\n const run = useRef(0);\n const askedAt = useRef(0);\n\n const keepFocus = useCallback(() => {\n const root = rootRef.current;\n // Focus follows the control between faces, but only when it was already inside; a timeout never steals focus from elsewhere.\n pendingFocus.current =\n !!root && (root.contains(document.activeElement) || document.activeElement === document.body);\n }, []);\n\n const go = useCallback(\n (next: ConfirmMorphState) => {\n if (next !== \"confirming\") setCounting(false);\n if (next === live.current.state) return;\n keepFocus();\n if (next === \"confirming\") askedAt.current = performance.now();\n if (!live.current.controlled) setInner(next);\n live.current.state = next;\n live.current.onStateChange?.(next);\n },\n [keepFocus],\n );\n\n // A controlled parent can move the state on its own; the countdown never survives that.\n const [seenState, setSeenState] = useState(state);\n if (seenState !== state) {\n setSeenState(state);\n if (state !== \"confirming\") setCounting(false);\n }\n\n const toIdle = useCallback(() => {\n run.current++;\n go(\"idle\");\n }, [go]);\n\n const perform = useCallback(\n async (kind: \"confirm\" | \"undo\") => {\n const handler = kind === \"confirm\" ? onConfirm : onUndo;\n const token = ++run.current;\n setWorking(kind);\n setCounting(false);\n let result: void | Promise<unknown> | undefined;\n try {\n result = handler?.();\n } catch {\n go(\"error\");\n setAnnouncement(errorLabel);\n return;\n }\n if (result && typeof result.then === \"function\") {\n go(\"pending\");\n setAnnouncement(kind === \"confirm\" ? pendingLabel : undoingLabel);\n try {\n await result;\n } catch {\n if (token !== run.current) return;\n go(\"error\");\n setAnnouncement(errorLabel);\n return;\n }\n if (token !== run.current) return;\n }\n if (kind === \"undo\") {\n go(\"idle\");\n setAnnouncement(\"Undone\");\n return;\n }\n go(\"done\");\n setAnnouncement(onUndo ? doneLabel + \". \" + undoLabel + \" is available.\" : doneLabel);\n },\n [doneLabel, errorLabel, go, onConfirm, onUndo, pendingLabel, undoLabel, undoingLabel],\n );\n\n const cancel = useCallback(() => {\n onCancel?.();\n toIdle();\n setAnnouncement(\"Cancelled\");\n }, [onCancel, toIdle]);\n const expire = useRef(() => {});\n useIsomorphicLayoutEffect(() => {\n expire.current = () => {\n if (live.current.state === \"confirming\") cancel();\n else toIdle();\n };\n });\n\n /* The shape: one pill whose width springs to whichever face is current. At rest it is auto, so it renders right before hydration. */\n const surfaceRef = useRef<HTMLDivElement>(null);\n const target = useRef(0);\n const flight = useRef(0);\n // Only the present face reports (a leaving face stops observing), so every report is the current face's width.\n const onFaceSize = useCallback((width: number) => {\n if (Math.abs(width - target.current) < 0.25) return;\n const from = target.current;\n target.current = width;\n const el = surfaceRef.current;\n if (!el) return;\n if (!from || prefersReducedMotion()) {\n flight.current++;\n el.style.width = \"\";\n el.style.transition = \"\";\n return;\n }\n // Start from where the pill is: the face it was showing, or wherever a spring in flight has got to.\n const start = el.style.width ? el.getBoundingClientRect().width : from;\n const token = ++flight.current;\n const curve = width > start ? \"morph\" : \"smooth\";\n el.style.transition = \"none\";\n el.style.width = start + \"px\";\n void el.offsetWidth;\n // Growing follows new content with a little life; shrinking settles without overshoot.\n el.style.transition =\n \"width var(--duration-\" +\n curve +\n \") var(--ease-\" +\n curve +\n \"), background-color var(--duration-standard) var(--ease-settle), box-shadow var(--duration-standard) var(--ease-settle)\";\n el.style.width = width + \"px\";\n const done = (event: TransitionEvent) => {\n if (event.target !== el || event.propertyName !== \"width\") return;\n el.removeEventListener(\"transitionend\", done);\n if (token !== flight.current) return;\n el.style.width = \"\";\n el.style.transition = \"\";\n };\n el.addEventListener(\"transitionend\", done);\n }, []);\n\n /* The timeout runs as an invisible clock. A pointer resting on the control holds it, and so does a hidden tab. */\n const timeout =\n state === \"confirming\" && !counting\n ? confirmTimeout\n : state === \"done\" || state === \"error\"\n ? resultTimeout\n : 0;\n const setHover = usePausableTimeout(timeout, state, () => expire.current());\n\n /* Countdown: a short grace period after the answer, drawn as a draining ring and ticking seconds. */\n const [seconds, setSeconds] = useState(countdown);\n const performRef = useRef(perform);\n useIsomorphicLayoutEffect(() => {\n performRef.current = perform;\n });\n useEffect(() => {\n if (!counting) return;\n const startedAt = performance.now();\n const timer = setInterval(() => {\n const elapsed = (performance.now() - startedAt) / 1000;\n if (elapsed >= countdown) {\n clearInterval(timer);\n void performRef.current(\"confirm\");\n } else {\n setSeconds(Math.max(1, Math.ceil(countdown - elapsed)));\n }\n }, 100);\n return () => {\n clearInterval(timer);\n setSeconds(countdown);\n };\n }, [counting, countdown]);\n\n /*\n * Hold: the confirm button fills with the destructive colour from the left\n * while pressed, and drains back if let go early. The fill is a clip-path\n * transitioned linearly over the hold, then over a spring on the way back.\n */\n const fillRef = useRef<HTMLSpanElement>(null);\n const holdRef = useRef<{ from: number; at: number; timer: ReturnType<typeof setTimeout> } | null>(\n null,\n );\n const progress = useRef(0);\n const setFill = (value: number, transition: string) => {\n const el = fillRef.current;\n if (!el) return;\n el.style.transition = transition;\n el.style.clipPath = \"inset(0 \" + (1 - value) * 100 + \"% 0 0 round 999px)\";\n };\n const startHold = () => {\n if (holdRef.current) return;\n const from = progress.current;\n const remaining = holdDuration * (1 - from);\n setFill(from, \"none\");\n void fillRef.current?.offsetWidth;\n setFill(1, \"clip-path \" + remaining + \"ms linear\");\n const timer = setTimeout(() => {\n holdRef.current = null;\n progress.current = 1;\n void perform(\"confirm\");\n }, remaining);\n holdRef.current = { from, at: performance.now(), timer };\n };\n const releaseHold = () => {\n const held = holdRef.current;\n if (!held) return;\n holdRef.current = null;\n clearTimeout(held.timer);\n const value = Math.min(1, held.from + (performance.now() - held.at) / holdDuration);\n setFill(value, \"none\");\n void fillRef.current?.offsetWidth;\n progress.current = 0;\n // Drains back on a spring that never overshoots (about 0.3s visual duration).\n setFill(0, prefersReducedMotion() ? \"none\" : \"clip-path 540ms var(--ease-smooth)\");\n setAnnouncement(\"Keep holding to \" + confirmLabel.toLowerCase());\n };\n // An unmounted control must not confirm: the hold's clock stops with it.\n useEffect(\n () => () => {\n if (holdRef.current) clearTimeout(holdRef.current.timer);\n },\n [],\n );\n useEffect(() => {\n if (state !== \"confirming\") {\n if (holdRef.current) clearTimeout(holdRef.current.timer);\n holdRef.current = null;\n progress.current = 0;\n setFill(0, \"none\");\n }\n }, [state]);\n\n const answer = () => {\n if (performance.now() - askedAt.current < ARM_MS) return;\n if (confirmMode === \"countdown\") {\n keepFocus();\n setCounting(true);\n setAnnouncement((countdownLabel ?? pendingLabel + \" in\") + \" \" + countdown + \" seconds\");\n return;\n }\n void perform(\"confirm\");\n };\n\n // An outside press answers the question with no.\n useEffect(() => {\n if (state !== \"confirming\" || !cancelOnOutsidePress) return;\n const down = (event: PointerEvent) => {\n if (!rootRef.current?.contains(event.target as Node)) cancel();\n };\n document.addEventListener(\"pointerdown\", down);\n return () => document.removeEventListener(\"pointerdown\", down);\n }, [cancel, cancelOnOutsidePress, state]);\n\n // Focus moves with the morph: Confirm while asking, the root while counting or working, Undo or Retry on a result, the trigger at rest.\n useIsomorphicLayoutEffect(() => {\n if (!pendingFocus.current) return;\n pendingFocus.current = false;\n const root = rootRef.current;\n if (!root) return;\n const face = root.querySelector<HTMLElement>('[data-face=\"' + faceKey + '\"]');\n const autofocus = face?.querySelector<HTMLElement>(\"[data-autofocus]:not(:disabled)\");\n (autofocus ?? root).focus({ preventScroll: true });\n }, [faceKey]);\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Escape\") {\n if (state === \"confirming\") {\n event.preventDefault();\n event.stopPropagation();\n cancel();\n } else if (state === \"done\" || state === \"error\") {\n event.preventDefault();\n event.stopPropagation();\n toIdle();\n }\n return;\n }\n // Enter answers yes from anywhere in the question except a button of its own (Cancel keeps meaning Cancel).\n if (\n event.key === \"Enter\" &&\n state === \"confirming\" &&\n !event.repeat &&\n !(event.target instanceof HTMLButtonElement)\n ) {\n event.preventDefault();\n if (counting) void perform(\"confirm\");\n else if (confirmMode !== \"hold\") answer();\n }\n };\n\n const holdKeys = (event: ReactKeyboardEvent<HTMLButtonElement>, down: boolean) => {\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n event.preventDefault();\n if (down) {\n if (!event.repeat) startHold();\n } else releaseHold();\n };\n const holdPointer = (event: ReactPointerEvent<HTMLButtonElement>) => {\n if (event.button !== 0) return;\n event.currentTarget.setPointerCapture?.(event.pointerId);\n startHold();\n };\n\n const shownPrompt = prompt ?? <>{label}?</>;\n const shownCountdown = countdownLabel ?? pendingLabel + \" in\";\n const confirmClasses = cn(\n confirmBase + \" \" + (tone === \"neutral\" ? confirmNeutral : confirmDanger),\n );\n const confirmButton =\n confirmMode === \"hold\" ? (\n <button\n type=\"button\"\n className={cn(confirmClasses + \" select-none [-webkit-touch-callout:none]\")}\n data-autofocus\n aria-describedby={holdHintId}\n onPointerDown={holdPointer}\n onPointerUp={releaseHold}\n onPointerCancel={releaseHold}\n onLostPointerCapture={releaseHold}\n onKeyDown={(event) => holdKeys(event, true)}\n onKeyUp={(event) => holdKeys(event, false)}\n onBlur={releaseHold}\n onContextMenu={(event) => event.preventDefault()}\n >\n <span>{confirmLabel}</span>\n <span\n ref={fillRef}\n aria-hidden=\"true\"\n style={{ clipPath: \"inset(0 100% 0 0 round 999px)\" }}\n className={cn(\n \"pointer-events-none absolute inset-0 inline-flex items-center justify-center \" +\n (tone === \"neutral\"\n ? \"bg-primary-strong text-primary-foreground\"\n : \"bg-destructive text-destructive-foreground\"),\n )}\n >\n <span>{confirmLabel}</span>\n </span>\n </button>\n ) : (\n <button\n type=\"button\"\n className={confirmClasses}\n data-autofocus\n onClick={answer}\n onKeyDown={(event) => {\n if (event.repeat) event.preventDefault();\n }}\n >\n {confirmLabel}\n </button>\n );\n\n const statusPart = \"gap-2 ps-3 pe-2.5 font-medium text-foreground tabular-nums\";\n const face = (() => {\n switch (faceKey) {\n case \"confirming\":\n return (\n <>\n <Part\n index={0}\n className=\"max-w-64 overflow-hidden ps-3 pe-2 font-medium text-foreground tabular-nums\"\n >\n <span id={promptId} className=\"overflow-hidden text-ellipsis\">\n {shownPrompt}\n </span>\n </Part>\n <Part index={1}>\n <button type=\"button\" className={secondary} onClick={cancel}>\n {cancelLabel}\n </button>\n </Part>\n <Part index={2}>{confirmButton}</Part>\n {confirmMode === \"hold\" ? (\n <span id={holdHintId} className=\"sr-only\">\n Press and hold\n </span>\n ) : null}\n </>\n );\n case \"counting\":\n return (\n <>\n <Part index={0} className={cn(statusPart + \" pe-1.5 text-muted-foreground\")}>\n <Ring seconds={countdown} tone={tone} />\n <span className=\"inline-flex items-baseline gap-1\">\n {shownCountdown} <Seconds value={seconds} />\n </span>\n </Part>\n <Part index={1}>\n <button type=\"button\" className={secondary} onClick={cancel}>\n {cancelLabel}\n </button>\n </Part>\n </>\n );\n case \"pending\":\n return (\n <Part index={0} className={cn(statusPart + \" ps-3 pe-4 text-muted-foreground\")}>\n <Spinner />\n <span className=\"inline-flex items-baseline gap-1\">\n {working === \"undo\" ? undoingLabel : pendingLabel}\n </span>\n </Part>\n );\n case \"done\":\n return (\n <>\n <Part index={0} className={cn(statusPart + (onUndo ? \"\" : \" pe-3.5\"))}>\n <Check />\n <span className=\"inline-flex items-baseline gap-1\">{doneLabel}</span>\n </Part>\n {onUndo ? (\n <Part index={1}>\n <button\n type=\"button\"\n className={cn(secondary + \" text-foreground\")}\n data-autofocus\n onClick={() => void perform(\"undo\")}\n >\n {undoLabel}\n </button>\n </Part>\n ) : null}\n </>\n );\n case \"error\":\n return (\n <>\n <Part index={0} className={statusPart}>\n <AlertIcon />\n <span className=\"inline-flex items-baseline gap-1\">{errorLabel}</span>\n </Part>\n <Part index={1}>\n <button\n type=\"button\"\n className={cn(secondary + \" text-foreground\")}\n data-autofocus\n onClick={() => void perform(working)}\n >\n {retryLabel}\n </button>\n </Part>\n </>\n );\n default:\n return (\n <Part index={0}>\n <button\n type=\"button\"\n className={trigger}\n data-autofocus\n disabled={isDisabled}\n onClick={() => {\n setAnnouncement(typeof shownPrompt === \"string\" ? shownPrompt : \"\");\n go(\"confirming\");\n }}\n >\n {icon ? (\n <span\n className=\"grid size-4 place-items-center text-muted-foreground [&>svg]:size-4\"\n aria-hidden=\"true\"\n >\n {icon}\n </span>\n ) : null}\n <span>{label}</span>\n </button>\n </Part>\n );\n }\n })();\n\n // A leaving face is absolute at the pill’s leading edge, so it needs no position of its own.\n const { leaving, changed, remove } = useLeavingItems<ReactNode>(faceKey, face, () => null);\n\n return (\n <div\n ref={rootRef}\n data-state={state}\n data-face={faceKey}\n data-tone={tone}\n data-mode={confirmMode}\n data-disabled={isDisabled || undefined}\n tabIndex={-1}\n aria-busy={state === \"pending\" || undefined}\n onKeyDown={onKeyDown}\n onPointerEnter={() => setHover(true)}\n onPointerLeave={() => setHover(false)}\n onPointerCancel={() => setHover(false)}\n className={cn(\n \"relative inline-flex max-w-full min-w-0 align-middle font-sans text-sm leading-5 text-foreground outline-none [-webkit-tap-highlight-color:transparent]\",\n className,\n )}\n >\n {/* One neutral pill for every state. Only its width moves; corners, text and border never scale. The border is an overlay so it never changes a measurement. */}\n <div\n ref={surfaceRef}\n className={cn(\n \"relative flex h-(--density-control-h-sm) max-w-full justify-start overflow-clip rounded-pill bg-card shadow-resting \" +\n \"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border-(length:--border-width) after:border-border after:content-[''] \" +\n \"motion-safe:transition-[background-color,box-shadow] motion-safe:duration-(--duration-standard) motion-safe:ease-settle \" +\n (isDisabled && state === \"idle\" ? \"shadow-none\" : \"\"),\n )}\n >\n {leaving.map((item) => (\n <LeavingFace key={item.id} id={item.id} onDone={remove}>\n {item.value}\n </LeavingFace>\n ))}\n <Face\n key={faceKey}\n id={faceKey}\n onSize={onFaceSize}\n labelledBy={faceKey === \"confirming\" ? promptId : undefined}\n animated={changed}\n >\n {face}\n </Face>\n </div>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {announcement}\n </span>\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/copy-button/copy-button.d.ts
|
|
2
|
+
export interface CopyButtonProps {
|
|
3
|
+
/** What goes on the clipboard. */
|
|
4
|
+
value: string;
|
|
5
|
+
/** The label at rest, and the button's accessible name. @default "Copy" */
|
|
6
|
+
children?: string;
|
|
7
|
+
/** Only the icon, a square button. The label is still its accessible name. */
|
|
8
|
+
iconOnly?: boolean;
|
|
9
|
+
/** `plain` has no border until hovered. @default "outline" */
|
|
10
|
+
variant?: "outline" | "plain";
|
|
11
|
+
isDisabled?: boolean;
|
|
12
|
+
/** Called once the text has reached the clipboard. */
|
|
13
|
+
onCopied?: () => void;
|
|
14
|
+
/** Extra classes. Merged last, so they win conflicts with ours. */
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Copies a value and says so, calmly. The icon trades places in one soft breath
|
|
19
|
+
* (copy, then a tick that draws itself, or an alert if the clipboard refused)
|
|
20
|
+
* while the label's changed letters rise in, and the button never changes width:
|
|
21
|
+
* its label cell reserves the widest of the three texts.
|
|
22
|
+
*
|
|
23
|
+
* It goes back to rest by itself after a moment, and a live region announces the
|
|
24
|
+
* result. The accessible name is always the resting label.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <CopyButton value={token} />
|
|
28
|
+
* <CopyButton value={url} iconOnly variant="plain" />
|
|
29
|
+
*/
|
|
30
|
+
export declare const CopyButton: import("react").ForwardRefExoticComponent<CopyButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
31
|
+
//#endregion
|
|
32
|
+
//# sourceMappingURL=copy-button.d.ts.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { buttonVariants } from "../button/button.js";
|
|
4
|
+
import { MorphText } from "../lib/morph-text.js";
|
|
5
|
+
import { IconSwap } from "../lib/icon-swap.js";
|
|
6
|
+
import { useCopyFeedback } from "../lib/use-copy-feedback.js";
|
|
7
|
+
import { forwardRef } from "react";
|
|
8
|
+
import { Button } from "react-aria-components";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region src/copy-button/copy-button.tsx
|
|
11
|
+
const ICON = "size-4";
|
|
12
|
+
/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */
|
|
13
|
+
function DrawnCheck() {
|
|
14
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
15
|
+
className: ICON,
|
|
16
|
+
viewBox: "0 0 24 24",
|
|
17
|
+
fill: "none",
|
|
18
|
+
stroke: "currentColor",
|
|
19
|
+
strokeWidth: 1.75,
|
|
20
|
+
strokeLinecap: "round",
|
|
21
|
+
strokeLinejoin: "round",
|
|
22
|
+
"aria-hidden": "true",
|
|
23
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
24
|
+
d: "M4 12l5 5L20 6",
|
|
25
|
+
pathLength: 1,
|
|
26
|
+
className: "[stroke-dasharray:1] motion-safe:animate-check-draw-calm"
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function StateIcon({ state }) {
|
|
31
|
+
if (state === "copied") return /* @__PURE__ */ jsx(DrawnCheck, {});
|
|
32
|
+
const common = {
|
|
33
|
+
className: ICON,
|
|
34
|
+
viewBox: "0 0 24 24",
|
|
35
|
+
fill: "none",
|
|
36
|
+
stroke: "currentColor",
|
|
37
|
+
strokeWidth: 1.75,
|
|
38
|
+
strokeLinecap: "round",
|
|
39
|
+
strokeLinejoin: "round",
|
|
40
|
+
"aria-hidden": true
|
|
41
|
+
};
|
|
42
|
+
if (state === "error") return /* @__PURE__ */ jsxs("svg", {
|
|
43
|
+
...common,
|
|
44
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
45
|
+
cx: "12",
|
|
46
|
+
cy: "12",
|
|
47
|
+
r: "10"
|
|
48
|
+
}), /* @__PURE__ */ jsx("path", { d: "M12 8v4M12 16h.01" })]
|
|
49
|
+
});
|
|
50
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
51
|
+
...common,
|
|
52
|
+
children: [/* @__PURE__ */ jsx("rect", {
|
|
53
|
+
x: "8",
|
|
54
|
+
y: "8",
|
|
55
|
+
width: "14",
|
|
56
|
+
height: "14",
|
|
57
|
+
rx: "2"
|
|
58
|
+
}), /* @__PURE__ */ jsx("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Copies a value and says so, calmly. The icon trades places in one soft breath
|
|
63
|
+
* (copy, then a tick that draws itself, or an alert if the clipboard refused)
|
|
64
|
+
* while the label's changed letters rise in, and the button never changes width:
|
|
65
|
+
* its label cell reserves the widest of the three texts.
|
|
66
|
+
*
|
|
67
|
+
* It goes back to rest by itself after a moment, and a live region announces the
|
|
68
|
+
* result. The accessible name is always the resting label.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* <CopyButton value={token} />
|
|
72
|
+
* <CopyButton value={url} iconOnly variant="plain" />
|
|
73
|
+
*/
|
|
74
|
+
const CopyButton = forwardRef(function CopyButton({ value, children: label = "Copy", iconOnly = false, variant = "outline", isDisabled, onCopied, className }, ref) {
|
|
75
|
+
const { state, copy } = useCopyFeedback();
|
|
76
|
+
const text = state === "copied" ? "Copied" : state === "error" ? "Failed" : label;
|
|
77
|
+
const pressStart = (event) => {
|
|
78
|
+
event.target.style.setProperty("--press-scale", iconOnly ? "0.96" : "0.97");
|
|
79
|
+
};
|
|
80
|
+
const classes = cn(buttonVariants({
|
|
81
|
+
variant: variant === "plain" ? "ghost" : "outlineNeutral",
|
|
82
|
+
size: "sm"
|
|
83
|
+
}) + " [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]" + (iconOnly ? " w-(--density-control-h-sm) px-0" : ""), className);
|
|
84
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
85
|
+
ref,
|
|
86
|
+
"data-slot": "button",
|
|
87
|
+
"data-copy-state": state,
|
|
88
|
+
"aria-label": label,
|
|
89
|
+
isDisabled,
|
|
90
|
+
onPressStart: pressStart,
|
|
91
|
+
onPress: () => {
|
|
92
|
+
copy(value).then((ok) => {
|
|
93
|
+
if (ok) onCopied?.();
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
className: classes,
|
|
97
|
+
children: [/* @__PURE__ */ jsx(IconSwap, {
|
|
98
|
+
iconKey: state,
|
|
99
|
+
calm: true,
|
|
100
|
+
className: cn(ICON, "relative"),
|
|
101
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
102
|
+
className: state === "copied" ? "text-success-strong" : state === "error" ? "text-destructive-strong" : void 0,
|
|
103
|
+
children: /* @__PURE__ */ jsx(StateIcon, { state })
|
|
104
|
+
})
|
|
105
|
+
}), iconOnly ? null : /* @__PURE__ */ jsxs("span", {
|
|
106
|
+
className: "-mx-1 grid min-w-0 overflow-x-clip px-1 text-start whitespace-nowrap",
|
|
107
|
+
"aria-hidden": "true",
|
|
108
|
+
children: [[
|
|
109
|
+
label,
|
|
110
|
+
"Copied",
|
|
111
|
+
"Failed"
|
|
112
|
+
].map((measure) => /* @__PURE__ */ jsx("span", {
|
|
113
|
+
className: "invisible col-start-1 row-start-1",
|
|
114
|
+
children: measure
|
|
115
|
+
}, measure)), /* @__PURE__ */ jsx("span", {
|
|
116
|
+
className: "col-start-1 row-start-1 justify-self-start",
|
|
117
|
+
children: /* @__PURE__ */ jsx(MorphText, {
|
|
118
|
+
text,
|
|
119
|
+
morphWidth: false,
|
|
120
|
+
calm: true
|
|
121
|
+
})
|
|
122
|
+
})]
|
|
123
|
+
})]
|
|
124
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
125
|
+
className: "sr-only",
|
|
126
|
+
role: "status",
|
|
127
|
+
"aria-live": "polite",
|
|
128
|
+
children: state === "idle" ? "" : state === "error" ? label + ": Could not copy" : label + ": Copied"
|
|
129
|
+
})] });
|
|
130
|
+
});
|
|
131
|
+
//#endregion
|
|
132
|
+
export { CopyButton };
|
|
133
|
+
|
|
134
|
+
//# sourceMappingURL=copy-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-button.js","names":["AriaButton"],"sources":["../../src/copy-button/copy-button.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef } from \"react\";\nimport { Button as AriaButton, type PressEvent } from \"react-aria-components\";\n\nimport { buttonVariants } from \"../button/button.js\";\nimport { cn } from \"../lib/cn.js\";\nimport { IconSwap } from \"../lib/icon-swap.js\";\nimport { MorphText } from \"../lib/morph-text.js\";\nimport { useCopyFeedback, type CopyFeedbackState } from \"../lib/use-copy-feedback.js\";\n\nexport interface CopyButtonProps {\n /** What goes on the clipboard. */\n value: string;\n /** The label at rest, and the button's accessible name. @default \"Copy\" */\n children?: string;\n /** Only the icon, a square button. The label is still its accessible name. */\n iconOnly?: boolean;\n /** `plain` has no border until hovered. @default \"outline\" */\n variant?: \"outline\" | \"plain\";\n isDisabled?: boolean;\n /** Called once the text has reached the clipboard. */\n onCopied?: () => void;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nconst ICON = \"size-4\";\n\n/** The tick draws itself from its short stroke, the way a hand would write it: quick to start, then easing into place. */\nfunction DrawnCheck() {\n return (\n <svg\n className={ICON}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.75}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M4 12l5 5L20 6\"\n pathLength={1}\n className=\"[stroke-dasharray:1] motion-safe:animate-check-draw-calm\"\n />\n </svg>\n );\n}\n\nfunction StateIcon({ state }: { state: CopyFeedbackState }) {\n if (state === \"copied\") return <DrawnCheck />;\n const common = {\n className: ICON,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 1.75,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n \"aria-hidden\": true,\n } as const;\n if (state === \"error\") {\n return (\n <svg {...common}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M12 8v4M12 16h.01\" />\n </svg>\n );\n }\n return (\n <svg {...common}>\n <rect x=\"8\" y=\"8\" width=\"14\" height=\"14\" rx=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n );\n}\n\n/**\n * Copies a value and says so, calmly. The icon trades places in one soft breath\n * (copy, then a tick that draws itself, or an alert if the clipboard refused)\n * while the label's changed letters rise in, and the button never changes width:\n * its label cell reserves the widest of the three texts.\n *\n * It goes back to rest by itself after a moment, and a live region announces the\n * result. The accessible name is always the resting label.\n *\n * @example\n * <CopyButton value={token} />\n * <CopyButton value={url} iconOnly variant=\"plain\" />\n */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>(function CopyButton(\n {\n value,\n children: label = \"Copy\",\n iconOnly = false,\n variant = \"outline\",\n isDisabled,\n onCopied,\n className,\n },\n ref,\n) {\n const { state, copy } = useCopyFeedback();\n const text = state === \"copied\" ? \"Copied\" : state === \"error\" ? \"Failed\" : label;\n\n // Not width-based like Button's press: an icon button presses a little deeper, every other one the same.\n const pressStart = (event: PressEvent) => {\n (event.target as HTMLElement).style.setProperty(\"--press-scale\", iconOnly ? \"0.96\" : \"0.97\");\n };\n\n const classes = cn(\n buttonVariants({ variant: variant === \"plain\" ? \"ghost\" : \"outlineNeutral\", size: \"sm\" }) +\n \" [--release-duration:var(--duration-gentle)] [--release-ease:var(--ease-gentle)]\" +\n (iconOnly ? \" w-(--density-control-h-sm) px-0\" : \"\"),\n className,\n );\n\n return (\n <>\n <AriaButton\n ref={ref}\n data-slot=\"button\"\n data-copy-state={state}\n aria-label={label}\n isDisabled={isDisabled}\n onPressStart={pressStart}\n onPress={() => {\n void copy(value).then((ok) => {\n if (ok) onCopied?.();\n });\n }}\n className={classes}\n >\n <IconSwap iconKey={state} calm className={cn(ICON, \"relative\")}>\n <span\n className={\n state === \"copied\"\n ? \"text-success-strong\"\n : state === \"error\"\n ? \"text-destructive-strong\"\n : undefined\n }\n >\n <StateIcon state={state} />\n </span>\n </IconSwap>\n {iconOnly ? null : (\n // The cell reserves the widest state, so nothing moves but the letters. The inline padding gives blurred\n // edge letters room before the horizontal clip; the negative margin keeps the box the same size.\n <span\n className=\"-mx-1 grid min-w-0 overflow-x-clip px-1 text-start whitespace-nowrap\"\n aria-hidden=\"true\"\n >\n {[label, \"Copied\", \"Failed\"].map((measure) => (\n <span key={measure} className=\"invisible col-start-1 row-start-1\">\n {measure}\n </span>\n ))}\n <span className=\"col-start-1 row-start-1 justify-self-start\">\n <MorphText text={text} morphWidth={false} calm />\n </span>\n </span>\n )}\n </AriaButton>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state === \"idle\"\n ? \"\"\n : state === \"error\"\n ? label + \": Could not copy\"\n : label + \": Copied\"}\n </span>\n </>\n );\n});\n"],"mappings":";;;;;;;;;;AA2BA,MAAM,OAAO;;AAGb,SAAS,aAAa;CACpB,OACE,oBAAC,OAAD;EACE,WAAW;EACX,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAa;EACb,eAAc;EACd,gBAAe;EACf,eAAY;EAEZ,UAAA,oBAAC,QAAD;GACE,GAAE;GACF,YAAY;GACZ,WAAU;EACX,CAAA;CACE,CAAA;AAET;AAEA,SAAS,UAAU,EAAE,SAAuC;CAC1D,IAAI,UAAU,UAAU,OAAO,oBAAC,YAAD,CAAa,CAAA;CAC5C,MAAM,SAAS;EACb,WAAW;EACX,SAAS;EACT,MAAM;EACN,QAAQ;EACR,aAAa;EACb,eAAe;EACf,gBAAgB;EAChB,eAAe;CACjB;CACA,IAAI,UAAU,SACZ,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,UAAD;GAAQ,IAAG;GAAK,IAAG;GAAK,GAAE;EAAM,CAAA,GAChC,oBAAC,QAAD,EAAM,GAAE,oBAAqB,CAAA,CAC1B;;CAGT,OACE,qBAAC,OAAD;EAAK,GAAI;EAAT,UAAA,CACE,oBAAC,QAAD;GAAM,GAAE;GAAI,GAAE;GAAI,OAAM;GAAK,QAAO;GAAK,IAAG;EAAK,CAAA,GACjD,oBAAC,QAAD,EAAM,GAAE,0DAA2D,CAAA,CAChE;;AAET;;;;;;;;;;;;;;AAeA,MAAa,aAAa,WAA+C,SAAS,WAChF,EACE,OACA,UAAU,QAAQ,QAClB,WAAW,OACX,UAAU,WACV,YACA,UACA,aAEF,KACA;CACA,MAAM,EAAE,OAAO,SAAS,gBAAgB;CACxC,MAAM,OAAO,UAAU,WAAW,WAAW,UAAU,UAAU,WAAW;CAG5E,MAAM,cAAc,UAAsB;EACxC,MAAO,OAAuB,MAAM,YAAY,iBAAiB,WAAW,SAAS,MAAM;CAC7F;CAEA,MAAM,UAAU,GACd,eAAe;EAAE,SAAS,YAAY,UAAU,UAAU;EAAkB,MAAM;CAAK,CAAC,IACtF,sFACC,WAAW,qCAAqC,KACnD,SACF;CAEA,OACE,qBAAA,YAAA,EAAA,UAAA,CACE,qBAACA,QAAD;EACO;EACL,aAAU;EACV,mBAAiB;EACjB,cAAY;EACA;EACZ,cAAc;EACd,eAAe;GACb,KAAU,KAAK,CAAC,CAAC,MAAM,OAAO;IAC5B,IAAI,IAAI,WAAW;GACrB,CAAC;EACH;EACA,WAAW;EAZb,UAAA,CAcE,oBAAC,UAAD;GAAU,SAAS;GAAO,MAAA;GAAK,WAAW,GAAG,MAAM,UAAU;GAC3D,UAAA,oBAAC,QAAD;IACE,WACE,UAAU,WACN,wBACA,UAAU,UACR,4BACA,KAAA;IAGR,UAAA,oBAAC,WAAD,EAAkB,MAAQ,CAAA;GACtB,CAAA;EACE,CAAA,GACT,WAAW,OAGV,qBAAC,QAAD;GACE,WAAU;GACV,eAAY;GAFd,UAAA,CAIG;IAAC;IAAO;IAAU;GAAQ,CAAC,CAAC,KAAK,YAChC,oBAAC,QAAD;IAAoB,WAAU;IAC3B,UAAA;GACG,GAFK,OAEL,CACP,GACD,oBAAC,QAAD;IAAM,WAAU;IACd,UAAA,oBAAC,WAAD;KAAiB;KAAM,YAAY;KAAO,MAAA;IAAM,CAAA;GAC5C,CAAA,CACF;EAEE,CAAA,CAAA;CACZ,CAAA,GAAA,oBAAC,QAAD;EAAM,WAAU;EAAU,MAAK;EAAS,aAAU;EAC/C,UAAA,UAAU,SACP,KACA,UAAU,UACR,QAAQ,qBACR,QAAQ;CACV,CAAA,CACN,EAAA,CAAA;AAEN,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PickerSharedProps, datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants } from "./picker-parts.js";
|
|
2
|
+
import { DateRangePicker, DateRangePickerMessages, DateRangePickerProps, DateRangePickerValue, DateRangePreset, defaultDateRangePresets } from "./date-range-picker.js";
|
|
3
|
+
import { DateValue } from "@internationalized/date";
|
|
4
|
+
//#region src/date-picker/date-picker.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* DatePicker and DateRangePicker: a field-sized trigger that shows the chosen
|
|
7
|
+
* value as words, opening a Popover with the Calendar in it and a typed date
|
|
8
|
+
* field above the calendar.
|
|
9
|
+
*
|
|
10
|
+
* WHY A TRIGGER AND NOT React Aria's segmented DatePicker. The segmented field
|
|
11
|
+
* shows `dd/mm/yyyy` digits in the field itself, which is exactly what a
|
|
12
|
+
* native `<input type="date">` shows; a product that prints dates as
|
|
13
|
+
* "1 Sep 2026" everywhere else wants the field to say that too. The typed
|
|
14
|
+
* field is still there, inside the popover, because a calendar alone makes a
|
|
15
|
+
* date years away a long walk of "previous month" presses.
|
|
16
|
+
*
|
|
17
|
+
* Values are `DateValue` objects (ADR 0006), the type Calendar already takes,
|
|
18
|
+
* and the import stays `import type`: the default display formats with
|
|
19
|
+
* `Intl.DateTimeFormat` over `value.toDate("UTC")`, an instance method, so the
|
|
20
|
+
* package still contributes zero bytes.
|
|
21
|
+
*
|
|
22
|
+
* Every class string below is a STATIC literal, for the same reason as every
|
|
23
|
+
* other component here: Tailwind scans source text.
|
|
24
|
+
*/
|
|
25
|
+
/** Props for {@link DatePicker}. */
|
|
26
|
+
export interface DatePickerProps extends PickerSharedProps {
|
|
27
|
+
/**
|
|
28
|
+
* The footer's status line when a day is chosen, followed by the day.
|
|
29
|
+
*
|
|
30
|
+
* @default "Selected"
|
|
31
|
+
*/
|
|
32
|
+
selectedStatusLabel?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The footer's status line while no day is chosen.
|
|
35
|
+
*
|
|
36
|
+
* @default "Choose a day"
|
|
37
|
+
*/
|
|
38
|
+
emptyStatusLabel?: string;
|
|
39
|
+
/** The chosen day (controlled). `null` is none. */
|
|
40
|
+
value?: DateValue | null;
|
|
41
|
+
/** The day chosen at first (uncontrolled). */
|
|
42
|
+
defaultValue?: DateValue | null;
|
|
43
|
+
/** Told the new day, or `null` when cleared. */
|
|
44
|
+
onChange?: (value: DateValue | null) => void;
|
|
45
|
+
/** Label of the typed date field inside the popover. @default "Type a date" */
|
|
46
|
+
typedLabel?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare const DatePicker: import("react").ForwardRefExoticComponent<DatePickerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export { DateRangePicker, type DateRangePickerMessages, type DateRangePickerProps, type DateRangePickerValue, type DateRangePreset, datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants, defaultDateRangePresets };
|
|
51
|
+
//# sourceMappingURL=date-picker.d.ts.map
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { prefersReducedMotion } from "../lib/motion.js";
|
|
4
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
5
|
+
import { useFlip } from "../lib/flip.js";
|
|
6
|
+
import { Calendar as Calendar$1 } from "../calendar/calendar.js";
|
|
7
|
+
import { Popover as Popover$1 } from "../popover/popover.js";
|
|
8
|
+
import { CalendarGlyph, Chevron, TypedDate, datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants, descriptionClass, errorClass, hintClass, pickerFooterButtonVariants, useDayFormat, useValue } from "./picker-parts.js";
|
|
9
|
+
import { DateRangePicker, defaultDateRangePresets } from "./date-range-picker.js";
|
|
10
|
+
import { forwardRef, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { Button, Dialog, DialogTrigger, useLocale } from "react-aria-components";
|
|
12
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region src/date-picker/date-picker.tsx
|
|
14
|
+
/** The shell both pickers share: label, trigger, popover, description and error. */
|
|
15
|
+
function PickerShell({ slot, shown, valueNode, props, triggerRef, children }) {
|
|
16
|
+
const id = useId();
|
|
17
|
+
const labelId = `${id}-label`;
|
|
18
|
+
const valueId = `${id}-value`;
|
|
19
|
+
const descriptionId = `${id}-description`;
|
|
20
|
+
const errorId = `${id}-error`;
|
|
21
|
+
const invalid = props.errorMessage != null;
|
|
22
|
+
const describedBy = [props.description != null && descriptionId, invalid && errorId].filter(Boolean).join(" ");
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
24
|
+
"data-slot": slot,
|
|
25
|
+
"data-invalid": invalid || void 0,
|
|
26
|
+
className: cn(datePickerVariants(), props.className),
|
|
27
|
+
children: [
|
|
28
|
+
/* @__PURE__ */ jsx("span", {
|
|
29
|
+
id: labelId,
|
|
30
|
+
className: cn(datePickerLabelVariants(), props.labelHidden && "sr-only"),
|
|
31
|
+
children: props.label
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ jsxs(DialogTrigger, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
34
|
+
ref: triggerRef,
|
|
35
|
+
"data-slot": "date-picker-trigger",
|
|
36
|
+
"aria-labelledby": `${labelId} ${valueId}`,
|
|
37
|
+
"aria-describedby": describedBy || void 0,
|
|
38
|
+
isDisabled: props.isDisabled,
|
|
39
|
+
className: datePickerTriggerVariants({ size: props.size }),
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsx(CalendarGlyph, {}),
|
|
42
|
+
valueNode ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
43
|
+
id: valueId,
|
|
44
|
+
"data-slot": "date-picker-value",
|
|
45
|
+
className: "sr-only",
|
|
46
|
+
children: shown ?? props.placeholder ?? "Choose a date"
|
|
47
|
+
}), valueNode] }) : /* @__PURE__ */ jsx("span", {
|
|
48
|
+
id: valueId,
|
|
49
|
+
"data-slot": "date-picker-value",
|
|
50
|
+
className: cn("min-w-0 flex-1 truncate", shown === null && "text-muted-foreground"),
|
|
51
|
+
children: shown ?? props.placeholder ?? "Choose a date"
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ jsx(Chevron, {})
|
|
54
|
+
]
|
|
55
|
+
}), /* @__PURE__ */ jsx(Popover$1, {
|
|
56
|
+
entrance: "none",
|
|
57
|
+
placement: "bottom start",
|
|
58
|
+
className: "picker-pop w-auto origin-[22px_-4px] overflow-y-auto",
|
|
59
|
+
children: /* @__PURE__ */ jsx(Dialog, {
|
|
60
|
+
"aria-label": props.label,
|
|
61
|
+
"data-slot": "date-picker-dialog",
|
|
62
|
+
className: "flex flex-col gap-3 outline-none",
|
|
63
|
+
children: ({ close }) => children(close)
|
|
64
|
+
})
|
|
65
|
+
})] }),
|
|
66
|
+
props.description == null ? null : /* @__PURE__ */ jsx("span", {
|
|
67
|
+
id: descriptionId,
|
|
68
|
+
"data-slot": "date-picker-description",
|
|
69
|
+
className: cn(descriptionClass, hintClass),
|
|
70
|
+
children: props.description
|
|
71
|
+
}),
|
|
72
|
+
invalid ? /* @__PURE__ */ jsx("span", {
|
|
73
|
+
id: errorId,
|
|
74
|
+
"data-slot": "date-picker-error",
|
|
75
|
+
className: errorClass,
|
|
76
|
+
children: props.errorMessage
|
|
77
|
+
}) : null
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** The pieces of a formatted day, so each can roll on its own: a later date rises, an earlier one drops. */
|
|
82
|
+
function useDayParts() {
|
|
83
|
+
const { locale } = useLocale();
|
|
84
|
+
return useMemo(() => {
|
|
85
|
+
const format = new Intl.DateTimeFormat(locale, {
|
|
86
|
+
dateStyle: "medium",
|
|
87
|
+
timeZone: "UTC"
|
|
88
|
+
});
|
|
89
|
+
return (value) => format.formatToParts(value.toDate("UTC"));
|
|
90
|
+
}, [locale]);
|
|
91
|
+
}
|
|
92
|
+
/** Which way a value moved in time, so its text rolls in that direction. */
|
|
93
|
+
function useDirection(value) {
|
|
94
|
+
const key = value ? String(value) : null;
|
|
95
|
+
const [previous, setPrevious] = useState(value);
|
|
96
|
+
const [direction, setDirection] = useState(1);
|
|
97
|
+
if ((previous ? String(previous) : null) !== key) {
|
|
98
|
+
setPrevious(value);
|
|
99
|
+
setDirection(value && previous && value.compare(previous) < 0 ? -1 : 1);
|
|
100
|
+
}
|
|
101
|
+
return direction;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The value the eye sees, in a trigger. Each part of a date rolls on its own
|
|
105
|
+
* (a part that did not change holds still), parts after a wider or narrower one
|
|
106
|
+
* slide along instead of jumping, and a value and the placeholder swap as wholes.
|
|
107
|
+
* Nothing animates on first mount. The caller keeps the plain text for screen
|
|
108
|
+
* readers; this copy is `aria-hidden`.
|
|
109
|
+
*/
|
|
110
|
+
function AnimatedValue({ parts, placeholder, direction }) {
|
|
111
|
+
const ref = useRef(null);
|
|
112
|
+
const text = parts ? parts.map((part) => part.value).join("") : "";
|
|
113
|
+
useFlip(ref, text);
|
|
114
|
+
return /* @__PURE__ */ jsx("span", {
|
|
115
|
+
ref,
|
|
116
|
+
"aria-hidden": "true",
|
|
117
|
+
className: "flex min-w-0 flex-1 overflow-hidden",
|
|
118
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
119
|
+
swapKey: parts ? "value" : "placeholder",
|
|
120
|
+
dir: parts ? 1 : -1,
|
|
121
|
+
motion: "roll",
|
|
122
|
+
className: "inline-flex min-w-0 max-w-full",
|
|
123
|
+
children: parts ? /* @__PURE__ */ jsx("span", {
|
|
124
|
+
className: "inline-flex whitespace-pre tabular-nums",
|
|
125
|
+
children: parts.map((part, index) => /* @__PURE__ */ jsx("span", {
|
|
126
|
+
"data-flip": "",
|
|
127
|
+
className: "inline-flex whitespace-pre",
|
|
128
|
+
children: part.type === "literal" ? /* @__PURE__ */ jsx("span", {
|
|
129
|
+
className: "whitespace-pre",
|
|
130
|
+
children: part.value
|
|
131
|
+
}) : /* @__PURE__ */ jsx(SwapText, {
|
|
132
|
+
swapKey: part.value,
|
|
133
|
+
dir: direction,
|
|
134
|
+
motion: "roll",
|
|
135
|
+
className: "inline-block whitespace-pre",
|
|
136
|
+
children: part.value
|
|
137
|
+
})
|
|
138
|
+
}, `${index}-${part.type}`))
|
|
139
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
140
|
+
"data-slot": "date-picker-placeholder",
|
|
141
|
+
className: "truncate text-muted-foreground",
|
|
142
|
+
children: placeholder
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The footer of a picker's calendar: Clear on one side and a status line on the
|
|
149
|
+
* other, which confirms a pick (the new line rises in while the old one lifts
|
|
150
|
+
* away).
|
|
151
|
+
*/
|
|
152
|
+
function PickerFooter({ status, statusKey, clearable, clearLabel, canClear, onClear }) {
|
|
153
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
154
|
+
"data-slot": "date-picker-footer",
|
|
155
|
+
className: "flex items-center justify-between gap-2 border-t-(length:--border-width) border-border pt-3 text-xs text-muted-foreground",
|
|
156
|
+
children: [clearable ? /* @__PURE__ */ jsx(Button, {
|
|
157
|
+
slot: null,
|
|
158
|
+
"data-slot": "date-picker-clear",
|
|
159
|
+
isDisabled: !canClear,
|
|
160
|
+
onPress: onClear,
|
|
161
|
+
className: pickerFooterButtonVariants(),
|
|
162
|
+
children: clearLabel
|
|
163
|
+
}) : /* @__PURE__ */ jsx("span", {}), /* @__PURE__ */ jsx("span", {
|
|
164
|
+
"aria-live": "polite",
|
|
165
|
+
className: "grid min-w-0 justify-items-end",
|
|
166
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
167
|
+
swapKey: statusKey,
|
|
168
|
+
className: "block max-w-full truncate",
|
|
169
|
+
children: status
|
|
170
|
+
})
|
|
171
|
+
})]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
const DatePicker = forwardRef(function DatePicker({ value, defaultValue = null, onChange, typedLabel = "Type a date", selectedStatusLabel = "Selected", emptyStatusLabel = "Choose a day", ...props }, ref) {
|
|
175
|
+
const [current, setCurrent] = useValue(value, defaultValue, onChange);
|
|
176
|
+
const defaultFormat = useDayFormat();
|
|
177
|
+
const dayParts = useDayParts();
|
|
178
|
+
const format = props.formatValue ?? defaultFormat;
|
|
179
|
+
const direction = useDirection(current);
|
|
180
|
+
const closeTimer = useRef(void 0);
|
|
181
|
+
useEffect(() => () => window.clearTimeout(closeTimer.current), []);
|
|
182
|
+
const parts = current ? props.formatValue ? [{
|
|
183
|
+
type: "value",
|
|
184
|
+
value: format(current)
|
|
185
|
+
}] : dayParts(current) : null;
|
|
186
|
+
return /* @__PURE__ */ jsx(PickerShell, {
|
|
187
|
+
slot: "date-picker",
|
|
188
|
+
shown: current ? format(current) : null,
|
|
189
|
+
valueNode: /* @__PURE__ */ jsx(AnimatedValue, {
|
|
190
|
+
parts,
|
|
191
|
+
placeholder: props.placeholder ?? "Choose a date",
|
|
192
|
+
direction
|
|
193
|
+
}),
|
|
194
|
+
props,
|
|
195
|
+
triggerRef: ref,
|
|
196
|
+
children: (close) => {
|
|
197
|
+
/** A picked day lets the disc glide onto it and the footer confirm it, then the calendar returns to the field. */
|
|
198
|
+
const pick = (next) => {
|
|
199
|
+
setCurrent(next);
|
|
200
|
+
window.clearTimeout(closeTimer.current);
|
|
201
|
+
if (!next || prefersReducedMotion()) close();
|
|
202
|
+
else closeTimer.current = window.setTimeout(close, 300);
|
|
203
|
+
};
|
|
204
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
205
|
+
/* @__PURE__ */ jsx(TypedDate, {
|
|
206
|
+
label: typedLabel,
|
|
207
|
+
value: current,
|
|
208
|
+
minValue: props.minValue,
|
|
209
|
+
maxValue: props.maxValue,
|
|
210
|
+
onChange: (next) => {
|
|
211
|
+
if (next || props.clearable) setCurrent(next);
|
|
212
|
+
}
|
|
213
|
+
}),
|
|
214
|
+
/* @__PURE__ */ jsx(Calendar$1, {
|
|
215
|
+
label: props.label,
|
|
216
|
+
labelHidden: true,
|
|
217
|
+
autoFocus: true,
|
|
218
|
+
value: current,
|
|
219
|
+
minValue: props.minValue,
|
|
220
|
+
maxValue: props.maxValue,
|
|
221
|
+
onChange: (next) => pick(next)
|
|
222
|
+
}),
|
|
223
|
+
/* @__PURE__ */ jsx(PickerFooter, {
|
|
224
|
+
status: current ? `${selectedStatusLabel} ${format(current)}` : emptyStatusLabel,
|
|
225
|
+
statusKey: current ? String(current) : "none",
|
|
226
|
+
clearable: Boolean(props.clearable),
|
|
227
|
+
clearLabel: props.clearLabel ?? "Clear",
|
|
228
|
+
canClear: current !== null,
|
|
229
|
+
onClear: () => {
|
|
230
|
+
window.clearTimeout(closeTimer.current);
|
|
231
|
+
pick(null);
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
] });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
//#endregion
|
|
239
|
+
export { DatePicker, DateRangePicker, datePickerInputVariants, datePickerLabelVariants, datePickerSegmentVariants, datePickerTriggerVariants, datePickerVariants, defaultDateRangePresets };
|
|
240
|
+
|
|
241
|
+
//# sourceMappingURL=date-picker.js.map
|