@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.
@@ -1,60 +1,11 @@
1
1
  'use client';
2
- import { createContext, forwardRef, useId, useState, useSyncExternalStore, useEffect, useRef, useCallback, useMemo } from 'react';
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
- "font-sans text-sm font-medium leading-none",
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(ThemeProvider, { children: /* @__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: [
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