@motir/design-system 0.13.0 → 0.15.1

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.
Files changed (42) hide show
  1. package/dist/appearance.d.ts +8 -0
  2. package/dist/components/theme/AppearancePickers.d.ts +5 -1
  3. package/dist/components/theme/AppearancePickers.js +2 -1
  4. package/dist/components/theme/AppearancePickers.js.map +1 -1
  5. package/dist/components/ui/Combobox.js +25 -12
  6. package/dist/components/ui/Combobox.js.map +1 -1
  7. package/dist/components/ui/Modal.d.ts +3 -1
  8. package/dist/components/ui/Modal.js +28 -13
  9. package/dist/components/ui/Modal.js.map +1 -1
  10. package/dist/components/ui/MultiSelectPicker.js +25 -12
  11. package/dist/components/ui/MultiSelectPicker.js.map +1 -1
  12. package/dist/components/ui/Popover.js +36 -14
  13. package/dist/components/ui/Popover.js.map +1 -1
  14. package/dist/components/ui/Tooltip.js +28 -13
  15. package/dist/components/ui/Tooltip.js.map +1 -1
  16. package/dist/contexts/theme-context.d.ts +23 -0
  17. package/dist/contexts/theme-context.js +203 -2
  18. package/dist/contexts/theme-context.js.map +1 -1
  19. package/dist/index.d.ts +4 -4
  20. package/dist/mock/index.js +159 -1
  21. package/dist/mock/index.js.map +1 -1
  22. package/dist/mock/pruneUnmatchedRules.d.ts +16 -0
  23. package/dist/mock/pruneUnmatchedRules.js +160 -0
  24. package/dist/mock/pruneUnmatchedRules.js.map +1 -0
  25. package/dist/mock/renderMock.js +159 -1
  26. package/dist/mock/renderMock.js.map +1 -1
  27. package/dist/theme/appearance-resolution.d.ts +1 -0
  28. package/dist/theme/appearance-resolution.js.map +1 -1
  29. package/dist/theme/fontSets.d.ts +16 -1
  30. package/dist/theme/fontSets.js +13 -1
  31. package/dist/theme/fontSets.js.map +1 -1
  32. package/dist/theme/init-script.d.ts +32 -2
  33. package/dist/theme/init-script.js +38 -4
  34. package/dist/theme/init-script.js.map +1 -1
  35. package/dist/theme/types.d.ts +7 -0
  36. package/dist/theme/types.js +8 -1
  37. package/dist/theme/types.js.map +1 -1
  38. package/dist/utils/fullscreen.d.ts +23 -4
  39. package/dist/utils/fullscreen.js +25 -2
  40. package/dist/utils/fullscreen.js.map +1 -1
  41. package/package.json +1 -1
  42. package/theme.css +26 -0
@@ -354,7 +354,14 @@ var THEME_STORAGE_KEYS = {
354
354
  * before MOTIR-6471 swapped the palette identities, so it is read through
355
355
  * {@link PALETTE_ID_MIGRATION} once and the marker is then written.
356
356
  */
357
- paletteIds: "motir.theme.paletteIds"
357
+ paletteIds: "motir.theme.paletteIds",
358
+ /**
359
+ * The signed-in person's per-language font picks as `<html>` attributes,
360
+ * `{ [locale]: { 'data-font-set-<role>': member } }` (MOTIR-7896). Written by
361
+ * every signed-in layout render and cleared by every signed-out one, so only
362
+ * the error page — which cannot read the database — ever reads it back.
363
+ */
364
+ fontPicks: "motir.theme.fontPicks"
358
365
  };
359
366
  var PALETTE_IDS_VERSION = "2";
