@teamofsilicons/silicon-ui 0.1.0 → 0.3.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 +21 -2
- package/check.mjs +245 -0
- package/components/activity-heatmap/activity-heatmap.module.css +4 -4
- package/components/alert/alert.module.css +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.module.css +4 -4
- package/components/billing-toggle/billing-toggle.module.css +10 -10
- 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 +6 -6
- package/components/calendar/calendar.module.css +7 -7
- package/components/card/card.module.css +2 -2
- package/components/card-stack/card-stack.module.css +2 -3
- package/components/carousel/carousel.module.css +2 -2
- package/components/chat-thread/chat-thread.module.css +41 -41
- package/components/checkbox/checkbox.module.css +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.module.css +2 -2
- 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.module.css +9 -9
- package/components/context-menu/context-menu.module.css +3 -3
- package/components/countdown/countdown.module.css +4 -4
- package/components/date-picker/date-picker.module.css +6 -6
- package/components/date-range-picker/date-range-picker.module.css +21 -21
- package/components/dialog/dialog.module.css +2 -2
- package/components/donut-chart/donut-chart.module.css +4 -4
- package/components/drawer/drawer.module.css +5 -5
- 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.module.css +4 -4
- package/components/expandable-card/expandable-card.module.css +1 -1
- package/components/expanding-button-group/expanding-button-group.module.css +1 -1
- package/components/expanding-search/expanding-search.module.css +9 -9
- 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.module.css +9 -9
- package/components/floating-button-group/floating-button-group.module.css +6 -6
- package/components/gauge/gauge.module.css +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.module.css +8 -8
- 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.module.css +9 -9
- package/components/morph-select/morph-select.module.css +13 -13
- 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 +103 -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.module.css +2 -2
- package/components/otp-input/otp-input.module.css +4 -4
- package/components/password-field/password-field.module.css +1 -1
- package/components/password-strength/password-strength.module.css +7 -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.module.css +1 -1
- package/components/resizable-panels/resizable-panels.module.css +5 -5
- 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 +2 -2
- package/components/segmented-control/segmented-control.module.css +7 -4
- package/components/select/select.module.css +5 -5
- package/components/shortcut-recorder/shortcut-recorder.module.css +15 -15
- 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/sortable-data-table/sortable-data-table.module.css +4 -4
- package/components/split-button/split-button.module.css +5 -5
- package/components/stepper/stepper.module.css +3 -3
- package/components/streamgraph/streamgraph.module.css +7 -7
- package/components/swipe-actions/swipe-actions.module.css +5 -5
- package/components/switch/switch.module.css +1 -1
- package/components/tabs/tabs.d.ts +9 -2
- package/components/tabs/tabs.js +4 -2
- package/components/tabs/tabs.module.css +7 -2
- package/components/tag-input/tag-input.module.css +4 -4
- package/components/time-picker/time-picker.module.css +3 -3
- package/components/timeline/timeline.module.css +3 -3
- package/components/toast/toast.module.css +5 -5
- package/components/toast-stack/toast-stack.module.css +5 -5
- package/components/tooltip/tooltip.module.css +1 -1
- package/components/tree-view/tree-view.module.css +7 -7
- package/components/treemap/treemap.module.css +13 -13
- package/components/usage-meter/usage-meter.module.css +5 -5
- 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 +85 -0
- package/package.json +12 -2
- package/styles.css +44 -4
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { ChevronRight as NavArrowRight } from "lucide-react";
|
|
3
|
+
import { Ellipsis, ChevronRight as NavArrowRight } from "lucide-react";
|
|
4
4
|
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
|
|
5
|
+
import { DropdownMenuContent, DropdownMenuItem, DropdownMenuRoot, DropdownMenuTrigger } from "../dropdown-menu/dropdown-menu.js";
|
|
5
6
|
import { SiliconLink } from "../../lib/silicon-provider.js";
|
|
6
7
|
import { motionTokens } from "../../lib/motion-tokens.js";
|
|
7
8
|
import styles from "./breadcrumb.module.css";
|
|
8
|
-
/** Crumbs present on first render stay still; crumbs added later slide in from the path before them.
|
|
9
|
-
|
|
9
|
+
/** Crumbs present on first render stay still; crumbs added later slide in from the path before them.
|
|
10
|
+
* With maxItems, the levels a long path hides move into a "…" menu, where they stay links. */
|
|
11
|
+
export function Breadcrumb({ items, ariaLabel = "Breadcrumb", maxItems }) {
|
|
10
12
|
const reduced = useReducedMotion() ?? false;
|
|
11
13
|
const still = { duration: 0 };
|
|
12
14
|
const path = items.map(item => item.label).join("/");
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
// The first crumb and the current page always show. Crumbs keep their index in `items` as their key, so the ones
|
|
16
|
+
// that stay visible never remount when the path collapses or grows.
|
|
17
|
+
const shown = Math.max(2, Math.floor(maxItems ?? Infinity));
|
|
18
|
+
const hidden = items.length > shown ? items.slice(1, items.length - shown + 1) : [];
|
|
19
|
+
const crumb = (key, first, children) => _jsxs(motion.li, { layout: reduced ? false : "position", layoutDependency: path, initial: reduced ? false : { opacity: 0, x: -8, filter: `blur(${motionTokens.blur.subtle}px)` }, animate: { opacity: 1, x: 0, filter: "blur(0px)" }, exit: reduced ? { opacity: 0, transition: still } : { opacity: 0, x: -4, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] } }, transition: reduced ? still : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter], layout: motionTokens.spring.smooth }, children: [!first && _jsx(NavArrowRight, { width: 14, height: 14, "aria-hidden": "true" }), children] }, key);
|
|
20
|
+
const crumbs = items.map((item, index) => {
|
|
21
|
+
const current = index === items.length - 1;
|
|
22
|
+
return crumb(`${item.label}-${index}`, index === 0, !current && item.href ? _jsx(SiliconLink, { href: item.href, "data-label": item.label, onClick: item.onClick, children: item.label })
|
|
23
|
+
: !current && item.onClick ? _jsx("button", { type: "button", "data-label": item.label, onClick: item.onClick, children: item.label })
|
|
24
|
+
: _jsx("span", { "aria-current": current ? "page" : undefined, "data-label": item.label, children: item.label }));
|
|
25
|
+
});
|
|
26
|
+
if (hidden.length)
|
|
27
|
+
crumbs.splice(1, hidden.length, crumb("more", false, _jsxs(DropdownMenuRoot, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { type: "button", className: styles.more, "aria-label": `Show ${hidden.length} more ${hidden.length === 1 ? "level" : "levels"}`, children: _jsx(Ellipsis, { width: 16, height: 16, "aria-hidden": "true" }) }) }), _jsx(DropdownMenuContent, { align: "start", children: hidden.map((item, index) => item.href
|
|
28
|
+
? _jsx(DropdownMenuItem, { asChild: true, onClick: item.onClick, children: _jsx(SiliconLink, { href: item.href, children: item.label }) }, `${item.label}-${index + 1}`)
|
|
29
|
+
: _jsx(DropdownMenuItem, { disabled: !item.onClick, onClick: item.onClick, children: item.label }, `${item.label}-${index + 1}`)) })] })));
|
|
30
|
+
return _jsx("nav", { "aria-label": ariaLabel, children: _jsx("ol", { className: styles.list, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: crumbs }) }) });
|
|
19
31
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/* The gap between crumbs matches the gap inside one, so every chevron sits centered between its two labels. */
|
|
2
|
-
.list { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap:
|
|
3
|
-
.list li { display: inline-flex; align-items: center; gap:
|
|
2
|
+
.list { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-hair) var(--space-related); margin: 0; padding: 0; list-style: none; }
|
|
3
|
+
.list li { display: inline-flex; align-items: center; gap: var(--space-related); color: var(--text-muted); font-size: var(--text-sm); line-height: var(--leading-body); white-space: nowrap; }
|
|
4
4
|
.list li > svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--border-strong); stroke-width: 1.75; }
|
|
5
5
|
/* Each crumb reserves the width of its medium weight label, so becoming the current page never shifts the path. */
|
|
6
|
-
.list a, .list li > span, .list li > button { display: inline-flex; flex-direction: column; border-radius: var(--radius-detail); padding:
|
|
6
|
+
.list a, .list li > span, .list li > button { display: inline-flex; flex-direction: column; border-radius: var(--radius-detail); padding: var(--space-tight) var(--space-hair); }
|
|
7
7
|
.list a::after, .list li > span::after, .list li > button::after { height: 0; overflow: hidden; content: attr(data-label); font-weight: 500; visibility: hidden; user-select: none; pointer-events: none; }
|
|
8
8
|
.list li > button { border: 0; margin: 0; background: none; font: inherit; line-height: inherit; text-align: start; cursor: pointer; }
|
|
9
9
|
.list a, .list li > button { color: var(--text-secondary); text-decoration: underline 1px; text-decoration-color: transparent; text-underline-offset: 4px; transition: color var(--duration-fast) var(--ease-standard), text-decoration-color var(--duration-fast) var(--ease-standard); }
|
|
10
10
|
.list [aria-current="page"] { color: var(--accent-strong); font-weight: 500; transition: color var(--duration-fast) var(--ease-standard); }
|
|
11
|
+
/* The "…" of a collapsed path: a 24px square target, accented while its menu of hidden levels is open. */
|
|
12
|
+
.list .more { padding: var(--space-tight); }
|
|
13
|
+
.list .more svg { width: 16px; height: 16px; stroke-width: 1.75; }
|
|
14
|
+
.list .more[data-state="open"] { color: var(--accent-strong); }
|
|
11
15
|
@media (hover: hover) and (pointer: fine) { .list a:hover, .list li > button:hover { color: var(--accent-strong); text-decoration-color: color-mix(in oklab, currentColor 45%, transparent); } }
|
|
12
16
|
@media (prefers-reduced-motion: reduce) { .list a, .list li > button, .list [aria-current="page"] { transition: none; } }
|
|
@@ -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; }
|
|
@@ -70,7 +70,7 @@
|
|
|
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
72
|
.menu[data-state="closed"] { opacity: 0; transform: translate(calc(var(--menu-x) * .5), calc(var(--menu-y) * .5)) scale(.985); transition: opacity 130ms var(--ease-standard), transform 130ms var(--ease-standard); animation: menu-exit 130ms linear both; pointer-events: none; }
|
|
73
|
-
.item { display: flex; min-height: 36px; align-items: center; gap:
|
|
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,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,22 @@
|
|
|
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
18
|
.navButton:active:not([aria-disabled]), .todayButton:active:not([aria-disabled]) { scale: .96; transition-duration: var(--duration-fast), var(--duration-fast), var(--duration-fast), 100ms; 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
|
-
.grid { display: grid; row-gap:
|
|
27
|
+
.grid { display: grid; row-gap: var(--space-tight); }
|
|
28
28
|
.placeholderDay { aspect-ratio: 1; }
|
|
29
29
|
.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
30
|
/* The inner circle carries hover and press; the grid cell itself never moves, so the hit area stays put. */
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
.day.outside { color: var(--text-muted); }
|
|
40
40
|
.day.outside:not(.selected) .dayNumber { opacity: .8; }
|
|
41
41
|
/* 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% +
|
|
42
|
+
.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
43
|
.day.selected, .day.today { font-weight: 500; }
|
|
44
44
|
.day.selected { color: var(--accent-foreground); }
|
|
45
45
|
.day:disabled { cursor: default; color: var(--text-muted); }
|
|
@@ -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; }
|
|
@@ -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; }
|
|
@@ -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
|
|
|
@@ -14,32 +14,32 @@
|
|
|
14
14
|
.scroller { min-height: 0; flex: 1; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-gutter: stable; }
|
|
15
15
|
/* The content springs up from where it was when the thread grows; the clip keeps that travel out of the scroll range. */
|
|
16
16
|
.track { overflow: clip; }
|
|
17
|
-
.content { padding:
|
|
17
|
+
.content { padding: var(--space-group) var(--space-inset) var(--space-inset); will-change: transform; }
|
|
18
18
|
|
|
19
19
|
.list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
|
|
20
20
|
|
|
21
|
-
.day { display: flex; align-items: center; gap:
|
|
21
|
+
.day { display: flex; align-items: center; gap: var(--space-group); padding: var(--space-comfort) var(--space-tight) var(--space-tight); color: var(--text-muted); font-size: var(--text-xs); font-weight: 500; }
|
|
22
22
|
.day::before, .day::after { height: 1px; flex: 1; background: var(--border-subtle); content: ""; }
|
|
23
|
-
.day:first-child { padding-top:
|
|
23
|
+
.day:first-child { padding-top: var(--space-tight); }
|
|
24
24
|
.day > span:empty::before { content: "\00a0"; }
|
|
25
25
|
|
|
26
|
-
.row { position: relative; display: flex; align-items: flex-end; gap:
|
|
27
|
-
.row[data-first] { margin-top:
|
|
28
|
-
.day + .row { margin-top:
|
|
26
|
+
.row { position: relative; display: flex; align-items: flex-end; gap: var(--space-related); margin-top: var(--space-hair); }
|
|
27
|
+
.row[data-first] { margin-top: var(--space-inset); }
|
|
28
|
+
.day + .row { margin-top: var(--space-group); }
|
|
29
29
|
.row[data-mine] { justify-content: flex-end; }
|
|
30
30
|
|
|
31
31
|
.gutter { width: 28px; flex: none; align-self: flex-end; }
|
|
32
|
-
.stack { display: flex; min-width: 0; max-width: min(78%, 34rem); flex-direction: column; align-items: flex-start; gap:
|
|
32
|
+
.stack { display: flex; min-width: 0; max-width: min(78%, 34rem); flex-direction: column; align-items: flex-start; gap: var(--space-tight); }
|
|
33
33
|
.row[data-mine] .stack { align-items: flex-end; }
|
|
34
34
|
|
|
35
|
-
.meta { display: flex; align-items: baseline; gap:
|
|
35
|
+
.meta { display: flex; align-items: baseline; gap: var(--space-related); margin: 0 0 var(--space-hair); padding: 0 var(--space-group); color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
|
|
36
36
|
.author { color: var(--text-secondary); font-weight: 500; }
|
|
37
37
|
|
|
38
|
-
.line { position: relative; display: flex; max-width: 100%; align-items: center; gap:
|
|
38
|
+
.line { position: relative; display: flex; max-width: 100%; align-items: center; gap: var(--space-related); }
|
|
39
39
|
/* The author's face sits in the gutter, level with the bottom of their last bubble rather than the reactions under it. */
|
|
40
|
-
.face { position: absolute; bottom: 0; left: -
|
|
40
|
+
.face { position: absolute; bottom: 0; left: calc(var(--space-block) * -1); display: flex; }
|
|
41
41
|
.row[data-mine] .line { flex-direction: row-reverse; }
|
|
42
|
-
.parts { display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap:
|
|
42
|
+
.parts { display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap: var(--space-tight); }
|
|
43
43
|
.row[data-mine] .parts { align-items: flex-end; }
|
|
44
44
|
|
|
45
45
|
/* Bubbles in a group share a soft edge on the author's side, so a run of messages reads as one voice. */
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
--r: 18px;
|
|
48
48
|
--tight: 6px;
|
|
49
49
|
max-width: 100%;
|
|
50
|
-
padding:
|
|
50
|
+
padding: var(--space-related) var(--space-group) var(--space-related);
|
|
51
51
|
border-radius: var(--r);
|
|
52
52
|
background: var(--surface-muted);
|
|
53
53
|
color: var(--foreground);
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
.row[data-mine]:not([data-last]) .bubble { border-bottom-right-radius: var(--tight); }
|
|
64
64
|
.row[data-failed] .bubble { opacity: .6; }
|
|
65
65
|
|
|
66
|
-
.gallery { display: grid; width: min(240px, 100%); gap:
|
|
66
|
+
.gallery { display: grid; width: min(240px, 100%); gap: var(--space-tight); grid-template-columns: 1fr; }
|
|
67
67
|
.gallery[data-count="2"],
|
|
68
68
|
.gallery[data-count="3"],
|
|
69
69
|
.gallery[data-count="4"] { grid-template-columns: 1fr 1fr; }
|
|
@@ -73,10 +73,10 @@
|
|
|
73
73
|
.gallery:not([data-count="1"]) .image { border-radius: var(--radius-detail); corner-shape: var(--corner-shape); }
|
|
74
74
|
.gallery:not([data-count="1"]) { overflow: hidden; border-radius: var(--radius-control); corner-shape: var(--corner-shape); }
|
|
75
75
|
.image img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
76
|
-
.more { position: absolute; inset: 0; display: grid; place-items: center; background:
|
|
76
|
+
.more { position: absolute; inset: 0; display: grid; place-items: center; background: var(--media-scrim); color: var(--on-media); font-size: var(--text-lg); font-weight: 500; }
|
|
77
77
|
|
|
78
78
|
.file {
|
|
79
|
-
display: flex; max-width: 100%; align-items: center; gap:
|
|
79
|
+
display: flex; max-width: 100%; align-items: center; gap: var(--space-group); padding: var(--space-related) var(--space-inset) var(--space-related) var(--space-group);
|
|
80
80
|
border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); color: var(--foreground); text-decoration: none;
|
|
81
81
|
transition: background-color var(--duration-fast) var(--ease-standard);
|
|
82
82
|
}
|
|
@@ -103,17 +103,17 @@
|
|
|
103
103
|
@media (hover: none) { .reactButton { opacity: .7; } }
|
|
104
104
|
|
|
105
105
|
.picker {
|
|
106
|
-
position: absolute; z-index: 5; bottom: calc(100% +
|
|
107
|
-
display: flex; gap:
|
|
106
|
+
position: absolute; z-index: 5; bottom: calc(100% + var(--space-related)); right: calc(var(--space-tight) * -1);
|
|
107
|
+
display: flex; gap: var(--space-hair); padding: var(--space-tight);
|
|
108
108
|
border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); box-shadow: var(--shadow-floating);
|
|
109
109
|
transform-origin: calc(100% - 18px) 100%;
|
|
110
110
|
}
|
|
111
|
-
.picker[data-mine] { right: auto; left: -
|
|
112
|
-
.pickerWrap[data-below] .picker { top: calc(100% +
|
|
111
|
+
.picker[data-mine] { right: auto; left: calc(var(--space-tight) * -1); transform-origin: 18px 100%; }
|
|
112
|
+
.pickerWrap[data-below] .picker { top: calc(100% + var(--space-related)); bottom: auto; transform-origin: calc(100% - 18px) 0; }
|
|
113
113
|
.pickerWrap[data-below] .picker[data-mine] { transform-origin: 18px 0; }
|
|
114
114
|
.emoji {
|
|
115
115
|
display: grid; width: 34px; height: 34px; place-items: center; padding: 0;
|
|
116
|
-
border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: pointer; font-size:
|
|
116
|
+
border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: pointer; font-size: var(--text-lg); line-height: 1;
|
|
117
117
|
-webkit-tap-highlight-color: transparent;
|
|
118
118
|
transition: background-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
|
|
119
119
|
}
|
|
@@ -121,31 +121,31 @@
|
|
|
121
121
|
.emoji:active { transform: scale(.9); }
|
|
122
122
|
@media (hover: hover) and (pointer: fine) { .emoji:hover { background: var(--surface-muted); transform: scale(1.12); } }
|
|
123
123
|
|
|
124
|
-
.reactionClip { overflow: hidden; margin: -
|
|
125
|
-
.row[data-mine] .reactionClip { padding:
|
|
126
|
-
.reactions { display: flex; flex-wrap: wrap; gap:
|
|
124
|
+
.reactionClip { overflow: hidden; margin: calc(var(--space-hair) * -1) calc(var(--space-tight) * -1); padding: var(--space-hair) var(--space-tight) var(--space-hair) var(--space-related); }
|
|
125
|
+
.row[data-mine] .reactionClip { padding: var(--space-hair) var(--space-related) var(--space-hair) var(--space-tight); }
|
|
126
|
+
.reactions { display: flex; flex-wrap: wrap; gap: var(--space-tight); margin: 0; padding: 0; list-style: none; }
|
|
127
127
|
.row[data-mine] .reactions { justify-content: flex-end; }
|
|
128
128
|
.reaction {
|
|
129
|
-
display: inline-flex; height: 26px; align-items: center; gap:
|
|
129
|
+
display: inline-flex; height: 26px; align-items: center; gap: var(--space-tight); padding: 0 var(--space-related) 0 var(--space-related);
|
|
130
130
|
border: 1px solid var(--border-subtle); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); color: var(--text-secondary); cursor: pointer;
|
|
131
131
|
font: inherit; font-size: var(--text-xs); font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums;
|
|
132
132
|
-webkit-tap-highlight-color: transparent;
|
|
133
133
|
transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
|
|
134
134
|
}
|
|
135
|
-
.reaction > span:first-child { font-size:
|
|
135
|
+
.reaction > span:first-child { font-size: var(--text-sm); }
|
|
136
136
|
.reaction[aria-pressed="true"] { border-color: color-mix(in oklab, var(--accent) 28%, transparent); background: color-mix(in oklab, var(--accent) 8%, var(--surface)); color: var(--foreground); }
|
|
137
137
|
@media (hover: hover) and (pointer: fine) { .reaction:hover { border-color: var(--border-strong); } }
|
|
138
138
|
.reaction:active { transform: scale(.95); }
|
|
139
139
|
.countRoll { display: inline-grid; overflow: hidden; font-variant-numeric: tabular-nums; }
|
|
140
140
|
.countRoll > span { grid-area: 1 / 1; }
|
|
141
141
|
|
|
142
|
-
.receipts { display: flex; min-height: 16px; align-items: center; gap:
|
|
142
|
+
.receipts { display: flex; min-height: 16px; align-items: center; gap: var(--space-related); padding: 1px var(--space-related) 0; color: var(--text-muted); font-size: var(--text-xs); }
|
|
143
143
|
.readers { display: inline-flex; }
|
|
144
144
|
.reader { display: inline-flex; border-radius: 50%; corner-shape: round; box-shadow: 0 0 0 2px var(--surface); }
|
|
145
|
-
.reader + .reader { margin-left: -
|
|
146
|
-
.status[data-status="failed"] { display: inline-flex; align-items: center; gap:
|
|
145
|
+
.reader + .reader { margin-left: calc(var(--space-tight) * -1); }
|
|
146
|
+
.status[data-status="failed"] { display: inline-flex; align-items: center; gap: var(--space-related); color: var(--danger); }
|
|
147
147
|
.retry {
|
|
148
|
-
display: inline-flex; align-items: center; gap:
|
|
148
|
+
display: inline-flex; align-items: center; gap: var(--space-tight); padding: var(--space-hair) var(--space-related);
|
|
149
149
|
border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); color: var(--foreground); cursor: pointer;
|
|
150
150
|
font: inherit; font-weight: 500; line-height: 16px;
|
|
151
151
|
-webkit-tap-highlight-color: transparent;
|
|
@@ -157,8 +157,8 @@
|
|
|
157
157
|
.avatar { display: inline-grid; flex: none; overflow: hidden; place-items: center; border-radius: 50%; corner-shape: round; background: var(--surface-muted); color: var(--text-secondary); font-weight: 500; }
|
|
158
158
|
.avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
159
159
|
|
|
160
|
-
.typing { display: grid; height: 36px; box-sizing: border-box; place-items: center; padding: 0
|
|
161
|
-
.dots { display: inline-flex; gap:
|
|
160
|
+
.typing { display: grid; height: 36px; box-sizing: border-box; place-items: center; padding: 0 var(--space-inset); }
|
|
161
|
+
.dots { display: inline-flex; gap: var(--space-tight); }
|
|
162
162
|
.dots i { width: 6px; height: 6px; border-radius: 50%; corner-shape: round; background: var(--text-muted); animation: breathe 1.2s var(--ease-in-out) infinite; }
|
|
163
163
|
.dots i:nth-child(2) { animation-delay: .16s; }
|
|
164
164
|
.dots i:nth-child(3) { animation-delay: .32s; }
|
|
@@ -169,8 +169,8 @@
|
|
|
169
169
|
@media (prefers-reduced-motion: reduce) { .dots i { animation: none; opacity: .6; } }
|
|
170
170
|
|
|
171
171
|
.pill {
|
|
172
|
-
position: absolute; z-index: 6; bottom:
|
|
173
|
-
display: inline-flex; height: 32px; align-items: center; gap:
|
|
172
|
+
position: absolute; z-index: 6; bottom: var(--space-group); left: 50%; translate: -50% 0;
|
|
173
|
+
display: inline-flex; height: 32px; align-items: center; gap: var(--space-related); padding: 0 var(--space-inset) 0 var(--space-group);
|
|
174
174
|
border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-raised); box-shadow: var(--shadow-floating); color: var(--foreground); cursor: pointer;
|
|
175
175
|
font: inherit; font-size: var(--text-xs); font-weight: 500; white-space: nowrap;
|
|
176
176
|
-webkit-tap-highlight-color: transparent;
|
|
@@ -180,22 +180,22 @@
|
|
|
180
180
|
.pill:active { scale: .97; }
|
|
181
181
|
|
|
182
182
|
/* Composer. */
|
|
183
|
-
.composer { display: grid; padding:
|
|
183
|
+
.composer { display: grid; padding: var(--space-group) var(--space-group) var(--space-group); border-top: 1px solid var(--border-subtle); transition: background-color var(--duration-fast) var(--ease-standard); }
|
|
184
184
|
.composer[data-dragging] { background: var(--accent-subtle); }
|
|
185
185
|
.trayClip { overflow: hidden; }
|
|
186
|
-
.tray { display: flex; gap:
|
|
186
|
+
.tray { display: flex; gap: var(--space-related); margin: 0; padding: var(--space-related) var(--space-related) var(--space-group) var(--space-tight); overflow-x: auto; list-style: none; scrollbar-width: none; }
|
|
187
187
|
.pending { position: relative; flex: none; }
|
|
188
188
|
.pendingImage { display: block; width: 56px; height: 56px; border-radius: var(--radius-control); corner-shape: var(--corner-shape); object-fit: cover; }
|
|
189
|
-
.pendingFile { display: grid; width: 168px; height: 56px; box-sizing: border-box; align-content: center; column-gap:
|
|
189
|
+
.pendingFile { display: grid; width: 168px; height: 56px; box-sizing: border-box; align-content: center; column-gap: var(--space-related); padding: 0 var(--space-group); border: 1px solid var(--border); border-radius: var(--radius-control); corner-shape: var(--corner-shape); background: var(--surface); grid-template-columns: auto minmax(0, 1fr); }
|
|
190
190
|
.pendingFile > svg { grid-row: span 2; align-self: center; color: var(--text-secondary); }
|
|
191
191
|
.pendingName { overflow: hidden; font-size: var(--text-xs); font-weight: 500; text-overflow: ellipsis; white-space: nowrap; }
|
|
192
192
|
.pendingSize { color: var(--text-muted); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
|
|
193
193
|
.remove {
|
|
194
|
-
position: absolute; top: -
|
|
194
|
+
position: absolute; top: calc(var(--space-related) * -1); right: calc(var(--space-related) * -1); display: grid; width: 20px; height: 20px; place-items: center; padding: 0;
|
|
195
195
|
border: 2px solid var(--surface); border-radius: 50%; corner-shape: round; background: var(--foreground); color: var(--background); cursor: pointer;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
.inputRow { display: flex; align-items: flex-end; gap:
|
|
198
|
+
.inputRow { display: flex; align-items: flex-end; gap: var(--space-related); }
|
|
199
199
|
.attach {
|
|
200
200
|
display: grid; width: 40px; height: 40px; flex: none; place-items: center; margin-bottom: 1px; padding: 0;
|
|
201
201
|
border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; color: var(--text-secondary); cursor: pointer;
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
.field { position: relative; min-width: 0; flex: 1; overflow: hidden; border-radius: var(--radius-control); corner-shape: var(--corner-shape); }
|
|
217
217
|
.mirror,
|
|
218
218
|
.textarea {
|
|
219
|
-
box-sizing: border-box; width: 100%; margin: 0; padding:
|
|
219
|
+
box-sizing: border-box; width: 100%; margin: 0; padding: var(--space-related) var(--space-related) var(--space-related) var(--space-inset); border: 0;
|
|
220
220
|
font: inherit; font-size: var(--text-base); letter-spacing: var(--tracking-body); line-height: 22px;
|
|
221
221
|
overflow-wrap: break-word; white-space: pre-wrap; word-break: break-word;
|
|
222
222
|
}
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
.textarea::placeholder { color: var(--text-muted); }
|
|
226
226
|
|
|
227
227
|
.send {
|
|
228
|
-
position: relative; display: grid; width: 32px; height: 32px; flex: none; place-items: center; margin: 0
|
|
228
|
+
position: relative; display: grid; width: 32px; height: 32px; flex: none; place-items: center; margin: 0 var(--space-tight) var(--space-tight) 0; overflow: hidden; padding: 0;
|
|
229
229
|
border: 0; border-radius: 50%; corner-shape: round; background: transparent; color: var(--text-muted); cursor: default;
|
|
230
230
|
-webkit-tap-highlight-color: transparent;
|
|
231
231
|
transition: background-color var(--duration-standard) var(--ease-standard), color var(--duration-standard) var(--ease-standard), transform var(--duration-instant) var(--ease-standard);
|
|
@@ -237,6 +237,6 @@
|
|
|
237
237
|
.srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
238
238
|
|
|
239
239
|
@media (max-width: 420px) {
|
|
240
|
-
.content { padding-inline:
|
|
240
|
+
.content { padding-inline: var(--space-group); }
|
|
241
241
|
.stack { max-width: 84%; }
|
|
242
242
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
.box:active:not(:disabled) .visual { transform: scale(.95); transition: border-color var(--duration-fast) var(--ease-standard), transform var(--duration-instant) var(--ease-standard); }
|
|
10
10
|
.box:disabled { opacity: .5; cursor: not-allowed; }
|
|
11
11
|
/* The label's first line centers on the square: (44px box - 19.6px line) / 2. */
|
|
12
|
-
.copy { display: grid; gap:
|
|
12
|
+
.copy { display: grid; gap: var(--space-hair); min-width: 0; margin-left: 1px; padding-top: calc((var(--control-height-md) - var(--text-sm) * 1.4) / 2); }
|
|
13
13
|
.copy label { color: var(--foreground); font-size: var(--text-sm); font-weight: 500; line-height: var(--leading-body); cursor: pointer; }
|
|
14
14
|
.copy span { color: var(--text-muted); font-size: var(--text-xs); line-height: var(--leading-body); }
|
|
15
15
|
@media (prefers-reduced-motion: reduce) { .visual, .box:active:not(:disabled) .visual { transition: none; transform: none; } }
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
/* The button is only the hit area and the layout box. What you see is the surface, which trails the box on a spring while the width morphs. */
|
|
7
7
|
.chip { position: relative; display: inline-flex; max-width: 100%; padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round; background: none; color: var(--text-secondary); font: inherit; font-size: var(--text-sm); font-weight: 500; letter-spacing: var(--tracking-body); line-height: 20px; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color var(--duration-fast) var(--ease-standard); }
|
|
8
8
|
/* Press answers on the body with the individual scale property, so it never fights the layout transform on the button. */
|
|
9
|
-
.body { position: relative; isolation: isolate; display: inline-flex; min-width: 0; height: var(--control-height-sm); align-items: center; padding: 0
|
|
9
|
+
.body { position: relative; isolation: isolate; display: inline-flex; min-width: 0; height: var(--control-height-sm); align-items: center; padding: 0 var(--space-inset); transition: scale var(--duration-spring) var(--ease-spring); }
|
|
10
10
|
.chip:active .body { scale: .97; transition-duration: var(--duration-instant); transition-timing-function: var(--ease-standard); }
|
|
11
11
|
.surface { position: absolute; z-index: -1; top: 0; bottom: 0; left: 0; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface); transition: background-color var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard); }
|
|
12
12
|
/* The check grows from its left edge where the label started; the slot hands its width to the label in one layout step, and the label's offset springs it over. */
|
|
13
|
-
.check { position: absolute; top: calc(50% -
|
|
13
|
+
.check { position: absolute; top: calc(50% - var(--space-related)); left: var(--space-group); display: grid; width: 14px; height: 14px; place-items: center; color: var(--accent-strong); transform-origin: 0 50%; }
|
|
14
14
|
.check svg { display: block; overflow: visible; }
|
|
15
15
|
.slot { width: 0; flex: none; }
|
|
16
16
|
.body[data-selected="true"] .slot { width: 18px; }
|