@akhil-saxena/design-system 1.2.0 → 1.4.2
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 +17 -17
- package/dist/chunk-34YNJKI2.js +231 -0
- package/dist/chunk-34YNJKI2.js.map +1 -0
- package/dist/hooks/index.js +3 -126
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +800 -63
- package/dist/index.js +2929 -279
- package/dist/index.js.map +1 -1
- package/dist/primitives.css +1197 -40
- package/dist/tokens.css +78 -29
- package/package.json +4 -2
- package/dist/chunk-FUXR6QZ3.js +0 -108
- package/dist/chunk-FUXR6QZ3.js.map +0 -1
package/dist/tokens.css
CHANGED
|
@@ -33,10 +33,15 @@
|
|
|
33
33
|
--ink-4: #8a8380;
|
|
34
34
|
--ink-5: #d6d3d1;
|
|
35
35
|
|
|
36
|
-
/* Cream ramp (warm background surfaces)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
/* Cream ramp (warm background surfaces).
|
|
37
|
+
Whiter than the original handoff so chrome surfaces (sidebar / topbar /
|
|
38
|
+
filter bar / page body) feel uplifting rather than dull. Brand warmth
|
|
39
|
+
is preserved by the faint yellow tint in every step; the ramp now
|
|
40
|
+
stays close to white through cream-2 with cream-3 reserved for the
|
|
41
|
+
most-recessed slots (column tracks, secondary hover wash). */
|
|
42
|
+
--cream: #fdfcf9;
|
|
43
|
+
--cream-2: #f7f5f1;
|
|
44
|
+
--cream-3: #efece5;
|
|
40
45
|
|
|
41
46
|
/* Amber accent */
|
|
42
47
|
--amber: #f59e0b;
|
|
@@ -103,6 +108,41 @@
|
|
|
103
108
|
--space-15: 60px;
|
|
104
109
|
--space-16: 64px;
|
|
105
110
|
|
|
111
|
+
/* Type scale — derived from auth-design-handoff README §Typography.
|
|
112
|
+
Tailwind-style nomenclature for 1:1 cross-app reuse. */
|
|
113
|
+
--text-2xs: 9.5px; /* mono caps, stage chip labels, smallest legal */
|
|
114
|
+
--text-xs: 11px; /* legal copy */
|
|
115
|
+
--text-sm: 12.5px; /* small body, footer links */
|
|
116
|
+
--text-base: 13px; /* default body text */
|
|
117
|
+
--text-md: 15px;
|
|
118
|
+
--text-lg: 17px;
|
|
119
|
+
--text-xl: 22px;
|
|
120
|
+
--text-2xl: 28px; /* form headlines */
|
|
121
|
+
--text-3xl: 40px;
|
|
122
|
+
--text-4xl: 44px; /* mobile hero / tablet-compact hero */
|
|
123
|
+
--text-5xl: 60px; /* desktop hero w/ subline */
|
|
124
|
+
--text-6xl: 72px; /* desktop hero w/o subline */
|
|
125
|
+
|
|
126
|
+
/* Weights */
|
|
127
|
+
--weight-regular: 400;
|
|
128
|
+
--weight-medium: 500;
|
|
129
|
+
--weight-semibold: 600;
|
|
130
|
+
--weight-bold: 700;
|
|
131
|
+
--weight-extrabold: 800;
|
|
132
|
+
--weight-black: 900;
|
|
133
|
+
|
|
134
|
+
/* Line heights */
|
|
135
|
+
--lh-tight: 0.94; /* hero display headlines */
|
|
136
|
+
--lh-snug: 1.08; /* form headlines */
|
|
137
|
+
--lh-normal: 1.5;
|
|
138
|
+
--lh-relaxed: 1.55; /* body */
|
|
139
|
+
|
|
140
|
+
/* Letter spacing — derived from the design's display-typography scale */
|
|
141
|
+
--ls-tighter: -0.038em; /* size > 28 (hero) */
|
|
142
|
+
--ls-tight: -0.024em; /* size > 20 */
|
|
143
|
+
--ls-base: -0.005em; /* default */
|
|
144
|
+
--ls-wide: 0.1em; /* mono caps, eyebrows, stage chips */
|
|
145
|
+
|
|
106
146
|
/* Motion tokens - handoff v1.0 */
|
|
107
147
|
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
108
148
|
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
@@ -132,29 +172,37 @@
|
|
|
132
172
|
define the same values so var(--ink) etc resolve correctly on either. */
|
|
133
173
|
:root.dark,
|
|
134
174
|
.dark {
|
|
135
|
-
/*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
--
|
|
144
|
-
--
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
--
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
--
|
|
151
|
-
--
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
/* Revised dark-mode ramp — Cairn · Dark Mode Revision.html spec:
|
|
176
|
+
- 3 distinct surface stops (~6L between) so page / column / card stop
|
|
177
|
+
collapsing into one slab. The old ramp lived inside a 14–17L band.
|
|
178
|
+
- Text inverts to WARM cream (was cool near-white) so it stops fighting
|
|
179
|
+
the warm-paper brand and lets the amber accent land as signal.
|
|
180
|
+
- Rules warm + lift to 7% (was 8% cool white); new `--rule-strong` at
|
|
181
|
+
12% for active dividers (e.g. column-header underline).
|
|
182
|
+
The headline move: drop warmth from background, put it into text. */
|
|
183
|
+
--ink: #f7ecdb;
|
|
184
|
+
--ink-2: #b8ac97;
|
|
185
|
+
--ink-3: #857b6c;
|
|
186
|
+
--ink-4: #5c554a;
|
|
187
|
+
--ink-5: #3a3530;
|
|
188
|
+
|
|
189
|
+
--cream: #0f0d0b;
|
|
190
|
+
--cream-2: #1a1714;
|
|
191
|
+
--cream-3: #241f1a;
|
|
192
|
+
|
|
193
|
+
--rule: rgba(247, 236, 219, 0.07);
|
|
194
|
+
--rule-strong: rgba(247, 236, 219, 0.12);
|
|
195
|
+
|
|
196
|
+
/* Glass tints to deepest surface (anchored instead of floating) */
|
|
197
|
+
--g-bg: rgba(15, 13, 11, 0.7);
|
|
198
|
+
--g-bd: rgba(247, 236, 219, 0.07);
|
|
199
|
+
|
|
200
|
+
/* Status colors — revised per dark-mode spec § Tokens. Each color is
|
|
201
|
+
pre-saturated to read on the dark warm bg without going neon. */
|
|
154
202
|
--amber-d: #fbbf24;
|
|
155
|
-
--blue: #
|
|
203
|
+
--blue: #7ca0d9;
|
|
156
204
|
--purple: #bfa3fb;
|
|
157
|
-
--green: #
|
|
205
|
+
--green: #7fc79d;
|
|
158
206
|
--red: #fb8888;
|
|
159
207
|
|
|
160
208
|
/* Vivids are decorative-only; restated explicitly even though values
|
|
@@ -166,10 +214,11 @@
|
|
|
166
214
|
--green-vivid: #22c55e;
|
|
167
215
|
--red-vivid: #ef4444;
|
|
168
216
|
|
|
169
|
-
/* Surface
|
|
170
|
-
|
|
171
|
-
--surf-
|
|
172
|
-
--surf-
|
|
217
|
+
/* Surface translucent overlays — warm-cream tint instead of cool white
|
|
218
|
+
so they blend with the new warm-cream ink. */
|
|
219
|
+
--surf-1: rgba(247, 236, 219, 0.04);
|
|
220
|
+
--surf-2: rgba(247, 236, 219, 0.06);
|
|
221
|
+
--surf-3: rgba(247, 236, 219, 0.1);
|
|
173
222
|
|
|
174
223
|
/* Focus ring - dark-mode amber-d glow */
|
|
175
224
|
--focus-ring: 0 0 0 3px rgba(251, 191, 36, 0.5);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akhil-saxena/design-system",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Accessible React primitives with semantic tokens",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Akhil Saxena <saxena.akhil42@gmail.com>",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/akhil-saxena/design-system.git"
|
|
10
|
+
"url": "git+https://github.com/akhil-saxena/design-system.git"
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"registry": "https://registry.npmjs.org",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"@fontsource/inter": "^5.0.0",
|
|
62
62
|
"@fontsource/jetbrains-mono": "^5.0.0",
|
|
63
63
|
"@tiptap/extension-code-block-lowlight": "^3.22.5",
|
|
64
|
+
"@tiptap/extension-highlight": "^3.22.5",
|
|
64
65
|
"@tiptap/extension-link": "^3.22.5",
|
|
65
66
|
"@tiptap/extension-placeholder": "^3.22.5",
|
|
66
67
|
"@tiptap/extension-underline": "^3.22.5",
|
|
@@ -77,6 +78,7 @@
|
|
|
77
78
|
"@storybook/preview-api": "^8.6.18",
|
|
78
79
|
"@storybook/react-vite": "^8.6.18",
|
|
79
80
|
"@storybook/test-runner": "^0.23.0",
|
|
81
|
+
"@testing-library/dom": "^10.4.1",
|
|
80
82
|
"@testing-library/jest-dom": "^6.9.1",
|
|
81
83
|
"@testing-library/react": "^16.3.2",
|
|
82
84
|
"@types/react": "^19.2.14",
|
package/dist/chunk-FUXR6QZ3.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
// src/hooks/useComposedRefs.ts
|
|
4
|
-
function setRef(ref, node) {
|
|
5
|
-
if (typeof ref === "function") ref(node);
|
|
6
|
-
else if (ref && "current" in ref) ref.current = node;
|
|
7
|
-
}
|
|
8
|
-
function useComposedRefs(...refs) {
|
|
9
|
-
return useCallback((node) => {
|
|
10
|
-
for (const ref of refs) setRef(ref, node);
|
|
11
|
-
}, refs);
|
|
12
|
-
}
|
|
13
|
-
function useClickOutside(ref, handler, enabled = true) {
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
if (!enabled) return;
|
|
16
|
-
function listener(e) {
|
|
17
|
-
const el = ref.current;
|
|
18
|
-
if (!el || el.contains(e.target)) return;
|
|
19
|
-
handler(e);
|
|
20
|
-
}
|
|
21
|
-
document.addEventListener("mousedown", listener);
|
|
22
|
-
document.addEventListener("touchstart", listener);
|
|
23
|
-
return () => {
|
|
24
|
-
document.removeEventListener("mousedown", listener);
|
|
25
|
-
document.removeEventListener("touchstart", listener);
|
|
26
|
-
};
|
|
27
|
-
}, [ref, handler, enabled]);
|
|
28
|
-
}
|
|
29
|
-
var FOCUSABLE_SELECTOR = "a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), input:not([disabled]):not([type='hidden']), select:not([disabled]), [tabindex]:not([tabindex='-1'])";
|
|
30
|
-
function useFocusTrap(container, active) {
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (!active || !container) return;
|
|
33
|
-
const c = container;
|
|
34
|
-
const previouslyFocused = document.activeElement;
|
|
35
|
-
const focusables = () => Array.from(c.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
|
|
36
|
-
(el) => !el.hasAttribute("inert")
|
|
37
|
-
);
|
|
38
|
-
const initial = focusables()[0];
|
|
39
|
-
if (initial) {
|
|
40
|
-
initial.focus();
|
|
41
|
-
} else {
|
|
42
|
-
c.focus();
|
|
43
|
-
}
|
|
44
|
-
function handleKeyDown(e) {
|
|
45
|
-
if (e.key !== "Tab") return;
|
|
46
|
-
const list = focusables();
|
|
47
|
-
const activeEl = document.activeElement;
|
|
48
|
-
const activeInside = activeEl ? c.contains(activeEl) : false;
|
|
49
|
-
if (list.length === 0) {
|
|
50
|
-
e.preventDefault();
|
|
51
|
-
if (!activeInside) c.focus();
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const firstEl = list[0];
|
|
55
|
-
const lastEl = list.at(-1);
|
|
56
|
-
if (!activeInside) {
|
|
57
|
-
e.preventDefault();
|
|
58
|
-
(e.shiftKey ? lastEl : firstEl).focus();
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (e.shiftKey && activeEl === firstEl) {
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
lastEl.focus();
|
|
64
|
-
} else if (!e.shiftKey && activeEl === lastEl) {
|
|
65
|
-
e.preventDefault();
|
|
66
|
-
firstEl.focus();
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
70
|
-
return () => {
|
|
71
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
72
|
-
previouslyFocused?.focus?.();
|
|
73
|
-
};
|
|
74
|
-
}, [active, container]);
|
|
75
|
-
}
|
|
76
|
-
function useReducedMotion() {
|
|
77
|
-
const [reduced, setReduced] = useState(() => {
|
|
78
|
-
if (typeof window === "undefined") return false;
|
|
79
|
-
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
80
|
-
});
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (typeof window === "undefined") return;
|
|
83
|
-
const mql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
84
|
-
const handler = (e) => setReduced(e.matches);
|
|
85
|
-
mql.addEventListener("change", handler);
|
|
86
|
-
return () => mql.removeEventListener("change", handler);
|
|
87
|
-
}, []);
|
|
88
|
-
return reduced;
|
|
89
|
-
}
|
|
90
|
-
function useMatchMedia(query) {
|
|
91
|
-
const [matches, setMatches] = useState(() => {
|
|
92
|
-
if (typeof window === "undefined") return false;
|
|
93
|
-
return window.matchMedia(query).matches;
|
|
94
|
-
});
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
if (typeof window === "undefined") return;
|
|
97
|
-
const mql = window.matchMedia(query);
|
|
98
|
-
const handler = (e) => setMatches(e.matches);
|
|
99
|
-
setMatches(mql.matches);
|
|
100
|
-
mql.addEventListener("change", handler);
|
|
101
|
-
return () => mql.removeEventListener("change", handler);
|
|
102
|
-
}, [query]);
|
|
103
|
-
return matches;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export { useClickOutside, useComposedRefs, useFocusTrap, useMatchMedia, useReducedMotion };
|
|
107
|
-
//# sourceMappingURL=chunk-FUXR6QZ3.js.map
|
|
108
|
-
//# sourceMappingURL=chunk-FUXR6QZ3.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useComposedRefs.ts","../src/hooks/useClickOutside.ts","../src/hooks/useFocusTrap.ts","../src/hooks/useReducedMotion.ts","../src/hooks/useMatchMedia.ts"],"names":["useEffect","useState"],"mappings":"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","file":"chunk-FUXR6QZ3.js","sourcesContent":["import { type Ref, useCallback } from \"react\";\n\nfunction setRef<T>(ref: Ref<T> | undefined, node: T | null): void {\n\tif (typeof ref === \"function\") ref(node);\n\telse if (ref && \"current\" in ref) (ref as { current: T | null }).current = node;\n}\n\n/**\n * Combine multiple refs (function or object) into one callback ref.\n * Used by every primitive that does forwardRef + needs an internal ref\n * for a hook (Popover trigger, Modal container, etc.).\n */\nexport function useComposedRefs<T>(...refs: Array<Ref<T> | undefined>): (node: T | null) => void {\n\treturn useCallback((node: T | null) => {\n\t\tfor (const ref of refs) setRef(ref, node);\n\t}, refs);\n}\n","import { type RefObject, useEffect } from \"react\";\n\n/**\n * Calls `handler` when a mousedown/touchstart fires outside of `ref`'s element.\n * Used by Popover (Phase 14), Modal close-on-backdrop, Select dropdown dismiss.\n */\nexport function useClickOutside<T extends HTMLElement>(\n\tref: RefObject<T | null>,\n\thandler: (e: MouseEvent | TouchEvent) => void,\n\tenabled = true,\n): void {\n\tuseEffect(() => {\n\t\tif (!enabled) return;\n\t\tfunction listener(e: MouseEvent | TouchEvent) {\n\t\t\tconst el = ref.current;\n\t\t\tif (!el || el.contains(e.target as Node)) return;\n\t\t\thandler(e);\n\t\t}\n\t\tdocument.addEventListener(\"mousedown\", listener);\n\t\tdocument.addEventListener(\"touchstart\", listener);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"mousedown\", listener);\n\t\t\tdocument.removeEventListener(\"touchstart\", listener);\n\t\t};\n\t}, [ref, handler, enabled]);\n}\n","import { useEffect } from \"react\";\n\nconst FOCUSABLE_SELECTOR =\n\t\"a[href]:not([disabled]), button:not([disabled]), textarea:not([disabled]), \" +\n\t\"input:not([disabled]):not([type='hidden']), select:not([disabled]), \" +\n\t\"[tabindex]:not([tabindex='-1'])\";\n\n/**\n * Trap Tab/Shift+Tab focus inside `container` while `active` is true.\n *\n * Pass the actual DOM node (use a callback ref + useState pattern in the\n * caller). This guarantees the effect re-runs as soon as the node attaches,\n * which is necessary for portal-mounted containers (Modal, Sheet, BottomSheet)\n * where the node materializes one tick after the parent renders.\n *\n * On activation: focuses the first focusable child, OR the container itself\n * when it has no focusable descendants (container must have `tabIndex={-1}`).\n * On deactivation: restores focus to the previously-focused element.\n *\n * Listens at `document` level so the trap engages even when focus is currently\n * outside the container (e.g., focus leaked to background, or content has no\n * focusables and initial focus didn't land inside).\n */\nexport function useFocusTrap<T extends HTMLElement>(container: T | null, active: boolean): void {\n\tuseEffect(() => {\n\t\tif (!active || !container) return;\n\t\t// Capture as non-null local - TypeScript loses narrowing across closure boundaries.\n\t\tconst c = container;\n\t\tconst previouslyFocused = document.activeElement as HTMLElement | null;\n\n\t\tconst focusables = () =>\n\t\t\tArray.from(c.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)).filter(\n\t\t\t\t(el) => !el.hasAttribute(\"inert\"),\n\t\t\t);\n\n\t\tconst initial = focusables()[0];\n\t\tif (initial) {\n\t\t\tinitial.focus();\n\t\t} else {\n\t\t\tc.focus();\n\t\t}\n\n\t\tfunction handleKeyDown(e: KeyboardEvent) {\n\t\t\tif (e.key !== \"Tab\") return;\n\t\t\tconst list = focusables();\n\t\t\tconst activeEl = document.activeElement as HTMLElement | null;\n\t\t\tconst activeInside = activeEl ? c.contains(activeEl) : false;\n\n\t\t\tif (list.length === 0) {\n\t\t\t\te.preventDefault();\n\t\t\t\tif (!activeInside) c.focus();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst firstEl = list[0]!;\n\t\t\tconst lastEl = list.at(-1)!;\n\n\t\t\tif (!activeInside) {\n\t\t\t\te.preventDefault();\n\t\t\t\t(e.shiftKey ? lastEl : firstEl).focus();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (e.shiftKey && activeEl === firstEl) {\n\t\t\t\te.preventDefault();\n\t\t\t\tlastEl.focus();\n\t\t\t} else if (!e.shiftKey && activeEl === lastEl) {\n\t\t\t\te.preventDefault();\n\t\t\t\tfirstEl.focus();\n\t\t\t}\n\t\t}\n\n\t\tdocument.addEventListener(\"keydown\", handleKeyDown);\n\t\treturn () => {\n\t\t\tdocument.removeEventListener(\"keydown\", handleKeyDown);\n\t\t\tpreviouslyFocused?.focus?.();\n\t\t};\n\t}, [active, container]);\n}\n","import { useEffect, useState } from \"react\";\n\n/**\n * Returns true when the user has set OS-level \"Reduce Motion\" preference.\n * Watches matchMedia for changes (e.g., user toggles preference at runtime).\n * SSR-safe - returns false on the server.\n * Used by Carousel (DS-65) autoplay gating, Accordion expand transition, etc.\n */\nexport function useReducedMotion(): boolean {\n\tconst [reduced, setReduced] = useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n\t});\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return;\n\t\tconst mql = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n\t\tconst handler = (e: MediaQueryListEvent) => setReduced(e.matches);\n\t\tmql.addEventListener(\"change\", handler);\n\t\treturn () => mql.removeEventListener(\"change\", handler);\n\t}, []);\n\treturn reduced;\n}\n","import { useEffect, useState } from \"react\";\n\n/**\n * Returns the `matches` boolean for an arbitrary CSS media query, reactive\n * to viewport changes (resize, rotation, prefers-* toggles at runtime).\n * SSR-safe - returns false on the server.\n * Used by Calendar (DS-68) for the mobile breakpoint switch between Popover\n * and BottomSheet at `(max-width: 640px)`. Generic over any media query string.\n */\nexport function useMatchMedia(query: string): boolean {\n\tconst [matches, setMatches] = useState<boolean>(() => {\n\t\tif (typeof window === \"undefined\") return false;\n\t\treturn window.matchMedia(query).matches;\n\t});\n\tuseEffect(() => {\n\t\tif (typeof window === \"undefined\") return;\n\t\tconst mql = window.matchMedia(query);\n\t\tconst handler = (e: MediaQueryListEvent) => setMatches(e.matches);\n\t\t// Sync once after subscription in case query changed between mount and effect.\n\t\tsetMatches(mql.matches);\n\t\tmql.addEventListener(\"change\", handler);\n\t\treturn () => mql.removeEventListener(\"change\", handler);\n\t}, [query]);\n\treturn matches;\n}\n"]}
|