@teamofsilicons/silicon-ui 0.2.0 → 0.4.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 +4 -3
- package/check.mjs +14 -5
- package/components/activity-heatmap/activity-heatmap.module.css +4 -4
- package/components/alert/alert.module.css +1 -1
- package/components/animated-counter/animated-counter.js +1 -1
- package/components/announcement-bar/announcement-bar.module.css +6 -6
- package/components/avatar/avatar.d.ts +2 -2
- package/components/avatar/avatar.js +1 -1
- package/components/avatar/avatar.module.css +5 -2
- package/components/avatar-group/avatar-group.module.css +4 -4
- package/components/badge/badge.module.css +4 -4
- package/components/bar-chart/bar-chart.js +1 -1
- package/components/bar-chart/bar-chart.module.css +4 -4
- package/components/billing-toggle/billing-toggle.js +2 -2
- package/components/billing-toggle/billing-toggle.module.css +10 -10
- package/components/bottom-sheet/bottom-sheet.js +1 -1
- package/components/bottom-sheet/bottom-sheet.module.css +4 -5
- package/components/breadcrumb/breadcrumb.d.ts +8 -2
- package/components/breadcrumb/breadcrumb.js +21 -9
- package/components/breadcrumb/breadcrumb.module.css +7 -3
- package/components/brush-chart/brush-chart.module.css +9 -9
- package/components/button/button.d.ts +7 -2
- package/components/button/button.js +3 -1
- package/components/button/button.module.css +10 -1
- package/components/button-group/button-group.module.css +7 -7
- package/components/calendar/calendar.js +15 -5
- package/components/calendar/calendar.module.css +13 -10
- package/components/card/card.js +4 -4
- package/components/card/card.module.css +2 -2
- package/components/card-stack/card-stack.js +2 -2
- package/components/card-stack/card-stack.module.css +2 -3
- package/components/carousel/carousel.module.css +3 -3
- package/components/chat-thread/chat-thread.module.css +41 -41
- package/components/checkbox/checkbox.module.css +1 -1
- package/components/chip-group/chip-group.js +1 -1
- package/components/chip-group/chip-group.module.css +2 -2
- package/components/code-block/code-block.module.css +7 -14
- package/components/color-picker/color-picker.module.css +20 -21
- package/components/combobox/combobox.js +5 -1
- package/components/combobox/combobox.module.css +2 -2
- package/components/command-palette/command-palette.js +18 -11
- package/components/command-palette/command-palette.module.css +18 -18
- package/components/comment-thread/comment-thread.module.css +35 -35
- package/components/confirm-morph/confirm-morph.js +5 -5
- package/components/confirm-morph/confirm-morph.module.css +9 -9
- package/components/context-menu/context-menu.js +10 -28
- package/components/context-menu/context-menu.module.css +3 -3
- package/components/copy-button/copy-button.js +4 -4
- package/components/countdown/countdown.module.css +4 -4
- package/components/date-picker/date-picker.js +3 -3
- package/components/date-picker/date-picker.module.css +7 -7
- package/components/date-range-picker/date-range-picker.js +13 -10
- package/components/date-range-picker/date-range-picker.module.css +23 -22
- package/components/dialog/dialog.module.css +2 -2
- package/components/donut-chart/donut-chart.module.css +5 -5
- package/components/drawer/drawer.module.css +7 -7
- package/components/dropdown-menu/dropdown-menu.d.ts +71 -1
- package/components/dropdown-menu/dropdown-menu.js +100 -6
- package/components/dropdown-menu/dropdown-menu.module.css +15 -5
- package/components/elastic-slider/elastic-slider.js +2 -2
- package/components/elastic-slider/elastic-slider.module.css +4 -4
- package/components/expandable-card/expandable-card.js +1 -1
- package/components/expandable-card/expandable-card.module.css +1 -1
- package/components/expanding-button-group/expanding-button-group.module.css +3 -3
- package/components/expanding-search/expanding-search.js +3 -3
- package/components/expanding-search/expanding-search.module.css +9 -9
- package/components/file-dropzone/file-dropzone.js +3 -3
- package/components/file-dropzone/file-dropzone.module.css +21 -21
- package/components/file-upload/file-upload.module.css +7 -7
- package/components/filter-toolbar/filter-toolbar.js +7 -3
- package/components/filter-toolbar/filter-toolbar.module.css +9 -9
- package/components/floating-button-group/floating-button-group.module.css +7 -7
- package/components/gauge/gauge.js +1 -1
- package/components/gauge/gauge.module.css +2 -2
- package/components/hover-card/hover-card.js +2 -2
- package/components/hover-card/hover-card.module.css +2 -2
- package/components/image-compare/image-compare.module.css +4 -4
- package/components/inline-edit/inline-edit.module.css +12 -12
- package/components/json-viewer/json-viewer.module.css +12 -12
- package/components/line-chart/line-chart.js +1 -1
- package/components/line-chart/line-chart.module.css +8 -8
- package/components/mention-input/mention-input.js +4 -2
- package/components/mention-input/mention-input.module.css +8 -8
- package/components/metric-card/metric-card.module.css +1 -1
- package/components/money-input/money-input.js +6 -3
- package/components/money-input/money-input.module.css +10 -10
- package/components/morph-select/morph-select.js +7 -5
- package/components/morph-select/morph-select.module.css +13 -13
- package/components/multi-select/multi-select.js +4 -2
- package/components/multi-select/multi-select.module.css +6 -6
- package/components/nav-list/nav-list.d.ts +64 -0
- package/components/nav-list/nav-list.js +96 -0
- package/components/nav-list/nav-list.module.css +25 -0
- package/components/notification-center/notification-center.module.css +27 -27
- package/components/number-field/number-field.js +3 -3
- package/components/number-field/number-field.module.css +3 -3
- package/components/otp-input/otp-input.module.css +4 -4
- package/components/pagination/pagination.module.css +1 -1
- package/components/password-field/password-field.module.css +2 -2
- package/components/password-strength/password-strength.module.css +8 -8
- package/components/phone-input/phone-input.js +9 -7
- package/components/phone-input/phone-input.module.css +19 -19
- package/components/popover/popover.module.css +2 -2
- package/components/progress/progress.module.css +1 -1
- package/components/radio-cards/radio-cards.module.css +8 -8
- package/components/radio-group/radio-group.js +4 -1
- package/components/radio-group/radio-group.module.css +4 -3
- package/components/resizable-panels/resizable-panels.module.css +5 -5
- package/components/rich-text-editor/rich-text-editor.js +3 -3
- package/components/rich-text-editor/rich-text-editor.module.css +12 -12
- package/components/ridgeline/ridgeline.module.css +7 -7
- package/components/scroll-area/scroll-area.module.css +8 -8
- package/components/segmented-control/segmented-control.d.ts +3 -1
- package/components/segmented-control/segmented-control.js +5 -2
- package/components/segmented-control/segmented-control.module.css +9 -4
- package/components/select/select.module.css +5 -5
- package/components/shortcut-recorder/shortcut-recorder.js +5 -5
- package/components/shortcut-recorder/shortcut-recorder.module.css +16 -16
- package/components/signature-pad/signature-pad.module.css +11 -11
- package/components/slider/slider.module.css +4 -4
- package/components/slope-chart/slope-chart.module.css +7 -7
- package/components/slot-text/slot-text.js +2 -1
- package/components/sortable-data-table/sortable-data-table.module.css +4 -4
- package/components/sparkline/sparkline.js +1 -1
- package/components/split-button/split-button.module.css +6 -6
- package/components/stepper/stepper.module.css +3 -3
- package/components/streamgraph/streamgraph.module.css +7 -7
- package/components/swipe-actions/swipe-actions.js +2 -2
- package/components/swipe-actions/swipe-actions.module.css +5 -5
- package/components/switch/switch.js +2 -2
- package/components/switch/switch.module.css +1 -1
- package/components/tabs/tabs.d.ts +9 -2
- package/components/tabs/tabs.js +11 -5
- package/components/tabs/tabs.module.css +10 -3
- package/components/tag-input/tag-input.module.css +4 -4
- package/components/text-reveal/text-reveal.js +1 -1
- package/components/time-picker/time-picker.js +4 -2
- package/components/time-picker/time-picker.module.css +3 -3
- package/components/timeline/timeline.js +4 -4
- package/components/timeline/timeline.module.css +3 -3
- package/components/toast/toast.js +1 -1
- package/components/toast/toast.module.css +5 -5
- package/components/toast-stack/toast-stack.js +1 -1
- package/components/toast-stack/toast-stack.module.css +5 -5
- package/components/tooltip/tooltip.module.css +1 -1
- package/components/tree-view/tree-view.js +16 -12
- package/components/tree-view/tree-view.module.css +10 -8
- package/components/treemap/treemap.module.css +13 -13
- package/components/usage-meter/usage-meter.js +1 -1
- package/components/usage-meter/usage-meter.module.css +5 -5
- package/components/user-menu/user-menu.js +16 -14
- package/components/user-menu/user-menu.module.css +22 -22
- package/components/waffle-chart/waffle-chart.module.css +7 -7
- package/components.json +416 -1
- package/eslint.js +35 -3
- package/lib/fluid-hover.d.ts +83 -0
- package/lib/fluid-hover.js +184 -0
- package/lib/menu-highlight.d.ts +24 -11
- package/lib/menu-highlight.js +29 -28
- package/lib/motion-tokens.d.ts +76 -18
- package/lib/motion-tokens.js +45 -10
- package/package.json +1 -1
- package/styles.css +50 -7
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useSta
|
|
|
4
4
|
import { AnimatePresence, animate, motion, useIsPresent, useMotionValue, useReducedMotion, useTransform } from "motion/react";
|
|
5
5
|
import { CalendarDays, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
|
|
6
6
|
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
7
|
+
import { FluidHoverHighlight, useFluidHover } from "../../lib/fluid-hover.js";
|
|
7
8
|
import { useToday } from "../../lib/use-today.js";
|
|
8
9
|
import styles from "./date-range-picker.module.css";
|
|
9
10
|
const DAY = 864e5;
|
|
@@ -50,21 +51,21 @@ const GROW = physical(.48, .12), SHRINK = physical(.38, 0), STRETCH = physical(.
|
|
|
50
51
|
const TRIGGER_RADIUS = 18, PANEL_RADIUS = 26, WIDE_CELL = 36, WIDE_MIN = 712, EDGE = 8;
|
|
51
52
|
const roll = {
|
|
52
53
|
enter: (direction) => ({ opacity: 0, y: `${direction * .45}em`, filter: `blur(${blur.subtle}px)` }),
|
|
53
|
-
center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { duration: .
|
|
54
|
-
exit: (direction) => ({ opacity: 0, y: `${direction * -.45}em`, filter: `blur(${blur.subtle}px)`, transition: { duration: .
|
|
54
|
+
center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { duration: motionTokens.duration.exit, ease: enterEase } },
|
|
55
|
+
exit: (direction) => ({ opacity: 0, y: `${direction * -.45}em`, filter: `blur(${blur.subtle}px)`, transition: { duration: motionTokens.duration.fast, ease: standardEase } }),
|
|
55
56
|
};
|
|
56
|
-
const fade = { enter: { opacity: 0 }, center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { duration: .
|
|
57
|
+
const fade = { enter: { opacity: 0 }, center: { opacity: 1, y: "0em", filter: "blur(0px)", transition: { duration: motionTokens.duration.instant } }, exit: { opacity: 0, transition: { duration: motionTokens.duration.instant } } };
|
|
57
58
|
/** Panel content arrives just behind the growing surface and leaves ahead of it. */
|
|
58
59
|
const faceIn = {
|
|
59
60
|
hidden: { opacity: 0, y: -4, filter: `blur(${blur.soft}px)` },
|
|
60
|
-
shown: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: .
|
|
61
|
-
gone: { opacity: 0, y: -2, filter: `blur(${blur.soft}px)`, transition: { duration: .
|
|
61
|
+
shown: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: motionTokens.duration.exit, ease: enterEase, delay: motionTokens.duration.instant } },
|
|
62
|
+
gone: { opacity: 0, y: -2, filter: `blur(${blur.soft}px)`, transition: { duration: motionTokens.duration.instant, ease: standardEase } },
|
|
62
63
|
};
|
|
63
|
-
const faceFade = { hidden: { opacity: 0 }, shown: { opacity: 1, transition: { duration: .
|
|
64
|
+
const faceFade = { hidden: { opacity: 0 }, shown: { opacity: 1, transition: { duration: motionTokens.duration.instant } }, gone: { opacity: 0, transition: { duration: motionTokens.duration.instant } } };
|
|
64
65
|
const slide = {
|
|
65
66
|
enter: (direction) => ({ opacity: 0, x: direction * 48, filter: `blur(${blur.soft}px)` }),
|
|
66
|
-
center: { opacity: 1, x: 0, filter: "blur(0px)", transition: { x: SLIDE, opacity: { duration: .
|
|
67
|
-
exit: (direction) => ({ opacity: 0, x: direction * -36, filter: `blur(${blur.soft}px)`, transition: { x: SLIDE, opacity: { duration: .
|
|
67
|
+
center: { opacity: 1, x: 0, filter: "blur(0px)", transition: { x: SLIDE, opacity: { duration: motionTokens.duration.exit, ease: enterEase }, filter: { duration: motionTokens.duration.exit, ease: enterEase } } },
|
|
68
|
+
exit: (direction) => ({ opacity: 0, x: direction * -36, filter: `blur(${blur.soft}px)`, transition: { x: SLIDE, opacity: { duration: motionTokens.duration.fast, ease: standardEase }, filter: { duration: motionTokens.duration.fast, ease: standardEase } } }),
|
|
68
69
|
};
|
|
69
70
|
/** Words roll one by one: a later date rises from below, an earlier one drops from above; words that stay the same hold still. */
|
|
70
71
|
function Rolling({ text, direction, reduced }) {
|
|
@@ -216,6 +217,8 @@ export function DateRangePicker({ value, defaultValue = null, onChange, label =
|
|
|
216
217
|
const triggerRef = useRef(null);
|
|
217
218
|
const panelRef = useRef(null);
|
|
218
219
|
const railRef = useRef(null);
|
|
220
|
+
// Fluid hover on the presets: the nearest one is lit, gaps and padding included, and a click there picks it. Focus has its own fill.
|
|
221
|
+
const presetHover = useFluidHover(railRef, { items: "[data-preset]", axis: "xy", followFocus: false });
|
|
219
222
|
const pendingFocus = useRef(null);
|
|
220
223
|
/* ---------- One surface: the trigger's box springs to the panel's box and back ---------- */
|
|
221
224
|
// Before the first measurement the surface simply fills the root and the root wraps the trigger, so server markup already looks right.
|
|
@@ -524,10 +527,10 @@ export function DateRangePicker({ value, defaultValue = null, onChange, label =
|
|
|
524
527
|
const layoutId = reduced ? undefined : `${uid}-value`;
|
|
525
528
|
const cellSize = compact && panelWidth ? Math.max(32, Math.min(42, Math.floor((panelWidth - 24) / 7))) : WIDE_CELL;
|
|
526
529
|
const quiet = open ? (reduced ? { opacity: 0 } : { opacity: 0, filter: `blur(${blur.subtle}px)` }) : { opacity: 1, filter: "blur(0px)" };
|
|
527
|
-
const quietTransition = open ? { duration: .
|
|
530
|
+
const quietTransition = open ? { duration: motionTokens.duration.instant, ease: standardEase } : { duration: motionTokens.duration.exit, ease: enterEase, delay: reduced ? 0 : .1 };
|
|
528
531
|
return _jsxs(motion.div, { ref: rootRef, className: [styles.root, className].filter(Boolean).join(" "), style: { width: rootWidth }, "data-open": open || undefined, onKeyDown: onRootKey, onBlur: onRootBlur, children: [_jsxs("button", { ref: triggerRef, type: "button", className: styles.trigger, "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? `${uid}-panel` : undefined, "aria-label": `${label}: ${committedText}`, disabled: !today, inert: open || undefined, onClick: openPanel, children: [_jsx(motion.span, { className: styles.triggerIcon, initial: false, animate: quiet, transition: quietTransition, children: _jsx(CalendarDays, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), open || !layoutId
|
|
529
532
|
? _jsx(motion.span, { className: styles.value, "data-empty": !committed || undefined, initial: false, animate: open ? { opacity: 0 } : { opacity: 1 }, transition: quietTransition, children: _jsx(Rolling, { text: committedText, direction: committedDirection, reduced: reduced }) }, "resting")
|
|
530
|
-
: _jsx(motion.span, { layoutId: layoutId, layout: "position", className: styles.value, "data-empty": !committed || undefined, transition: SHRINK, children: _jsx(Rolling, { text: committedText, direction: committedDirection, reduced: reduced }) }, "shared"), _jsx(motion.span, { className: styles.chevron, initial: false, animate: quiet, transition: quietTransition, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }), _jsx(motion.div, { className: styles.surface, style: { width, height, borderRadius: radius, x }, children: _jsx(AnimatePresence, { children: open && today && _jsxs(motion.div, { ref: panelRef, id: `${uid}-panel`, className: styles.panel, role: "dialog", "aria-label": label, "data-compact": compact || undefined, style: { "--cell": `${cellSize}px`, width: compact && panelWidth ? panelWidth : undefined }, exit: { opacity: 1, transition: { duration: .
|
|
533
|
+
: _jsx(motion.span, { layoutId: layoutId, layout: "position", className: styles.value, "data-empty": !committed || undefined, transition: SHRINK, children: _jsx(Rolling, { text: committedText, direction: committedDirection, reduced: reduced }) }, "shared"), _jsx(motion.span, { className: styles.chevron, initial: false, animate: quiet, transition: quietTransition, children: _jsx(ChevronDown, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }), _jsx(motion.div, { className: styles.surface, style: { width, height, borderRadius: radius, x }, children: _jsx(AnimatePresence, { children: open && today && _jsxs(motion.div, { ref: panelRef, id: `${uid}-panel`, className: styles.panel, role: "dialog", "aria-label": label, "data-compact": compact || undefined, style: { "--cell": `${cellSize}px`, width: compact && panelWidth ? panelWidth : undefined }, exit: { opacity: 1, transition: { duration: motionTokens.duration.fast } }, children: [_jsxs(motion.div, { className: styles.body, variants: reduced ? faceFade : faceIn, initial: "hidden", animate: "shown", exit: "gone", children: [_jsxs("div", { ref: railRef, className: styles.rail, role: "group", "aria-label": "Presets", onKeyDown: onRailKey, ...presetHover.handlers, children: [_jsx(FluidHoverHighlight, { hover: presetHover, className: styles.railHover }), _jsx(motion.span, { className: styles.railHighlight, style: { x: gx, y: gy, width: gw, height: gh, opacity: go }, "aria-hidden": "true" }), presets.map((preset, index) => _jsx("button", { type: "button", className: styles.preset, "data-preset": index, "aria-pressed": index === activePreset, onClick: () => choosePreset(preset), children: preset.label }, preset.label))] }), _jsxs("div", { className: styles.calendars, onPointerLeave: () => { if (anchor)
|
|
531
534
|
setHover(null); }, children: [_jsx("button", { type: "button", className: styles.navButton, "data-side": "prev", "aria-label": "Previous month", onClick: () => goTo(addMonths(view, -1)), disabled: Boolean(minDate && monthDiff(view, minDate) <= 0), children: _jsx(ChevronLeft, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: styles.navButton, "data-side": "next", "aria-label": "Next month", onClick: () => goTo(addMonths(view, 1)), disabled: Boolean(maxDate && monthDiff(addMonths(view, count - 1), maxDate) >= 0), children: _jsx(ChevronRight, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), _jsx("div", { className: styles.viewport, children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", custom: direction, children: _jsx(Months, { direction: direction, reduced: reduced, children: visible.map(month => _jsx(Month, { month: month, range: shown, tabbable: tabbable, today: today, minDate: minDate, maxDate: maxDate, weekStartsOn: weekStartsOn, reduced: reduced, formatters: formatters, idBase: uid, onPick: pick, onHover: date => { if (anchor)
|
|
532
535
|
setHover(date); }, onKey: onDayKey, onFocusDay: date => setFocusKey(keyOf(date)) }, keyOf(month))) }, `${keyOf(view)}-${count}`) }) })] })] }), _jsxs("div", { className: styles.footer, children: [_jsxs("div", { className: styles.summary, children: [_jsx(motion.span, { layoutId: layoutId, layout: layoutId ? "position" : undefined, className: styles.summaryValue, transition: GROW, variants: layoutId ? undefined : faceFade, initial: layoutId ? undefined : "hidden", animate: layoutId ? undefined : "shown", exit: layoutId ? undefined : "gone", children: _jsx(Rolling, { text: shownText, direction: shownDirection, reduced: reduced }) }), _jsx(motion.span, { className: styles.summaryCount, variants: reduced ? faceFade : faceIn, initial: "hidden", animate: "shown", exit: "gone", children: countText && _jsx(Rolling, { text: anchor && sameDay(anchor, hover) ? "Pick an end date" : countText, direction: shownDirection, reduced: reduced }) })] }), _jsxs(motion.div, { className: styles.actions, variants: reduced ? faceFade : faceIn, initial: "hidden", animate: "shown", exit: "gone", children: [_jsx("button", { type: "button", className: styles.ghost, onClick: () => close(true), children: "Cancel" }), _jsx("button", { type: "button", className: styles.primary, onClick: apply, disabled: !shown, children: "Apply" })] })] }), _jsx("p", { className: styles.srOnly, "aria-live": "polite", children: status })] }, "panel") }) })] });
|
|
533
536
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
line-height: var(--leading-body);
|
|
12
12
|
}
|
|
13
13
|
.root { z-index: 0; transition: z-index 0s linear .5s; }
|
|
14
|
-
.root[data-open] { z-index:
|
|
14
|
+
.root[data-open] { z-index: var(--layer-popover); transition-delay: 0s; }
|
|
15
15
|
.root:not([data-open]) .surface { z-index: 0; }
|
|
16
16
|
.root *, .root *::before, .root *::after { box-sizing: border-box; }
|
|
17
17
|
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
width: max-content;
|
|
39
39
|
height: var(--control-height-md);
|
|
40
40
|
align-items: center;
|
|
41
|
-
gap:
|
|
42
|
-
padding: 0
|
|
41
|
+
gap: var(--space-group);
|
|
42
|
+
padding: 0 var(--space-group) 0 var(--space-inset);
|
|
43
43
|
border: 0;
|
|
44
44
|
background: transparent;
|
|
45
45
|
color: var(--foreground);
|
|
@@ -62,10 +62,12 @@
|
|
|
62
62
|
.wordInner { display: inline-block; }
|
|
63
63
|
|
|
64
64
|
/* Panel */
|
|
65
|
-
.panel { position: absolute; top: 0; left: 0; width: max-content; padding:
|
|
66
|
-
.body { display: flex; gap:
|
|
65
|
+
.panel { position: absolute; top: 0; left: 0; width: max-content; padding: var(--space-group); --cell: 36px; }
|
|
66
|
+
.body { display: flex; gap: var(--space-group); }
|
|
67
67
|
|
|
68
|
-
.rail { position: relative; display: flex; width: 9.25rem; flex: none; flex-direction: column; gap:
|
|
68
|
+
.rail { position: relative; display: flex; width: 9.25rem; flex: none; flex-direction: column; gap: var(--space-hair); padding-right: var(--space-group); border-right: 1px solid var(--border-subtle); }
|
|
69
|
+
/* The hover fill glides between presets, under the chosen preset's highlight. */
|
|
70
|
+
.railHover { border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: color-mix(in oklab, var(--foreground) 4%, transparent); }
|
|
69
71
|
.railHighlight { position: absolute; top: 0; left: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: color-mix(in oklab, var(--foreground) 6.5%, transparent); pointer-events: none; }
|
|
70
72
|
.preset {
|
|
71
73
|
position: relative;
|
|
@@ -73,7 +75,7 @@
|
|
|
73
75
|
height: 32px;
|
|
74
76
|
flex: none;
|
|
75
77
|
align-items: center;
|
|
76
|
-
padding: 0
|
|
78
|
+
padding: 0 var(--space-group);
|
|
77
79
|
border: 0;
|
|
78
80
|
border-radius: var(--radius-control); corner-shape: var(--corner-shape);
|
|
79
81
|
background: transparent;
|
|
@@ -87,7 +89,7 @@
|
|
|
87
89
|
}
|
|
88
90
|
.preset[aria-pressed="true"] { color: var(--foreground); font-weight: 500; }
|
|
89
91
|
|
|
90
|
-
.calendars { position: relative; padding: 0
|
|
92
|
+
.calendars { position: relative; padding: 0 var(--space-hair); }
|
|
91
93
|
.navButton {
|
|
92
94
|
position: absolute;
|
|
93
95
|
z-index: 2;
|
|
@@ -105,14 +107,14 @@
|
|
|
105
107
|
-webkit-tap-highlight-color: transparent;
|
|
106
108
|
transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
|
|
107
109
|
}
|
|
108
|
-
.navButton[data-side="prev"] { left:
|
|
109
|
-
.navButton[data-side="next"] { right:
|
|
110
|
+
.navButton[data-side="prev"] { left: var(--space-hair); }
|
|
111
|
+
.navButton[data-side="next"] { right: var(--space-hair); }
|
|
110
112
|
.navButton:active:not(:disabled) { transform: scale(.92); }
|
|
111
113
|
.navButton:disabled { opacity: .35; cursor: default; }
|
|
112
114
|
|
|
113
115
|
/* Months slide horizontally inside a clipped window that keeps a little room for the blur. */
|
|
114
116
|
.viewport { position: relative; overflow: clip; overflow-clip-margin: 2px; }
|
|
115
|
-
.months { display: flex; gap:
|
|
117
|
+
.months { display: flex; gap: var(--space-section); }
|
|
116
118
|
.month { width: calc(var(--cell) * 7); }
|
|
117
119
|
.monthTitle { height: 32px; margin: 0; color: var(--foreground); font-weight: 500; line-height: 32px; text-align: center; white-space: nowrap; }
|
|
118
120
|
.grid { display: grid; }
|
|
@@ -125,7 +127,7 @@
|
|
|
125
127
|
.layer { position: absolute; inset: 0; pointer-events: none; }
|
|
126
128
|
.bar { position: absolute; height: calc(var(--cell) - 6px); border-radius: var(--radius-full); corner-shape: round; background: var(--accent-subtle); }
|
|
127
129
|
.thumb { position: absolute; top: 0; left: 0; width: var(--cell); height: var(--cell); }
|
|
128
|
-
.thumb::before { content: ""; position: absolute; inset:
|
|
130
|
+
.thumb::before { content: ""; position: absolute; inset: var(--space-tight); border-radius: 50%; corner-shape: round; background: var(--accent); }
|
|
129
131
|
|
|
130
132
|
.day {
|
|
131
133
|
position: relative;
|
|
@@ -148,23 +150,23 @@
|
|
|
148
150
|
.day > span { position: relative; display: grid; width: calc(100% - 6px); height: calc(100% - 6px); place-items: center; border-radius: 50%; corner-shape: round; transition: background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
|
|
149
151
|
.day:active:not(:disabled) > span { transform: scale(.9); }
|
|
150
152
|
.day[data-edge] { color: var(--accent-foreground); font-weight: 500; transition-delay: 40ms; }
|
|
151
|
-
.day[data-today]::after { content: ""; position: absolute; bottom:
|
|
153
|
+
.day[data-today]::after { content: ""; position: absolute; bottom: var(--space-tight); left: 50%; width: 4px; height: 4px; margin-left: calc(var(--space-hair) * -1); border-radius: 50%; corner-shape: round; background: var(--accent); }
|
|
152
154
|
.day[data-today][data-edge]::after { background: var(--accent-foreground); }
|
|
153
155
|
.day:disabled { color: var(--text-muted); cursor: default; opacity: .5; }
|
|
154
156
|
.day:focus-visible:not([data-edge]) > span { background: color-mix(in oklab, var(--foreground) 7%, transparent); }
|
|
155
157
|
|
|
156
158
|
/* Footer: the formatted range and the two decisions. */
|
|
157
|
-
.footer { display: flex; align-items: center; justify-content: space-between; gap:
|
|
159
|
+
.footer { display: flex; align-items: center; justify-content: space-between; gap: var(--space-group); margin-top: var(--space-group); padding: var(--space-group) var(--space-hair) 0 var(--space-group); border-top: 1px solid var(--border-subtle); }
|
|
158
160
|
.summary { display: grid; min-width: 0; gap: 1px; }
|
|
159
161
|
.summaryValue { display: inline-flex; width: max-content; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
160
162
|
.summaryCount { display: inline-flex; min-height: 1.4em; color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
|
|
161
|
-
.actions { display: flex; flex: none; align-items: center; gap:
|
|
163
|
+
.actions { display: flex; flex: none; align-items: center; gap: var(--space-related); }
|
|
162
164
|
.ghost, .primary {
|
|
163
165
|
display: inline-flex;
|
|
164
166
|
height: var(--control-height-sm);
|
|
165
167
|
align-items: center;
|
|
166
168
|
justify-content: center;
|
|
167
|
-
padding: 0
|
|
169
|
+
padding: 0 var(--space-inset);
|
|
168
170
|
border: 0;
|
|
169
171
|
border-radius: var(--radius-control); corner-shape: var(--corner-shape);
|
|
170
172
|
cursor: pointer;
|
|
@@ -184,13 +186,13 @@
|
|
|
184
186
|
.srOnly { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
185
187
|
|
|
186
188
|
/* Compact: one month under a scrolling row of presets. */
|
|
187
|
-
.panel[data-compact] { padding:
|
|
188
|
-
.panel[data-compact] .body { flex-direction: column; gap:
|
|
189
|
-
.panel[data-compact] .rail { width: auto; flex-direction: row; gap:
|
|
189
|
+
.panel[data-compact] { padding: var(--space-related); }
|
|
190
|
+
.panel[data-compact] .body { flex-direction: column; gap: var(--space-related); }
|
|
191
|
+
.panel[data-compact] .rail { width: auto; flex-direction: row; gap: var(--space-hair); margin: 0 calc(var(--space-related) * -1); padding: 0 var(--space-related) var(--space-related); overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--border-subtle); overscroll-behavior-x: contain; scrollbar-width: none; }
|
|
190
192
|
.panel[data-compact] .rail::-webkit-scrollbar { display: none; }
|
|
191
|
-
.panel[data-compact] .preset { padding: 0
|
|
193
|
+
.panel[data-compact] .preset { padding: 0 var(--space-group); }
|
|
192
194
|
.panel[data-compact] .calendars { align-self: center; }
|
|
193
|
-
.panel[data-compact] .footer { flex-wrap: wrap; margin-top:
|
|
195
|
+
.panel[data-compact] .footer { flex-wrap: wrap; margin-top: var(--space-related); padding-left: var(--space-group); }
|
|
194
196
|
.panel[data-compact] .actions { margin-left: auto; }
|
|
195
197
|
.panel[data-compact] .summary { flex: 1 1 8rem; }
|
|
196
198
|
|
|
@@ -198,7 +200,6 @@
|
|
|
198
200
|
.root:not([data-open]):has(> .trigger:hover:not(:disabled)) .surface { background: color-mix(in oklab, var(--foreground) 3%, var(--surface-raised)); }
|
|
199
201
|
.root:not([data-open]):has(> .trigger:hover:not(:disabled)) .surface::after { border-color: var(--border-strong); }
|
|
200
202
|
.preset:hover { color: var(--foreground); }
|
|
201
|
-
.preset:not([aria-pressed="true"]):hover { background: color-mix(in oklab, var(--foreground) 4%, transparent); }
|
|
202
203
|
.navButton:hover:not(:disabled) { background: color-mix(in oklab, var(--foreground) 6.5%, transparent); color: var(--foreground); }
|
|
203
204
|
.day:hover:not(:disabled):not([data-edge]) > span { background: color-mix(in oklab, var(--foreground) 7%, transparent); }
|
|
204
205
|
.ghost:hover { background: var(--surface-muted); color: var(--foreground); }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
.overlay { position: fixed; inset: 0; z-index:
|
|
1
|
+
.overlay { position: fixed; inset: 0; z-index: var(--layer-overlay); background: var(--scrim); backdrop-filter: blur(7px); }
|
|
2
2
|
/* Centered with auto margins (like a native modal dialog) so transform stays free for the entrance spring. */
|
|
3
|
-
.content { position: fixed; z-index:
|
|
3
|
+
.content { position: fixed; z-index: var(--layer-dialog); inset: 0; width: min(calc(100vw - var(--space-block)), 440px); height: fit-content; max-height: calc(100vh - var(--space-block)); max-height: calc(100dvh - var(--space-block)); margin: auto; overflow: auto; border: 1px solid var(--border); border-radius: var(--radius-overlay); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); }
|
|
4
4
|
/* Motion animates both layers. Keyframes only run when DialogContent sits under a bare Radix root. */
|
|
5
5
|
.keyframes.overlay[data-state="open"] { animation: overlay-in var(--duration-standard) var(--ease-enter) both; }
|
|
6
6
|
.keyframes.overlay[data-state="closed"] { animation: overlay-out var(--duration-fast) var(--ease-standard) both; }
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
.svg { display: block; width: 100%; height: 100%; overflow: visible; cursor: default; }
|
|
11
11
|
.svg[data-hovering] { cursor: pointer; }
|
|
12
12
|
/* The track shows only before the first sweep and when there is nothing to draw, so the gaps between segments stay the page colour. */
|
|
13
|
-
.track { fill: none; stroke: var(--surface-muted); transition: opacity var(--duration-
|
|
13
|
+
.track { fill: none; stroke: var(--surface-muted); transition: opacity var(--duration-expressive, .48s) var(--ease-standard); opacity: 0; }
|
|
14
14
|
.track[data-idle] { opacity: 1; }
|
|
15
15
|
/* Shapes are written every frame from script; only colour and dimming ease here, on the compositor. */
|
|
16
16
|
.segment { fill: var(--slice); shape-rendering: geometricPrecision; transition: fill var(--duration-standard) var(--ease-standard), opacity var(--duration-standard) var(--ease-standard); }
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
/* The centre reads the active segment, or the total at rest. Old and new readouts share one grid cell, so a swap never moves or resizes anything. */
|
|
20
20
|
.center { position: absolute; top: 50%; left: 50%; display: grid; width: calc(var(--inner) - 16px); transform: translate(-50%, -50%); pointer-events: none; }
|
|
21
|
-
.readout { grid-area: 1 / 1; display: grid; min-width: 0; justify-items: center; gap:
|
|
21
|
+
.readout { grid-area: 1 / 1; display: grid; min-width: 0; justify-items: center; gap: var(--space-hair); text-align: center; }
|
|
22
22
|
.centerLabel, .centerMeta { display: block; width: 100%; min-height: calc(1em * var(--leading-body)); overflow: hidden; font-size: var(--text-xs); line-height: var(--leading-body); text-overflow: ellipsis; white-space: nowrap; }
|
|
23
23
|
.centerLabel { color: var(--text-secondary); }
|
|
24
24
|
.centerMeta { color: var(--text-muted); font-variant-numeric: tabular-nums; }
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
|
|
27
27
|
/* Legend rows mirror the segments: hovering either lights both. */
|
|
28
28
|
/* Rows carry their own 2px spacing so a row closing its height takes its gap with it; the list's negative margin drops the last one. */
|
|
29
|
-
.legend { display: grid; margin: 0 0 -
|
|
29
|
+
.legend { display: grid; margin: 0 0 calc(var(--space-hair) * -1); padding: 0; list-style: none; }
|
|
30
30
|
.item { overflow: hidden; }
|
|
31
|
-
.item > .row { margin-bottom:
|
|
32
|
-
.row { display: grid; width: 100%; min-height: 40px; grid-template-columns: 10px minmax(0, 1fr) auto 3.25em; align-items: center; column-gap:
|
|
31
|
+
.item > .row { margin-bottom: var(--space-hair); }
|
|
32
|
+
.row { display: grid; width: 100%; min-height: 40px; grid-template-columns: 10px minmax(0, 1fr) auto 3.25em; align-items: center; column-gap: var(--space-group); padding: var(--space-related) var(--space-group); border: 0; border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: transparent; color: var(--foreground); cursor: pointer; font: inherit; font-size: var(--text-sm); line-height: var(--leading-body); text-align: left; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
|
|
33
33
|
.row:active { transform: scale(.985); }
|
|
34
34
|
.row[data-active], .row[data-pinned] { background: color-mix(in oklab, var(--foreground) 5%, transparent); }
|
|
35
35
|
.dot { width: 10px; height: 10px; border-radius: 50%; corner-shape: round; background: var(--slice); box-shadow: inset 0 0 0 1.5px var(--slice); transition: background-color var(--duration-standard) var(--ease-standard), box-shadow var(--duration-standard) var(--ease-standard); }
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
.overlay {
|
|
2
2
|
position: fixed;
|
|
3
3
|
inset: 0;
|
|
4
|
-
z-index:
|
|
5
|
-
background:
|
|
4
|
+
z-index: var(--layer-overlay);
|
|
5
|
+
background: var(--scrim);
|
|
6
6
|
backdrop-filter: blur(4px);
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
--ease-drawer: cubic-bezier(.32,.72,0,1);
|
|
13
13
|
--drawer-from: translateX(100%);
|
|
14
14
|
position: fixed;
|
|
15
|
-
z-index:
|
|
15
|
+
z-index: var(--layer-dialog);
|
|
16
16
|
display: flex;
|
|
17
17
|
flex-direction: column;
|
|
18
18
|
width: var(--drawer-size);
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
.content[data-side="left"] { inset: 0 auto 0 0; border-width: 1px 1px 1px 0; border-radius: 0 var(--radius-container) var(--radius-container) 0; corner-shape: var(--corner-shape); }
|
|
33
33
|
.content[data-side="top"] { inset: 0 0 auto; width: 100%; max-width: none; max-height: min(32rem, 100dvh); border-width: 0 0 1px; border-radius: 0 0 var(--radius-container) var(--radius-container); corner-shape: var(--corner-shape); }
|
|
34
34
|
.content[data-side="bottom"] { inset: auto 0 0; width: 100%; max-width: none; max-height: min(32rem, 100dvh); border-width: 1px 0 0; border-radius: var(--radius-container) var(--radius-container) 0 0; corner-shape: var(--corner-shape); }
|
|
35
|
-
.content[data-side="bottom"]::before { position: absolute; top:
|
|
35
|
+
.content[data-side="bottom"]::before { position: absolute; top: var(--space-related); left: 50%; width: 2.25rem; height: 4px; border-radius: var(--radius-full); corner-shape: round; background: var(--border-strong); content: ""; opacity: .75; transform: translateX(-50%); }
|
|
36
36
|
|
|
37
37
|
/* Motion springs the panel from its edge. Keyframes only run when DrawerContent sits under a bare Radix root. */
|
|
38
38
|
.content[data-side="left"] { --drawer-from: translateX(-100%); }
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
.content:focus { outline: none; }
|
|
46
46
|
/* Inside a container the layers anchor to that element instead of the viewport. */
|
|
47
47
|
.overlay[data-contained], .content[data-contained] { position: absolute; }
|
|
48
|
-
.overlay[data-contained] { z-index: 1; background:
|
|
48
|
+
.overlay[data-contained] { z-index: 1; background: var(--scrim-soft); backdrop-filter: none; }
|
|
49
49
|
.content[data-contained] { --drawer-size: min(22rem, calc(100% - var(--space-block))); z-index: 2; max-height: 100%; box-shadow: none; }
|
|
50
50
|
.content[data-contained][data-side="top"], .content[data-contained][data-side="bottom"] { max-height: min(32rem, calc(100% - var(--space-block))); }
|
|
51
51
|
/* The container already draws the outer edges, so only the inner edge keeps a border. */
|
|
@@ -89,9 +89,9 @@
|
|
|
89
89
|
|
|
90
90
|
@media (prefers-reduced-motion: reduce) {
|
|
91
91
|
.keyframes.overlay[data-state="open"] { animation: overlay-in var(--duration-instant) linear both; }
|
|
92
|
-
.keyframes.overlay[data-state="closed"] { animation: overlay-out
|
|
92
|
+
.keyframes.overlay[data-state="closed"] { animation: overlay-out var(--duration-fast) linear both; }
|
|
93
93
|
.keyframes.content[data-state="open"] { animation: drawer-fade-in var(--duration-instant) linear both; }
|
|
94
|
-
.keyframes.content[data-state="closed"] { animation: drawer-fade-out
|
|
94
|
+
.keyframes.content[data-state="closed"] { animation: drawer-fade-out var(--duration-fast) linear both; }
|
|
95
95
|
.close, .close:active { transition: none; }
|
|
96
96
|
.close:active { transform: none; }
|
|
97
97
|
}
|
|
@@ -1,16 +1,86 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import * as DropdownPrimitive from "@radix-ui/react-dropdown-menu";
|
|
3
|
+
/** The menu's state. Uncontrolled by default; pass `open` and `onOpenChange` to control it. */
|
|
4
|
+
export declare function DropdownMenuRoot(props: ComponentPropsWithoutRef<typeof DropdownPrimitive.Root>): import("react").JSX.Element;
|
|
5
|
+
export interface DropdownMenuTriggerProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.Trigger> {
|
|
6
|
+
/** Render your own trigger (an icon-only Button with an aria-label, an avatar and name) instead of the default one. */
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** What opens the menu. Without `asChild` it is the default outlined trigger with a chevron. */
|
|
10
|
+
export declare const DropdownMenuTrigger: import("react").ForwardRefExoticComponent<DropdownMenuTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
11
|
+
export interface DropdownMenuContentProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.Content> {
|
|
12
|
+
/** Which side of the trigger it opens on (default bottom). */
|
|
13
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
14
|
+
/** How it lines up with the trigger (default end). */
|
|
15
|
+
align?: "start" | "center" | "end";
|
|
16
|
+
}
|
|
17
|
+
/** The menu itself. One highlight glides between items for the pointer and jumps instantly for the keyboard. */
|
|
18
|
+
export declare const DropdownMenuContent: import("react").ForwardRefExoticComponent<DropdownMenuContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
19
|
+
export interface DropdownMenuItemProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.Item> {
|
|
20
|
+
/** A 16px icon before the label. */
|
|
21
|
+
icon?: ReactNode;
|
|
22
|
+
/** A keyboard shortcut hint after the label, such as "⌘D". Shown only; bind the keys yourself. */
|
|
23
|
+
shortcut?: string;
|
|
24
|
+
/** A destructive action (delete, remove): shown in the danger colour. */
|
|
25
|
+
destructive?: boolean;
|
|
26
|
+
/** Make your own element the item, such as a link: `<DropdownMenuItem asChild><a href="/q3">Q3 2026</a></DropdownMenuItem>`.
|
|
27
|
+
* Its children become the label, and Enter follows the link. */
|
|
28
|
+
asChild?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** One action, or with `asChild` one link. */
|
|
31
|
+
export declare const DropdownMenuItem: import("react").ForwardRefExoticComponent<DropdownMenuItemProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
32
|
+
export interface DropdownMenuCheckboxItemProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.CheckboxItem> {
|
|
33
|
+
/** A keyboard shortcut hint after the label. */
|
|
34
|
+
shortcut?: string;
|
|
35
|
+
}
|
|
36
|
+
/** An on/off setting, such as "Full width". A check shows in the icon column while it is on. */
|
|
37
|
+
export declare const DropdownMenuCheckboxItem: import("react").ForwardRefExoticComponent<DropdownMenuCheckboxItemProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
38
|
+
/** One choice out of several (the current space, a sort order). Wrap DropdownMenuRadioItems in it. */
|
|
39
|
+
export declare const DropdownMenuRadioGroup: import("react").ForwardRefExoticComponent<DropdownPrimitive.DropdownMenuRadioGroupProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
40
|
+
export interface DropdownMenuRadioItemProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.RadioItem> {
|
|
41
|
+
/** A 16px icon or avatar before the label; the check shows at the end while this is the current choice. */
|
|
42
|
+
icon?: ReactNode;
|
|
43
|
+
}
|
|
44
|
+
/** One of the choices in a DropdownMenuRadioGroup. The current choice shows a check at the end. */
|
|
45
|
+
export declare const DropdownMenuRadioItem: import("react").ForwardRefExoticComponent<DropdownMenuRadioItemProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
46
|
+
/** A section heading inside the menu, such as "Spaces". Not focusable. */
|
|
47
|
+
export declare const DropdownMenuLabel: import("react").ForwardRefExoticComponent<Omit<DropdownPrimitive.DropdownMenuLabelProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
48
|
+
/** A hairline between groups of items. */
|
|
49
|
+
export declare const DropdownMenuSeparator: import("react").ForwardRefExoticComponent<Omit<DropdownPrimitive.DropdownMenuSeparatorProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
50
|
+
/** Groups items for assistive technology, without visual change. */
|
|
51
|
+
export declare const DropdownMenuGroup: import("react").ForwardRefExoticComponent<DropdownPrimitive.DropdownMenuGroupProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
52
|
+
/** A nested menu: a DropdownMenuSubTrigger and its DropdownMenuSubContent. */
|
|
53
|
+
export declare const DropdownMenuSub: import("react").FC<DropdownPrimitive.DropdownMenuSubProps>;
|
|
54
|
+
export interface DropdownMenuSubTriggerProps extends ComponentPropsWithoutRef<typeof DropdownPrimitive.SubTrigger> {
|
|
55
|
+
/** A 16px icon before the label. */
|
|
56
|
+
icon?: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
/** The item that opens a submenu (on hover, click, or the right arrow key). */
|
|
59
|
+
export declare const DropdownMenuSubTrigger: import("react").ForwardRefExoticComponent<DropdownMenuSubTriggerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
60
|
+
/** The submenu's own menu, opening beside its trigger. */
|
|
61
|
+
export declare const DropdownMenuSubContent: import("react").ForwardRefExoticComponent<Omit<DropdownPrimitive.DropdownMenuSubContentProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
2
62
|
export interface DropdownItem {
|
|
63
|
+
/** The item's text. */
|
|
3
64
|
label: string;
|
|
65
|
+
/** Runs when the item is chosen. */
|
|
4
66
|
onSelect?: () => void;
|
|
67
|
+
/** Shown but not choosable. */
|
|
5
68
|
disabled?: boolean;
|
|
69
|
+
/** A 16px icon before the label. */
|
|
6
70
|
icon?: ReactNode;
|
|
71
|
+
/** A destructive action, shown in the danger colour. */
|
|
7
72
|
destructive?: boolean;
|
|
73
|
+
/** Draws a separator above this item. */
|
|
8
74
|
separatorBefore?: boolean;
|
|
9
75
|
}
|
|
10
76
|
export interface DropdownMenuProps {
|
|
77
|
+
/** The trigger's text; when it names the current choice, a new label rises in and the trigger springs to its width. */
|
|
11
78
|
label: string;
|
|
79
|
+
/** The actions, in order. */
|
|
12
80
|
items: DropdownItem[];
|
|
81
|
+
/** An icon before the trigger's text. */
|
|
13
82
|
icon?: ReactNode;
|
|
14
83
|
}
|
|
84
|
+
/** The shorthand: a labelled trigger and a flat list of actions, built from the parts above. */
|
|
15
85
|
export declare function DropdownMenu({ label, items, icon }: DropdownMenuProps): import("react").JSX.Element;
|
|
16
86
|
export default DropdownMenu;
|
|
@@ -1,12 +1,107 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* Dropdown menu: a list of actions behind a trigger.
|
|
5
|
+
*
|
|
6
|
+
* Two ways to use it:
|
|
7
|
+
*
|
|
8
|
+
* // Shorthand: a labelled trigger and a flat list.
|
|
9
|
+
* <DropdownMenu label="Manage silicon" items={[{ label: "Rename", onSelect: rename }, …]} />
|
|
10
|
+
*
|
|
11
|
+
* // Composable (experimental): any trigger, labels, checked and radio items, shortcuts and submenus.
|
|
12
|
+
* <DropdownMenuRoot>
|
|
13
|
+
* <DropdownMenuTrigger asChild><Button variant="ghost" aria-label="Page options"><Ellipsis /></Button></DropdownMenuTrigger>
|
|
14
|
+
* <DropdownMenuContent align="end">
|
|
15
|
+
* <DropdownMenuLabel>Page</DropdownMenuLabel>
|
|
16
|
+
* <DropdownMenuItem icon={<Copy />} shortcut="⌘D" onSelect={duplicate}>Duplicate</DropdownMenuItem>
|
|
17
|
+
* <DropdownMenuCheckboxItem checked={wide} onCheckedChange={setWide}>Full width</DropdownMenuCheckboxItem>
|
|
18
|
+
* <DropdownMenuSub>
|
|
19
|
+
* <DropdownMenuSubTrigger icon={<FolderInput />}>Move to</DropdownMenuSubTrigger>
|
|
20
|
+
* <DropdownMenuSubContent>…</DropdownMenuSubContent>
|
|
21
|
+
* </DropdownMenuSub>
|
|
22
|
+
* <DropdownMenuSeparator />
|
|
23
|
+
* <DropdownMenuItem destructive icon={<Trash2 />} onSelect={remove}>Delete</DropdownMenuItem>
|
|
24
|
+
* </DropdownMenuContent>
|
|
25
|
+
* </DropdownMenuRoot>
|
|
26
|
+
*
|
|
27
|
+
* Every part shares one look: the same surface, the gliding highlight, the entrance that grows from the trigger.
|
|
28
|
+
*/
|
|
29
|
+
import { Fragment, cloneElement, forwardRef, isValidElement, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
4
30
|
import * as DropdownPrimitive from "@radix-ui/react-dropdown-menu";
|
|
5
31
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
6
|
-
import { ChevronDown } from "lucide-react";
|
|
32
|
+
import { Check, ChevronDown, ChevronRight } from "lucide-react";
|
|
7
33
|
import { MenuHighlight, useMenuHighlight } from "../../lib/menu-highlight.js";
|
|
8
34
|
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
9
35
|
import styles from "./dropdown-menu.module.css";
|
|
36
|
+
const ITEMS = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]';
|
|
37
|
+
const cx = (...names) => names.filter(Boolean).join(" ");
|
|
38
|
+
/** A menu surface's own ref, also handed to the caller's ref. It numbers the items in order, so their entrance
|
|
39
|
+
* staggers whatever groups and parts they sit in. */
|
|
40
|
+
function useSurfaceRef(forwarded) {
|
|
41
|
+
const own = useRef(null);
|
|
42
|
+
useImperativeHandle(forwarded, () => own.current, []);
|
|
43
|
+
useLayoutEffect(() => {
|
|
44
|
+
own.current?.querySelectorAll(ITEMS).forEach((item, index) => item.style.setProperty("--i", String(index)));
|
|
45
|
+
});
|
|
46
|
+
return own;
|
|
47
|
+
}
|
|
48
|
+
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
49
|
+
/* Composable parts */
|
|
50
|
+
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
51
|
+
/** The menu's state. Uncontrolled by default; pass `open` and `onOpenChange` to control it. */
|
|
52
|
+
export function DropdownMenuRoot(props) {
|
|
53
|
+
return _jsx(DropdownPrimitive.Root, { ...props });
|
|
54
|
+
}
|
|
55
|
+
/** What opens the menu. Without `asChild` it is the default outlined trigger with a chevron. */
|
|
56
|
+
export const DropdownMenuTrigger = forwardRef(function DropdownMenuTrigger({ asChild, className, children, ...props }, ref) {
|
|
57
|
+
if (asChild)
|
|
58
|
+
return _jsx(DropdownPrimitive.Trigger, { ...props, ref: ref, asChild: true, children: children });
|
|
59
|
+
return (_jsxs(DropdownPrimitive.Trigger, { ...props, ref: ref, type: "button", className: cx(styles.trigger, className), children: [children, _jsx(ChevronDown, { className: styles.chevron, size: 15, strokeWidth: 1.8, "aria-hidden": "true" })] }));
|
|
60
|
+
});
|
|
61
|
+
/** The menu itself. One highlight glides between items for the pointer and jumps instantly for the keyboard. */
|
|
62
|
+
export const DropdownMenuContent = forwardRef(function DropdownMenuContent({ className, children, side = "bottom", align = "end", sideOffset = 6, collisionPadding = 12, ...props }, ref) {
|
|
63
|
+
const { highlight, contentProps } = useMenuHighlight(ITEMS);
|
|
64
|
+
const surface = useSurfaceRef(ref);
|
|
65
|
+
return (_jsx(DropdownPrimitive.Portal, { children: _jsxs(DropdownPrimitive.Content, { ...props, ...contentProps, ref: surface, side: side, align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, loop: true, className: cx(styles.menu, className), children: [_jsx(MenuHighlight, { state: highlight, className: styles.highlight }), children] }) }));
|
|
66
|
+
});
|
|
67
|
+
/** One action, or with `asChild` one link. */
|
|
68
|
+
export const DropdownMenuItem = forwardRef(function DropdownMenuItem({ icon, shortcut, destructive, asChild, className, children, ...props }, ref) {
|
|
69
|
+
const content = (label) => _jsxs(_Fragment, { children: [icon ? _jsx("span", { className: styles.icon, "aria-hidden": "true", children: icon }) : null, _jsx("span", { className: styles.itemLabel, children: label }), shortcut ? _jsx("kbd", { className: styles.shortcut, children: shortcut }) : null] });
|
|
70
|
+
const own = asChild && isValidElement(children) ? children : null;
|
|
71
|
+
return (_jsx(DropdownPrimitive.Item, { ...props, ref: ref, asChild: !!own, className: cx(styles.item, destructive && styles.destructive, className), "data-tone": destructive ? "danger" : undefined, children: own ? cloneElement(own, undefined, content(own.props.children)) : content(children) }));
|
|
72
|
+
});
|
|
73
|
+
/** An on/off setting, such as "Full width". A check shows in the icon column while it is on. */
|
|
74
|
+
export const DropdownMenuCheckboxItem = forwardRef(function DropdownMenuCheckboxItem({ shortcut, className, children, ...props }, ref) {
|
|
75
|
+
return (_jsxs(DropdownPrimitive.CheckboxItem, { ...props, ref: ref, className: cx(styles.item, className), children: [_jsx("span", { className: styles.icon, "aria-hidden": "true", children: _jsx(DropdownPrimitive.ItemIndicator, { children: _jsx(Check, { size: 16, strokeWidth: 2 }) }) }), _jsx("span", { className: styles.itemLabel, children: children }), shortcut ? _jsx("kbd", { className: styles.shortcut, children: shortcut }) : null] }));
|
|
76
|
+
});
|
|
77
|
+
/** One choice out of several (the current space, a sort order). Wrap DropdownMenuRadioItems in it. */
|
|
78
|
+
export const DropdownMenuRadioGroup = DropdownPrimitive.RadioGroup;
|
|
79
|
+
/** One of the choices in a DropdownMenuRadioGroup. The current choice shows a check at the end. */
|
|
80
|
+
export const DropdownMenuRadioItem = forwardRef(function DropdownMenuRadioItem({ icon, className, children, ...props }, ref) {
|
|
81
|
+
return (_jsxs(DropdownPrimitive.RadioItem, { ...props, ref: ref, className: cx(styles.item, className), children: [icon ? _jsx("span", { className: styles.icon, "aria-hidden": "true", children: icon }) : null, _jsx("span", { className: styles.itemLabel, children: children }), _jsx("span", { className: styles.indicator, "aria-hidden": "true", children: _jsx(DropdownPrimitive.ItemIndicator, { children: _jsx(Check, { size: 16, strokeWidth: 2 }) }) })] }));
|
|
82
|
+
});
|
|
83
|
+
/** A section heading inside the menu, such as "Spaces". Not focusable. */
|
|
84
|
+
export const DropdownMenuLabel = forwardRef(function DropdownMenuLabel({ className, ...props }, ref) {
|
|
85
|
+
return _jsx(DropdownPrimitive.Label, { ...props, ref: ref, className: cx(styles.sectionLabel, className) });
|
|
86
|
+
});
|
|
87
|
+
/** A hairline between groups of items. */
|
|
88
|
+
export const DropdownMenuSeparator = forwardRef(function DropdownMenuSeparator({ className, ...props }, ref) {
|
|
89
|
+
return _jsx(DropdownPrimitive.Separator, { ...props, ref: ref, className: cx(styles.separator, className) });
|
|
90
|
+
});
|
|
91
|
+
/** Groups items for assistive technology, without visual change. */
|
|
92
|
+
export const DropdownMenuGroup = DropdownPrimitive.Group;
|
|
93
|
+
/** A nested menu: a DropdownMenuSubTrigger and its DropdownMenuSubContent. */
|
|
94
|
+
export const DropdownMenuSub = DropdownPrimitive.Sub;
|
|
95
|
+
/** The item that opens a submenu (on hover, click, or the right arrow key). */
|
|
96
|
+
export const DropdownMenuSubTrigger = forwardRef(function DropdownMenuSubTrigger({ icon, className, children, ...props }, ref) {
|
|
97
|
+
return (_jsxs(DropdownPrimitive.SubTrigger, { ...props, ref: ref, className: cx(styles.item, styles.subTrigger, className), children: [icon ? _jsx("span", { className: styles.icon, "aria-hidden": "true", children: icon }) : null, _jsx("span", { className: styles.itemLabel, children: children }), _jsx(ChevronRight, { className: styles.subChevron, size: 15, strokeWidth: 1.8, "aria-hidden": "true" })] }));
|
|
98
|
+
});
|
|
99
|
+
/** The submenu's own menu, opening beside its trigger. */
|
|
100
|
+
export const DropdownMenuSubContent = forwardRef(function DropdownMenuSubContent({ className, children, sideOffset = 6, collisionPadding = 12, ...props }, ref) {
|
|
101
|
+
const { highlight, contentProps } = useMenuHighlight(ITEMS);
|
|
102
|
+
const surface = useSurfaceRef(ref);
|
|
103
|
+
return (_jsx(DropdownPrimitive.Portal, { children: _jsxs(DropdownPrimitive.SubContent, { ...props, ...contentProps, ref: surface, sideOffset: sideOffset, collisionPadding: collisionPadding, loop: true, className: cx(styles.menu, className), children: [_jsx(MenuHighlight, { state: highlight, className: styles.highlight }), children] }) }));
|
|
104
|
+
});
|
|
10
105
|
/** A new trigger label rises in while the old one leaves, and the trigger width springs to the measured text instead of snapping. */
|
|
11
106
|
function TriggerLabel({ text }) {
|
|
12
107
|
const reduced = useReducedMotion();
|
|
@@ -31,9 +126,8 @@ function TriggerLabel({ text }) {
|
|
|
31
126
|
}, []);
|
|
32
127
|
return _jsxs(motion.span, { className: styles.label, initial: false, animate: { width: size.width }, transition: size.animate && !reduced ? motionTokens.spring.morph : { duration: 0 }, children: [_jsx("span", { ref: measure, className: styles.labelMeasure, "aria-hidden": "true", children: text }), _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(motion.span, { className: styles.labelText, initial: reduced ? false : { opacity: 0, y: "0.3em", filter: `blur(${motionTokens.blur.soft}px)` }, animate: { opacity: 1, y: 0, filter: "blur(0px)" }, exit: reduced ? { opacity: 0, transition: { duration: 0 } } : { opacity: 0, y: "-0.3em", filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] } }, transition: { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter] }, children: text }, text) })] });
|
|
33
128
|
}
|
|
129
|
+
/** The shorthand: a labelled trigger and a flat list of actions, built from the parts above. */
|
|
34
130
|
export function DropdownMenu({ label, items, icon }) {
|
|
35
|
-
|
|
36
|
-
return _jsxs(DropdownPrimitive.Root, { onOpenChange: open => { if (open)
|
|
37
|
-
reset(); }, children: [_jsxs(DropdownPrimitive.Trigger, { className: styles.trigger, type: "button", children: [icon && _jsx("span", { className: styles.triggerIcon, "aria-hidden": "true", children: icon }), _jsx(TriggerLabel, { text: label }), _jsx(ChevronDown, { className: styles.chevron, size: 15, strokeWidth: 1.8, "aria-hidden": "true" })] }), _jsx(DropdownPrimitive.Portal, { children: _jsxs(DropdownPrimitive.Content, { className: styles.menu, sideOffset: 6, align: "end", collisionPadding: 12, loop: true, ...contentProps, children: [_jsx(MenuHighlight, { state: highlight, className: styles.highlight }), items.map((item, index) => _jsxs(Fragment, { children: [item.separatorBefore && _jsx(DropdownPrimitive.Separator, { className: styles.separator }), _jsxs(DropdownPrimitive.Item, { className: [styles.item, item.destructive ? styles.destructive : ""].filter(Boolean).join(" "), "data-tone": item.destructive ? "danger" : undefined, style: { "--i": index }, disabled: item.disabled, onSelect: item.onSelect, children: [item.icon && _jsx("span", { className: styles.icon, "aria-hidden": "true", children: item.icon }), item.label] })] }, item.label))] }) })] });
|
|
131
|
+
return (_jsxs(DropdownMenuRoot, { children: [_jsxs(DropdownMenuTrigger, { children: [icon ? _jsx("span", { className: styles.triggerIcon, "aria-hidden": "true", children: icon }) : null, _jsx(TriggerLabel, { text: label })] }), _jsx(DropdownMenuContent, { children: items.map(item => (_jsxs(Fragment, { children: [item.separatorBefore ? _jsx(DropdownMenuSeparator, {}) : null, _jsx(DropdownMenuItem, { icon: item.icon, destructive: item.destructive, disabled: item.disabled, onSelect: item.onSelect, children: item.label })] }, item.label))) })] }));
|
|
38
132
|
}
|
|
39
133
|
export default DropdownMenu;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
.trigger[data-state="open"] .chevron { transform: rotate(180deg); }
|
|
13
13
|
/* The menu grows from the trigger edge: offset toward the trigger, scale from the Radix origin, fade quickly.
|
|
14
14
|
Transitions instead of keyframes, so a close that interrupts the open (or a reopen during the close) reverses from where it is. */
|
|
15
|
-
.menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index:
|
|
15
|
+
.menu { --menu-x: 0px; --menu-y: -5px; position: relative; z-index: var(--layer-dropdown); min-width: min(12rem, var(--radix-dropdown-menu-content-available-width)); max-width: var(--radix-dropdown-menu-content-available-width); padding: var(--space-tight); border: 1px solid var(--border); border-radius: var(--radius-container); background: var(--surface-raised); box-shadow: var(--shadow-floating); transform-origin: var(--radix-dropdown-menu-content-transform-origin); transition: opacity var(--duration-fast) var(--ease-enter), transform var(--duration-spring) var(--ease-spring); }
|
|
16
16
|
.menu[data-side="top"] { --menu-y: 5px; }
|
|
17
17
|
.menu[data-side="left"] { --menu-x: 5px; --menu-y: 0px; }
|
|
18
18
|
.menu[data-side="right"] { --menu-x: -5px; --menu-y: 0px; }
|
|
@@ -21,11 +21,21 @@
|
|
|
21
21
|
.menu[data-state="open"] .item { opacity: 0; transform: translate(calc(var(--menu-x) * .4), calc(var(--menu-y) * .4)); }
|
|
22
22
|
}
|
|
23
23
|
/* Radix unmounts when the exit animation ends, so a no-op keyframe times the exit while the transition does the visual work. */
|
|
24
|
-
.menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity
|
|
25
|
-
.highlight { position: absolute; top: 0; right:
|
|
24
|
+
.menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity var(--duration-instant) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); animation: menu-exit var(--duration-instant) linear both; pointer-events: none; }
|
|
25
|
+
.highlight { position: absolute; top: 0; right: var(--space-tight); left: var(--space-tight); border-radius: calc(var(--radius-container) - var(--space-related)); background: var(--surface-muted); opacity: 0; pointer-events: none; transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
26
26
|
.highlight[data-tone="danger"] { background: color-mix(in oklab, var(--danger) 8%, var(--surface)); }
|
|
27
|
-
|
|
28
|
-
.
|
|
27
|
+
/* An item may be a link (asChild), which keeps the item's look. */
|
|
28
|
+
.item { position: relative; display: flex; min-height: 36px; align-items: center; gap: var(--space-group); padding: 0 var(--space-group); border-radius: calc(var(--radius-container) - var(--space-related)); color: var(--foreground); font-size: var(--text-sm); text-decoration: none; cursor: pointer; outline: none; transition: color var(--duration-fast) var(--ease-standard), opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * var(--stagger-item)), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * var(--stagger-item)); }
|
|
29
|
+
/* At least the 16px icon's column; wider content (an avatar) takes the room it needs. */
|
|
30
|
+
.icon { display: inline-flex; min-width: 17px; flex: none; color: var(--text-secondary); }
|
|
31
|
+
/* The composable parts. The label takes the free width, so a shortcut or check sits at the end of the row. */
|
|
32
|
+
.itemLabel { min-width: 0; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
33
|
+
.shortcut { flex: none; padding-left: var(--space-group); color: var(--text-muted); font: inherit; font-size: var(--text-xs); letter-spacing: var(--tracking-body); }
|
|
34
|
+
.indicator { display: inline-flex; flex: none; color: var(--foreground); }
|
|
35
|
+
.sectionLabel { padding: var(--space-related) var(--space-group) var(--space-tight); color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; }
|
|
36
|
+
.subChevron { flex: none; color: var(--text-muted); }
|
|
37
|
+
/* While its submenu is open, the item that opened it keeps a quiet fill, so the path stays visible. */
|
|
38
|
+
.subTrigger[data-state="open"] { background: var(--surface-muted); }
|
|
29
39
|
.separator { height: 1px; margin: var(--space-tight) calc(var(--space-tight) * -1); background: var(--border-subtle); }
|
|
30
40
|
.destructive { color: var(--danger); }
|
|
31
41
|
.destructive .icon { color: var(--danger); }
|
|
@@ -109,7 +109,7 @@ export function ElasticSlider({ label, value, defaultValue, onValueChange, onVal
|
|
|
109
109
|
function pulse(mark, haptic) {
|
|
110
110
|
const node = tickRefs.current.get(mark);
|
|
111
111
|
if (node && !reduced)
|
|
112
|
-
animate(node, { scale: [1, 2.1, 1] }, { duration: .
|
|
112
|
+
animate(node, { scale: [1, 2.1, 1] }, { duration: motionTokens.duration.standard, ease: enterEase });
|
|
113
113
|
if (haptic && typeof navigator !== "undefined" && "vibrate" in navigator)
|
|
114
114
|
navigator.vibrate?.(6);
|
|
115
115
|
}
|
|
@@ -255,7 +255,7 @@ export function ElasticSlider({ label, value, defaultValue, onValueChange, onVal
|
|
|
255
255
|
return _jsxs("div", { className: [styles.root, className].filter(Boolean).join(" "), "data-disabled": disabled || undefined, "data-dragging": dragging || undefined, "data-tag": bubble || undefined, "data-marks": labelled.length > 0 || undefined, children: [_jsxs("div", { className: styles.header, children: [_jsx("span", { id: labelId, className: styles.label, children: label }), showValue && _jsx("span", { className: styles.readout, "aria-hidden": "true", children: text })] }), _jsxs("div", { className: styles.body, children: [startIcon && _jsx(motion.span, { className: styles.start, style: { x: startShift }, "aria-hidden": "true", children: startIcon }), _jsxs("div", { className: styles.control, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerEnd, onPointerCancel: onPointerEnd, onLostPointerCapture: onPointerEnd, onMouseDown: event => event.preventDefault(), children: [_jsxs("div", { ref: trackRef, className: styles.rail, children: [_jsx(motion.div, { className: styles.track, style: { scaleX, scaleY, originX }, children: _jsx(motion.div, { className: styles.fill, style: { clipPath } }) }), markList.filter(mark => mark.value > min && mark.value < max).map(mark => _jsx("span", { ref: node => { if (node)
|
|
256
256
|
tickRefs.current.set(mark.value, node);
|
|
257
257
|
else
|
|
258
|
-
tickRefs.current.delete(mark.value); }, className: styles.tick, "data-on": mark.value <= current || undefined, style: { left: `${pct(mark.value)}%` } }, mark.value))] }), _jsxs("div", { className: styles.thumbs, children: [_jsx(motion.div, { className: styles.layer, style: { x: bubbleX }, children: _jsx(motion.span, { className: styles.bubbleAnchor, style: { x: over, "--at": bubbleAt }, children: _jsx(AnimatePresence, { children: bubble && _jsx(motion.span, { className: styles.bubble, "aria-hidden": "true", style: { rotate }, initial: reduced ? { opacity: 0 } : bubbleIn, animate: bubbleRest, exit: reduced ? { opacity: 0, transition: { duration: duration.instant } } : bubbleOut, transition: reduced ? { duration: .
|
|
258
|
+
tickRefs.current.delete(mark.value); }, className: styles.tick, "data-on": mark.value <= current || undefined, style: { left: `${pct(mark.value)}%` } }, mark.value))] }), _jsxs("div", { className: styles.thumbs, children: [_jsx(motion.div, { className: styles.layer, style: { x: bubbleX }, children: _jsx(motion.span, { className: styles.bubbleAnchor, style: { x: over, "--at": bubbleAt }, children: _jsx(AnimatePresence, { children: bubble && _jsx(motion.span, { className: styles.bubble, "aria-hidden": "true", style: { rotate }, initial: reduced ? { opacity: 0 } : bubbleIn, animate: bubbleRest, exit: reduced ? { opacity: 0, transition: { duration: duration.instant } } : bubbleOut, transition: reduced ? { duration: motionTokens.duration.fast } : bubbleEnter, children: text }, "bubble") }) }) }), _jsx(motion.div, { className: styles.layer, style: { x: thumbX }, children: _jsx(motion.div, { className: styles.shift, style: { x: over }, children: _jsx(motion.div, { ref: thumbRef, role: "slider", "data-thumb": "", className: styles.thumb, tabIndex: disabled ? -1 : 0, "aria-labelledby": labelId, "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": current, "aria-valuetext": text, "aria-orientation": "horizontal", "aria-disabled": disabled || undefined, initial: false, animate: { scale: dragging ? 1.28 : 1 }, transition: reduced ? { duration: 0 } : spring.snappy, onKeyDown: onKeyDown, onBlur: () => setKeyed(false) }) }) })] })] }), endIcon && _jsx(motion.span, { className: styles.end, style: { x: endShift }, "aria-hidden": "true", children: endIcon }), labelled.length > 0 && _jsx("div", { className: styles.marks, "aria-hidden": "true", children: labelled.map(mark => _jsx("span", { className: styles.markLabel, "data-on": mark.value === current || undefined, "data-edge": mark.value === min ? "start" : mark.value === max ? "end" : undefined, style: { left: `${pct(mark.value)}%` }, onClick: () => { if (disabled || mark.value === latest.current)
|
|
259
259
|
return; emit(mark.value); pulse(mark.value, false); onValueCommit?.(mark.value); }, children: mark.label }, mark.value)) })] }), name && _jsx("input", { type: "hidden", name: name, value: current, disabled: disabled })] });
|
|
260
260
|
}
|
|
261
261
|
export default ElasticSlider;
|