@ai-matrx/design-system 0.3.0 → 0.4.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.
@@ -0,0 +1,175 @@
1
+ /* @ai-matrx/design-system — REQUIRED structural stylesheet.
2
+
3
+ Import once at your app root, BEFORE or AFTER your own CSS (the cascade is
4
+ settled by layers, not by order — see below):
5
+
6
+ import "@ai-matrx/design-system/tokens.css"; // default token VALUES
7
+ import "@ai-matrx/design-system/styles.css"; // this file — structure
8
+
9
+ WHAT LIVES HERE (C26): every CSS rule this package's components REQUIRE to
10
+ render correctly — layout, blur/border mechanics, mask geometry, safe-area
11
+ padding, mobile density. Colour and brand enter ONLY through the semantic
12
+ token vocabulary (`var(--…)`); this file hardcodes no colour. Token VALUES
13
+ ship separately in `tokens.css` and are meant to be overridden by the host.
14
+
15
+ THE CASCADE CONTRACT
16
+ --------------------
17
+ Rules that must behave like ordinary utilities (so a host utility on the
18
+ same element still wins, exactly as it did when these lived in the host's
19
+ `@layer components` / `@layer utilities`) live in the
20
+ `matrx-design-system` layer. A host puts that layer LOWEST by declaring the
21
+ order once, as the FIRST line of its Tailwind/CSS entry:
22
+
23
+ @layer matrx-design-system-tokens, matrx-design-system;
24
+ @import "tailwindcss";
25
+
26
+ Rules that must beat utilities (the scroll-fade mask, the mobile-sheet
27
+ density restatement) are UNLAYERED on purpose, matching how they were
28
+ authored in matrx-frontend's globals.css. Unlayered CSS beats every layered
29
+ rule regardless of specificity — that is the only way to re-scale
30
+ utility-set font sizes, and the only way a mask survives a `mask-*` reset.
31
+
32
+ Extracted from matrx-frontend `app/globals.css` (the tap-target precedent);
33
+ the host originals were deleted in the same session that shipped this file.
34
+ ========================================================================= */
35
+
36
+ @layer matrx-design-system {
37
+ /* -----------------------------------------------------------------------
38
+ shadow-input — the Input family's resting elevation.
39
+ Tailwind v4 hosts also generate a `.shadow-input` utility from a
40
+ `--shadow-input` theme entry; that generated utility sits in
41
+ `@layer utilities` and therefore WINS over this one, with the identical
42
+ value. This rule exists so the Input renders correctly in a host that has
43
+ no such theme entry (a fresh app, a non-Tailwind app).
44
+ ----------------------------------------------------------------------- */
45
+ .shadow-input {
46
+ box-shadow: var(--shadow-input);
47
+ }
48
+
49
+ /* -----------------------------------------------------------------------
50
+ pb-safe — safe-area bottom padding (BottomSheet body/footer, CommandDialog
51
+ mobile sheet). max() guarantees a minimum padding even when the inset is
52
+ 0 (env() resolves to 0 on non-notched devices and when viewport-fit=cover
53
+ is absent).
54
+ ----------------------------------------------------------------------- */
55
+ .pb-safe {
56
+ padding-bottom: max(0.75rem, env(safe-area-inset-bottom, 0px));
57
+ }
58
+
59
+ /* -----------------------------------------------------------------------
60
+ matrx-glass-thin-border — the glass chrome the BottomSheet close control
61
+ and drag affordances wear. The STRUCTURE (tinted fill, backdrop blur +
62
+ saturate, elevation, thin stroke) is package-owned; every colour, blur
63
+ radius and stroke width resolves from a --matrx-glass-* token, so a host
64
+ re-tints the whole system by redefining those values and nothing else.
65
+
66
+ matrx-frontend authored these with Tailwind's `@apply bg-glass
67
+ shadow-glass backdrop-blur-glass backdrop-saturate-glass`, whose theme
68
+ aliases are `var(--matrx-glass-bg)` / `-shadow` / `-blur` / `-saturate`.
69
+ Expanded to the leaf tokens here so the rule needs no Tailwind.
70
+ ----------------------------------------------------------------------- */
71
+ .matrx-glass-thin-border {
72
+ background-color: var(--matrx-glass-bg);
73
+ box-shadow: var(--matrx-glass-shadow);
74
+ -webkit-backdrop-filter: blur(var(--matrx-glass-blur))
75
+ saturate(var(--matrx-glass-saturate));
76
+ backdrop-filter: blur(var(--matrx-glass-blur))
77
+ saturate(var(--matrx-glass-saturate));
78
+ border: var(--matrx-glass-border-width-thin) solid
79
+ var(--matrx-glass-border-color);
80
+ }
81
+ .matrx-glass-thin-border:hover {
82
+ background-color: var(--matrx-glass-bg-hover);
83
+ }
84
+ .matrx-glass-thin-border:active {
85
+ background-color: var(--matrx-glass-bg-active);
86
+ }
87
+ /* Segmented pills: direct child segments own hover — don't lift the shell. */
88
+ .matrx-glass-thin-border:has(> a:hover),
89
+ .matrx-glass-thin-border:has(> a:active) {
90
+ background-color: var(--matrx-glass-bg);
91
+ }
92
+ }
93
+
94
+ /* ────────────────────────────────────────────────────────────────────────
95
+ Scroll fade — the "there is more below" cue. UNLAYERED on purpose.
96
+ Driven by `useScrollFade`, which sets the data attributes ONLY on edges
97
+ that actually overflow. A hard-clipped last row reads as "finished" and the
98
+ user never scrolls; a permanent fade makes a non-scrolling list's last item
99
+ look broken. Hence: measured, not assumed.
100
+
101
+ mask-image is used (not an overlay element) so the fade works over any
102
+ background — popovers, drawers, cards — with no extra DOM and no
103
+ pointer-event interception.
104
+ ──────────────────────────────────────────────────────────────────────── */
105
+ .matrx-scroll-fade[data-fade-bottom] {
106
+ mask-image: linear-gradient(
107
+ to bottom,
108
+ black calc(100% - 2rem),
109
+ transparent 100%
110
+ );
111
+ }
112
+ .matrx-scroll-fade[data-fade-top] {
113
+ mask-image: linear-gradient(
114
+ to top,
115
+ black calc(100% - 2rem),
116
+ transparent 100%
117
+ );
118
+ }
119
+ .matrx-scroll-fade[data-fade-top][data-fade-bottom] {
120
+ mask-image: linear-gradient(
121
+ to bottom,
122
+ transparent 0,
123
+ black 2rem,
124
+ black calc(100% - 2rem),
125
+ transparent 100%
126
+ );
127
+ }
128
+
129
+ /* ────────────────────────────────────────────────────────────────────────
130
+ .matrx-mobile-sheet — ONE mobile density for any desktop panel hosted in a
131
+ bottom sheet (TabbedBottomSheet sets it; see that file's header).
132
+
133
+ The panels a sheet hosts are usually the SAME components a desktop window
134
+ renders, authored at desktop density — text-[9px]…text-xs and h-7 rows. On
135
+ a phone that is unreadable, untappable, and inconsistent with the 16px
136
+ index list one tap away, so the sheet restates the scale once, here,
137
+ instead of every panel growing an `isMobile` branch (which is what made
138
+ these surfaces diverge in the first place).
139
+
140
+ Deliberately UNLAYERED: Tailwind utilities live in @layer utilities, and
141
+ unlayered CSS beats every layered rule regardless of specificity — the only
142
+ way to re-scale utility-set font sizes. Scoped to this class, so the blast
143
+ radius is exactly the sheet. Font size and control height ONLY — never
144
+ colour, spacing, or layout.
145
+
146
+ Fields are forced to 16px because anything smaller makes iOS Safari zoom the
147
+ page on focus, which is itself a "the panel resized" bug.
148
+ ──────────────────────────────────────────────────────────────────────── */
149
+ .matrx-mobile-sheet .text-\[9px\],
150
+ .matrx-mobile-sheet .text-\[10px\],
151
+ .matrx-mobile-sheet .text-\[11px\] {
152
+ font-size: 0.8125rem;
153
+ line-height: 1.15rem;
154
+ }
155
+ .matrx-mobile-sheet .text-xs {
156
+ font-size: 0.875rem;
157
+ line-height: 1.25rem;
158
+ }
159
+ .matrx-mobile-sheet .text-sm {
160
+ font-size: 0.9375rem;
161
+ line-height: 1.375rem;
162
+ }
163
+ .matrx-mobile-sheet input,
164
+ .matrx-mobile-sheet textarea,
165
+ .matrx-mobile-sheet select,
166
+ .matrx-mobile-sheet [contenteditable="true"] {
167
+ font-size: 16px;
168
+ }
169
+ /* h-7 rows (list rows, search fields) are below the 44pt touch target. Raise
170
+ the floor with min-height — it wins over the utility's height without
171
+ unpinning it. Square icon buttons (h-7 w-7) are excluded so they stay
172
+ square. */
173
+ .matrx-mobile-sheet .h-7:not([class*="w-7"]) {
174
+ min-height: 2.25rem;
175
+ }
package/dist/theme.css ADDED
@@ -0,0 +1,56 @@
1
+ /* @ai-matrx/design-system — Tailwind v4 registration (OPTIONAL, Tailwind hosts).
2
+
3
+ The package's components are authored in semantic utility classes
4
+ (`bg-background`, `text-muted-foreground`, `border-border`, …). On a Tailwind
5
+ v4 host those utilities only exist if (a) the package's built output is a
6
+ scan source and (b) the semantic colours are mapped in `@theme`. This file is
7
+ both, so a fresh app is one line from drop-in:
8
+
9
+ @import "tailwindcss";
10
+ @import "@ai-matrx/design-system/theme.css";
11
+
12
+ It must be `@import`ed from your Tailwind ENTRY stylesheet (so Tailwind
13
+ processes it) — never `import`ed from JavaScript; `@theme` is a Tailwind
14
+ at-rule, not CSS. `tokens.css` (the default VALUES) comes along with it.
15
+
16
+ A host that already maps this vocabulary in its own `@theme` — matrx-frontend
17
+ does, as a superset with its own brand — does not need this file. Nothing
18
+ here is a brand: every mapping points back at a token whose value the host
19
+ owns. */
20
+
21
+ @source "./";
22
+
23
+ @import "./tokens.css";
24
+
25
+ @theme inline {
26
+ --color-background: hsl(var(--background));
27
+ --color-foreground: hsl(var(--foreground));
28
+ --color-card: hsl(var(--card));
29
+ --color-card-foreground: hsl(var(--card-foreground));
30
+ --color-popover: hsl(var(--popover));
31
+ --color-popover-foreground: hsl(var(--popover-foreground));
32
+ --color-primary: hsl(var(--primary));
33
+ --color-primary-foreground: hsl(var(--primary-foreground));
34
+ --color-secondary: hsl(var(--secondary));
35
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
36
+ --color-muted: hsl(var(--muted));
37
+ --color-muted-foreground: hsl(var(--muted-foreground));
38
+ --color-accent: hsl(var(--accent));
39
+ --color-accent-foreground: hsl(var(--accent-foreground));
40
+ --color-destructive: hsl(var(--destructive));
41
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
42
+ --color-success: hsl(var(--success));
43
+ --color-success-foreground: hsl(var(--success-foreground));
44
+ --color-warning: hsl(var(--warning));
45
+ --color-warning-foreground: hsl(var(--warning-foreground));
46
+ --color-info: hsl(var(--info));
47
+ --color-border: hsl(var(--border));
48
+ --color-input: hsl(var(--input));
49
+ --color-ring: hsl(var(--ring));
50
+ }
51
+
52
+ /* NOTE: no `--shadow-*` entry for the input elevation. `styles.css` ships the
53
+ `.shadow-input` rule itself (reading `--shadow-input` from tokens.css), so
54
+ adding a Tailwind theme entry of the same name here would only define the
55
+ token in terms of itself. A host that wants `shadow-input` composable with
56
+ ring utilities declares `--shadow-input` in its own `@theme`, which wins. */
@@ -0,0 +1,156 @@
1
+ /* @ai-matrx/design-system — DEFAULT token values.
2
+
3
+ Import once at your app root, before `styles.css`:
4
+
5
+ import "@ai-matrx/design-system/tokens.css";
6
+ import "@ai-matrx/design-system/styles.css";
7
+
8
+ WHY THIS FILE EXISTS (C26): the package is opinionated about the token
9
+ CONTRACT — every colour its components and its structural CSS reference
10
+ resolves from one of the custom properties below — but the token VALUES are
11
+ host-owned. This sheet ships a neutral, sensible default for every one of
12
+ them so the package renders correctly UNTHEMED in a fresh app. It is not a
13
+ brand: hosts override any subset with their own values and this file gets
14
+ out of the way.
15
+
16
+ HOW OVERRIDING WORKS
17
+ --------------------
18
+ Every default is declared inside `@layer matrx-design-system-tokens`, and
19
+ the host declares that layer FIRST so it sits at the BOTTOM of the cascade:
20
+
21
+ @layer matrx-design-system-tokens, matrx-design-system; ← first line
22
+ @import "tailwindcss";
23
+
24
+ With that one line, ANY host declaration of the same token wins — layered
25
+ (`@layer base { :root { --primary: … } }`) or unlayered, earlier in the file
26
+ or later, higher specificity or lower. Without it, layer order falls back to
27
+ first-appearance order and a host whose own tokens are layered could lose to
28
+ these defaults; declare it and the question never arises.
29
+
30
+ COLOUR FORMAT: bare HSL channels (`240 5% 96%`), consumed as
31
+ `hsl(var(--background))`. That is the shadcn/Tailwind-v4 convention the
32
+ whole AI Matrx vocabulary uses, and it is what makes `bg-primary/15` style
33
+ alpha compositing work.
34
+
35
+ TAILWIND v4 HOSTS: these raw values are only half the story — the semantic
36
+ utilities (`bg-background`, `text-muted-foreground`, …) also need the theme
37
+ mapping. `@import "@ai-matrx/design-system/theme.css";` from your Tailwind
38
+ entry does that (and registers the package's dist as a scan source).
39
+ ========================================================================= */
40
+
41
+ @layer matrx-design-system-tokens {
42
+ :root {
43
+ /* Surfaces */
44
+ --background: 0 0% 100%;
45
+ --foreground: 240 10% 4%;
46
+ --card: 0 0% 100%;
47
+ --card-foreground: 240 10% 4%;
48
+ --popover: 0 0% 100%;
49
+ --popover-foreground: 240 10% 4%;
50
+
51
+ /* Brand roles — deliberately neutral. Override these first. */
52
+ --primary: 240 6% 10%;
53
+ --primary-foreground: 0 0% 98%;
54
+ --secondary: 240 5% 96%;
55
+ --secondary-foreground: 240 6% 10%;
56
+
57
+ /* Quiet roles */
58
+ --muted: 240 5% 96%;
59
+ --muted-foreground: 240 4% 46%;
60
+ --accent: 240 5% 96%;
61
+ --accent-foreground: 240 6% 10%;
62
+
63
+ /* Status roles */
64
+ --destructive: 0 84% 60%;
65
+ --destructive-foreground: 0 0% 98%;
66
+ --success: 142 76% 36%;
67
+ --success-foreground: 144 70% 98%;
68
+ --warning: 38 92% 50%;
69
+ --warning-foreground: 48 96% 98%;
70
+ --info: 200 98% 39%;
71
+
72
+ /* Lines and focus */
73
+ --border: 240 6% 90%;
74
+ --input: 240 6% 90%;
75
+ --ring: 240 5% 65%;
76
+
77
+ /* Input elevation — consumed by the `.shadow-input` rule in styles.css
78
+ and by the Tailwind `shadow-input` utility on Tailwind hosts. */
79
+ --shadow-input:
80
+ 0px 2px 3px -1px rgba(0, 0, 0, 0.1), 0px 1px 0px 0px rgba(25, 28, 33, 0.02),
81
+ 0px 0px 0px 1px rgba(25, 28, 33, 0.08);
82
+
83
+ /* Overlay scrims — the dimming behind Sheet / BottomSheet / CommandDialog.
84
+ Separate from the colour roles on purpose: a scrim is a neutral
85
+ darkening of whatever is behind it, not a brand colour. */
86
+ --matrx-overlay-scrim: rgb(0 0 0 / 0.8);
87
+ --matrx-overlay-scrim-soft: rgb(0 0 0 / 0.2);
88
+ /* The drawer (BottomSheet) scrim is deliberately far lighter: the sheet
89
+ covers most of a phone screen already, so a heavy scrim reads as a
90
+ broken page rather than as depth. */
91
+ --matrx-overlay-scrim-drawer: rgb(0 0 0 / 0.08);
92
+ /* Hairline divider inside glass sheets (BottomSheet footer). */
93
+ --matrx-sheet-divider: rgb(255 255 255 / 0.06);
94
+
95
+ /* Matrx Glass — the physical-material tokens the glass chrome resolves
96
+ from. Every one is consumed by `.matrx-glass-thin-border` in
97
+ styles.css; redefine them to re-tint the whole glass system. */
98
+ --matrx-glass-bg: rgba(210, 225, 255, 0.14);
99
+ --matrx-glass-bg-hover: rgba(210, 225, 255, 0.22);
100
+ --matrx-glass-bg-active: rgba(210, 225, 255, 0.32);
101
+ --matrx-glass-border-color: rgba(180, 205, 255, 0.7);
102
+ --matrx-glass-border-width: 3.5px;
103
+ --matrx-glass-border-width-thin: 1.5px;
104
+ --matrx-glass-blur: 4px;
105
+ --matrx-glass-saturate: 1.8;
106
+ --matrx-glass-shadow:
107
+ 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
108
+ --matrx-glass-shadow-lg:
109
+ 0 4px 16px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.04);
110
+ }
111
+
112
+ /* Dark theme. Matches the `.dark`-class convention (Tailwind v4 hosts wire
113
+ it with `@custom-variant dark (&:where(.dark, .dark *))`); a host using a
114
+ different dark switch redeclares these under its own selector. */
115
+ .dark {
116
+ --background: 240 10% 4%;
117
+ --foreground: 0 0% 98%;
118
+ --card: 240 10% 4%;
119
+ --card-foreground: 0 0% 98%;
120
+ --popover: 240 10% 4%;
121
+ --popover-foreground: 0 0% 98%;
122
+
123
+ --primary: 0 0% 98%;
124
+ --primary-foreground: 240 6% 10%;
125
+ --secondary: 240 4% 16%;
126
+ --secondary-foreground: 0 0% 98%;
127
+
128
+ --muted: 240 4% 16%;
129
+ --muted-foreground: 240 5% 65%;
130
+ --accent: 240 4% 16%;
131
+ --accent-foreground: 0 0% 98%;
132
+
133
+ --destructive: 0 72% 60%;
134
+ --destructive-foreground: 0 86% 97%;
135
+ --success: 142 69% 58%;
136
+ --success-foreground: 144 70% 98%;
137
+ --warning: 48 96% 58%;
138
+ --warning-foreground: 48 96% 98%;
139
+ --info: 200 90% 60%;
140
+
141
+ --border: 240 4% 16%;
142
+ --input: 240 4% 16%;
143
+ --ring: 240 5% 34%;
144
+
145
+ --matrx-overlay-scrim-soft: rgb(0 0 0 / 0.3);
146
+
147
+ --matrx-glass-bg: rgba(180, 200, 255, 0.05);
148
+ --matrx-glass-bg-hover: rgba(180, 200, 255, 0.09);
149
+ --matrx-glass-bg-active: rgba(180, 200, 255, 0.13);
150
+ --matrx-glass-border-color: rgba(255, 255, 255, 0.07);
151
+ --matrx-glass-shadow:
152
+ 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.12);
153
+ --matrx-glass-shadow-lg:
154
+ 0 4px 16px rgba(0, 0, 0, 0.25), 0 2px 6px rgba(0, 0, 0, 0.12);
155
+ }
156
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/design-system",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "AI Matrx semantic React primitives shared across Vite, Next.js, desktop, and customer-built applications.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,9 +30,13 @@
30
30
  "default": "./dist/index.cjs"