360
367
  var PALETTE_ID_MIGRATION = {
@@ -369,6 +376,162 @@ function migrateStoredPaletteId(value, marker) {
369
376
  var THEME_DEFAULTS = {
370
377
  pattern: "system",
371
378
  style: DEFAULT_STYLE_ID};
379
+
380
+ // src/theme/fontSets.ts
381
+ var FONT_SET_LOCALES = [
382
+ "en",
383
+ "zh",
384
+ "ja",
385
+ "ko",
386
+ "de",
387
+ "fr",
388
+ "es",
389
+ "it",
390
+ "nl",
391
+ "pl",
392
+ "pt"
393
+ ];
394
+ var FONT_SET_ROLES = ["sans", "serif", "mono"];
395
+ var TYPE_PAIRING = {
396
+ id: "type-pairing",
397
+ family: null,
398
+ source: { kind: "type-pairing" },
399
+ inDooooWeb: false
400
+ };
401
+ function google(id, family, inDooooWeb, sameFaceAs) {
402
+ return {
403
+ id,
404
+ family,
405
+ source: { kind: "next/font/google", googleFamily: family.replace(/ /g, "_") },
406
+ inDooooWeb,
407
+ ...sameFaceAs ? { sameFaceAs } : {}
408
+ };
409
+ }
410
+ var FONT_SET_REGISTRY = {
411
+ // dooooWeb: `latin`. Its Latin picker faces are deliberately not members:
412
+ // behind the pairing's face they would never draw a glyph (font-sets.md § latin).
413
+ latin: {
414
+ id: "latin",
415
+ label: "Latin",
416
+ lang: null,
417
+ cjk: false,
418
+ roles: {
419
+ sans: { default: "type-pairing", members: [TYPE_PAIRING] },
420
+ serif: { default: "type-pairing", members: [TYPE_PAIRING] },
421
+ mono: { default: "type-pairing", members: [TYPE_PAIRING] }
422
+ }
423
+ },
424
+ // dooooWeb: `chinese-simplified`.
425
+ "zh-Hans": {
426
+ id: "zh-Hans",
427
+ label: "Simplified Chinese",
428
+ lang: "zh",
429
+ cjk: true,
430
+ roles: {
431
+ sans: { default: "noto-sans-sc", members: [google("noto-sans-sc", "Noto Sans SC", false)] },
432
+ serif: {
433
+ default: "noto-serif-sc",
434
+ members: [
435
+ google("noto-serif-sc", "Noto Serif SC", true),
436
+ // Draws Traditional / inherited forms, so never the default (font-sets.md § Region forms).
437
+ google("lxgw-wenkai-tc", "LXGW WenKai TC", true)
438
+ ]
439
+ },
440
+ mono: {
441
+ default: "noto-sans-sc",
442
+ members: [google("noto-sans-sc", "Noto Sans SC", false, "sans")]
443
+ }
444
+ }
445
+ },
446
+ // dooooWeb: `japanese`.
447
+ ja: {
448
+ id: "ja",
449
+ label: "Japanese",
450
+ lang: "ja",
451
+ cjk: true,
452
+ roles: {
453
+ sans: {
454
+ default: "noto-sans-jp",
455
+ members: [
456
+ google("noto-sans-jp", "Noto Sans JP", true),
457
+ google("m-plus-rounded-1c", "M PLUS Rounded 1c", true)
458
+ ]
459
+ },
460
+ serif: {
461
+ default: "noto-serif-jp",
462
+ members: [google("noto-serif-jp", "Noto Serif JP", false)]
463
+ },
464
+ mono: {
465
+ default: "noto-sans-jp",
466
+ members: [google("noto-sans-jp", "Noto Sans JP", false, "sans")]
467
+ }
468
+ }
469
+ },
470
+ // dooooWeb: `korean`.
471
+ ko: {
472
+ id: "ko",
473
+ label: "Korean",
474
+ lang: "ko",
475
+ cjk: true,
476
+ roles: {
477
+ sans: {
478
+ default: "noto-sans-kr",
479
+ members: [
480
+ google("noto-sans-kr", "Noto Sans KR", true),
481
+ google("nanum-gothic", "Nanum Gothic", true)
482
+ ]
483
+ },
484
+ serif: {
485
+ default: "noto-serif-kr",
486
+ members: [google("noto-serif-kr", "Noto Serif KR", false)]
487
+ },
488
+ mono: {
489
+ default: "noto-sans-kr",
490
+ members: [google("noto-sans-kr", "Noto Sans KR", false, "sans")]
491
+ }
492
+ }
493
+ }
494
+ };
495
+ var LOCALE_FONT_SET = {
496
+ en: "latin",
497
+ zh: "zh-Hans",
498
+ ja: "ja",
499
+ ko: "ko",
500
+ de: "latin",
501
+ fr: "latin",
502
+ es: "latin",
503
+ it: "latin",
504
+ nl: "latin",
505
+ pl: "latin",
506
+ pt: "latin"
507
+ };
508
+ function isFontSetLocale(value) {
509
+ return typeof value === "string" && FONT_SET_LOCALES.includes(value);
510
+ }
511
+ function fontSetPickAttributes(locale, memberId) {
512
+ if (memberId === null) return {};
513
+ const set = FONT_SET_REGISTRY[LOCALE_FONT_SET[locale]];
514
+ const attrs = {};
515
+ for (const role of FONT_SET_ROLES) {
516
+ const r = set.roles[role];
517
+ if (memberId === r.default) continue;
518
+ const member = r.members.find((m) => m.id === memberId);
519
+ if (member && member.source.kind !== "type-pairing") attrs[`data-font-set-${role}`] = memberId;
520
+ }
521
+ return attrs;
522
+ }
523
+ function pageFontSetLocale() {
524
+ if (typeof document === "undefined") return null;
525
+ const primary = document.documentElement.lang.trim().split(/[-_]/)[0]?.toLowerCase() ?? "";
526
+ return isFontSetLocale(primary) ? primary : null;
527
+ }
528
+ function applyPageFontPick(locale, memberId) {
529
+ const root = document.documentElement;
530
+ for (const role of FONT_SET_ROLES) root.removeAttribute(`data-font-set-${role}`);
531
+ for (const [attr, value] of Object.entries(fontSetPickAttributes(locale, memberId))) {
532
+ root.setAttribute(attr, value);
533
+ }
534
+ }
372
535
  var SYNC_DEBOUNCE_MS = 250;
373
536
  var ThemeContext = createContext(null);
374
537
  function readStorage(key, fallback) {
@@ -448,11 +611,28 @@ function ThemeProvider({
448
611
  const syncSeqRef = useRef(0);
449
612
  const pendingPatchRef = useRef({});
450
613
  const debounceRef = useRef(null);
614
+ const [fontPicks, setFontPicksState] = useState(
615
+ {}
616
+ );
617
+ const appliedPageFontPickRef = useRef(void 0);
451
618
  const reconcile = useCallback((sent, resolved) => {
452
619
  if ("pattern" in sent) setPatternState(resolved.pattern);
453
620
  if ("styleId" in sent && isStyleId(resolved.styleId)) setStyleIdState(resolved.styleId);
454
621
  if ("paletteId" in sent && isPaletteId(resolved.paletteId)) setPaletteState(resolved.paletteId);
455
622
  if ("typeId" in sent && isTypeId(resolved.typeId)) setTypeChoiceState(resolved.typeId);
623
+ if (sent.fontPicks) {
624
+ const pageLocale = pageFontSetLocale();
625
+ const adopted = {};
626
+ for (const locale of Object.keys(sent.fontPicks)) {
627
+ const value2 = resolved.fontPicks?.[locale] ?? null;
628
+ adopted[locale] = value2;
629
+ if (locale === pageLocale && value2 !== appliedPageFontPickRef.current) {
630
+ applyPageFontPick(locale, value2);
631
+ appliedPageFontPickRef.current = value2;
632
+ }
633
+ }
634
+ setFontPicksState((prev) => ({ ...prev, ...adopted }));
635
+ }
456
636
  }, []);
457
637
  const flushSync = useCallback(() => {
458
638
  debounceRef.current = null;
@@ -478,7 +658,12 @@ function ThemeProvider({
478
658
  const queueSync = useCallback(
479
659
  (patch) => {
480
660
  if (!signedIn) return;
481
- pendingPatchRef.current = { ...pendingPatchRef.current, ...patch };
661
+ const prev = pendingPatchRef.current;
662
+ const next = { ...prev, ...patch };
663
+ if (prev.fontPicks || patch.fontPicks) {
664
+ next.fontPicks = { ...prev.fontPicks, ...patch.fontPicks };
665
+ }
666
+ pendingPatchRef.current = next;
482
667
  if (debounceRef.current) clearTimeout(debounceRef.current);
483
668
  debounceRef.current = setTimeout(flushSync, SYNC_DEBOUNCE_MS);
484
669
  },
@@ -537,6 +722,18 @@ function ThemeProvider({
537
722
  },
538
723
  [queueSync]
539
724
  );
725
+ const setFontPick = useCallback(
726
+ (locale, memberId) => {
727
+ if (!signedIn) return;
728
+ setFontPicksState((prev) => ({ ...prev, [locale]: memberId }));
729
+ if (locale === pageFontSetLocale()) {
730
+ applyPageFontPick(locale, memberId);
731
+ appliedPageFontPickRef.current = memberId;
732
+ }
733
+ queueSync({ fontPicks: { [locale]: memberId } });
734
+ },
735
+ [signedIn, queueSync]
736
+ );
540
737
  const value = useMemo(
541
738
  () => ({
542
739
  pattern,
@@ -548,6 +745,8 @@ function ThemeProvider({
548
745
  setStyleId,
549
746
  setPalette,
550
747
  setType,
748
+ fontPicks,
749
+ setFontPick,
551
750
  syncState
552
751
  }),
553
752
  [
@@ -560,6 +759,8 @@ function ThemeProvider({
560
759
  setStyleId,
561
760
  setPalette,
562
761
  setType,
762
+ fontPicks,
763
+ setFontPick,
563
764
  syncState
564
765
  ]
565
766
  );