@tristan2828/ui-foundation 3.0.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 (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/bin/ui-foundation.mjs +249 -0
  4. package/conventions/AGENTS.md +149 -0
  5. package/conventions/agents/deny-impl-read.mjs +36 -0
  6. package/conventions/agents/skills/new-entity/SKILL.md +22 -0
  7. package/conventions/agents/spec-tester.md +35 -0
  8. package/conventions/docs/add-an-entity.md +204 -0
  9. package/conventions/docs/cell-patterns.md +204 -0
  10. package/conventions/docs/column-options.md +122 -0
  11. package/conventions/docs/design-language.md +116 -0
  12. package/conventions/docs/entity-plan-template.md +67 -0
  13. package/dist/api/contracts.d.ts +20 -0
  14. package/dist/api/contracts.js +4 -0
  15. package/dist/api/gateway/auth.d.ts +9 -0
  16. package/dist/api/gateway/auth.js +37 -0
  17. package/dist/api/gateway/errors.d.ts +5 -0
  18. package/dist/api/gateway/errors.js +36 -0
  19. package/dist/api/query-client.d.ts +2 -0
  20. package/dist/api/query-client.js +19 -0
  21. package/dist/api/schema.d.ts +246 -0
  22. package/dist/api/schema.js +5 -0
  23. package/dist/api/transport/index.d.ts +5 -0
  24. package/dist/api/transport/index.js +14 -0
  25. package/dist/auth/auth-context.d.ts +19 -0
  26. package/dist/auth/auth-context.js +12 -0
  27. package/dist/auth/auth-provider.d.ts +4 -0
  28. package/dist/auth/auth-provider.js +101 -0
  29. package/dist/auth/use-auth.d.ts +2 -0
  30. package/dist/auth/use-auth.js +9 -0
  31. package/dist/components/app/app-shell.d.ts +28 -0
  32. package/dist/components/app/app-shell.js +64 -0
  33. package/dist/components/app/data-table.d.ts +56 -0
  34. package/dist/components/app/data-table.js +167 -0
  35. package/dist/components/app/entity-form.d.ts +14 -0
  36. package/dist/components/app/entity-form.js +8 -0
  37. package/dist/components/app/error-state.d.ts +6 -0
  38. package/dist/components/app/error-state.js +20 -0
  39. package/dist/components/app/foundation-providers.d.ts +9 -0
  40. package/dist/components/app/foundation-providers.js +15 -0
  41. package/dist/components/app/mock-mode-banner.d.ts +3 -0
  42. package/dist/components/app/mock-mode-banner.js +16 -0
  43. package/dist/components/app/multi-choice.d.ts +14 -0
  44. package/dist/components/app/multi-choice.js +13 -0
  45. package/dist/components/app/password-input.d.ts +3 -0
  46. package/dist/components/app/password-input.js +30 -0
  47. package/dist/components/app/route-error-boundary.d.ts +1 -0
  48. package/dist/components/app/route-error-boundary.js +13 -0
  49. package/dist/components/theme-provider.d.ts +2 -0
  50. package/dist/components/theme-provider.js +8 -0
  51. package/dist/components/ui/badge.d.ts +7 -0
  52. package/dist/components/ui/badge.js +50 -0
  53. package/dist/components/ui/button.d.ts +8 -0
  54. package/dist/components/ui/button.js +34 -0
  55. package/dist/components/ui/calendar.d.ts +10 -0
  56. package/dist/components/ui/calendar.js +76 -0
  57. package/dist/components/ui/card.d.ts +11 -0
  58. package/dist/components/ui/card.js +25 -0
  59. package/dist/components/ui/combobox.d.ts +24 -0
  60. package/dist/components/ui/combobox.js +62 -0
  61. package/dist/components/ui/dialog.d.ts +17 -0
  62. package/dist/components/ui/dialog.js +37 -0
  63. package/dist/components/ui/empty.d.ts +11 -0
  64. package/dist/components/ui/empty.js +33 -0
  65. package/dist/components/ui/field.d.ts +24 -0
  66. package/dist/components/ui/field.js +67 -0
  67. package/dist/components/ui/input-group.d.ts +18 -0
  68. package/dist/components/ui/input-group.js +57 -0
  69. package/dist/components/ui/input.d.ts +3 -0
  70. package/dist/components/ui/input.js +8 -0
  71. package/dist/components/ui/label.d.ts +3 -0
  72. package/dist/components/ui/label.js +7 -0
  73. package/dist/components/ui/popover.d.ts +9 -0
  74. package/dist/components/ui/popover.js +23 -0
  75. package/dist/components/ui/select.d.ts +15 -0
  76. package/dist/components/ui/select.js +35 -0
  77. package/dist/components/ui/separator.d.ts +3 -0
  78. package/dist/components/ui/separator.js +8 -0
  79. package/dist/components/ui/sheet.d.ts +14 -0
  80. package/dist/components/ui/sheet.js +38 -0
  81. package/dist/components/ui/sidebar.d.ts +63 -0
  82. package/dist/components/ui/sidebar.js +255 -0
  83. package/dist/components/ui/skeleton.d.ts +2 -0
  84. package/dist/components/ui/skeleton.js +6 -0
  85. package/dist/components/ui/sonner.d.ts +3 -0
  86. package/dist/components/ui/sonner.js +24 -0
  87. package/dist/components/ui/spinner.d.ts +2 -0
  88. package/dist/components/ui/spinner.js +7 -0
  89. package/dist/components/ui/table.d.ts +13 -0
  90. package/dist/components/ui/table.js +45 -0
  91. package/dist/components/ui/textarea.d.ts +3 -0
  92. package/dist/components/ui/textarea.js +7 -0
  93. package/dist/components/ui/tooltip.d.ts +6 -0
  94. package/dist/components/ui/tooltip.js +16 -0
  95. package/dist/gateway.d.ts +2 -0
  96. package/dist/gateway.js +5 -0
  97. package/dist/hooks/table-url-changes.d.ts +14 -0
  98. package/dist/hooks/table-url-changes.js +32 -0
  99. package/dist/hooks/use-debounced-value.d.ts +1 -0
  100. package/dist/hooks/use-debounced-value.js +12 -0
  101. package/dist/hooks/use-mobile.d.ts +1 -0
  102. package/dist/hooks/use-mobile.js +14 -0
  103. package/dist/hooks/use-table-url-state.d.ts +14 -0
  104. package/dist/hooks/use-table-url-state.js +43 -0
  105. package/dist/index.d.ts +22 -0
  106. package/dist/index.js +28 -0
  107. package/dist/lib/utils.d.ts +1 -0
  108. package/dist/lib/utils.js +1 -0
  109. package/dist/mocks/auth.d.ts +9 -0
  110. package/dist/mocks/auth.js +79 -0
  111. package/dist/mocks/e2e-hooks.d.ts +3 -0
  112. package/dist/mocks/e2e-hooks.js +28 -0
  113. package/dist/mocks/index.d.ts +3 -0
  114. package/dist/mocks/index.js +5 -0
  115. package/dist/mocks/override.d.ts +23 -0
  116. package/dist/mocks/override.js +1 -0
  117. package/dist/routes/login-schema.d.ts +7 -0
  118. package/dist/routes/login-schema.js +13 -0
  119. package/dist/routes/login.d.ts +8 -0
  120. package/dist/routes/login.js +58 -0
  121. package/dist/routes/register-schema.d.ts +9 -0
  122. package/dist/routes/register-schema.js +23 -0
  123. package/dist/routes/register.d.ts +1 -0
  124. package/dist/routes/register.js +58 -0
  125. package/dist/routes/return-path.d.ts +1 -0
  126. package/dist/routes/return-path.js +9 -0
  127. package/dist/testing/index.d.ts +40 -0
  128. package/dist/testing/index.js +103 -0
  129. package/eslint/index.d.ts +11 -0
  130. package/eslint/index.js +179 -0
  131. package/openapi/foundation.yaml +156 -0
  132. package/package.json +158 -0
  133. package/styles/index.css +88 -0
  134. package/styles/theme.css +209 -0
@@ -0,0 +1,88 @@
1
+ /* The foundation's styles: design tokens (theme.css), the Tailwind theme
2
+ mapping them to utilities, and base styles. An app's own CSS entry imports
3
+ Tailwind first, then this:
4
+
5
+ @import "tailwindcss";
6
+ @import "@tristan2828/ui-foundation/styles.css";
7
+
8
+ @source points Tailwind at the package's compiled components, which it
9
+ would otherwise skip (it never scans node_modules on its own). */
10
+ @import "tw-animate-css";
11
+ @import "shadcn/tailwind.css";
12
+ @import "@fontsource-variable/geist";
13
+ @import "./theme.css";
14
+
15
+ @source "../dist";
16
+
17
+ @custom-variant dark (&:is(.dark *));
18
+
19
+ @theme inline {
20
+ --font-heading: var(--font-sans);
21
+ --font-sans: 'Geist Variable', sans-serif;
22
+ --color-sidebar-ring: var(--sidebar-ring);
23
+ --color-sidebar-border: var(--sidebar-border);
24
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
25
+ --color-sidebar-accent: var(--sidebar-accent);
26
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
27
+ --color-sidebar-primary: var(--sidebar-primary);
28
+ --color-sidebar-foreground: var(--sidebar-foreground);
29
+ --color-sidebar: var(--sidebar);
30
+ --color-chart-5: var(--chart-5);
31
+ --color-chart-4: var(--chart-4);
32
+ --color-chart-3: var(--chart-3);
33
+ --color-chart-2: var(--chart-2);
34
+ --color-chart-1: var(--chart-1);
35
+ --color-ring: var(--ring);
36
+ --color-input: var(--input);
37
+ --color-border: var(--border);
38
+ --color-destructive: var(--destructive);
39
+ --color-destructive-foreground: var(--destructive-foreground);
40
+ --color-destructive-text: var(--destructive-text);
41
+ --color-success: var(--success);
42
+ --color-success-foreground: var(--success-foreground);
43
+ --color-success-text: var(--success-text);
44
+ --color-warning: var(--warning);
45
+ --color-warning-foreground: var(--warning-foreground);
46
+ --color-warning-text: var(--warning-text);
47
+ --color-category-1: var(--category-1);
48
+ --color-category-2: var(--category-2);
49
+ --color-category-3: var(--category-3);
50
+ --color-category-4: var(--category-4);
51
+ --color-category-5: var(--category-5);
52
+ --color-category-6: var(--category-6);
53
+ --color-category-7: var(--category-7);
54
+ --color-category-8: var(--category-8);
55
+ --color-accent-foreground: var(--accent-foreground);
56
+ --color-accent: var(--accent);
57
+ --color-muted-foreground: var(--muted-foreground);
58
+ --color-muted: var(--muted);
59
+ --color-secondary-foreground: var(--secondary-foreground);
60
+ --color-secondary: var(--secondary);
61
+ --color-primary-foreground: var(--primary-foreground);
62
+ --color-primary: var(--primary);
63
+ --color-popover-foreground: var(--popover-foreground);
64
+ --color-popover: var(--popover);
65
+ --color-card-foreground: var(--card-foreground);
66
+ --color-card: var(--card);
67
+ --color-foreground: var(--foreground);
68
+ --color-background: var(--background);
69
+ --radius-sm: calc(var(--radius) * 0.6);
70
+ --radius-md: calc(var(--radius) * 0.8);
71
+ --radius-lg: var(--radius);
72
+ --radius-xl: calc(var(--radius) * 1.4);
73
+ --radius-2xl: calc(var(--radius) * 1.8);
74
+ --radius-3xl: calc(var(--radius) * 2.2);
75
+ --radius-4xl: calc(var(--radius) * 2.6);
76
+ }
77
+
78
+ @layer base {
79
+ * {
80
+ @apply border-border outline-ring/50;
81
+ }
82
+ body {
83
+ @apply bg-background text-foreground;
84
+ }
85
+ html {
86
+ @apply font-sans;
87
+ }
88
+ }
@@ -0,0 +1,209 @@
1
+ /*
2
+ * Two-layer design tokens (conventions/docs/design-language.md).
3
+ *
4
+ * Layer 1 — primitives: raw palette values. Never referenced from
5
+ * components directly.
6
+ * Layer 2 — semantic: what components actually consume (bg-primary,
7
+ * text-muted-foreground, ...). Remapped per theme/mode; primitives are not.
8
+ *
9
+ * The values below are shadcn's generated defaults (Nova preset),
10
+ * reorganized into the two-layer shape. Phase 1 only needs the structure to
11
+ * exist; Phase 5 is where the primitive palette itself is deliberately
12
+ * chosen and this file's diff should be small.
13
+ */
14
+
15
+ :root {
16
+ /* Layer 1 — primitives */
17
+ --white: oklch(1 0 0);
18
+ --white-a10: oklch(1 0 0 / 10%);
19
+ --white-a15: oklch(1 0 0 / 15%);
20
+ --black: oklch(0.145 0 0);
21
+
22
+ --gray-50: oklch(0.985 0 0);
23
+ --gray-100: oklch(0.97 0 0);
24
+ --gray-200: oklch(0.922 0 0);
25
+ --gray-300: oklch(0.87 0 0);
26
+ --gray-400: oklch(0.708 0 0);
27
+ --gray-500: oklch(0.556 0 0);
28
+ --gray-600: oklch(0.439 0 0);
29
+ --gray-700: oklch(0.371 0 0);
30
+ --gray-900: oklch(0.269 0 0);
31
+ --gray-950: oklch(0.205 0 0);
32
+
33
+ /*
34
+ * Tone primitives. Each tone carries more than one shade, because which
35
+ * one is correct depends on what sits on top of it — the distinction
36
+ * that makes a single "red" token wrong:
37
+ *
38
+ * -500 / -700 a SOLID fill, always with white text on it
39
+ * -700 / -800 the same tone as TEXT or a BORDER on the light page
40
+ * -400 the same, on the dark page
41
+ *
42
+ * Reusing a fill shade as text is the classic failure: it was chosen to
43
+ * contrast with white, not with the page behind it. Every ratio below is
44
+ * measured (WCAG 2.x, sRGB), and axe re-checks them against the rendered
45
+ * DOM in both themes — see e2e/storybook-visual.spec.ts.
46
+ */
47
+ --red-500: oklch(0.577 0.245 27.325); /* white on it: 4.76 */
48
+ --red-700: oklch(0.5 0.21 27.325); /* on white: 6.57 */
49
+ --red-400: oklch(0.74 0.19 27.325); /* on near-black: 7.39 */
50
+ --green-700: oklch(0.527 0.154 150.069); /* white on it: 4.94 */
51
+ --green-800: oklch(0.43 0.13 150.069); /* on white: 7.55 */
52
+ --green-400: oklch(0.75 0.16 150.069); /* on near-black: 9.47 */
53
+ --amber-700: oklch(0.555 0.163 48.998); /* white on it: 5.05 */
54
+ --amber-800: oklch(0.47 0.14 48.998); /* on white: 7.20 */
55
+ --amber-400: oklch(0.828 0.189 84.429); /* on near-black: 11.53 */
56
+ /*
57
+ * Categorical slots. For values that differ from each other but are not
58
+ * better or worse than each other — a genre, a team, a category. Tones
59
+ * (success/warning/destructive) say good or bad; these say nothing but
60
+ * "not the same as its neighbour".
61
+ *
62
+ * Generic slots, deliberately not per-value tokens: an app maps its enum
63
+ * to a slot in its own columns file, so a new enum value costs a line
64
+ * there rather than three tokens here.
65
+ *
66
+ * Hues avoid the red and green bands that destructive/success already
67
+ * spend, and the amber that warning does — a category drawn in red reads
68
+ * as "bad". They are ordered by distinctness, not by hue angle, so an app
69
+ * using only the first three gets three obviously different colours.
70
+ * Past about five, hue alone stops separating them and the glyph has to
71
+ * carry the meaning; see docs/design-language.md.
72
+ *
73
+ * Ratios are against the page, measured (WCAG 2.x, sRGB), and all clear
74
+ * the 3:1 non-text minimum in both themes with room to spare. Note that
75
+ * axe does NOT check icon contrast, so unlike the tones nothing
76
+ * re-measures these automatically.
77
+ */
78
+ --category-1-700: oklch(0.5 0.15 240);
79
+ --category-1-400: oklch(0.78 0.12 240);
80
+ --category-2-700: oklch(0.5 0.15 300);
81
+ --category-2-400: oklch(0.78 0.12 300);
82
+ --category-3-700: oklch(0.5 0.15 95);
83
+ --category-3-400: oklch(0.78 0.12 95);
84
+ --category-4-700: oklch(0.5 0.15 190);
85
+ --category-4-400: oklch(0.78 0.12 190);
86
+ --category-5-700: oklch(0.5 0.15 330);
87
+ --category-5-400: oklch(0.78 0.12 330);
88
+ --category-6-700: oklch(0.5 0.15 265);
89
+ --category-6-400: oklch(0.78 0.12 265);
90
+ --category-7-700: oklch(0.5 0.15 120);
91
+ --category-7-400: oklch(0.78 0.12 120);
92
+ --category-8-700: oklch(0.5 0.15 215);
93
+ --category-8-400: oklch(0.78 0.12 215);
94
+ --blue-500: oklch(0.488 0.243 264.376);
95
+
96
+ --radius-base: 0.625rem;
97
+
98
+ /* Layer 2 — semantic (light) */
99
+ --background: var(--white);
100
+ --foreground: var(--black);
101
+ --card: var(--white);
102
+ --card-foreground: var(--black);
103
+ --popover: var(--white);
104
+ --popover-foreground: var(--black);
105
+ --primary: var(--gray-950);
106
+ --primary-foreground: var(--gray-50);
107
+ --secondary: var(--gray-100);
108
+ --secondary-foreground: var(--gray-950);
109
+ --muted: var(--gray-100);
110
+ --muted-foreground: var(--gray-500);
111
+ --accent: var(--gray-100);
112
+ --accent-foreground: var(--gray-950);
113
+ --destructive: var(--red-500);
114
+ --destructive-foreground: var(--white);
115
+ --success: var(--green-700);
116
+ --success-foreground: var(--white);
117
+ --warning: var(--amber-700);
118
+ --warning-foreground: var(--white);
119
+ /* The three pairs above are a SOLID fill plus its own text color, and
120
+ nothing else. Used as text or a border on the page's own background —
121
+ an outline badge, a tinted chip, an icon tint — a fill shade is the
122
+ wrong value: it was picked to carry white, not to read against the
123
+ page. The *-text tokens below are the per-theme shades for that job,
124
+ and they are the only ones that get remapped in .dark. */
125
+ --destructive-text: var(--red-700);
126
+ --success-text: var(--green-800);
127
+ --warning-text: var(--amber-800);
128
+ /* Categorical slots — a glyph tint, never a fill behind text. */
129
+ --category-1: var(--category-1-700);
130
+ --category-2: var(--category-2-700);
131
+ --category-3: var(--category-3-700);
132
+ --category-4: var(--category-4-700);
133
+ --category-5: var(--category-5-700);
134
+ --category-6: var(--category-6-700);
135
+ --category-7: var(--category-7-700);
136
+ --category-8: var(--category-8-700);
137
+ --border: var(--gray-200);
138
+ --input: var(--gray-200);
139
+ --ring: var(--gray-400);
140
+ --chart-1: var(--gray-300);
141
+ --chart-2: var(--gray-500);
142
+ --chart-3: var(--gray-600);
143
+ --chart-4: var(--gray-700);
144
+ --chart-5: var(--gray-900);
145
+ --radius: var(--radius-base);
146
+ --sidebar: var(--gray-50);
147
+ --sidebar-foreground: var(--black);
148
+ --sidebar-primary: var(--gray-950);
149
+ --sidebar-primary-foreground: var(--gray-50);
150
+ --sidebar-accent: var(--gray-100);
151
+ --sidebar-accent-foreground: var(--gray-950);
152
+ --sidebar-border: var(--gray-200);
153
+ --sidebar-ring: var(--gray-400);
154
+ }
155
+
156
+ .dark {
157
+ /* Layer 2 — semantic (dark). Only this block is remapped; Layer 1 is not. */
158
+ --background: var(--black);
159
+ --foreground: var(--gray-50);
160
+ --card: var(--gray-950);
161
+ --card-foreground: var(--gray-50);
162
+ --popover: var(--gray-950);
163
+ --popover-foreground: var(--gray-50);
164
+ --primary: var(--gray-200);
165
+ --primary-foreground: var(--gray-950);
166
+ --secondary: var(--gray-900);
167
+ --secondary-foreground: var(--gray-50);
168
+ --muted: var(--gray-900);
169
+ --muted-foreground: var(--gray-400);
170
+ --accent: var(--gray-900);
171
+ --accent-foreground: var(--gray-50);
172
+ --destructive: var(--red-500);
173
+ --destructive-foreground: var(--white);
174
+ --success: var(--green-700);
175
+ --success-foreground: var(--white);
176
+ --warning: var(--amber-700);
177
+ --warning-foreground: var(--white);
178
+ /* Solid fills keep one shade in both themes — they always carry white
179
+ text, so the page behind them never enters the calculation. Only the
180
+ *-text shades flip, because those read against the page. */
181
+ --destructive-text: var(--red-400);
182
+ --success-text: var(--green-400);
183
+ --warning-text: var(--amber-400);
184
+ /* Categorical slots — lighter shades to read against the dark page. */
185
+ --category-1: var(--category-1-400);
186
+ --category-2: var(--category-2-400);
187
+ --category-3: var(--category-3-400);
188
+ --category-4: var(--category-4-400);
189
+ --category-5: var(--category-5-400);
190
+ --category-6: var(--category-6-400);
191
+ --category-7: var(--category-7-400);
192
+ --category-8: var(--category-8-400);
193
+ --border: var(--white-a10);
194
+ --input: var(--white-a15);
195
+ --ring: var(--gray-500);
196
+ --chart-1: var(--gray-300);
197
+ --chart-2: var(--gray-500);
198
+ --chart-3: var(--gray-600);
199
+ --chart-4: var(--gray-700);
200
+ --chart-5: var(--gray-900);
201
+ --sidebar: var(--gray-950);
202
+ --sidebar-foreground: var(--gray-50);
203
+ --sidebar-primary: var(--blue-500);
204
+ --sidebar-primary-foreground: var(--gray-50);
205
+ --sidebar-accent: var(--gray-900);
206
+ --sidebar-accent-foreground: var(--gray-50);
207
+ --sidebar-border: var(--white-a10);
208
+ --sidebar-ring: var(--gray-500);
209
+ }