31
31
  }
32
32
  },
33
+ "./styles.css": "./dist/styles.css",
34
+ "./tokens.css": "./dist/tokens.css",
35
+ "./theme.css": "./dist/theme.css",
33
36
  "./package.json": "./package.json"
34
37
  },
35
38
  "dependencies": {
39
+ "@ai-matrx/kit": "^0.7.3",
36
40
  "@radix-ui/react-dialog": "^1.1.17",
37
41
  "@radix-ui/react-label": "^2.1.8",
38
42
  "@radix-ui/react-popover": "^1.1.17",
@@ -47,7 +51,6 @@
47
51
  "vaul": "^1.1.2"
48
52
  },
49
53
  "peerDependencies": {
50
- "@ai-matrx/kit": ">=0.5.1",
51
54
  "react": ">=18.0.0",
52
55
  "react-dom": ">=18.0.0"
53
56
  },
@@ -63,8 +66,7 @@
63
66
  "react-dom": "^19.1.0",
64
67
  "tsup": "^8.5.1",
65
68
  "typescript": "^5.9.3",
66
- "vitest": "^4.1.6",
67
- "@ai-matrx/kit": "0.5.1"
69
+ "vitest": "^4.1.6"
68
70
  },
69
71
  "publishConfig": {
70
72
  "access": "public",
@@ -73,7 +75,9 @@
73
75
  "engines": {
74
76
  "node": ">=20"
75
77
  },
76
- "sideEffects": false,
78
+ "sideEffects": [
79
+ "*.css"
80
+ ],
77
81
  "scripts": {
78
82
  "build": "tsup && node ./scripts/stamp-client-boundary.mjs",
79
83
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",