@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
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
/* Events: a hairline through the plot, a dot in the second chart series on top, and a label when there is room for it. */
|
|
20
20
|
.eventRule { stroke: var(--border-strong); stroke-width: 1; shape-rendering: crispEdges; transition: stroke var(--duration-fast) var(--ease-standard); }
|
|
21
21
|
.eventRule[data-active] { stroke: var(--series-2); }
|
|
22
|
-
.event { position: absolute; top:
|
|
23
|
-
.event[data-flip] { flex-direction: row-reverse; padding: 0 0 0
|
|
22
|
+
.event { position: absolute; top: var(--space-related); display: flex; box-sizing: border-box; min-height: 24px; align-items: center; gap: var(--space-related); padding: 0 var(--space-related) 0 0; margin-left: calc(var(--space-group) * -1); border: 0; background: transparent; color: var(--text-secondary); cursor: default; font: inherit; font-size: var(--text-xs); line-height: 1; white-space: nowrap; -webkit-tap-highlight-color: transparent; }
|
|
23
|
+
.event[data-flip] { flex-direction: row-reverse; padding: 0 0 0 var(--space-related); margin-left: 0; translate: calc(-100% + 12px) 0; }
|
|
24
24
|
.eventDot { display: grid; width: 24px; height: 24px; flex: none; place-items: center; }
|
|
25
25
|
.eventDot::before { width: 7px; height: 7px; border-radius: 50%; corner-shape: round; background: var(--series-2); box-shadow: 0 0 0 2px var(--surface); content: ""; transition: transform var(--duration-fast) var(--ease-standard); }
|
|
26
26
|
.event[data-active] .eventDot::before { transform: scale(1.3); }
|
|
@@ -28,23 +28,23 @@
|
|
|
28
28
|
.event[data-labelled] .eventLabel { opacity: 1; }
|
|
29
29
|
.event[data-active] .eventLabel { color: var(--foreground); }
|
|
30
30
|
|
|
31
|
-
.tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 132px; max-width: min(220px, 100%); gap:
|
|
31
|
+
.tooltip { position: absolute; top: 0; left: 0; z-index: 2; display: grid; min-width: 132px; max-width: min(220px, 100%); gap: var(--space-hair); padding: var(--space-group) var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface-raised); box-shadow: var(--shadow-floating); opacity: 0; pointer-events: none; scale: .96; transition: opacity var(--duration-fast) var(--ease-standard), scale var(--duration-fast) var(--ease-standard); }
|
|
32
32
|
.chart[data-active] .tooltip { opacity: 1; scale: 1; }
|
|
33
|
-
.tipTitle { margin: 0 0
|
|
33
|
+
.tipTitle { margin: 0 0 var(--space-hair); color: var(--text-secondary); font-size: var(--text-xs); line-height: var(--leading-body); white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
34
34
|
.tipValue { margin: 0; color: var(--foreground); font-size: var(--text-base); font-weight: 500; line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
|
|
35
35
|
.tipNote { margin: 0; color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); font-variant-numeric: tabular-nums; }
|
|
36
|
-
.tipEvent { display: flex; align-items: center; gap:
|
|
36
|
+
.tipEvent { display: flex; align-items: center; gap: var(--space-related); margin: var(--space-tight) 0 0; color: var(--foreground); font-size: var(--text-xs); line-height: var(--leading-body); }
|
|
37
37
|
.tipEventDot { width: 7px; height: 7px; flex: none; border-radius: 50%; corner-shape: round; background: var(--series-2); }
|
|
38
38
|
|
|
39
39
|
.message { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; color: var(--text-muted); font-size: var(--text-sm); pointer-events: none; }
|
|
40
40
|
|
|
41
41
|
.gutter { position: relative; grid-area: 1 / 2; min-width: 0; }
|
|
42
|
-
.tick { position: absolute; left:
|
|
42
|
+
.tick { position: absolute; left: var(--space-group); color: var(--text-muted); font-size: var(--text-xs); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; translate: 0 -50%; pointer-events: none; }
|
|
43
43
|
.axis { position: relative; grid-area: 2 / 1; min-width: 0; }
|
|
44
|
-
.axisLabel { position: absolute; top:
|
|
44
|
+
.axisLabel { position: absolute; top: var(--space-related); color: var(--text-muted); font-size: var(--text-xs); line-height: 1; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
45
45
|
|
|
46
46
|
/* Overview: the whole history, with everything outside the window stepped back. */
|
|
47
|
-
.strip { --strip-radius: var(--radius-detail); position: relative; grid-area: 3 / 1; min-width: 0; margin-top:
|
|
47
|
+
.strip { --strip-radius: var(--radius-detail); position: relative; grid-area: 3 / 1; min-width: 0; margin-top: var(--space-related); border-radius: var(--strip-radius); background: color-mix(in oklab, var(--foreground) 2.5%, transparent); cursor: crosshair; touch-action: pan-y; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; }
|
|
48
48
|
/* The overview drawing is clipped to the strip's corners; the handles stay outside the clip so they can overhang the ends. */
|
|
49
49
|
.strip > .svg { clip-path: inset(0 round var(--strip-radius)); }
|
|
50
50
|
.overviewLine { fill: none; stroke: color-mix(in oklab, var(--foreground) 28%, var(--surface)); stroke-width: 1; stroke-linejoin: round; }
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
.shade[data-edge="end"] { border-radius: 0 var(--strip-radius) var(--strip-radius) 0; }
|
|
58
58
|
.window { position: absolute; top: 0; bottom: 0; border-radius: var(--strip-radius); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--series-1) 45%, transparent); cursor: grab; outline: none; transition: box-shadow var(--duration-fast) var(--ease-standard); }
|
|
59
59
|
.window:active { cursor: grabbing; }
|
|
60
|
-
.handle { position: absolute; top: 50%; display: grid; width: 28px; height: 44px; place-items: center; margin-left: -
|
|
60
|
+
.handle { position: absolute; top: 50%; display: grid; width: 28px; height: 44px; place-items: center; margin-left: calc(var(--space-inset) * -1); translate: 0 -50%; cursor: ew-resize; outline: none; touch-action: none; }
|
|
61
61
|
.grip { width: 6px; height: 22px; border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); box-shadow: 0 0 0 1px color-mix(in oklab, var(--foreground) 26%, transparent), var(--shadow-resting); transition: transform var(--duration-instant) var(--ease-standard), background-color var(--duration-fast) var(--ease-standard); }
|
|
62
62
|
.handle:active .grip { transform: scaleY(1.12); }
|
|
63
63
|
@media (hover: hover) and (pointer: fine) { .handle:hover .grip { transform: scaleY(1.12); } }
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { ButtonHTMLAttributes } from "react";
|
|
2
|
-
|
|
3
|
-
export type
|
|
2
|
+
/** `soft` is filled and borderless, for controls in a bar (experimental). */
|
|
3
|
+
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger" | "soft";
|
|
4
|
+
/** `xs` (24px) is for dense UI such as tree rows and toolbars, usually icon-only with an aria-label. */
|
|
5
|
+
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
|
4
6
|
export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "onDrag" | "onDragEnd" | "onDragStart" | "onAnimationStart"> {
|
|
7
|
+
/** The look: `primary` (default) for the main action, `secondary` bordered, `ghost` quiet, `danger` destructive, `soft` filled for bars. */
|
|
5
8
|
variant?: ButtonVariant;
|
|
9
|
+
/** Height: `xs` 24px, `sm` 36px, `md` 44px (default), `lg` 50px. */
|
|
6
10
|
size?: ButtonSize;
|
|
11
|
+
/** Shows a spinner in place of the label and sets aria-busy, keeping the button's width. */
|
|
7
12
|
loading?: boolean;
|
|
8
13
|
}
|
|
9
14
|
export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -91,7 +91,9 @@ export const Button = forwardRef(function Button({ className, variant = "primary
|
|
|
91
91
|
const anchorsLayer = (popup !== undefined && popup !== false && popup !== "false") || props.role === "combobox" || props["data-state"] !== undefined;
|
|
92
92
|
const inert = disabled || loading || props["aria-disabled"] === true || props["aria-disabled"] === "true";
|
|
93
93
|
const classes = [styles.button, styles[variant], styles[size], className].filter(Boolean).join(" ");
|
|
94
|
-
|
|
94
|
+
// An icon with no text: square at the compact size.
|
|
95
|
+
const iconOnly = !/\S/.test(key.replace(/<[^>]*>/g, ""));
|
|
96
|
+
return (_jsxs(motion.button, { ref: setRefs, tabIndex: props.tabIndex ?? 0, className: classes, "data-variant": variant, "data-icon-only": iconOnly || undefined, disabled: disabled, "aria-busy": loading || undefined, custom: buttonRef, variants: pressVariants, whileTap: reduceMotion || anchorsLayer || inert ? undefined : "pressed", transition: motionTokens.spring.snappy, ...props, "aria-disabled": loading || props["aria-disabled"] || undefined, onClick: loading ? event => event.preventDefault() : onClick, children: [_jsx(AnimatePresence, { initial: false, children: loading ? _jsx(motion.span, { className: styles.loader, "aria-hidden": "true", initial: reduceMotion ? { opacity: 0 } : { opacity: 0, scale: .6 }, animate: { opacity: 1, scale: 1 }, exit: reduceMotion ? fadeOut : { ...iconOut, scale: .8 }, transition: reduceMotion ? { duration: motionTokens.duration.instant } : iconEnter, children: _jsx("span", { className: styles.spinner }) }, "loader") : null }), _jsx(motion.span, { className: [styles.labelSlot, loading ? styles.loadingLabel : ""].filter(Boolean).join(" "), style: { width }, children: _jsx("span", { ref: contentRef, className: styles.labelContent, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: _jsx(LabelPhase, { icon: !/\S/.test(key.replace(/<[^>]*>/g, "")), reduced: reduceMotion, children: children }, key) }) }) })] }));
|
|
95
97
|
});
|
|
96
98
|
Button.displayName = "Button";
|
|
97
99
|
export default Button;
|
|
@@ -15,20 +15,29 @@
|
|
|
15
15
|
.secondary { color: var(--foreground); background: var(--surface); border-color: var(--border); }
|
|
16
16
|
.ghost { color: var(--text-secondary); background: transparent; border-color: transparent; }
|
|
17
17
|
.danger { color: var(--danger); background: var(--surface); border-color: var(--border); }
|
|
18
|
+
/* Filled and borderless, for controls that live in a bar (a search trigger, a toolbar, a switcher). Place it on --background or
|
|
19
|
+
--surface; on a --surface-muted panel it has no fill to show. */
|
|
20
|
+
.soft { color: var(--foreground); background: var(--surface-muted); border-color: transparent; }
|
|
18
21
|
/* A disabled outlined button keeps its shape and fades only its label, so it still reads as a button that is off, not as plain text. */
|
|
19
22
|
.secondary:disabled, .danger:disabled { opacity: 1; color: var(--text-muted); background: var(--surface-muted); border-color: var(--border); }
|
|
20
23
|
.danger:disabled { color: color-mix(in oklab, var(--danger) 55%, var(--surface-muted)); }
|
|
21
|
-
.ghost:disabled { opacity: 1; color: var(--text-muted); }
|
|
24
|
+
.ghost:disabled, .soft:disabled { opacity: 1; color: var(--text-muted); }
|
|
22
25
|
@media (hover: hover) and (pointer: fine) {
|
|
23
26
|
.primary:hover:not(:disabled) { opacity: .91; box-shadow: var(--shadow-resting); }
|
|
24
27
|
.secondary:hover:not(:disabled) { background: var(--surface-muted); box-shadow: var(--shadow-resting); }
|
|
25
28
|
.ghost:hover:not(:disabled) { color: var(--foreground); background: var(--surface-muted); }
|
|
26
29
|
.danger:hover:not(:disabled) { border-color: var(--danger); background: var(--surface-muted); }
|
|
30
|
+
.soft:hover:not(:disabled) { background: var(--surface-pressed); }
|
|
27
31
|
}
|
|
28
32
|
/* Color feedback on press, so touch and menu triggers (which never scale) still answer the finger. */
|
|
29
33
|
.primary:active:not(:disabled) { opacity: .84; }
|
|
30
34
|
.secondary:active:not(:disabled), .ghost:active:not(:disabled) { color: var(--foreground); background: var(--surface-muted); }
|
|
31
35
|
.danger:active:not(:disabled) { border-color: var(--danger); background: var(--surface-muted); }
|
|
36
|
+
.soft:active:not(:disabled) { background: var(--surface-pressed); }
|
|
37
|
+
.xs { min-height: var(--control-height-xs); gap: var(--space-tight); padding-inline: var(--space-related); font-size: var(--text-xs); }
|
|
38
|
+
/* A compact icon-only button is a square, with the icon at its usual 16px. */
|
|
39
|
+
.xs[data-icon-only] { min-width: var(--control-height-xs); padding-inline: 0; }
|
|
40
|
+
.xs svg { width: 16px; height: 16px; }
|
|
32
41
|
.sm { min-height: var(--control-height-sm); padding-inline: var(--space-group); font-size: var(--text-sm); }
|
|
33
42
|
.md { min-height: var(--control-height-md); }
|
|
34
43
|
.lg { min-height: var(--control-height-lg); padding-inline: var(--space-comfort); font-size: var(--text-sm); }
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
/* The travelling highlight. Motion sets its box to the active segment; the visible shape sits inset so the border stays crisp. */
|
|
14
14
|
.highlight { position: absolute; z-index: 0; top: 0; left: 0; pointer-events: none; }
|
|
15
|
-
.highlight::before { position: absolute; inset: var(--bg-inset); border-radius: calc(var(--bg-radius) - var(--bg-inset) -
|
|
15
|
+
.highlight::before { position: absolute; inset: var(--bg-inset); border-radius: calc(var(--bg-radius) - var(--bg-inset) - var(--space-hair)); corner-shape: var(--corner-shape); background: var(--bg-highlight); content: ""; transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
16
16
|
.highlight[data-pressed]::before { background: var(--bg-highlight-pressed); transition-duration: var(--duration-instant); }
|
|
17
17
|
|
|
18
18
|
.segment { position: relative; z-index: 1; display: inline-flex; flex: none; min-width: calc(var(--bg-h) - 2px); height: calc(var(--bg-h) - 2px); align-items: center; justify-content: center; margin: 0; padding: 0 var(--bg-pad-x); border: 0; border-radius: 0; corner-shape: round; background: transparent; color: inherit; font: inherit; font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-body); line-height: 1; white-space: nowrap; text-decoration: none; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; }
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
.compact .label[data-collapsible] { display: none; }
|
|
30
30
|
|
|
31
31
|
/* Hairline dividers sit between segments and fade where the highlight arrives, so it never sits beside a line. */
|
|
32
|
-
.segment + .segment::before { position: absolute; top: var(--bg-divider-inset); bottom: var(--bg-divider-inset); left:
|
|
33
|
-
.vertical .segment + .segment::before { top:
|
|
32
|
+
.segment + .segment::before { position: absolute; top: var(--bg-divider-inset); bottom: var(--bg-divider-inset); left: calc(var(--space-hair) * -1); width: 1px; background: var(--bg-divider); content: ""; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
|
|
33
|
+
.vertical .segment + .segment::before { top: calc(var(--space-hair) * -1); right: var(--bg-divider-inset); bottom: auto; left: var(--bg-divider-inset); width: auto; height: 1px; }
|
|
34
34
|
.segment[data-quiet]::before { opacity: 0; }
|
|
35
35
|
|
|
36
36
|
/* The press answers inside the segment: content dips and springs back, the group and its neighbours stay still. */
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
.phase { display: inline-flex; align-items: center; gap: var(--space-related); white-space: nowrap; }
|
|
52
52
|
.sizer { visibility: hidden; pointer-events: none; }
|
|
53
53
|
.iconBox { display: inline-block; width: 16px; height: 16px; flex: none; }
|
|
54
|
-
.sm .phase { gap:
|
|
54
|
+
.sm .phase { gap: var(--space-related); }
|
|
55
55
|
.icon { display: inline-flex; flex: none; }
|
|
56
56
|
.icon svg { width: 16px; height: 16px; stroke-width: 1.75; }
|
|
57
57
|
.value { display: inline-flex; align-items: center; font-variant-numeric: tabular-nums; }
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
/* The menu grows from the group's edge: offset toward it, scale from the Radix origin, and leave faster than it arrives.
|
|
62
62
|
Transitions instead of keyframes, so a close that interrupts the open reverses from where the menu is. */
|
|
63
|
-
.menu { --menu-x: 0px; --menu-y: -5px; z-index:
|
|
63
|
+
.menu { --menu-x: 0px; --menu-y: -5px; z-index: var(--layer-dropdown); min-width: min(13rem, 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); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); 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); }
|
|
64
64
|
.menu[data-side="top"] { --menu-y: 5px; }
|
|
65
65
|
.menu[data-side="left"] { --menu-x: 5px; --menu-y: 0px; }
|
|
66
66
|
.menu[data-side="right"] { --menu-x: -5px; --menu-y: 0px; }
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
.menu[data-state="open"] .item { opacity: 0; transform: translate(calc(var(--menu-x) * .4), calc(var(--menu-y) * .4)); }
|
|
70
70
|
}
|
|
71
71
|
/* Radix unmounts when the exit animation ends, so a no-op keyframe times the exit while the transition does the visual work. */
|
|
72
|
-
.menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity
|
|
73
|
-
.item { display: flex; min-height: 36px; align-items: center; gap:
|
|
72
|
+
.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; }
|
|
73
|
+
.item { 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)); corner-shape: var(--corner-shape); color: inherit; font-size: var(--text-sm); text-decoration: none; cursor: pointer; outline: none; transition: opacity var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms), transform var(--duration-standard) var(--ease-enter) calc(min(var(--i, 0), 4) * 35ms); }
|
|
74
74
|
.item[data-highlighted] { background: var(--surface-muted); }
|
|
75
75
|
.item[data-disabled] { cursor: default; opacity: .45; }
|
|
76
76
|
.itemIcon { display: inline-flex; flex: none; color: var(--text-secondary); }
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
5
|
import { animate, AnimatePresence, motion, useMotionValue, useMotionValueEvent, useReducedMotion, useTransform } from "motion/react";
|
|
6
6
|
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
7
7
|
import { useToday } from "../../lib/use-today.js";
|
|
8
|
+
import { FluidHoverHighlight, useFluidHover } from "../../lib/fluid-hover.js";
|
|
8
9
|
import styles from "./calendar.module.css";
|
|
9
10
|
const startOfDay = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
10
11
|
const monthStart = (date) => new Date(date.getFullYear(), date.getMonth(), 1);
|
|
@@ -29,14 +30,14 @@ const serverSnapshot = () => false;
|
|
|
29
30
|
export { useToday } from "../../lib/use-today.js";
|
|
30
31
|
const { spring, duration, ease } = motionTokens;
|
|
31
32
|
/** Months sit side by side on one strip. Rapid clicks retarget the same spring, so the strip never queues or stacks panes. */
|
|
32
|
-
const stripSpring = { type: "spring", visualDuration:
|
|
33
|
+
const stripSpring = { type: "spring", visualDuration: motionTokens.spring.moderate.visualDuration, bounce: 0, restDelta: 0.002 };
|
|
33
34
|
const monthIndex = (date) => date.getFullYear() * 12 + date.getMonth();
|
|
34
35
|
const fromIndex = (index) => new Date(Math.floor(index / 12), ((index % 12) + 12) % 12, 1);
|
|
35
36
|
/** The title crossfades in place: the old month leaves toward the side the strip travels to while the new one arrives from the other. */
|
|
36
37
|
const titleSlide = {
|
|
37
38
|
enter: (direction) => ({ opacity: 0, x: direction * 12 }),
|
|
38
39
|
center: { opacity: 1, x: 0, transition: { duration: duration.standard, ease: ease.enter } },
|
|
39
|
-
exit: (direction) => ({ opacity: 0, x: direction * -12, transition: { duration:
|
|
40
|
+
exit: (direction) => ({ opacity: 0, x: direction * -12, transition: { duration: motionTokens.duration.fast, ease: ease.standard } }),
|
|
40
41
|
};
|
|
41
42
|
/** Reduced motion swaps the title in place, with no travel and no blank frame. */
|
|
42
43
|
const titleFade = { enter: { opacity: 1, x: 0 }, center: { opacity: 1, x: 0, transition: { duration: 0 } }, exit: { opacity: 0, transition: { duration: 0 } } };
|
|
@@ -57,7 +58,7 @@ function SelectionDisc({ cell, reduced }) {
|
|
|
57
58
|
useLayoutEffect(() => {
|
|
58
59
|
if (!visible) {
|
|
59
60
|
hidden.current = true;
|
|
60
|
-
const fade = reduced ? { duration: 0 } : { duration:
|
|
61
|
+
const fade = reduced ? { duration: 0 } : { duration: motionTokens.duration.fast, ease: ease.standard };
|
|
61
62
|
const controls = [animate(opacity, 0, fade), animate(scale, 0.6, fade)];
|
|
62
63
|
return () => controls.forEach((control) => control.stop());
|
|
63
64
|
}
|
|
@@ -75,6 +76,15 @@ function SelectionDisc({ cell, reduced }) {
|
|
|
75
76
|
}, [cell, visible, reduced, col, row, opacity, scale]);
|
|
76
77
|
return _jsx(motion.span, { className: styles.highlight, style: { transform, opacity }, "aria-hidden": "true", children: _jsx("span", { className: styles.highlightFill }) });
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Fluid hover for one month's days: one fill glides to the day nearest the pointer, under the selected disc. Unavailable
|
|
81
|
+
* days light nothing, a gap between days stays inert, and focus keeps its own ring.
|
|
82
|
+
*/
|
|
83
|
+
function MonthDays({ children }) {
|
|
84
|
+
const box = useRef(null);
|
|
85
|
+
const hover = useFluidHover(box, { items: `.${styles.day}`, axis: "xy", disabled: "clear", gapClick: false, followFocus: false });
|
|
86
|
+
return _jsxs("div", { ref: box, className: styles.days, ...hover.handlers, children: [_jsx(FluidHoverHighlight, { hover: hover, className: styles.dayHover }), children] });
|
|
87
|
+
}
|
|
78
88
|
/** One month on the strip. Only the month being navigated to is focusable and exposed; the one sliding past is inert. */
|
|
79
89
|
function MonthPane({ index, position, present, children }) {
|
|
80
90
|
const x = useTransform(position, (value) => `calc(${(index - value) * 100}% + ${(index - value) * 16}px)`);
|
|
@@ -239,7 +249,7 @@ export function Calendar({ value, onChange, month: controlledMonth, onMonthChang
|
|
|
239
249
|
const index = Math.round((startOfDay(value).getTime() - first.getTime()) / 864e5);
|
|
240
250
|
return index >= 0 && index < 42 ? index : -1;
|
|
241
251
|
};
|
|
242
|
-
const renderMonth = (paneMonth, present) => _jsxs(
|
|
252
|
+
const renderMonth = (paneMonth, present) => _jsxs(MonthDays, { children: [_jsx(SelectionDisc, { cell: cellOf(paneMonth), reduced: reducedMotion }), _jsx("div", { className: styles.grid, role: "grid", "aria-label": formatter.format(paneMonth), children: makeWeeks(paneMonth).map((week) => _jsx("div", { className: styles.week, role: "row", children: week.map((date) => {
|
|
243
253
|
const key = dateKey(date);
|
|
244
254
|
const selected = sameDay(date, value);
|
|
245
255
|
const isToday = sameDay(date, today);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.calendar { width: min(100%, 328px); min-width: 0; color: var(--foreground); }
|
|
2
2
|
/* The header is its own size container: below 260px the title takes a full row and the controls sit under it, decided by width alone so a long month name never reflows it. */
|
|
3
|
-
.header { container-type: inline-size; display: grid; min-height: 34px; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap:
|
|
3
|
+
.header { container-type: inline-size; display: grid; min-height: 34px; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-tight) var(--space-group); margin-bottom: var(--space-inset); }
|
|
4
4
|
@container (max-width: 259px) { .heading, .navigation { grid-column: 1 / -1; } .navigation { justify-self: end; } }
|
|
5
5
|
.heading { min-width: 0; margin: 0; font-family: var(--font-body); font-size: var(--text-base); font-weight: 500; line-height: var(--leading-body); letter-spacing: var(--tracking-body); }
|
|
6
6
|
/* The global heading rule forces a tight line-height on h2, so the title row sets its own to center with the controls. */
|
|
@@ -9,22 +9,25 @@
|
|
|
9
9
|
.title > .titleRow { grid-area: 1 / 1; justify-self: start; }
|
|
10
10
|
.titleRow { display: inline-block; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
11
11
|
.srOnly { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
|
|
12
|
-
.navigation { display: inline-flex; flex: 0 0 auto; align-items: center; gap:
|
|
12
|
+
.navigation { display: inline-flex; flex: 0 0 auto; align-items: center; gap: var(--space-hair); margin-right: calc(var(--space-related) * -1); }
|
|
13
13
|
/* Quiet header controls: a fill on hover, a short press, and a spring release. */
|
|
14
14
|
.navButton, .todayButton { display: grid; height: 32px; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); font: inherit; cursor: pointer; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), scale var(--duration-spring) var(--ease-spring); }
|
|
15
15
|
.navButton { width: 32px; }
|
|
16
|
-
.todayButton { padding: 0
|
|
16
|
+
.todayButton { padding: 0 var(--space-group); font-size: var(--text-sm); font-weight: 500; line-height: 1; }
|
|
17
17
|
@media (hover: hover) and (pointer: fine) { .navButton:hover:not([aria-disabled]), .todayButton:hover:not([aria-disabled]) { background: var(--surface-muted); color: var(--foreground); } }
|
|
18
|
-
.navButton:active:not([aria-disabled]), .todayButton:active:not([aria-disabled]) { scale: .96; transition-duration: var(--duration-fast), var(--duration-fast), var(--duration-fast),
|
|
18
|
+
.navButton:active:not([aria-disabled]), .todayButton:active:not([aria-disabled]) { scale: .96; transition-duration: var(--duration-fast), var(--duration-fast), var(--duration-fast), var(--duration-fast); transition-timing-function: var(--ease-standard); }
|
|
19
19
|
.navButton[aria-disabled], .todayButton[aria-disabled] { cursor: default; opacity: .36; }
|
|
20
|
-
.weekdays, .week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); column-gap:
|
|
21
|
-
.weekdays { margin-bottom:
|
|
20
|
+
.weekdays, .week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); column-gap: var(--space-tight); }
|
|
21
|
+
.weekdays { margin-bottom: var(--space-related); color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; line-height: 24px; text-align: center; }
|
|
22
22
|
.weekdays span { min-width: 0; overflow: hidden; }
|
|
23
23
|
/* Months sit on one strip inside a clipped window. Every pane shares one grid cell and has six weeks, so the height never changes. */
|
|
24
24
|
.monthViewport { position: relative; display: grid; overflow: hidden; contain: paint; }
|
|
25
25
|
.monthBody { position: relative; grid-area: 1 / 1; min-width: 0; will-change: transform; }
|
|
26
26
|
.monthBody[inert] { pointer-events: none; }
|
|
27
|
-
|
|
27
|
+
/* One month's days: the hover fill and the selected disc are placed against this box. */
|
|
28
|
+
.days { position: relative; }
|
|
29
|
+
.dayHover { border-radius: 50%; corner-shape: round; background: var(--surface-muted); }
|
|
30
|
+
.grid { display: grid; row-gap: var(--space-tight); }
|
|
28
31
|
.placeholderDay { aspect-ratio: 1; }
|
|
29
32
|
.day { position: relative; display: grid; width: 100%; aspect-ratio: 1; min-width: 0; place-items: center; border: 0; border-radius: 50%; corner-shape: round; padding: 0; background: transparent; color: var(--foreground); font: inherit; font-size: var(--text-sm); font-weight: 400; line-height: 1; font-variant-numeric: tabular-nums; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard) var(--number-delay, 0ms); }
|
|
30
33
|
/* The inner circle carries hover and press; the grid cell itself never moves, so the hit area stays put. */
|
|
@@ -33,13 +36,13 @@
|
|
|
33
36
|
/* The selected disc is one element per month, one cell in size, placed by week and weekday under the day buttons. It glides on a transform, so the strip sliding around it never bends its path. Press scales the inner fill so it never fights the glide. */
|
|
34
37
|
.highlight { position: absolute; top: 0; left: 0; width: calc((100% - 24px) / 7); aspect-ratio: 1; pointer-events: none; will-change: transform; }
|
|
35
38
|
.highlightFill { position: absolute; inset: 0; border-radius: 50%; corner-shape: round; background: var(--accent); transition: background-color var(--duration-fast) var(--ease-standard), scale var(--duration-spring) var(--ease-spring); }
|
|
36
|
-
@media (hover: hover) and (pointer: fine) { .
|
|
37
|
-
.day:active:not(:disabled)::before, .day:active:not(:disabled) .dayNumber, .monthBody:has(.day.selected:active:not(:disabled)) .highlightFill { scale: .94; transition-duration: var(--duration-fast),
|
|
39
|
+
@media (hover: hover) and (pointer: fine) { .monthBody:has(.day.selected:hover) .highlightFill { background: var(--accent-strong); } }
|
|
40
|
+
.day:active:not(:disabled)::before, .day:active:not(:disabled) .dayNumber, .monthBody:has(.day.selected:active:not(:disabled)) .highlightFill { scale: .94; transition-duration: var(--duration-fast), var(--duration-fast); transition-timing-function: var(--ease-standard); }
|
|
38
41
|
/* Outside days dim their number, not the button, so a disc gliding onto one keeps its full strength. */
|
|
39
42
|
.day.outside { color: var(--text-muted); }
|
|
40
43
|
.day.outside:not(.selected) .dayNumber { opacity: .8; }
|
|
41
44
|
/* Today is a small dot under the number. It takes the number's color, so it flips with it when the disc arrives. */
|
|
42
|
-
.day.today .dayNumber::after { content: ""; position: absolute; top: calc(100% +
|
|
45
|
+
.day.today .dayNumber::after { content: ""; position: absolute; top: calc(100% + var(--space-tight)); left: 50%; width: 4px; height: 4px; margin-left: calc(var(--space-hair) * -1); border-radius: 50%; corner-shape: round; background: currentColor; }
|
|
43
46
|
.day.selected, .day.today { font-weight: 500; }
|
|
44
47
|
.day.selected { color: var(--accent-foreground); }
|
|
45
48
|
.day:disabled { cursor: default; color: var(--text-muted); }
|
package/components/card/card.js
CHANGED
|
@@ -8,14 +8,14 @@ import { motionTokens } from "../../lib/motion-tokens.js";
|
|
|
8
8
|
import styles from "./card.module.css";
|
|
9
9
|
const { blur, duration, ease, spring, stagger } = motionTokens;
|
|
10
10
|
/** One critically damped spring carries the surface both ways, so it never overshoots and can reverse mid-flight. Closing is a touch quicker. */
|
|
11
|
-
const grow = { ...spring.smooth, visualDuration: .
|
|
12
|
-
const settle = { ...spring.smooth, visualDuration: .
|
|
11
|
+
const grow = { ...spring.smooth, visualDuration: motionTokens.spring.slow.visualDuration };
|
|
12
|
+
const settle = { ...spring.smooth, visualDuration: motionTokens.spring.moderate.visualDuration };
|
|
13
13
|
const RETURN_MS = 300;
|
|
14
14
|
const fade = { duration: duration.instant };
|
|
15
15
|
/** The photo drifts in slowly while the card is pointed at, and eases back a little faster. */
|
|
16
16
|
const ZOOM = 1.04;
|
|
17
|
-
const zoomIn = { duration: duration.
|
|
18
|
-
const zoomOut = { duration: duration.
|
|
17
|
+
const zoomIn = { duration: duration.expressive * 2, ease: [...ease.standard] };
|
|
18
|
+
const zoomOut = { duration: duration.expressive, ease: [...ease.standard] };
|
|
19
19
|
/** Quick look extras arrive once the surface has mostly grown, so they never ride the stretch. */
|
|
20
20
|
const reveal = { delay: duration.instant, duration: duration.standard, ease: [...ease.enter] };
|
|
21
21
|
const closeIn = { delay: duration.instant, duration: duration.fast, ease: [...ease.enter] };
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
30
30
|
.action { position: relative; z-index: 1; display: flex; flex: none; flex-wrap: wrap; gap: var(--space-group); }
|
|
31
31
|
|
|
32
|
-
.overlay { position: fixed; inset: 0; z-index:
|
|
32
|
+
.overlay { position: fixed; inset: 0; z-index: var(--layer-overlay); background: var(--scrim); backdrop-filter: blur(7px); }
|
|
33
33
|
/* Centered with auto margins, so transform stays free for the morph. */
|
|
34
|
-
.panel { position: fixed; inset: 0; z-index:
|
|
34
|
+
.panel { position: fixed; inset: 0; z-index: var(--layer-dialog); width: min(calc(100vw - var(--space-block)), 30rem); height: fit-content; max-height: calc(100dvh - var(--space-block)); margin: auto; overflow: hidden auto; overscroll-behavior: contain; border: 1px solid var(--border); border-radius: var(--radius-overlay); corner-shape: var(--corner-shape); background: var(--surface-raised); color: var(--foreground); box-shadow: var(--shadow-floating); transition: background-color var(--duration-standard) var(--ease-standard), box-shadow var(--duration-standard) var(--ease-standard); }
|
|
35
35
|
.panel:focus { outline: none; }
|
|
36
36
|
/* Closing: the panel takes the card's box and look, so it lands exactly where the card sits and hands back without a seam. */
|
|
37
37
|
.panel[data-returning] { inset: auto; top: var(--landing-top); left: var(--landing-left); width: var(--landing-width); height: var(--landing-height); max-height: none; margin: 0; overflow: hidden; border-color: var(--border); background: var(--surface); box-shadow: none; pointer-events: none; }
|
|
@@ -35,8 +35,8 @@ const unresistY = (shown) => {
|
|
|
35
35
|
};
|
|
36
36
|
const clampUnit = (value) => Math.min(1, Math.max(0, value));
|
|
37
37
|
const throwSpring = { type: "spring", visualDuration: .5, bounce: 0 };
|
|
38
|
-
const fadeOut = { duration: .
|
|
39
|
-
const reducedFade = { duration: .
|
|
38
|
+
const fadeOut = { duration: motionTokens.duration.exit, delay: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] };
|
|
39
|
+
const reducedFade = { duration: motionTokens.duration.fast, ease: "linear" };
|
|
40
40
|
function velocityOf(samples, now) {
|
|
41
41
|
const recent = samples.filter(sample => now - sample.t <= 80);
|
|
42
42
|
const first = recent[0], last = recent[recent.length - 1];
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
.card::before { position: absolute; inset: 0; border-radius: inherit; corner-shape: inherit; box-shadow: var(--shadow-floating); content: ""; opacity: 0; pointer-events: none; transition: opacity var(--duration-fast) var(--ease-standard); }
|
|
13
13
|
.card[data-dragging]::before, .card[data-flying]::before { opacity: 1; }
|
|
14
14
|
.face { position: relative; height: 100%; overflow: hidden; border: 1px solid var(--border); border-radius: inherit; corner-shape: inherit; background: var(--surface); color: var(--foreground); }
|
|
15
|
-
.shade { position: absolute; inset: 0; background:
|
|
16
|
-
:global(:root[data-theme="dark"]) .shade { background: oklch(0% 0 0 / .22); }
|
|
15
|
+
.shade { position: absolute; inset: 0; background: var(--shade); pointer-events: none; }
|
|
17
16
|
|
|
18
|
-
.stamp { position: absolute; top: var(--space-inset); display: inline-flex; height: 2rem; align-items: center; gap:
|
|
17
|
+
.stamp { position: absolute; top: var(--space-inset); display: inline-flex; height: 2rem; align-items: center; gap: var(--space-related); padding: 0 var(--space-group) 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); color: var(--foreground); font-size: var(--text-sm); font-weight: 500; white-space: nowrap; pointer-events: none; }
|
|
19
18
|
.stamp[data-side="right"] { left: var(--space-inset); border-color: color-mix(in oklab, var(--success) 32%, var(--border)); background: color-mix(in oklab, var(--success) 9%, var(--surface-raised)); }
|
|
20
19
|
.stamp[data-side="right"] svg { color: var(--success); }
|
|
21
20
|
.stamp[data-side="left"] { right: var(--space-inset); }
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
/* Controls line up with the edges of the centered slide. */
|
|
14
14
|
.controls { display: flex; min-width: 0; align-items: center; gap: var(--space-related); padding-inline: calc((100cqw - var(--carousel-slide)) / 2); }
|
|
15
15
|
.tabs { display: flex; min-width: 0; align-items: center; }
|
|
16
|
-
.tab { display: grid; height: var(--control-height-sm); flex: none; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; padding: 0
|
|
16
|
+
.tab { display: grid; height: var(--control-height-sm); flex: none; place-items: center; border: 0; border-radius: var(--radius-full); corner-shape: round; padding: 0 var(--space-tight); background: transparent; cursor: pointer; -webkit-tap-highlight-color: transparent; }
|
|
17
17
|
/* Resting dots use the strong border color; the fill fades in as the pill forms, and the drain covers it from the right while rotation runs. */
|
|
18
18
|
.dot { position: relative; display: block; width: 6px; height: 6px; overflow: hidden; border-radius: var(--radius-full); corner-shape: round; background: var(--border-strong); transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
19
19
|
.dotFill, .dotDrain { position: absolute; inset: 0; }
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
.arrows { display: flex; flex: none; gap: var(--space-related); margin-left: auto; }
|
|
26
26
|
.control { display: grid; width: var(--control-height-sm); height: var(--control-height-sm); flex: none; place-items: center; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; padding: 0; background: var(--surface); color: var(--foreground); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), transform var(--duration-spring) var(--ease-spring); }
|
|
27
27
|
@media (hover: hover) and (pointer: fine) { .control:hover:not([aria-disabled="true"]) { background: var(--surface-muted); } }
|
|
28
|
-
.control:active:not([aria-disabled="true"]) { transform: scale(.97); transition: transform
|
|
28
|
+
.control:active:not([aria-disabled="true"]) { transform: scale(.97); transition: transform var(--duration-fast) var(--ease-standard); }
|
|
29
29
|
.control[aria-disabled="true"] { color: var(--text-muted); cursor: default; opacity: .5; }
|
|
30
30
|
.iconSlot { display: grid; width: 16px; height: 16px; place-items: center; }
|
|
31
31
|
.icon { grid-area: 1 / 1; display: grid; place-items: center; }
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
@container (max-width: 340px) {
|
|
34
34
|
.controls { gap: var(--space-tight); padding-inline: max(0px, calc((100cqw - var(--carousel-slide)) / 2 - var(--space-related))); }
|
|
35
35
|
.arrows { gap: var(--space-tight); }
|
|
36
|
-
.tab { padding: 0
|
|
36
|
+
.tab { padding: 0 var(--space-tight); }
|
|
37
37
|
}
|
|
38
38
|
.visuallyHidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
39
39
|
|