@motir/design-system 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.
Files changed (107) hide show
  1. package/README.md +126 -0
  2. package/dist/appearance.d.ts +47 -0
  3. package/dist/appearance.js +3 -0
  4. package/dist/appearance.js.map +1 -0
  5. package/dist/components/theme/AppearancePickers.d.ts +97 -0
  6. package/dist/components/theme/AppearancePickers.js +542 -0
  7. package/dist/components/theme/AppearancePickers.js.map +1 -0
  8. package/dist/components/theme/HandDrawnFilter.d.ts +23 -0
  9. package/dist/components/theme/HandDrawnFilter.js +41 -0
  10. package/dist/components/theme/HandDrawnFilter.js.map +1 -0
  11. package/dist/components/theme/ImmersiveTilt.d.ts +3 -0
  12. package/dist/components/theme/ImmersiveTilt.js +113 -0
  13. package/dist/components/theme/ImmersiveTilt.js.map +1 -0
  14. package/dist/components/theme/StyleVignette.d.ts +60 -0
  15. package/dist/components/theme/StyleVignette.js +382 -0
  16. package/dist/components/theme/StyleVignette.js.map +1 -0
  17. package/dist/components/ui/Button.d.ts +32 -0
  18. package/dist/components/ui/Button.js +92 -0
  19. package/dist/components/ui/Button.js.map +1 -0
  20. package/dist/components/ui/Card.d.ts +33 -0
  21. package/dist/components/ui/Card.js +64 -0
  22. package/dist/components/ui/Card.js.map +1 -0
  23. package/dist/components/ui/ColorSwatchPicker.d.ts +19 -0
  24. package/dist/components/ui/ColorSwatchPicker.js +71 -0
  25. package/dist/components/ui/ColorSwatchPicker.js.map +1 -0
  26. package/dist/components/ui/Combobox.d.ts +84 -0
  27. package/dist/components/ui/Combobox.js +374 -0
  28. package/dist/components/ui/Combobox.js.map +1 -0
  29. package/dist/components/ui/EmptyState.d.ts +38 -0
  30. package/dist/components/ui/EmptyState.js +73 -0
  31. package/dist/components/ui/EmptyState.js.map +1 -0
  32. package/dist/components/ui/ErrorState.d.ts +43 -0
  33. package/dist/components/ui/ErrorState.js +169 -0
  34. package/dist/components/ui/ErrorState.js.map +1 -0
  35. package/dist/components/ui/FormField.d.ts +31 -0
  36. package/dist/components/ui/FormField.js +43 -0
  37. package/dist/components/ui/FormField.js.map +1 -0
  38. package/dist/components/ui/Input.d.ts +29 -0
  39. package/dist/components/ui/Input.js +105 -0
  40. package/dist/components/ui/Input.js.map +1 -0
  41. package/dist/components/ui/Modal.d.ts +84 -0
  42. package/dist/components/ui/Modal.js +153 -0
  43. package/dist/components/ui/Modal.js.map +1 -0
  44. package/dist/components/ui/MultiSelectPicker.d.ts +73 -0
  45. package/dist/components/ui/MultiSelectPicker.js +358 -0
  46. package/dist/components/ui/MultiSelectPicker.js.map +1 -0
  47. package/dist/components/ui/Pill.d.ts +49 -0
  48. package/dist/components/ui/Pill.js +107 -0
  49. package/dist/components/ui/Pill.js.map +1 -0
  50. package/dist/components/ui/Popover.d.ts +65 -0
  51. package/dist/components/ui/Popover.js +57 -0
  52. package/dist/components/ui/Popover.js.map +1 -0
  53. package/dist/components/ui/SectionLabel.d.ts +28 -0
  54. package/dist/components/ui/SectionLabel.js +27 -0
  55. package/dist/components/ui/SectionLabel.js.map +1 -0
  56. package/dist/components/ui/Segmented.d.ts +30 -0
  57. package/dist/components/ui/Segmented.js +65 -0
  58. package/dist/components/ui/Segmented.js.map +1 -0
  59. package/dist/components/ui/Spinner.d.ts +22 -0
  60. package/dist/components/ui/Spinner.js +39 -0
  61. package/dist/components/ui/Spinner.js.map +1 -0
  62. package/dist/components/ui/Switch.d.ts +34 -0
  63. package/dist/components/ui/Switch.js +50 -0
  64. package/dist/components/ui/Switch.js.map +1 -0
  65. package/dist/components/ui/Textarea.d.ts +19 -0
  66. package/dist/components/ui/Textarea.js +72 -0
  67. package/dist/components/ui/Textarea.js.map +1 -0
  68. package/dist/components/ui/Toast.d.ts +45 -0
  69. package/dist/components/ui/Toast.js +116 -0
  70. package/dist/components/ui/Toast.js.map +1 -0
  71. package/dist/components/ui/Tooltip.d.ts +25 -0
  72. package/dist/components/ui/Tooltip.js +43 -0
  73. package/dist/components/ui/Tooltip.js.map +1 -0
  74. package/dist/contexts/theme-context.d.ts +95 -0
  75. package/dist/contexts/theme-context.js +561 -0
  76. package/dist/contexts/theme-context.js.map +1 -0
  77. package/dist/index.d.ts +41 -0
  78. package/dist/index.js +67 -0
  79. package/dist/specimen/TokensSpecimen.d.ts +13 -0
  80. package/dist/specimen/TokensSpecimen.js +1090 -0
  81. package/dist/specimen/TokensSpecimen.js.map +1 -0
  82. package/dist/theme/appearance-resolution.d.ts +59 -0
  83. package/dist/theme/appearance-resolution.js +379 -0
  84. package/dist/theme/appearance-resolution.js.map +1 -0
  85. package/dist/theme/init-script.d.ts +48 -0
  86. package/dist/theme/init-script.js +391 -0
  87. package/dist/theme/init-script.js.map +1 -0
  88. package/dist/theme/palettes.d.ts +146 -0
  89. package/dist/theme/palettes.js +85 -0
  90. package/dist/theme/palettes.js.map +1 -0
  91. package/dist/theme/styles.d.ts +351 -0
  92. package/dist/theme/styles.js +264 -0
  93. package/dist/theme/styles.js.map +1 -0
  94. package/dist/theme/tilt.d.ts +41 -0
  95. package/dist/theme/tilt.js +18 -0
  96. package/dist/theme/tilt.js.map +1 -0
  97. package/dist/theme/types.d.ts +61 -0
  98. package/dist/theme/types.js +234 -0
  99. package/dist/theme/types.js.map +1 -0
  100. package/dist/theme/typography.d.ts +144 -0
  101. package/dist/theme/typography.js +57 -0
  102. package/dist/theme/typography.js.map +1 -0
  103. package/dist/utils/cn.d.ts +16 -0
  104. package/dist/utils/cn.js +11 -0
  105. package/dist/utils/cn.js.map +1 -0
  106. package/package.json +60 -0
  107. package/theme.css +3924 -0
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @motir/design-system
2
+
3
+ Motir's **3-axis design system** (Colour · Style · Type), extracted from
4
+ `motir-core` into a distributable package so the same tokens + primitives that
5
+ skin the Motir app can skin planner-scaffolded products — and the design choice
6
+ a user makes in onboarding can be re-applied by the coding agent at scaffold
7
+ time. One source, no vendored copies (the drift `notes.html` #18 warns of).
8
+
9
+ See the keystone decision: [`docs/decisions/design-system-package.md`](../../docs/decisions/design-system-package.md).
10
+
11
+ > **Scope of this package (Subtask MOTIR-1526).** This package is _created and
12
+ > verifiable in isolation_ here. Rewiring `motir-core` to consume it (replacing
13
+ > its inline `lib/theme/*` + `components/ui/*` with thin re-exports of this
14
+ > package) is the **next** subtask (MOTIR-1527), and the base-starter port is
15
+ > MOTIR-1528 — so each lands as its own reviewable PR. `motir-core`'s own files
16
+ > are untouched by this PR.
17
+
18
+ ## What it ships
19
+
20
+ - **`@motir/design-system/theme.css`** — the full token layer as a distributable
21
+ stylesheet: the Tailwind v4 `@theme` block, `@custom-variant dark`, the Tier-3
22
+ `--el-*` element tokens, and every `[data-palette]` / `[data-style]` /
23
+ `[data-type]` / `[data-surface]` override + surface-material block.
24
+ - **`@motir/design-system`** (JS entry) — the axis **registries**
25
+ (`STYLE_IDS` / `PALETTE_IDS` / `TYPE_IDS`, `resolve*`, `is*Id`,
26
+ `STYLE_DEFAULT_TYPE`), the **theme-apply API** (`buildThemeInitScript`,
27
+ `resolveAxesToApplied`, `resolveAppliedAppearance`, `THEME_STORAGE_KEYS`,
28
+ `THEME_DEFAULTS`), the applied-appearance **types**
29
+ (`AppliedAppearanceDto`, `AppearancePreferenceDto`, `ThemePattern`, `StyleId`,
30
+ `PaletteId`, `TypeId`), the `cn` helper, the **theme provider** (`ThemeProvider`
31
+ / `useTheme`), the axis **pickers** (`StylePicker` / `PalettePicker` /
32
+ `TypePicker` / `ThemeSegmentedControl`, from `AppearancePickers`) + the
33
+ `StyleVignette` live preview, and the framework-agnostic **UI primitives**.
34
+
35
+ ## Usage
36
+
37
+ Compose the tokens in your `globals.css` (Tailwind v4 reads `@theme` from the
38
+ imported CSS, so utilities generate with no extra config):
39
+
40
+ ```css
41
+ @import 'tailwindcss';
42
+ @import '@motir/design-system/theme.css';
43
+ ```
44
+
45
+ Apply a stored `{styleId, paletteId, typeId}` choice before hydration (FOUC-safe):
46
+
47
+ ```tsx
48
+ import { buildThemeInitScript, resolveAxesToApplied } from '@motir/design-system';
49
+
50
+ const applied = resolveAxesToApplied(storedChoice);
51
+ // render <script dangerouslySetInnerHTML={{ __html: buildThemeInitScript(applied) }} /> in <head>
52
+ ```
53
+
54
+ Use the primitives + provider as normal React components:
55
+
56
+ ```tsx
57
+ import { ThemeProvider, Button, Card } from '@motir/design-system';
58
+ ```
59
+
60
+ ## Boundary notes (how the extraction applied the ADR's rule)
61
+
62
+ The ADR's boundary rule: a file moves in **iff** its only imports are other
63
+ package files, framework peers, or `cn`. Applying it to the ADR's known lists
64
+ turned up three cases the audit's frozen list didn't fully reconcile — resolved
65
+ here, per the ADR's explicit delegation to this subtask:
66
+
67
+ - **`StyleVignette`** used the domain `IssueTypeIcon` for a single decorative
68
+ glyph. Kept in the package, decoupled by inlining that glyph
69
+ (`BookOpen` + `--el-type-story`) — pixel-identical, boundary-clean.
70
+ - **`Modal` / `Toast` / `ErrorState`** used `next-intl` for one aria/label string
71
+ each. Kept in, decoupled via optional label props (`closeLabel` /
72
+ `dialogLabel` / `retryLabel`) defaulting to the current English strings — so the
73
+ package carries no `next-intl` dependency (§5 "framework-agnostic where it
74
+ can"). motir-core will pass its translated labels when it consumes them (1527).
75
+ - **`DatePicker`** is genuinely coupled to the app's i18n (`useLocale` +
76
+ `@/lib/i18n/locales` + locale-aware date formatting). Per the ADR §1 escape
77
+ hatch ("a primitive that turns out to have a domain import the audit missed
78
+ stays out"), it is **excluded** from this extraction; decoupling it (inject a
79
+ formatter/locale) is a follow-up for the consume/port work.
80
+ - The token CSS ships as **`theme.css`** (the ADR §2 name; the card's provisional
81
+ `tokens.css` was reconciled to this).
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ pnpm --filter @motir/design-system build # tsup → dist (ESM + d.ts, 'use client' preserved)
87
+ pnpm --filter @motir/design-system typecheck # tsc --noEmit
88
+ pnpm --filter @motir/design-system test # vitest (registries, apply API, render)
89
+ ```
90
+
91
+ `TokensSpecimen` is a live `/tokens`-style render of the whole system for
92
+ in-isolation verification; drop it into any route or render it headless.
93
+
94
+ ## Releasing
95
+
96
+ Releases publish to the **public npm registry** (ADR §3) via CI — the
97
+ `.github/workflows/release-design-system.yml` workflow, triggered by a
98
+ package-scoped git tag. There is no manual `npm publish` in the normal path.
99
+
100
+ 1. **Bump the version** in `packages/design-system/package.json` (semver: the
101
+ token contract — `--el-*` names, the `data-*` attribute set, the apply API — is
102
+ the public surface, so renaming a token or attribute is a **major** bump).
103
+ 2. **Open + merge** the PR with the bump.
104
+ 3. **Tag and push** — the tag version MUST equal the `package.json` version (the
105
+ workflow guards this and fails fast otherwise):
106
+
107
+ ```bash
108
+ git tag design-system-v<x.y.z> # e.g. design-system-v0.1.1
109
+ git push origin design-system-v<x.y.z>
110
+ ```
111
+
112
+ 4. The workflow builds, verifies the tarball (`dist/**` + `theme.css`), and
113
+ publishes `@motir/design-system@<x.y.z>` with public access. Re-running an
114
+ already-published version is a no-op (idempotent), not a failure.
115
+
116
+ **Dry run:** trigger the workflow from the Actions tab via **Run workflow**
117
+ (`workflow_dispatch`) with **dry run** checked to build + pack without publishing.
118
+
119
+ **Auth:** the publish step authenticates with the `NPM_TOKEN` repository secret (an
120
+ npm automation/granular token with publish rights on the `@motir` scope). npm
121
+ provenance is left off by default — enabling it needs a `repository` field in this
122
+ `package.json` plus `id-token: write` in the workflow (see the workflow header).
123
+
124
+ > The **first** publish (`0.1.0`) may instead be cut manually (`npm publish`), or
125
+ > by pushing a `design-system-v0.1.0` tag once `NPM_TOKEN` is set — the workflow
126
+ > handles both the first and every subsequent release identically.
@@ -0,0 +1,47 @@
1
+ import { ThemePattern } from './theme/types.js';
2
+ import './theme/styles.js';
3
+ import './theme/typography.js';
4
+ import './theme/palettes.js';
5
+
6
+ /** The current user's resolved appearance preference — one valid id per axis. */
7
+ interface AppearancePreferenceDto {
8
+ /** Light/dark base (`system` follows the OS at runtime). */
9
+ pattern: ThemePattern;
10
+ /** Axis 2 — the active `data-style` id (e.g. `warm-editorial`). */
11
+ styleId: string;
12
+ /** Axis 1 — the active `data-palette` id (e.g. `motir`). */
13
+ paletteId: string;
14
+ /** Axis 3 — the active `data-type` id (e.g. `motir`). */
15
+ typeId: string;
16
+ }
17
+ /**
18
+ * The APPLIED appearance — what actually drives the four `<html>` data-attributes
19
+ * (Subtask 7.3.61). It differs from `AppearancePreferenceDto` in ONE place that
20
+ * the per-axis DTO deliberately deferred to this subtask: the **type axis is
21
+ * application-resolved**, not per-axis-resolved. A user who has not pinned a type
22
+ * follows the active STYLE's `defaultTypeId` here — NOT the global default the
23
+ * per-axis mapper falls back to. `typePinned` records whether the user actually
24
+ * pinned a type, so the client can seed the theme context's `typeChoice`
25
+ * (pinned id vs. `null` = follow-style) and the init script can reconcile the
26
+ * localStorage `type` key without converting an unpinned user into a pinned one.
27
+ *
28
+ * `pattern` is the RAW choice (`system` | `light` | `dark`): `system` only
29
+ * resolves to light/dark at runtime via `matchMedia`, so the server renders
30
+ * `data-theme` only for an explicit `light`/`dark` and leaves `system` to the
31
+ * init script. The other three axes have no runtime input and are fully
32
+ * server-resolvable, so they render on the first byte.
33
+ */
34
+ interface AppliedAppearanceDto {
35
+ /** Raw light/dark base — `system` resolves to light/dark via matchMedia. */
36
+ pattern: ThemePattern;
37
+ /** Axis 2 — the resolved `data-style` id. */
38
+ styleId: string;
39
+ /** Axis 1 — the resolved `data-palette` id. */
40
+ paletteId: string;
41
+ /** Axis 3 — the EFFECTIVE `data-type` id (pinned id, else the style default). */
42
+ typeId: string;
43
+ /** Whether the user explicitly pinned a type (vs. following the style default). */
44
+ typePinned: boolean;
45
+ }
46
+
47
+ export type { AppearancePreferenceDto, AppliedAppearanceDto };
@@ -0,0 +1,3 @@
1
+
2
+ //# sourceMappingURL=appearance.js.map
3
+ //# sourceMappingURL=appearance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"appearance.js"}
@@ -0,0 +1,97 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+ import { StyleId } from '../../theme/styles.js';
4
+ import { PaletteId } from '../../theme/palettes.js';
5
+ import { TypeId } from '../../theme/typography.js';
6
+ import { ThemePattern } from '../../theme/types.js';
7
+
8
+ /**
9
+ * The three-axis design-system PICKERS — the shared control vocabulary for
10
+ * Motir's own appearance (the account-settings Appearance pane, 7.3.58) AND the
11
+ * onboarding design wizard (7.3.27), which themes the user's OWN product with the
12
+ * same axes. They are kept here, beside `StyleVignette` (the 7.3.37 specimen), so
13
+ * the two surfaces share ONE implementation instead of forking the picker
14
+ * language (the 7.3.58 "share the axis pickers" requirement).
15
+ *
16
+ * Every picker is CONTROLLED + presentational: it renders the chips from the
17
+ * shipped registries (`lib/theme/{styles,palettes,typography}.ts`) and reports a
18
+ * `value` / `onChange` — it does NOT read `theme-context`. Each consumer wires the
19
+ * source of truth it owns: the Appearance pane wires `useTheme()` (so picking
20
+ * re-skins Motir live via the localStorage → `<html>` bootstrap); the onboarding
21
+ * wizard wires its own product-design state. Option NAMES + taglines come from the
22
+ * registries (brand/style names, like the `/tokens` composer), so the pickers can
23
+ * never drift from what the app can actually wear.
24
+ *
25
+ * Colour flows through `--el-*` only; chip / segmented shape flows through the
26
+ * element-semantic shape tokens (`--radius-badge` / `--radius-btn`,
27
+ * `--spacing-chip-*` / `--spacing-btn-x`, `--shadow-subtle`) so a `data-style`
28
+ * swap reshapes them too (CLAUDE.md colour + shape rules). The mock of record is
29
+ * `design/settings/appearance.mock.html` (7.3.57 / MOTIR-1074).
30
+ */
31
+ /**
32
+ * One hairline-separated axis row: a name + helper head, the control, and a live
33
+ * registry note showing the active selection. Matches the mock's `.axis-field`.
34
+ */
35
+ declare function AxisField({ name, help, children, note, }: {
36
+ name: string;
37
+ help: string;
38
+ children: ReactNode;
39
+ note?: ReactNode;
40
+ }): react_jsx_runtime.JSX.Element;
41
+ /** A live registry note — the bolded active name followed by its tagline. */
42
+ declare function AxisNote({ name, tagline }: {
43
+ name: string;
44
+ tagline: string;
45
+ }): react_jsx_runtime.JSX.Element;
46
+ interface AxisOption<T extends string> {
47
+ id: T;
48
+ /** Visible chip label. */
49
+ label: string;
50
+ /** Optional leading visual (e.g. a palette swatch dot) before the label. */
51
+ leading?: ReactNode;
52
+ /** Optional class on the label span (e.g. a per-type font scope). */
53
+ labelClassName?: string;
54
+ /** Optional data-* attrs on the chip so it can preview that axis value. */
55
+ scope?: Record<string, string>;
56
+ }
57
+ /**
58
+ * A `role="radiogroup"` of `.pick`-style chips with roving-tabindex + arrow-key
59
+ * navigation (the standard radiogroup interaction). Selecting moves focus too, so
60
+ * arrow keys both change and focus the selection. Generic over the id union.
61
+ */
62
+ declare function AxisRadioGroup<T extends string>({ label, value, onChange, options, }: {
63
+ label: string;
64
+ value: T;
65
+ onChange: (value: T) => void;
66
+ options: AxisOption<T>[];
67
+ }): react_jsx_runtime.JSX.Element;
68
+ /**
69
+ * The light/dark/system base as a segmented control (the mock's `.segmented`).
70
+ * `labels` maps each pattern to its translated chip text (the consumer owns i18n).
71
+ */
72
+ declare function ThemeSegmentedControl({ value, onChange, label, labels, }: {
73
+ value: ThemePattern;
74
+ onChange: (value: ThemePattern) => void;
75
+ label: string;
76
+ labels: Record<ThemePattern, string>;
77
+ }): react_jsx_runtime.JSX.Element;
78
+ /** Style (shape/feel) — the 6-style chip row. */
79
+ declare function StylePicker({ value, onChange, label, }: {
80
+ value: StyleId;
81
+ onChange: (value: StyleId) => void;
82
+ label: string;
83
+ }): react_jsx_runtime.JSX.Element;
84
+ /** Palette (colour) — the 10-palette chip row, each with its accent swatch dot. */
85
+ declare function PalettePicker({ value, onChange, label, }: {
86
+ value: PaletteId;
87
+ onChange: (value: PaletteId) => void;
88
+ label: string;
89
+ }): react_jsx_runtime.JSX.Element;
90
+ /** Typography — the 6-pairing chip row; each label set in its own headline face. */
91
+ declare function TypePicker({ value, onChange, label, }: {
92
+ value: TypeId;
93
+ onChange: (value: TypeId) => void;
94
+ label: string;
95
+ }): react_jsx_runtime.JSX.Element;
96
+
97
+ export { AxisField, AxisNote, type AxisOption, AxisRadioGroup, PalettePicker, StylePicker, ThemeSegmentedControl, TypePicker };