@motir/design-system 0.10.0 → 0.11.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/dist/components/theme/StyleVignette.js +5 -2
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Button.js +5 -2
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Combobox.d.ts +32 -1
- package/dist/components/ui/Combobox.js +38 -11
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/ErrorState.js +5 -2
- package/dist/components/ui/ErrorState.js.map +1 -1
- package/dist/components/ui/Segmented.js +5 -2
- package/dist/components/ui/Segmented.js.map +1 -1
- package/dist/specimen/TokensSpecimen.d.ts +11 -3
- package/dist/specimen/TokensSpecimen.js +9 -290
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/package.json +3 -1
- package/theme.css +29 -11
|
@@ -1,60 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef, useId } from 'react';
|
|
4
3
|
import { cva } from 'class-variance-authority';
|
|
5
4
|
import { clsx } from 'clsx';
|
|
6
5
|
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
7
|
import { BookOpen, Search, SlidersHorizontal } from 'lucide-react';
|
|
8
8
|
|
|
9
|
-
// src/theme/typography.ts
|
|
10
|
-
var TYPE_REGISTRY = {
|
|
11
|
-
motir: {
|
|
12
|
-
id: "motir",
|
|
13
|
-
name: "Motir",
|
|
14
|
-
tagline: "Editorial serif headlines over a clean sans body \u2014 the house pairing.",
|
|
15
|
-
faces: "Source Serif 4 headlines \xB7 Inter body \xB7 JetBrains Mono meta.",
|
|
16
|
-
designDoc: "docs/typography/motir.md"
|
|
17
|
-
},
|
|
18
|
-
"motir-sans": {
|
|
19
|
-
id: "motir-sans",
|
|
20
|
-
name: "Motir Sans",
|
|
21
|
-
tagline: "All-sans \u2014 Inter for headlines and body; structural, no serif.",
|
|
22
|
-
faces: "Inter headlines + body \xB7 JetBrains Mono meta.",
|
|
23
|
-
designDoc: "docs/typography/motir-sans.md"
|
|
24
|
-
},
|
|
25
|
-
"motir-mono": {
|
|
26
|
-
id: "motir-mono",
|
|
27
|
-
name: "Motir Mono",
|
|
28
|
-
tagline: "Monospace throughout \u2014 a code-native UI, headlines, body and all.",
|
|
29
|
-
faces: "JetBrains Mono headlines + body/UI + meta.",
|
|
30
|
-
designDoc: "docs/typography/motir-mono.md"
|
|
31
|
-
},
|
|
32
|
-
grotesk: {
|
|
33
|
-
id: "grotesk",
|
|
34
|
-
name: "Grotesk",
|
|
35
|
-
tagline: "Geometric neo-grotesque across the whole UI \u2014 tight, confident, product-y.",
|
|
36
|
-
faces: "Space Grotesk headlines + body/UI \xB7 JetBrains Mono meta.",
|
|
37
|
-
designDoc: "docs/typography/grotesk.md"
|
|
38
|
-
},
|
|
39
|
-
editorial: {
|
|
40
|
-
id: "editorial",
|
|
41
|
-
name: "Editorial",
|
|
42
|
-
tagline: "A characterful display serif over a humanist sans \u2014 magazine, considered.",
|
|
43
|
-
faces: "Fraunces display headlines \xB7 Inter body \xB7 JetBrains Mono meta.",
|
|
44
|
-
designDoc: "docs/typography/editorial.md"
|
|
45
|
-
},
|
|
46
|
-
"mono-technical": {
|
|
47
|
-
id: "mono-technical",
|
|
48
|
-
name: "Mono-Technical",
|
|
49
|
-
tagline: "IBM Plex Mono throughout \u2014 a precise, developer-grade UI.",
|
|
50
|
-
faces: "IBM Plex Mono headlines + body/UI + meta/code.",
|
|
51
|
-
designDoc: "docs/typography/mono-technical.md"
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
function isTypeId(value) {
|
|
55
|
-
return typeof value === "string" && Object.prototype.hasOwnProperty.call(TYPE_REGISTRY, value);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
9
|
// src/theme/styles.ts
|
|
59
10
|
var STYLE_REGISTRY = {
|
|
60
11
|
"warm-editorial": {
|
|
@@ -257,13 +208,6 @@ var STYLE_REGISTRY = {
|
|
|
257
208
|
}
|
|
258
209
|
};
|
|
259
210
|
var STYLE_IDS = Object.keys(STYLE_REGISTRY);
|
|
260
|
-
var DEFAULT_STYLE_ID = "warm-editorial";
|
|
261
|
-
function isStyleId(value) {
|
|
262
|
-
return typeof value === "string" && Object.prototype.hasOwnProperty.call(STYLE_REGISTRY, value);
|
|
263
|
-
}
|
|
264
|
-
function resolveStyle(value) {
|
|
265
|
-
return STYLE_REGISTRY[isStyleId(value) ? value : DEFAULT_STYLE_ID];
|
|
266
|
-
}
|
|
267
211
|
Object.fromEntries(
|
|
268
212
|
STYLE_IDS.map((id) => [id, STYLE_REGISTRY[id].defaultTypeId])
|
|
269
213
|
);
|
|
@@ -342,234 +286,6 @@ var PALETTE_REGISTRY = {
|
|
|
342
286
|
}
|
|
343
287
|
};
|
|
344
288
|
var PALETTE_IDS = Object.keys(PALETTE_REGISTRY);
|
|
345
|
-
var DEFAULT_PALETTE_ID = "motir";
|
|
346
|
-
function isPaletteId(value) {
|
|
347
|
-
return typeof value === "string" && Object.prototype.hasOwnProperty.call(PALETTE_REGISTRY, value);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// src/theme/types.ts
|
|
351
|
-
var THEME_STORAGE_KEYS = {
|
|
352
|
-
pattern: "motir.theme.pattern",
|
|
353
|
-
style: "motir.theme.style",
|
|
354
|
-
palette: "motir.theme.palette",
|
|
355
|
-
type: "motir.theme.type",
|
|
356
|
-
/**
|
|
357
|
-
* The version marker of the STORED palette id's meaning. Absent (or anything
|
|
358
|
-
* but {@link PALETTE_IDS_VERSION}) means the stored `palette` was written
|
|
359
|
-
* before MOTIR-6471 swapped the palette identities, so it is read through
|
|
360
|
-
* {@link PALETTE_ID_MIGRATION} once and the marker is then written.
|
|
361
|
-
*/
|
|
362
|
-
paletteIds: "motir.theme.paletteIds"
|
|
363
|
-
};
|
|
364
|
-
var PALETTE_IDS_VERSION = "2";
|
|
365
|
-
var PALETTE_ID_MIGRATION = {
|
|
366
|
-
motir: "amethyst",
|
|
367
|
-
graphite: "motir"
|
|
368
|
-
};
|
|
369
|
-
function migrateStoredPaletteId(value, marker) {
|
|
370
|
-
if (value === null || marker === PALETTE_IDS_VERSION) return value;
|
|
371
|
-
if (!Object.prototype.hasOwnProperty.call(PALETTE_ID_MIGRATION, value)) return value;
|
|
372
|
-
return PALETTE_ID_MIGRATION[value] ?? value;
|
|
373
|
-
}
|
|
374
|
-
var THEME_DEFAULTS = {
|
|
375
|
-
pattern: "system",
|
|
376
|
-
style: DEFAULT_STYLE_ID};
|
|
377
|
-
var SYNC_DEBOUNCE_MS = 250;
|
|
378
|
-
var ThemeContext = createContext(null);
|
|
379
|
-
function readStorage(key, fallback) {
|
|
380
|
-
if (typeof window === "undefined") return fallback;
|
|
381
|
-
try {
|
|
382
|
-
return window.localStorage.getItem(key) ?? fallback;
|
|
383
|
-
} catch {
|
|
384
|
-
return fallback;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
function subscribeColorScheme(callback) {
|
|
388
|
-
if (typeof window === "undefined") return () => {
|
|
389
|
-
};
|
|
390
|
-
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
391
|
-
mql.addEventListener("change", callback);
|
|
392
|
-
return () => mql.removeEventListener("change", callback);
|
|
393
|
-
}
|
|
394
|
-
function getColorSchemeSnapshot() {
|
|
395
|
-
if (typeof window === "undefined") return "light";
|
|
396
|
-
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
397
|
-
}
|
|
398
|
-
function getColorSchemeServerSnapshot() {
|
|
399
|
-
return "light";
|
|
400
|
-
}
|
|
401
|
-
function ThemeProvider({
|
|
402
|
-
children,
|
|
403
|
-
initialPreference = null,
|
|
404
|
-
signedIn = false
|
|
405
|
-
}) {
|
|
406
|
-
const [pattern, setPatternState] = useState(
|
|
407
|
-
() => initialPreference ? initialPreference.pattern : readStorage(THEME_STORAGE_KEYS.pattern, THEME_DEFAULTS.pattern)
|
|
408
|
-
);
|
|
409
|
-
const [styleId, setStyleIdState] = useState(() => {
|
|
410
|
-
if (initialPreference) {
|
|
411
|
-
return isStyleId(initialPreference.styleId) ? initialPreference.styleId : DEFAULT_STYLE_ID;
|
|
412
|
-
}
|
|
413
|
-
const stored = readStorage(THEME_STORAGE_KEYS.style, THEME_DEFAULTS.style);
|
|
414
|
-
return isStyleId(stored) ? stored : DEFAULT_STYLE_ID;
|
|
415
|
-
});
|
|
416
|
-
const [palette, setPaletteState] = useState(() => {
|
|
417
|
-
if (initialPreference) {
|
|
418
|
-
return isPaletteId(initialPreference.paletteId) ? initialPreference.paletteId : DEFAULT_PALETTE_ID;
|
|
419
|
-
}
|
|
420
|
-
const stored = migrateStoredPaletteId(
|
|
421
|
-
readStorage(THEME_STORAGE_KEYS.palette, "") || null,
|
|
422
|
-
readStorage(THEME_STORAGE_KEYS.paletteIds, "") || null
|
|
423
|
-
);
|
|
424
|
-
return isPaletteId(stored) ? stored : DEFAULT_PALETTE_ID;
|
|
425
|
-
});
|
|
426
|
-
const [typeChoice, setTypeChoiceState] = useState(() => {
|
|
427
|
-
if (initialPreference) {
|
|
428
|
-
return initialPreference.typePinned && isTypeId(initialPreference.typeId) ? initialPreference.typeId : null;
|
|
429
|
-
}
|
|
430
|
-
const stored = readStorage(THEME_STORAGE_KEYS.type, "");
|
|
431
|
-
return isTypeId(stored) ? stored : null;
|
|
432
|
-
});
|
|
433
|
-
const osColorScheme = useSyncExternalStore(
|
|
434
|
-
subscribeColorScheme,
|
|
435
|
-
getColorSchemeSnapshot,
|
|
436
|
-
getColorSchemeServerSnapshot
|
|
437
|
-
);
|
|
438
|
-
const resolvedPattern = pattern === "system" ? osColorScheme : pattern;
|
|
439
|
-
const type = typeChoice ?? resolveStyle(styleId).defaultTypeId;
|
|
440
|
-
useEffect(() => {
|
|
441
|
-
document.documentElement.setAttribute("data-theme", resolvedPattern);
|
|
442
|
-
}, [resolvedPattern]);
|
|
443
|
-
useEffect(() => {
|
|
444
|
-
document.documentElement.setAttribute("data-style", styleId);
|
|
445
|
-
}, [styleId]);
|
|
446
|
-
useEffect(() => {
|
|
447
|
-
document.documentElement.setAttribute("data-palette", palette);
|
|
448
|
-
}, [palette]);
|
|
449
|
-
useEffect(() => {
|
|
450
|
-
document.documentElement.setAttribute("data-type", type);
|
|
451
|
-
}, [type]);
|
|
452
|
-
const [syncState, setSyncState] = useState("idle");
|
|
453
|
-
const syncSeqRef = useRef(0);
|
|
454
|
-
const pendingPatchRef = useRef({});
|
|
455
|
-
const debounceRef = useRef(null);
|
|
456
|
-
const reconcile = useCallback((sent, resolved) => {
|
|
457
|
-
if ("pattern" in sent) setPatternState(resolved.pattern);
|
|
458
|
-
if ("styleId" in sent && isStyleId(resolved.styleId)) setStyleIdState(resolved.styleId);
|
|
459
|
-
if ("paletteId" in sent && isPaletteId(resolved.paletteId)) setPaletteState(resolved.paletteId);
|
|
460
|
-
if ("typeId" in sent && isTypeId(resolved.typeId)) setTypeChoiceState(resolved.typeId);
|
|
461
|
-
}, []);
|
|
462
|
-
const flushSync = useCallback(() => {
|
|
463
|
-
debounceRef.current = null;
|
|
464
|
-
const patch = pendingPatchRef.current;
|
|
465
|
-
pendingPatchRef.current = {};
|
|
466
|
-
if (Object.keys(patch).length === 0) return;
|
|
467
|
-
const seq = ++syncSeqRef.current;
|
|
468
|
-
fetch("/api/appearance-preference", {
|
|
469
|
-
method: "PATCH",
|
|
470
|
-
headers: { "Content-Type": "application/json" },
|
|
471
|
-
body: JSON.stringify(patch)
|
|
472
|
-
}).then(async (res) => {
|
|
473
|
-
if (!res.ok) throw new Error(`appearance sync failed: ${res.status}`);
|
|
474
|
-
const body = await res.json();
|
|
475
|
-
if (seq !== syncSeqRef.current) return;
|
|
476
|
-
reconcile(patch, body.preference);
|
|
477
|
-
setSyncState("idle");
|
|
478
|
-
}).catch(() => {
|
|
479
|
-
if (seq !== syncSeqRef.current) return;
|
|
480
|
-
setSyncState("error");
|
|
481
|
-
});
|
|
482
|
-
}, [reconcile]);
|
|
483
|
-
const queueSync = useCallback(
|
|
484
|
-
(patch) => {
|
|
485
|
-
if (!signedIn) return;
|
|
486
|
-
pendingPatchRef.current = { ...pendingPatchRef.current, ...patch };
|
|
487
|
-
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
488
|
-
debounceRef.current = setTimeout(flushSync, SYNC_DEBOUNCE_MS);
|
|
489
|
-
},
|
|
490
|
-
[signedIn, flushSync]
|
|
491
|
-
);
|
|
492
|
-
useEffect(() => {
|
|
493
|
-
return () => {
|
|
494
|
-
if (debounceRef.current) {
|
|
495
|
-
clearTimeout(debounceRef.current);
|
|
496
|
-
flushSync();
|
|
497
|
-
}
|
|
498
|
-
};
|
|
499
|
-
}, [flushSync]);
|
|
500
|
-
const setPattern = useCallback(
|
|
501
|
-
(next) => {
|
|
502
|
-
setPatternState(next);
|
|
503
|
-
try {
|
|
504
|
-
window.localStorage.setItem(THEME_STORAGE_KEYS.pattern, next);
|
|
505
|
-
} catch {
|
|
506
|
-
}
|
|
507
|
-
queueSync({ pattern: next });
|
|
508
|
-
},
|
|
509
|
-
[queueSync]
|
|
510
|
-
);
|
|
511
|
-
const setStyleId = useCallback(
|
|
512
|
-
(next) => {
|
|
513
|
-
setStyleIdState(next);
|
|
514
|
-
try {
|
|
515
|
-
window.localStorage.setItem(THEME_STORAGE_KEYS.style, next);
|
|
516
|
-
} catch {
|
|
517
|
-
}
|
|
518
|
-
queueSync({ styleId: next });
|
|
519
|
-
},
|
|
520
|
-
[queueSync]
|
|
521
|
-
);
|
|
522
|
-
const setPalette = useCallback(
|
|
523
|
-
(next) => {
|
|
524
|
-
setPaletteState(next);
|
|
525
|
-
try {
|
|
526
|
-
window.localStorage.setItem(THEME_STORAGE_KEYS.palette, next);
|
|
527
|
-
window.localStorage.setItem(THEME_STORAGE_KEYS.paletteIds, PALETTE_IDS_VERSION);
|
|
528
|
-
} catch {
|
|
529
|
-
}
|
|
530
|
-
queueSync({ paletteId: next });
|
|
531
|
-
},
|
|
532
|
-
[queueSync]
|
|
533
|
-
);
|
|
534
|
-
const setType = useCallback(
|
|
535
|
-
(next) => {
|
|
536
|
-
setTypeChoiceState(next);
|
|
537
|
-
try {
|
|
538
|
-
window.localStorage.setItem(THEME_STORAGE_KEYS.type, next);
|
|
539
|
-
} catch {
|
|
540
|
-
}
|
|
541
|
-
queueSync({ typeId: next });
|
|
542
|
-
},
|
|
543
|
-
[queueSync]
|
|
544
|
-
);
|
|
545
|
-
const value = useMemo(
|
|
546
|
-
() => ({
|
|
547
|
-
pattern,
|
|
548
|
-
resolvedPattern,
|
|
549
|
-
styleId,
|
|
550
|
-
palette,
|
|
551
|
-
type,
|
|
552
|
-
setPattern,
|
|
553
|
-
setStyleId,
|
|
554
|
-
setPalette,
|
|
555
|
-
setType,
|
|
556
|
-
syncState
|
|
557
|
-
}),
|
|
558
|
-
[
|
|
559
|
-
pattern,
|
|
560
|
-
resolvedPattern,
|
|
561
|
-
styleId,
|
|
562
|
-
palette,
|
|
563
|
-
type,
|
|
564
|
-
setPattern,
|
|
565
|
-
setStyleId,
|
|
566
|
-
setPalette,
|
|
567
|
-
setType,
|
|
568
|
-
syncState
|
|
569
|
-
]
|
|
570
|
-
);
|
|
571
|
-
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
572
|
-
}
|
|
573
289
|
function cn(...inputs) {
|
|
574
290
|
return twMerge(clsx(inputs));
|
|
575
291
|
}
|
|
@@ -602,8 +318,11 @@ var buttonVariants = cva(
|
|
|
602
318
|
cn(
|
|
603
319
|
// Base layout
|
|
604
320
|
"inline-flex items-center justify-center gap-2",
|
|
605
|
-
// Typography
|
|
606
|
-
|
|
321
|
+
// Typography. `whitespace-nowrap`: a label longer than its English draft
|
|
322
|
+
// (German and Polish run 30–40% longer) WIDENS the button instead of
|
|
323
|
+
// wrapping to a second line inside a box one line tall, where the text
|
|
324
|
+
// spills out of the control (MOTIR-7759).
|
|
325
|
+
"font-sans text-sm font-medium leading-none whitespace-nowrap",
|
|
607
326
|
// Shape — semantic tokens that flip with the active style
|
|
608
327
|
"rounded-(--radius-btn)",
|
|
609
328
|
// Interaction
|
|
@@ -1124,7 +843,7 @@ function Swatches() {
|
|
|
1124
843
|
] }, token)) });
|
|
1125
844
|
}
|
|
1126
845
|
function TokensSpecimen({ className }) {
|
|
1127
|
-
return /* @__PURE__ */ jsx(
|
|
846
|
+
return /* @__PURE__ */ jsx("div", { className, "data-surface": "page", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-[64rem] flex-col gap-8 p-8", children: [
|
|
1128
847
|
/* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-1", children: [
|
|
1129
848
|
/* @__PURE__ */ jsx("h1", { className: "font-serif text-2xl font-semibold text-(--el-text)", children: "@motir/design-system" }),
|
|
1130
849
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-(--el-text-muted)", children: "Colour \xB7 Style \xB7 Type \u2014 the extracted 3-axis system, rendered in isolation." })
|
|
@@ -1154,7 +873,7 @@ function TokensSpecimen({ className }) {
|
|
|
1154
873
|
/* @__PURE__ */ jsx(SectionLabel, { children: "Palette axis (data-palette)" }),
|
|
1155
874
|
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: PALETTE_IDS.map((palette) => /* @__PURE__ */ jsx(StyleVignette, { palette, label: `Palette: ${palette}` }, palette)) })
|
|
1156
875
|
] })
|
|
1157
|
-
] }) })
|
|
876
|
+
] }) });
|
|
1158
877
|
}
|
|
1159
878
|
var TokensSpecimen_default = TokensSpecimen;
|
|
1160
879
|
|