premium-ds 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/alert.d.ts +31 -0
- package/dist/alert.js +6 -0
- package/dist/alert.js.map +1 -0
- package/dist/avatar-group.d.ts +13 -0
- package/dist/avatar-group.js +3 -0
- package/dist/avatar-group.js.map +1 -0
- package/dist/avatar.d.ts +25 -0
- package/dist/avatar.js +3 -0
- package/dist/avatar.js.map +1 -0
- package/dist/badge.d.ts +23 -0
- package/dist/badge.js +3 -0
- package/dist/badge.js.map +1 -0
- package/dist/button.d.ts +20 -0
- package/dist/button.js +3 -0
- package/dist/button.js.map +1 -0
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +3 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-2OWHZ4JT.js +36 -0
- package/dist/chunk-2OWHZ4JT.js.map +1 -0
- package/dist/chunk-34SIXSYL.js +64 -0
- package/dist/chunk-34SIXSYL.js.map +1 -0
- package/dist/chunk-37O2ZXD6.js +55 -0
- package/dist/chunk-37O2ZXD6.js.map +1 -0
- package/dist/chunk-4AZL76UJ.js +89 -0
- package/dist/chunk-4AZL76UJ.js.map +1 -0
- package/dist/chunk-4HSCN5TZ.js +86 -0
- package/dist/chunk-4HSCN5TZ.js.map +1 -0
- package/dist/chunk-5DDOOT33.js +258 -0
- package/dist/chunk-5DDOOT33.js.map +1 -0
- package/dist/chunk-5FVHWIMY.js +117 -0
- package/dist/chunk-5FVHWIMY.js.map +1 -0
- package/dist/chunk-5K6KRJGX.js +147 -0
- package/dist/chunk-5K6KRJGX.js.map +1 -0
- package/dist/chunk-5PQMQBQC.js +74 -0
- package/dist/chunk-5PQMQBQC.js.map +1 -0
- package/dist/chunk-7OCTVQ7C.js +95 -0
- package/dist/chunk-7OCTVQ7C.js.map +1 -0
- package/dist/chunk-7OPMOET7.js +39 -0
- package/dist/chunk-7OPMOET7.js.map +1 -0
- package/dist/chunk-BXXS7YRC.js +270 -0
- package/dist/chunk-BXXS7YRC.js.map +1 -0
- package/dist/chunk-CV2Q4YXX.js +272 -0
- package/dist/chunk-CV2Q4YXX.js.map +1 -0
- package/dist/chunk-EIMMDWIW.js +282 -0
- package/dist/chunk-EIMMDWIW.js.map +1 -0
- package/dist/chunk-EZ2CWTBE.js +230 -0
- package/dist/chunk-EZ2CWTBE.js.map +1 -0
- package/dist/chunk-FGHDG3Y4.js +89 -0
- package/dist/chunk-FGHDG3Y4.js.map +1 -0
- package/dist/chunk-FPP2XLKX.js +127 -0
- package/dist/chunk-FPP2XLKX.js.map +1 -0
- package/dist/chunk-G6OY35DI.js +295 -0
- package/dist/chunk-G6OY35DI.js.map +1 -0
- package/dist/chunk-H6KWJNOE.js +65 -0
- package/dist/chunk-H6KWJNOE.js.map +1 -0
- package/dist/chunk-HGILYGY3.js +45 -0
- package/dist/chunk-HGILYGY3.js.map +1 -0
- package/dist/chunk-I3BCB4Z5.js +88 -0
- package/dist/chunk-I3BCB4Z5.js.map +1 -0
- package/dist/chunk-KBWNUUWM.js +582 -0
- package/dist/chunk-KBWNUUWM.js.map +1 -0
- package/dist/chunk-KN7JFAZ6.js +113 -0
- package/dist/chunk-KN7JFAZ6.js.map +1 -0
- package/dist/chunk-MEF7PI6U.js +16 -0
- package/dist/chunk-MEF7PI6U.js.map +1 -0
- package/dist/chunk-NKGMQL6I.js +310 -0
- package/dist/chunk-NKGMQL6I.js.map +1 -0
- package/dist/chunk-NMFQRGLL.js +127 -0
- package/dist/chunk-NMFQRGLL.js.map +1 -0
- package/dist/chunk-OUBWD6CX.js +433 -0
- package/dist/chunk-OUBWD6CX.js.map +1 -0
- package/dist/chunk-PFNXVBLU.js +96 -0
- package/dist/chunk-PFNXVBLU.js.map +1 -0
- package/dist/chunk-PUPZ4HME.js +165 -0
- package/dist/chunk-PUPZ4HME.js.map +1 -0
- package/dist/chunk-QFS52OK5.js +690 -0
- package/dist/chunk-QFS52OK5.js.map +1 -0
- package/dist/chunk-QNC6O3PG.js +45 -0
- package/dist/chunk-QNC6O3PG.js.map +1 -0
- package/dist/chunk-QUHOXWBK.js +82 -0
- package/dist/chunk-QUHOXWBK.js.map +1 -0
- package/dist/chunk-UIQGSTBJ.js +106 -0
- package/dist/chunk-UIQGSTBJ.js.map +1 -0
- package/dist/chunk-UJQKVP6V.js +193 -0
- package/dist/chunk-UJQKVP6V.js.map +1 -0
- package/dist/chunk-VVPGEAC6.js +11 -0
- package/dist/chunk-VVPGEAC6.js.map +1 -0
- package/dist/chunk-XA3T5KWA.js +58 -0
- package/dist/chunk-XA3T5KWA.js.map +1 -0
- package/dist/chunk-YSHJHSJM.js +19 -0
- package/dist/chunk-YSHJHSJM.js.map +1 -0
- package/dist/chunk-YVHOAVSM.js +182 -0
- package/dist/chunk-YVHOAVSM.js.map +1 -0
- package/dist/collapse.d.ts +16 -0
- package/dist/collapse.js +3 -0
- package/dist/collapse.js.map +1 -0
- package/dist/count-badge.d.ts +11 -0
- package/dist/count-badge.js +4 -0
- package/dist/count-badge.js.map +1 -0
- package/dist/date-field.d.ts +39 -0
- package/dist/date-field.js +8 -0
- package/dist/date-field.js.map +1 -0
- package/dist/date-range-field.d.ts +30 -0
- package/dist/date-range-field.js +8 -0
- package/dist/date-range-field.js.map +1 -0
- package/dist/datetime-field.d.ts +28 -0
- package/dist/datetime-field.js +10 -0
- package/dist/datetime-field.js.map +1 -0
- package/dist/dialog.d.ts +26 -0
- package/dist/dialog.js +7 -0
- package/dist/dialog.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/motion-tokens.d.ts +29 -0
- package/dist/motion-tokens.js +3 -0
- package/dist/motion-tokens.js.map +1 -0
- package/dist/multi-select.d.ts +25 -0
- package/dist/multi-select.js +7 -0
- package/dist/multi-select.js.map +1 -0
- package/dist/number-field.d.ts +24 -0
- package/dist/number-field.js +4 -0
- package/dist/number-field.js.map +1 -0
- package/dist/otp-field.d.ts +20 -0
- package/dist/otp-field.js +3 -0
- package/dist/otp-field.js.map +1 -0
- package/dist/overlay.d.ts +31 -0
- package/dist/overlay.js +4 -0
- package/dist/overlay.js.map +1 -0
- package/dist/pagination.d.ts +24 -0
- package/dist/pagination.js +5 -0
- package/dist/pagination.js.map +1 -0
- package/dist/radio-group.d.ts +46 -0
- package/dist/radio-group.js +6 -0
- package/dist/radio-group.js.map +1 -0
- package/dist/select-core-SAyS-8w0.d.ts +16 -0
- package/dist/select.d.ts +27 -0
- package/dist/select.js +7 -0
- package/dist/select.js.map +1 -0
- package/dist/status-badge.d.ts +17 -0
- package/dist/status-badge.js +5 -0
- package/dist/status-badge.js.map +1 -0
- package/dist/table.d.ts +65 -0
- package/dist/table.js +5 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.d.ts +44 -0
- package/dist/tabs.js +5 -0
- package/dist/tabs.js.map +1 -0
- package/dist/tag.d.ts +28 -0
- package/dist/tag.js +5 -0
- package/dist/tag.js.map +1 -0
- package/dist/text-field.d.ts +30 -0
- package/dist/text-field.js +6 -0
- package/dist/text-field.js.map +1 -0
- package/dist/textarea.d.ts +33 -0
- package/dist/textarea.js +5 -0
- package/dist/textarea.js.map +1 -0
- package/dist/time-field.d.ts +27 -0
- package/dist/time-field.js +6 -0
- package/dist/time-field.js.map +1 -0
- package/dist/toast-store.d.ts +75 -0
- package/dist/toast-store.js +3 -0
- package/dist/toast-store.js.map +1 -0
- package/dist/toast.d.ts +3 -0
- package/dist/toast.js +6 -0
- package/dist/toast.js.map +1 -0
- package/dist/toggle-tag.d.ts +24 -0
- package/dist/toggle-tag.js +4 -0
- package/dist/toggle-tag.js.map +1 -0
- package/dist/toggle.d.ts +21 -0
- package/dist/toggle.js +3 -0
- package/dist/toggle.js.map +1 -0
- package/dist/tooltip.d.ts +27 -0
- package/dist/tooltip.js +4 -0
- package/dist/tooltip.js.map +1 -0
- package/llms.txt +165 -0
- package/package.json +205 -0
- package/src/components/alert/Alert.tsx +118 -0
- package/src/components/alert/alert.css +136 -0
- package/src/components/avatar/Avatar.tsx +128 -0
- package/src/components/avatar/AvatarGroup.tsx +50 -0
- package/src/components/avatar/avatar.css +200 -0
- package/src/components/badge/Badge.tsx +66 -0
- package/src/components/badge/CountBadge.tsx +46 -0
- package/src/components/badge/StatusBadge.tsx +132 -0
- package/src/components/badge/badge.css +243 -0
- package/src/components/button/Button.tsx +68 -0
- package/src/components/button/button.css +222 -0
- package/src/components/checkbox/Checkbox.tsx +90 -0
- package/src/components/checkbox/checkbox.css +179 -0
- package/src/components/date-picker/DateField.tsx +362 -0
- package/src/components/date-picker/DateRangeField.tsx +533 -0
- package/src/components/date-picker/DateTimeField.tsx +177 -0
- package/src/components/date-picker/TimeField.tsx +100 -0
- package/src/components/date-picker/date-picker.css +591 -0
- package/src/components/date-picker/date-utils.ts +55 -0
- package/src/components/date-picker/field-shell.tsx +78 -0
- package/src/components/date-picker/glide-pill.tsx +81 -0
- package/src/components/date-picker/time-core.tsx +305 -0
- package/src/components/dialog/Dialog.tsx +181 -0
- package/src/components/dialog/dialog.css +170 -0
- package/src/components/glass/glass.css +100 -0
- package/src/components/icon/Icon.tsx +76 -0
- package/src/components/icon/IconSlot.tsx +11 -0
- package/src/components/icon/icon.css +33 -0
- package/src/components/input/NumberField.tsx +117 -0
- package/src/components/input/OtpField.tsx +118 -0
- package/src/components/input/TextField.tsx +123 -0
- package/src/components/input/input.css +335 -0
- package/src/components/motion/Collapse.tsx +33 -0
- package/src/components/motion/collapse.css +41 -0
- package/src/components/overlay/Overlay.tsx +239 -0
- package/src/components/overlay/overlay-core.tsx +565 -0
- package/src/components/overlay/overlay.css +119 -0
- package/src/components/overlay/sheet-drag.tsx +146 -0
- package/src/components/pagination/Pagination.tsx +140 -0
- package/src/components/pagination/pagination.css +48 -0
- package/src/components/radio-group/RadioGroup.tsx +182 -0
- package/src/components/radio-group/radio-group.css +277 -0
- package/src/components/select/MultiSelect.tsx +251 -0
- package/src/components/select/Select.tsx +235 -0
- package/src/components/select/select-core.tsx +417 -0
- package/src/components/select/select.css +386 -0
- package/src/components/table/Table.tsx +433 -0
- package/src/components/table/table.css +348 -0
- package/src/components/tabs/Tabs.tsx +371 -0
- package/src/components/tabs/tabs.css +228 -0
- package/src/components/tag/Tag.tsx +145 -0
- package/src/components/tag/ToggleTag.tsx +125 -0
- package/src/components/tag/tag.css +248 -0
- package/src/components/textarea/Textarea.tsx +197 -0
- package/src/components/textarea/textarea.css +219 -0
- package/src/components/toast/Toast.tsx +349 -0
- package/src/components/toast/toast-store.ts +266 -0
- package/src/components/toast/toast.css +233 -0
- package/src/components/toggle/Toggle.tsx +94 -0
- package/src/components/toggle/toggle.css +152 -0
- package/src/components/tooltip/Tooltip.tsx +365 -0
- package/src/components/tooltip/tooltip.css +86 -0
- package/src/index.ts +42 -0
- package/src/styles.css +39 -0
- package/src/tokens/avatar.css +20 -0
- package/src/tokens/color.css +56 -0
- package/src/tokens/elevation.css +20 -0
- package/src/tokens/fonts.css +3 -0
- package/src/tokens/glass.css +21 -0
- package/src/tokens/icons.css +7 -0
- package/src/tokens/layers.css +6 -0
- package/src/tokens/motion-tokens.ts +72 -0
- package/src/tokens/motion.css +49 -0
- package/src/tokens/radius.css +11 -0
- package/src/tokens/semantic.css +75 -0
- package/src/tokens/spacing.css +26 -0
- package/src/tokens/typography.css +54 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/* Tooltip - a transient, non-interactive hint on hover/focus (one shared bubble). */
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import * as ReactDOM from 'react-dom';
|
|
6
|
+
import { createRoot } from 'react-dom/client';
|
|
7
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
8
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
9
|
+
import { UIMotion } from '../../tokens/motion-tokens';
|
|
10
|
+
|
|
11
|
+
const SM = UIMotion;
|
|
12
|
+
const { useRef, useEffect, useLayoutEffect, useState, useId, useSyncExternalStore } = React;
|
|
13
|
+
|
|
14
|
+
type Placement = 'top' | 'bottom' | 'left' | 'right';
|
|
15
|
+
|
|
16
|
+
interface ActivePayload {
|
|
17
|
+
id: string;
|
|
18
|
+
content: ReactNode;
|
|
19
|
+
shortcut?: string | null;
|
|
20
|
+
placement: Placement;
|
|
21
|
+
rect: () => DOMRect;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Size {
|
|
25
|
+
w: number;
|
|
26
|
+
h: number;
|
|
27
|
+
}
|
|
28
|
+
interface TargetBox extends Size {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
placement: Placement;
|
|
32
|
+
}
|
|
33
|
+
interface RenderedBox extends TargetBox {
|
|
34
|
+
bodyW: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const OPEN_DELAY = 350;
|
|
38
|
+
const CLOSE_GRACE = 140;
|
|
39
|
+
const WARM_WINDOW = 300;
|
|
40
|
+
|
|
41
|
+
const TIP_GAP =
|
|
42
|
+
typeof document !== 'undefined'
|
|
43
|
+
? parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--space-2')) || 8
|
|
44
|
+
: 8;
|
|
45
|
+
|
|
46
|
+
/* Store - what's showing, where; triggers write, the host renders. */
|
|
47
|
+
const store = {
|
|
48
|
+
active: null as ActivePayload | null,
|
|
49
|
+
closeTimer: 0 as ReturnType<typeof setTimeout> | 0,
|
|
50
|
+
warmUntil: 0,
|
|
51
|
+
listeners: new Set<() => void>(),
|
|
52
|
+
get: () => store.active,
|
|
53
|
+
subscribe(l: () => void) {
|
|
54
|
+
store.listeners.add(l);
|
|
55
|
+
return () => store.listeners.delete(l);
|
|
56
|
+
},
|
|
57
|
+
emit() {
|
|
58
|
+
store.listeners.forEach((l) => l());
|
|
59
|
+
},
|
|
60
|
+
isWarm: () => store.active !== null || performance.now() < store.warmUntil,
|
|
61
|
+
open(payload: ActivePayload) {
|
|
62
|
+
clearTimeout(store.closeTimer);
|
|
63
|
+
store.active = payload;
|
|
64
|
+
store.emit();
|
|
65
|
+
},
|
|
66
|
+
close(id: string, grace = CLOSE_GRACE) {
|
|
67
|
+
// graced; only the owning trigger can close
|
|
68
|
+
clearTimeout(store.closeTimer);
|
|
69
|
+
store.closeTimer = setTimeout(() => {
|
|
70
|
+
if (store.active && store.active.id === id) store.closeNow();
|
|
71
|
+
}, grace);
|
|
72
|
+
},
|
|
73
|
+
closeNow() {
|
|
74
|
+
clearTimeout(store.closeTimer);
|
|
75
|
+
if (!store.active) return;
|
|
76
|
+
store.warmUntil = performance.now() + WARM_WINDOW;
|
|
77
|
+
store.active = null;
|
|
78
|
+
store.emit();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/* Target box from the trigger rect + the measured content size; flips + clamps. */
|
|
83
|
+
function targetBox(size: Size, t: DOMRect, want: Placement): TargetBox {
|
|
84
|
+
const vw = window.innerWidth,
|
|
85
|
+
vh = window.innerHeight,
|
|
86
|
+
M = TIP_GAP;
|
|
87
|
+
let p = want;
|
|
88
|
+
if (p === 'top' && t.top - size.h - TIP_GAP < M) p = 'bottom';
|
|
89
|
+
else if (p === 'bottom' && t.bottom + size.h + TIP_GAP > vh - M) p = 'top';
|
|
90
|
+
else if (p === 'left' && t.left - size.w - TIP_GAP < M) p = 'right';
|
|
91
|
+
else if (p === 'right' && t.right + size.w + TIP_GAP > vw - M) p = 'left';
|
|
92
|
+
let x, y;
|
|
93
|
+
if (p === 'top') {
|
|
94
|
+
x = t.left + t.width / 2 - size.w / 2;
|
|
95
|
+
y = t.top - size.h - TIP_GAP;
|
|
96
|
+
} else if (p === 'bottom') {
|
|
97
|
+
x = t.left + t.width / 2 - size.w / 2;
|
|
98
|
+
y = t.bottom + TIP_GAP;
|
|
99
|
+
} else if (p === 'left') {
|
|
100
|
+
x = t.left - size.w - TIP_GAP;
|
|
101
|
+
y = t.top + t.height / 2 - size.h / 2;
|
|
102
|
+
} else {
|
|
103
|
+
x = t.right + TIP_GAP;
|
|
104
|
+
y = t.top + t.height / 2 - size.h / 2;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
x: Math.round(Math.min(Math.max(x, M), vw - size.w - M)),
|
|
108
|
+
y: Math.round(Math.min(Math.max(y, M), vh - size.h - M)),
|
|
109
|
+
w: size.w,
|
|
110
|
+
h: size.h,
|
|
111
|
+
placement: p,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fromEdge = (p: Placement) => ({
|
|
116
|
+
// enter/exit offset toward the trigger
|
|
117
|
+
x: p === 'left' ? 4 : p === 'right' ? -4 : 0,
|
|
118
|
+
y: p === 'top' ? 4 : p === 'bottom' ? -4 : 0,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
function Body({ a, width }: { a: ActivePayload; width?: number }) {
|
|
122
|
+
return (
|
|
123
|
+
<span className="tooltip__body" style={width ? { width } : undefined}>
|
|
124
|
+
{a.shortcut ? (
|
|
125
|
+
<span className="tooltip__row">
|
|
126
|
+
<span>{a.content}</span>
|
|
127
|
+
<kbd className="tooltip__shortcut">{a.shortcut}</kbd>
|
|
128
|
+
</span>
|
|
129
|
+
) : (
|
|
130
|
+
a.content
|
|
131
|
+
)}
|
|
132
|
+
</span>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Host - the one bubble + its hidden measuring twin. */
|
|
137
|
+
function TooltipHost() {
|
|
138
|
+
const active = useSyncExternalStore(store.subscribe, store.get);
|
|
139
|
+
const measureRef = useRef<HTMLDivElement>(null);
|
|
140
|
+
const [box, setBox] = useState<RenderedBox | null>(null);
|
|
141
|
+
|
|
142
|
+
// Measure the clone before paint: box = its border box; bodyW locks the live body's wrapping to the clone's.
|
|
143
|
+
useLayoutEffect(() => {
|
|
144
|
+
if (!active) {
|
|
145
|
+
setBox(null);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const r = measureRef.current.getBoundingClientRect();
|
|
149
|
+
const b = (measureRef.current.firstChild as HTMLElement).getBoundingClientRect();
|
|
150
|
+
setBox({
|
|
151
|
+
...targetBox(
|
|
152
|
+
{ w: Math.ceil(r.width), h: Math.ceil(r.height) },
|
|
153
|
+
active.rect(),
|
|
154
|
+
active.placement,
|
|
155
|
+
),
|
|
156
|
+
bodyW: Math.ceil(b.width),
|
|
157
|
+
});
|
|
158
|
+
}, [active]);
|
|
159
|
+
|
|
160
|
+
// Esc or any scroll dismisses (a stale hint is worse than none)
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!active) return undefined;
|
|
163
|
+
const onKey = (e: KeyboardEvent) => {
|
|
164
|
+
if (e.key === 'Escape') store.closeNow();
|
|
165
|
+
};
|
|
166
|
+
const onScroll = () => store.closeNow();
|
|
167
|
+
document.addEventListener('keydown', onKey);
|
|
168
|
+
window.addEventListener('scroll', onScroll, true);
|
|
169
|
+
return () => {
|
|
170
|
+
document.removeEventListener('keydown', onKey);
|
|
171
|
+
window.removeEventListener('scroll', onScroll, true);
|
|
172
|
+
};
|
|
173
|
+
}, [active]);
|
|
174
|
+
|
|
175
|
+
const off = box ? fromEdge(box.placement) : { x: 0, y: 0 };
|
|
176
|
+
|
|
177
|
+
return ReactDOM.createPortal(
|
|
178
|
+
<React.Fragment>
|
|
179
|
+
{active && (
|
|
180
|
+
<div className="tooltip tooltip--measure" ref={measureRef} aria-hidden="true">
|
|
181
|
+
<Body a={active} />
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
<AnimatePresence>
|
|
185
|
+
{active && box && (
|
|
186
|
+
<motion.div
|
|
187
|
+
key="tip"
|
|
188
|
+
className="tooltip"
|
|
189
|
+
id="scheduly-tooltip"
|
|
190
|
+
role="tooltip"
|
|
191
|
+
data-placement={box.placement}
|
|
192
|
+
initial={{
|
|
193
|
+
x: box.x + off.x,
|
|
194
|
+
y: box.y + off.y,
|
|
195
|
+
width: box.w,
|
|
196
|
+
height: box.h,
|
|
197
|
+
opacity: 0,
|
|
198
|
+
scale: 0.96,
|
|
199
|
+
}}
|
|
200
|
+
animate={{ x: box.x, y: box.y, width: box.w, height: box.h, opacity: 1, scale: 1 }}
|
|
201
|
+
exit={{
|
|
202
|
+
x: box.x + off.x,
|
|
203
|
+
y: box.y + off.y,
|
|
204
|
+
opacity: 0,
|
|
205
|
+
scale: 0.96,
|
|
206
|
+
transition: { duration: SM.dur.fast, ease: SM.ease.exit },
|
|
207
|
+
}}
|
|
208
|
+
transition={{
|
|
209
|
+
x: SM.t.layout,
|
|
210
|
+
y: SM.t.layout,
|
|
211
|
+
width: SM.t.layout,
|
|
212
|
+
height: SM.t.layout,
|
|
213
|
+
opacity: SM.t.enter,
|
|
214
|
+
scale: SM.t.enter,
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
{/* one node keyed by trigger: old cuts, new fades in while the box travels (fixed width keeps wrapping stable) */}
|
|
218
|
+
<motion.span
|
|
219
|
+
key={active.id}
|
|
220
|
+
initial={{ opacity: 0 }}
|
|
221
|
+
animate={{ opacity: 1, transition: SM.t.enter }}
|
|
222
|
+
>
|
|
223
|
+
<Body a={active} width={box.bodyW} />
|
|
224
|
+
</motion.span>
|
|
225
|
+
</motion.div>
|
|
226
|
+
)}
|
|
227
|
+
</AnimatePresence>
|
|
228
|
+
</React.Fragment>,
|
|
229
|
+
document.body,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Mount the host exactly once, lazily, in its own root.
|
|
234
|
+
let hostMounted = false;
|
|
235
|
+
function ensureHost() {
|
|
236
|
+
if (hostMounted) return;
|
|
237
|
+
hostMounted = true;
|
|
238
|
+
const el = document.createElement('div');
|
|
239
|
+
el.setAttribute('data-tooltip-host', '');
|
|
240
|
+
document.body.appendChild(el);
|
|
241
|
+
createRoot(el).render(<TooltipHost />);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const chain = (theirs: ((e: any) => void) | undefined, mine: (e: any) => void) => (e: any) => {
|
|
245
|
+
if (theirs) theirs(e);
|
|
246
|
+
mine(e);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
/* Trigger - reports to the store; default wraps the child in a display:contents anchor (any element, no ref), asChild clones instead. */
|
|
250
|
+
export interface TooltipProps {
|
|
251
|
+
/** The hint - a string or small node; never interactive content. */
|
|
252
|
+
content: ReactNode;
|
|
253
|
+
/** Optional keyboard hint, rendered as mono metadata ("⌘↩", "S"). */
|
|
254
|
+
shortcut?: string | null;
|
|
255
|
+
/** Preferred side; flips automatically when out of viewport room. */
|
|
256
|
+
placement?: Placement;
|
|
257
|
+
/** Suppress the tooltip entirely (trigger renders untouched). */
|
|
258
|
+
disabled?: boolean;
|
|
259
|
+
/** ms before a cold hover shows; warm hovers + focus are instant. Default 350. */
|
|
260
|
+
openDelay?: number;
|
|
261
|
+
/** ms the bubble lingers after leaving (bridges moving to a neighbour). Default 140. */
|
|
262
|
+
closeDelay?: number;
|
|
263
|
+
/** Skip the wrapper - clone the child and merge handlers + ref onto it (child must take a ref). Default false. */
|
|
264
|
+
asChild?: boolean;
|
|
265
|
+
id?: string;
|
|
266
|
+
/** Exactly one element; any element works by default, asChild requires one that accepts a ref. */
|
|
267
|
+
children: ReactElement;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function Tooltip({
|
|
271
|
+
content,
|
|
272
|
+
shortcut = null,
|
|
273
|
+
placement = 'top',
|
|
274
|
+
disabled = false,
|
|
275
|
+
openDelay = OPEN_DELAY,
|
|
276
|
+
closeDelay = CLOSE_GRACE,
|
|
277
|
+
asChild = false,
|
|
278
|
+
id,
|
|
279
|
+
children,
|
|
280
|
+
}: TooltipProps) {
|
|
281
|
+
const triggerRef = useRef<HTMLElement | null>(null);
|
|
282
|
+
const wrapRef = useRef<HTMLSpanElement | null>(null);
|
|
283
|
+
const openTimer = useRef<ReturnType<typeof setTimeout> | 0>(0);
|
|
284
|
+
const myId = id || 'tip-' + useId();
|
|
285
|
+
|
|
286
|
+
// The element we anchor to + set aria-describedby on (clone, or the wrapper's child).
|
|
287
|
+
const anchorEl = (): HTMLElement | null =>
|
|
288
|
+
asChild ? triggerRef.current : ((wrapRef.current?.firstElementChild as HTMLElement) ?? null);
|
|
289
|
+
|
|
290
|
+
useEffect(
|
|
291
|
+
() => () => {
|
|
292
|
+
clearTimeout(openTimer.current);
|
|
293
|
+
store.close(myId, 0);
|
|
294
|
+
},
|
|
295
|
+
[],
|
|
296
|
+
); // eslint-disable-line react-hooks/exhaustive-deps
|
|
297
|
+
|
|
298
|
+
function show(immediate: boolean) {
|
|
299
|
+
if (disabled) return;
|
|
300
|
+
ensureHost();
|
|
301
|
+
clearTimeout(openTimer.current);
|
|
302
|
+
const el = anchorEl();
|
|
303
|
+
if (!el) return;
|
|
304
|
+
el.setAttribute('aria-describedby', 'scheduly-tooltip');
|
|
305
|
+
const open = () =>
|
|
306
|
+
store.open({
|
|
307
|
+
id: myId,
|
|
308
|
+
content,
|
|
309
|
+
shortcut,
|
|
310
|
+
placement,
|
|
311
|
+
rect: () => anchorEl()!.getBoundingClientRect(),
|
|
312
|
+
});
|
|
313
|
+
if (immediate || store.isWarm()) open();
|
|
314
|
+
else openTimer.current = setTimeout(open, openDelay);
|
|
315
|
+
}
|
|
316
|
+
function hide() {
|
|
317
|
+
clearTimeout(openTimer.current);
|
|
318
|
+
const el = anchorEl();
|
|
319
|
+
if (el) el.removeAttribute('aria-describedby');
|
|
320
|
+
store.close(myId, closeDelay);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const onEnter = (e: React.PointerEvent) => {
|
|
324
|
+
if (e.pointerType !== 'touch') show(false);
|
|
325
|
+
};
|
|
326
|
+
const onFocusIn = (e: React.FocusEvent) => {
|
|
327
|
+
if ((e.target as HTMLElement).matches(':focus-visible')) show(true);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Default: a display:contents wrapper carries the listeners + rect; the child needs no ref.
|
|
331
|
+
if (!asChild) {
|
|
332
|
+
return (
|
|
333
|
+
<span
|
|
334
|
+
ref={wrapRef}
|
|
335
|
+
className="tooltip-anchor"
|
|
336
|
+
onPointerEnter={onEnter}
|
|
337
|
+
onPointerLeave={hide}
|
|
338
|
+
onPointerDown={hide}
|
|
339
|
+
onFocus={onFocusIn}
|
|
340
|
+
onBlur={hide}
|
|
341
|
+
>
|
|
342
|
+
{children}
|
|
343
|
+
</span>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// asChild: clone the child, merging our handlers + ref (a press dismisses: activating != hinting).
|
|
348
|
+
const child = React.Children.only(children) as ReactElement & { ref?: any };
|
|
349
|
+
const childRef = child.ref;
|
|
350
|
+
const childProps = child.props as any;
|
|
351
|
+
return React.cloneElement(child, {
|
|
352
|
+
ref: (node: HTMLElement | null) => {
|
|
353
|
+
triggerRef.current = node;
|
|
354
|
+
if (typeof childRef === 'function') childRef(node);
|
|
355
|
+
else if (childRef && typeof childRef === 'object') childRef.current = node;
|
|
356
|
+
},
|
|
357
|
+
onPointerEnter: chain(childProps.onPointerEnter, onEnter),
|
|
358
|
+
onPointerLeave: chain(childProps.onPointerLeave, hide),
|
|
359
|
+
onPointerDown: chain(childProps.onPointerDown, hide),
|
|
360
|
+
onFocus: chain(childProps.onFocus, onFocusIn),
|
|
361
|
+
onBlur: chain(childProps.onBlur, hide),
|
|
362
|
+
} as any);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export { Tooltip, TooltipHost };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* tooltip.css - the floating tooltip surface (one shared bubble). */
|
|
2
|
+
|
|
3
|
+
.tooltip {
|
|
4
|
+
position: fixed;
|
|
5
|
+
left: 0;
|
|
6
|
+
top: 0;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
overflow: hidden; /* clips content while the box morphs */
|
|
9
|
+
z-index: var(--layer-tooltip);
|
|
10
|
+
pointer-events: none;
|
|
11
|
+
width: max-content; /* the live bubble gets an animated width; this also sizes the measuring clone */
|
|
12
|
+
padding: var(--space-2) var(--space-3);
|
|
13
|
+
background: var(--bg-surface-raised);
|
|
14
|
+
color: var(--text-body);
|
|
15
|
+
border: var(--border-hairline) solid var(--border-default);
|
|
16
|
+
border-radius: var(--radius-md);
|
|
17
|
+
box-shadow: var(--shadow-lg);
|
|
18
|
+
font: var(--type-caption);
|
|
19
|
+
letter-spacing: var(--tracking-normal);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* the hidden twin the host measures the NEXT box from (same styles, no motion) */
|
|
23
|
+
.tooltip--measure {
|
|
24
|
+
visibility: hidden;
|
|
25
|
+
left: -9999px;
|
|
26
|
+
top: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* default trigger wrapper - display:contents, so it adds no layout; carries the listeners + rect */
|
|
30
|
+
.tooltip-anchor {
|
|
31
|
+
display: contents;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* wraps at the measure cap; the live body also gets its measured width inline (locked wrapping during the morph) */
|
|
35
|
+
.tooltip__body {
|
|
36
|
+
display: block;
|
|
37
|
+
width: max-content;
|
|
38
|
+
max-inline-size: var(--measure-floating);
|
|
39
|
+
text-wrap: pretty;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* grow from the trigger's edge (per placement) */
|
|
43
|
+
.tooltip[data-placement='top'] {
|
|
44
|
+
transform-origin: center bottom;
|
|
45
|
+
}
|
|
46
|
+
.tooltip[data-placement='bottom'] {
|
|
47
|
+
transform-origin: center top;
|
|
48
|
+
}
|
|
49
|
+
.tooltip[data-placement='left'] {
|
|
50
|
+
transform-origin: right center;
|
|
51
|
+
}
|
|
52
|
+
.tooltip[data-placement='right'] {
|
|
53
|
+
transform-origin: left center;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.tooltip__row {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: baseline;
|
|
59
|
+
gap: var(--space-3);
|
|
60
|
+
}
|
|
61
|
+
.tooltip__shortcut {
|
|
62
|
+
flex: 0 0 auto;
|
|
63
|
+
font-family: var(--font-mono);
|
|
64
|
+
font-size: var(--size-micro);
|
|
65
|
+
font-variant-numeric: tabular-nums;
|
|
66
|
+
color: var(--text-subtle);
|
|
67
|
+
background: var(--bg-muted);
|
|
68
|
+
padding: 0 var(--space-1);
|
|
69
|
+
border-radius: var(--radius-sm);
|
|
70
|
+
white-space: nowrap;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Multi-line content - rich nodes style themselves with these; keep hierarchy minimal. */
|
|
74
|
+
.tooltip strong {
|
|
75
|
+
font-weight: var(--weight-medium);
|
|
76
|
+
color: var(--text-strong);
|
|
77
|
+
}
|
|
78
|
+
.tooltip p {
|
|
79
|
+
margin: 0;
|
|
80
|
+
}
|
|
81
|
+
.tooltip p + p {
|
|
82
|
+
margin-top: var(--space-1);
|
|
83
|
+
}
|
|
84
|
+
.tooltip .tooltip__muted {
|
|
85
|
+
color: var(--text-muted);
|
|
86
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* premium-ds public barrel - shipped components + prop types; internal helpers (*-core, field-shell, glide-pill, sheet-drag, date-utils) are intentionally not re-exported. */
|
|
2
|
+
|
|
3
|
+
export * from './components/button/Button';
|
|
4
|
+
export * from './components/motion/Collapse';
|
|
5
|
+
|
|
6
|
+
export * from './components/badge/Badge';
|
|
7
|
+
export * from './components/badge/StatusBadge';
|
|
8
|
+
export * from './components/badge/CountBadge';
|
|
9
|
+
export * from './components/avatar/Avatar';
|
|
10
|
+
export * from './components/avatar/AvatarGroup';
|
|
11
|
+
export * from './components/tag/Tag';
|
|
12
|
+
export * from './components/tag/ToggleTag';
|
|
13
|
+
export * from './components/table/Table';
|
|
14
|
+
export * from './components/pagination/Pagination';
|
|
15
|
+
|
|
16
|
+
export * from './components/input/TextField';
|
|
17
|
+
export * from './components/input/NumberField';
|
|
18
|
+
export * from './components/input/OtpField';
|
|
19
|
+
export * from './components/textarea/Textarea';
|
|
20
|
+
export * from './components/checkbox/Checkbox';
|
|
21
|
+
export * from './components/toggle/Toggle';
|
|
22
|
+
export * from './components/radio-group/RadioGroup';
|
|
23
|
+
export * from './components/select/Select';
|
|
24
|
+
export * from './components/select/MultiSelect';
|
|
25
|
+
export type { SelectOption, SelectGroup } from './components/select/select-core';
|
|
26
|
+
|
|
27
|
+
export * from './components/date-picker/DateField';
|
|
28
|
+
export * from './components/date-picker/DateTimeField';
|
|
29
|
+
export * from './components/date-picker/DateRangeField';
|
|
30
|
+
export * from './components/date-picker/TimeField';
|
|
31
|
+
|
|
32
|
+
export * from './components/tabs/Tabs';
|
|
33
|
+
|
|
34
|
+
export * from './components/overlay/Overlay';
|
|
35
|
+
export * from './components/dialog/Dialog';
|
|
36
|
+
export * from './components/tooltip/Tooltip';
|
|
37
|
+
export * from './components/alert/Alert';
|
|
38
|
+
export * from './components/toast/Toast';
|
|
39
|
+
export * from './components/toast/toast-store';
|
|
40
|
+
|
|
41
|
+
export { UIMotion } from './tokens/motion-tokens';
|
|
42
|
+
export type { MotionTokens, Bezier } from './tokens/motion-tokens';
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* premium-ds entry - @import manifest; order matters: fonts - primitives - semantics - components. */
|
|
2
|
+
|
|
3
|
+
@import url('./tokens/fonts.css');
|
|
4
|
+
|
|
5
|
+
@import url('./tokens/color.css');
|
|
6
|
+
@import url('./tokens/typography.css');
|
|
7
|
+
@import url('./tokens/spacing.css');
|
|
8
|
+
@import url('./tokens/radius.css');
|
|
9
|
+
@import url('./tokens/elevation.css');
|
|
10
|
+
@import url('./tokens/motion.css');
|
|
11
|
+
@import url('./tokens/glass.css');
|
|
12
|
+
@import url('./tokens/icons.css');
|
|
13
|
+
@import url('./tokens/layers.css');
|
|
14
|
+
@import url('./tokens/avatar.css');
|
|
15
|
+
|
|
16
|
+
@import url('./tokens/semantic.css');
|
|
17
|
+
|
|
18
|
+
@import url('./components/button/button.css');
|
|
19
|
+
@import url('./components/motion/collapse.css');
|
|
20
|
+
@import url('./components/icon/icon.css');
|
|
21
|
+
@import url('./components/input/input.css');
|
|
22
|
+
@import url('./components/textarea/textarea.css');
|
|
23
|
+
@import url('./components/checkbox/checkbox.css');
|
|
24
|
+
@import url('./components/radio-group/radio-group.css');
|
|
25
|
+
@import url('./components/toggle/toggle.css');
|
|
26
|
+
@import url('./components/dialog/dialog.css');
|
|
27
|
+
@import url('./components/select/select.css');
|
|
28
|
+
@import url('./components/glass/glass.css');
|
|
29
|
+
@import url('./components/badge/badge.css');
|
|
30
|
+
@import url('./components/tag/tag.css');
|
|
31
|
+
@import url('./components/tooltip/tooltip.css');
|
|
32
|
+
@import url('./components/toast/toast.css');
|
|
33
|
+
@import url('./components/tabs/tabs.css');
|
|
34
|
+
@import url('./components/overlay/overlay.css');
|
|
35
|
+
@import url('./components/pagination/pagination.css');
|
|
36
|
+
@import url('./components/alert/alert.css');
|
|
37
|
+
@import url('./components/avatar/avatar.css');
|
|
38
|
+
@import url('./components/table/table.css');
|
|
39
|
+
@import url('./components/date-picker/date-picker.css');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Avatar tokens - reserved identity palette + lit-from-above face finish. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--avatar-1-bg: oklch(0.945 0.04 250);
|
|
5
|
+
--avatar-1-fg: oklch(0.45 0.08 250);
|
|
6
|
+
--avatar-2-bg: oklch(0.945 0.04 292);
|
|
7
|
+
--avatar-2-fg: oklch(0.45 0.08 292);
|
|
8
|
+
--avatar-3-bg: oklch(0.945 0.04 334);
|
|
9
|
+
--avatar-3-fg: oklch(0.45 0.08 334);
|
|
10
|
+
--avatar-4-bg: oklch(0.945 0.04 5);
|
|
11
|
+
--avatar-4-fg: oklch(0.45 0.08 5);
|
|
12
|
+
--avatar-5-bg: oklch(0.945 0.04 55);
|
|
13
|
+
--avatar-5-fg: oklch(0.45 0.08 55);
|
|
14
|
+
--avatar-6-bg: oklch(0.945 0.04 125);
|
|
15
|
+
--avatar-6-fg: oklch(0.45 0.08 125);
|
|
16
|
+
|
|
17
|
+
/* Surface finish - white sheen from top, cool near-black shade from bottom (never pure black). */
|
|
18
|
+
--avatar-sheen: oklch(1 0 0 / 0.35);
|
|
19
|
+
--avatar-shade: oklch(0.225 0.012 264 / 0.05);
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/* Color primitives - the closed neutral + status ramps. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--gray-0: oklch(1 0 0);
|
|
5
|
+
--gray-25: oklch(0.992 0.0015 264);
|
|
6
|
+
--gray-50: oklch(0.984 0.003 264);
|
|
7
|
+
--gray-100: oklch(0.968 0.004 264);
|
|
8
|
+
--gray-150: oklch(0.948 0.005 264);
|
|
9
|
+
--gray-200: oklch(0.922 0.006 264);
|
|
10
|
+
--gray-300: oklch(0.872 0.008 264);
|
|
11
|
+
--gray-400: oklch(0.765 0.01 264);
|
|
12
|
+
--gray-500: oklch(0.642 0.012 264);
|
|
13
|
+
--gray-600: oklch(0.532 0.013 264);
|
|
14
|
+
--gray-700: oklch(0.422 0.013 264);
|
|
15
|
+
--gray-800: oklch(0.305 0.012 264);
|
|
16
|
+
--gray-900: oklch(0.225 0.01 264);
|
|
17
|
+
--gray-950: oklch(0.165 0.008 264);
|
|
18
|
+
|
|
19
|
+
--teal-50: oklch(0.972 0.02 198);
|
|
20
|
+
--teal-100: oklch(0.935 0.04 198);
|
|
21
|
+
--teal-200: oklch(0.88 0.066 198);
|
|
22
|
+
--teal-300: oklch(0.795 0.092 198);
|
|
23
|
+
--teal-400: oklch(0.705 0.112 198);
|
|
24
|
+
--teal-500: oklch(0.63 0.118 198);
|
|
25
|
+
--teal-600: oklch(0.56 0.114 198);
|
|
26
|
+
--teal-700: oklch(0.478 0.1 198);
|
|
27
|
+
--teal-800: oklch(0.4 0.082 198);
|
|
28
|
+
--teal-900: oklch(0.32 0.062 198);
|
|
29
|
+
|
|
30
|
+
--green-50: oklch(0.965 0.028 152);
|
|
31
|
+
--green-500: oklch(0.62 0.13 152);
|
|
32
|
+
--green-600: oklch(0.548 0.122 152);
|
|
33
|
+
--green-700: oklch(0.462 0.1 152);
|
|
34
|
+
|
|
35
|
+
--amber-50: oklch(0.972 0.034 75);
|
|
36
|
+
--amber-500: oklch(0.76 0.14 75);
|
|
37
|
+
--amber-600: oklch(0.7 0.142 75);
|
|
38
|
+
--amber-700: oklch(0.56 0.118 75);
|
|
39
|
+
|
|
40
|
+
--red-50: oklch(0.968 0.02 27);
|
|
41
|
+
--red-300: oklch(0.8 0.09 27);
|
|
42
|
+
--red-400: oklch(0.64 0.2 27);
|
|
43
|
+
--red-500: oklch(0.602 0.196 27);
|
|
44
|
+
--red-600: oklch(0.545 0.196 27);
|
|
45
|
+
--red-700: oklch(0.478 0.172 27);
|
|
46
|
+
|
|
47
|
+
/* Wash tints - glassy active/selected fills that let the surface show through; low alpha per hue. */
|
|
48
|
+
--gray-wash: oklch(0.42 0.012 264 / 0.06);
|
|
49
|
+
--teal-wash: oklch(0.63 0.118 198 / 0.05);
|
|
50
|
+
--green-wash: oklch(0.62 0.13 152 / 0.1);
|
|
51
|
+
--amber-wash: oklch(0.76 0.14 75 / 0.12);
|
|
52
|
+
--red-wash: oklch(0.602 0.196 27 / 0.1);
|
|
53
|
+
|
|
54
|
+
/* Cool near-black shadow ink - pure black looks muddy on a cool canvas. */
|
|
55
|
+
--shadow-rgb: 13 18 30;
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* Elevation - two-layer shadows, focus/selection rings, border widths. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--shadow-xs: 0 1px 1px rgb(var(--shadow-rgb) / 0.04);
|
|
5
|
+
--shadow-sm: 0 1px 2px rgb(var(--shadow-rgb) / 0.05), 0 1px 1px rgb(var(--shadow-rgb) / 0.04);
|
|
6
|
+
--shadow-md: 0 2px 4px rgb(var(--shadow-rgb) / 0.04), 0 6px 12px rgb(var(--shadow-rgb) / 0.07);
|
|
7
|
+
--shadow-lg: 0 4px 8px rgb(var(--shadow-rgb) / 0.04), 0 12px 28px rgb(var(--shadow-rgb) / 0.1);
|
|
8
|
+
--shadow-xl: 0 8px 16px rgb(var(--shadow-rgb) / 0.06), 0 24px 48px rgb(var(--shadow-rgb) / 0.14);
|
|
9
|
+
|
|
10
|
+
/* Borders - one hairline (1px); weight comes from the border color, not thickness; 2px for focus/selected. */
|
|
11
|
+
--border-hairline: 1px;
|
|
12
|
+
--border-emphasis: 1.5px;
|
|
13
|
+
|
|
14
|
+
/* Focus & selection rings - box-shadows (not outlines) so they follow border-radius. */
|
|
15
|
+
--ring-accent: 0 0 0 3px oklch(0.63 0.118 198 / 0.32);
|
|
16
|
+
--ring-danger: 0 0 0 3px oklch(0.602 0.196 27 / 0.3);
|
|
17
|
+
--ring-warning: 0 0 0 3px color-mix(in oklab, var(--warning) 30%, transparent);
|
|
18
|
+
--ring-success: 0 0 0 3px color-mix(in oklab, var(--success) 30%, transparent);
|
|
19
|
+
--ring-offset: 0 0 0 2px var(--bg-surface), 0 0 0 4px oklch(0.63 0.118 198 / 0.4);
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Glass tokens - structural frosted-surface pieces; per-tone tints live in semantic.css. */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--glass-blur: 9px;
|
|
5
|
+
--glass-blur-strong: 16px;
|
|
6
|
+
--glass-saturate: 1.5;
|
|
7
|
+
|
|
8
|
+
--glass-sheen: linear-gradient(
|
|
9
|
+
145deg,
|
|
10
|
+
rgb(255 255 255 / 0.55) 0%,
|
|
11
|
+
rgb(255 255 255 / 0.1) 24%,
|
|
12
|
+
rgb(255 255 255 / 0) 46%
|
|
13
|
+
);
|
|
14
|
+
--glass-sheen-rest: 0.8;
|
|
15
|
+
--glass-sheen-hover: 1;
|
|
16
|
+
|
|
17
|
+
--glass-highlight: inset 0 1px 0 0 rgb(255 255 255 / 0.55);
|
|
18
|
+
--glass-shadow: 0 1px 2px rgb(var(--shadow-rgb) / 0.05), 0 2px 5px rgb(var(--shadow-rgb) / 0.05);
|
|
19
|
+
--glass-shadow-hover:
|
|
20
|
+
0 2px 4px rgb(var(--shadow-rgb) / 0.06), 0 8px 18px rgb(var(--shadow-rgb) / 0.1);
|
|
21
|
+
}
|