@fgv/ts-app-shell 5.1.0-29 → 5.1.0-30

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.
package/README.md CHANGED
@@ -10,6 +10,7 @@ Shared React UI primitives for application shells in the `@fgv` monorepo.
10
10
  - **Log Message Panel** — Collapsible bottom panel with severity-filtered message history
11
11
  - **Command Palette** — Cmd+K quick navigation overlay
12
12
  - **Keybinding Registry** — Infrastructure for registering and managing keyboard shortcuts
13
+ - **Default Theme** — CSS-variable tokens + Tailwind preset for all semantic colors (light + dark)
13
14
 
14
15
  ## Usage
15
16
 
@@ -17,6 +18,164 @@ Shared React UI primitives for application shells in the `@fgv` monorepo.
17
18
  import { /* components */ } from '@fgv/ts-app-shell';
18
19
  ```
19
20
 
21
+ ## Theming
22
+
23
+ All visual components use custom Tailwind semantic tokens (e.g. `bg-surface`, `text-primary`,
24
+ `bg-status-error-bg`). To make them render correctly in your application, add the Tailwind
25
+ preset and import the base CSS once.
26
+
27
+ ### 1. Add the Tailwind preset
28
+
29
+ In your `tailwind.config.js` (or `tailwind.config.ts`):
30
+
31
+ ```js
32
+ const appShellPreset = require('@fgv/ts-app-shell/tailwind-preset');
33
+
34
+ module.exports = {
35
+ presets: [appShellPreset],
36
+ // Make sure Tailwind scans the package's components for class names:
37
+ content: [
38
+ './src/**/*.{ts,tsx}',
39
+ './node_modules/@fgv/ts-app-shell/lib/**/*.{js,jsx}',
40
+ ],
41
+ // ...rest of your config
42
+ };
43
+ ```
44
+
45
+ ### 2. Import the base CSS
46
+
47
+ In your application entry point (e.g. `main.tsx` or `index.css`):
48
+
49
+ ```ts
50
+ import '@fgv/ts-app-shell/theme.css';
51
+ ```
52
+
53
+ ### 3. Toggle dark mode
54
+
55
+ The preset uses Tailwind's `darkMode: 'class'` strategy. Add the class `dark` to any ancestor
56
+ element (typically `<html>`) to activate dark mode. The `ThemeProvider` component does this
57
+ automatically when the user selects `'dark'` or `'system'` (if the OS prefers dark):
58
+
59
+ ```tsx
60
+ import { ThemeProvider } from '@fgv/ts-app-shell';
61
+
62
+ <ThemeProvider initialTheme="system" onThemeChange={saveTheme}>
63
+ <App />
64
+ </ThemeProvider>
65
+ ```
66
+
67
+ ### Overriding tokens
68
+
69
+ Every token is a CSS custom property under the `--fgv-` prefix. Override any subset in your
70
+ own CSS after importing `theme.css`:
71
+
72
+ ```css
73
+ :root {
74
+ --fgv-brand-primary: #7c3aed; /* purple brand instead of blue */
75
+ --fgv-brand-accent: #a78bfa;
76
+ }
77
+ .dark {
78
+ --fgv-brand-primary: #8b5cf6;
79
+ --fgv-brand-accent: #c4b5fd;
80
+ }
81
+ ```
82
+
83
+ ## Complete Token Reference
84
+
85
+ ### Surfaces
86
+
87
+ | Token name | CSS variable | Light default | Dark default | Purpose |
88
+ |---|---|---|---|---|
89
+ | `surface` | `--fgv-surface` | `#ffffff` | `#1f2937` | Base page / panel background |
90
+ | `surface-raised` | `--fgv-surface-raised` | `#f3f4f6` | `#374151` | Elevated cards, filter toolbars |
91
+ | `surface-alt` | `--fgv-surface-alt` | `#f9fafb` | `#111827` | Alternate / zebra row background |
92
+ | `backdrop` | `--fgv-backdrop` | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.65)` | Modal/dialog overlay |
93
+ | `hover` | `--fgv-hover` | `#f3f4f6` | `#374151` | Row hover highlight |
94
+ | `selected` | `--fgv-selected` | `#eff6ff` | `#1e3a5f` | Selected list item background |
95
+
96
+ ### Text
97
+
98
+ | Token name | CSS variable | Light | Dark | Purpose |
99
+ |---|---|---|---|---|
100
+ | `primary` | `--fgv-text-primary` | `#111827` | `#f9fafb` | High-emphasis body text (WCAG AA+) |
101
+ | `secondary` | `--fgv-text-secondary` | `#374151` | `#d1d5db` | Medium-emphasis labels |
102
+ | `muted` | `--fgv-text-muted` | `#6b7280` | `#9ca3af` | Supporting / helper text |
103
+ | `faint` | `--fgv-text-faint` | `#9ca3af` | `#6b7280` | Ghost / ornamental text |
104
+ | `star` | `--fgv-text-star` | `#d97706` | `#fbbf24` | Star / favorite icon color |
105
+
106
+ ### Borders & Focus
107
+
108
+ | Token name | CSS variable | Light | Dark | Purpose |
109
+ |---|---|---|---|---|
110
+ | `border` | `--fgv-border` | `#d1d5db` | `#4b5563` | Default input / divider border |
111
+ | `border-subtle` | `--fgv-border-subtle` | `#e5e7eb` | `#374151` | Subtle row separator |
112
+ | `focus-ring` | `--fgv-focus-ring` | `#3b82f6` | `#60a5fa` | Keyboard focus ring |
113
+ | `selected-border` | `--fgv-selected-border` | `#2563eb` | `#3b82f6` | Selected item left-border accent |
114
+
115
+ ### Brand
116
+
117
+ | Token name | CSS variable | Light | Dark | Purpose |
118
+ |---|---|---|---|---|
119
+ | `brand-primary` | `--fgv-brand-primary` | `#2563eb` | `#3b82f6` | Primary action / active nav |
120
+ | `brand-secondary` | `--fgv-brand-secondary` | `#1d4ed8` | `#1e3a5f` | Pressed / dark top-bar variant |
121
+ | `brand-accent` | `--fgv-brand-accent` | `#3b82f6` | `#60a5fa` | Hover text, checkmarks, accents |
122
+
123
+ ### Status: Error
124
+
125
+ | Token name | CSS variable | Purpose |
126
+ |---|---|---|
127
+ | `status-error-bg` | `--fgv-status-error-bg` | Error row / badge background |
128
+ | `status-error-surface` | `--fgv-status-error-surface` | Hover background on error items |
129
+ | `status-error-btn` | `--fgv-status-error-btn` | Danger action button fill |
130
+ | `status-error-btn-hover` | `--fgv-status-error-btn-hover` | Danger button hover fill |
131
+ | `status-error-icon` | `--fgv-status-error-icon` | Error icon / dot color |
132
+ | `status-error-border` | `--fgv-status-error-border` | Error section border |
133
+ | `status-error-accent` | `--fgv-status-error-accent` | Error focus ring |
134
+ | `status-error-text` | `--fgv-status-error-text` | Error message text |
135
+ | `status-error-strong` | `--fgv-status-error-strong` | High-emphasis error text |
136
+
137
+ ### Status: Warning
138
+
139
+ | Token name | CSS variable | Purpose |
140
+ |---|---|---|
141
+ | `status-warning-bg` | `--fgv-status-warning-bg` | Warning row / badge background |
142
+ | `status-warning-surface` | `--fgv-status-warning-surface` | Hover background on warning items |
143
+ | `status-warning-btn` | `--fgv-status-warning-btn` | Warning action button fill |
144
+ | `status-warning-btn-hover` | `--fgv-status-warning-btn-hover` | Warning button hover fill |
145
+ | `status-warning-icon` | `--fgv-status-warning-icon` | Warning icon / dot color |
146
+ | `status-warning-border` | `--fgv-status-warning-border` | Warning section border |
147
+ | `status-warning-accent` | `--fgv-status-warning-accent` | Warning focus ring |
148
+ | `status-warning-text` | `--fgv-status-warning-text` | Warning message text |
149
+ | `status-warning-strong` | `--fgv-status-warning-strong` | High-emphasis warning text |
150
+
151
+ ### Status: Info
152
+
153
+ | Token name | CSS variable | Purpose |
154
+ |---|---|---|
155
+ | `status-info-bg` | `--fgv-status-info-bg` | Info row / badge background |
156
+ | `status-info-surface` | `--fgv-status-info-surface` | Hover background on info items |
157
+ | `status-info-btn` | `--fgv-status-info-btn` | Info action button fill |
158
+ | `status-info-btn-hover` | `--fgv-status-info-btn-hover` | Info button hover fill |
159
+ | `status-info-icon` | `--fgv-status-info-icon` | Info icon / dot color |
160
+ | `status-info-border` | `--fgv-status-info-border` | Info section border |
161
+ | `status-info-accent` | `--fgv-status-info-accent` | Info focus ring |
162
+ | `status-info-text` | `--fgv-status-info-text` | Info message text |
163
+ | `status-info-strong` | `--fgv-status-info-strong` | High-emphasis info text |
164
+
165
+ ### Status: Success
166
+
167
+ | Token name | CSS variable | Purpose |
168
+ |---|---|---|
169
+ | `status-success-bg` | `--fgv-status-success-bg` | Success row / badge background |
170
+ | `status-success-surface` | `--fgv-status-success-surface` | Hover background on success items |
171
+ | `status-success-btn` | `--fgv-status-success-btn` | Success action button fill |
172
+ | `status-success-btn-hover` | `--fgv-status-success-btn-hover` | Success button hover fill |
173
+ | `status-success-icon` | `--fgv-status-success-icon` | Success icon / dot color |
174
+ | `status-success-border` | `--fgv-status-success-border` | Success section border |
175
+ | `status-success-accent` | `--fgv-status-success-accent` | Success focus ring |
176
+ | `status-success-text` | `--fgv-status-success-text` | Success message text |
177
+ | `status-success-strong` | `--fgv-status-success-strong` | High-emphasis success text |
178
+
20
179
  ## Development
21
180
 
22
181
  ```bash
@@ -0,0 +1,194 @@
1
+ /*
2
+ * @fgv/ts-app-shell — Default theme
3
+ *
4
+ * Defines all semantic design tokens as CSS custom properties.
5
+ * Import this file once at your application entry point:
6
+ *
7
+ * import '@fgv/ts-app-shell/theme.css';
8
+ *
9
+ * Then add the Tailwind preset in your tailwind.config.js:
10
+ *
11
+ * const appShellPreset = require('@fgv/ts-app-shell/tailwind-preset');
12
+ * module.exports = { presets: [appShellPreset], ... };
13
+ *
14
+ * Dark mode: put class="dark" on <html> (or any ancestor).
15
+ * The ThemeProvider component handles this automatically.
16
+ *
17
+ * All variables use the --fgv- prefix and can be overridden by the consumer.
18
+ */
19
+
20
+ /* ============================================================
21
+ * LIGHT MODE (default / :root)
22
+ * ============================================================ */
23
+ :root {
24
+ /* --- Surfaces --- */
25
+ /* Base application surface (page background) */
26
+ --fgv-surface: #ffffff;
27
+ /* Slightly elevated surface (cards, panels) */
28
+ --fgv-surface-raised: #f3f4f6;
29
+ /* Alternate surface (zebra rows, secondary panels) */
30
+ --fgv-surface-alt: #f9fafb;
31
+ /* Modal / dialog backdrop overlay */
32
+ --fgv-backdrop: rgba(0, 0, 0, 0.45);
33
+ /* Hover highlight background (subtle, interactive rows) */
34
+ --fgv-hover: #f3f4f6;
35
+ /* Selected item background (listbox rows, picker items) */
36
+ --fgv-selected: #eff6ff;
37
+
38
+ /* --- Text --- */
39
+ /* High-emphasis body text — minimum 7:1 on --fgv-surface (WCAG AAA) */
40
+ --fgv-text-primary: #111827;
41
+ /* Medium-emphasis secondary text — ~5.4:1 on #fff (WCAG AA) */
42
+ --fgv-text-secondary: #374151;
43
+ /* De-emphasized muted text — ~3.5:1, use only for decorative / supporting copy */
44
+ --fgv-text-muted: #6b7280;
45
+ /* Faintest ghost text — intentionally low contrast, only for truly ornamental content */
46
+ --fgv-text-faint: #9ca3af;
47
+ /* Star / favorite accent (golden) */
48
+ --fgv-text-star: #d97706;
49
+
50
+ /* --- Borders --- */
51
+ /* Default divider / input border */
52
+ --fgv-border: #d1d5db;
53
+ /* Subtle divider (row separators, inner section borders) */
54
+ --fgv-border-subtle: #e5e7eb;
55
+
56
+ /* --- Brand colors --- */
57
+ /* Primary action color (buttons, active nav, links) */
58
+ --fgv-brand-primary: #2563eb;
59
+ /* Darker tonal variant (pressed state, dark top-bar) */
60
+ --fgv-brand-secondary: #1d4ed8;
61
+ /* Accent / highlight (hover text on dark surfaces, checkmarks) */
62
+ --fgv-brand-accent: #3b82f6;
63
+
64
+ /* --- Focus ring --- */
65
+ --fgv-focus-ring: #3b82f6;
66
+ /* Selected item left-border accent */
67
+ --fgv-selected-border: #2563eb;
68
+
69
+ /* --- Status: error / danger --- */
70
+ --fgv-status-error-bg: #fef2f2;
71
+ --fgv-status-error-surface: #fee2e2;
72
+ --fgv-status-error-btn: #dc2626;
73
+ --fgv-status-error-btn-hover: #b91c1c;
74
+ --fgv-status-error-icon: #dc2626;
75
+ --fgv-status-error-border: #fca5a5;
76
+ --fgv-status-error-accent: #ef4444;
77
+ --fgv-status-error-text: #b91c1c;
78
+ --fgv-status-error-strong: #991b1b;
79
+
80
+ /* --- Status: warning --- */
81
+ --fgv-status-warning-bg: #fffbeb;
82
+ --fgv-status-warning-surface: #fef3c7;
83
+ --fgv-status-warning-btn: #d97706;
84
+ --fgv-status-warning-btn-hover: #b45309;
85
+ --fgv-status-warning-icon: #d97706;
86
+ --fgv-status-warning-border: #fcd34d;
87
+ --fgv-status-warning-accent: #f59e0b;
88
+ --fgv-status-warning-text: #92400e;
89
+ --fgv-status-warning-strong: #78350f;
90
+
91
+ /* --- Status: info --- */
92
+ --fgv-status-info-bg: #eff6ff;
93
+ --fgv-status-info-surface: #dbeafe;
94
+ --fgv-status-info-btn: #2563eb;
95
+ --fgv-status-info-btn-hover: #1d4ed8;
96
+ --fgv-status-info-icon: #3b82f6;
97
+ --fgv-status-info-border: #93c5fd;
98
+ --fgv-status-info-accent: #60a5fa;
99
+ --fgv-status-info-text: #1e40af;
100
+ --fgv-status-info-strong: #1e3a8a;
101
+
102
+ /* --- Status: success --- */
103
+ --fgv-status-success-bg: #f0fdf4;
104
+ --fgv-status-success-surface: #dcfce7;
105
+ --fgv-status-success-btn: #16a34a;
106
+ --fgv-status-success-btn-hover: #15803d;
107
+ --fgv-status-success-icon: #16a34a;
108
+ --fgv-status-success-border: #86efac;
109
+ --fgv-status-success-accent: #4ade80;
110
+ --fgv-status-success-text: #166534;
111
+ --fgv-status-success-strong: #14532d;
112
+ }
113
+
114
+ /* ============================================================
115
+ * DARK MODE (.dark on any ancestor element)
116
+ * ============================================================ */
117
+ .dark {
118
+ /* --- Surfaces --- */
119
+ --fgv-surface: #1f2937;
120
+ --fgv-surface-raised: #374151;
121
+ --fgv-surface-alt: #111827;
122
+ --fgv-backdrop: rgba(0, 0, 0, 0.65);
123
+ --fgv-hover: #374151;
124
+ --fgv-selected: #1e3a5f;
125
+
126
+ /* --- Text --- */
127
+ /* ~14:1 on --fgv-surface dark (WCAG AAA) */
128
+ --fgv-text-primary: #f9fafb;
129
+ /* ~8:1 on dark surface (WCAG AA) */
130
+ --fgv-text-secondary: #d1d5db;
131
+ /* ~4.5:1 on dark surface (WCAG AA) */
132
+ --fgv-text-muted: #9ca3af;
133
+ /* Faint ghost text — intentionally low contrast */
134
+ --fgv-text-faint: #6b7280;
135
+ /* Star accent — brightened slightly for dark bg */
136
+ --fgv-text-star: #fbbf24;
137
+
138
+ /* --- Borders --- */
139
+ --fgv-border: #4b5563;
140
+ --fgv-border-subtle: #374151;
141
+
142
+ /* --- Brand colors --- */
143
+ --fgv-brand-primary: #3b82f6;
144
+ --fgv-brand-secondary: #1e3a5f;
145
+ --fgv-brand-accent: #60a5fa;
146
+
147
+ /* --- Focus ring --- */
148
+ --fgv-focus-ring: #60a5fa;
149
+ --fgv-selected-border: #3b82f6;
150
+
151
+ /* --- Status: error / danger --- */
152
+ --fgv-status-error-bg: #450a0a;
153
+ --fgv-status-error-surface: #7f1d1d;
154
+ --fgv-status-error-btn: #dc2626;
155
+ --fgv-status-error-btn-hover: #b91c1c;
156
+ --fgv-status-error-icon: #f87171;
157
+ --fgv-status-error-border: #7f1d1d;
158
+ --fgv-status-error-accent: #ef4444;
159
+ --fgv-status-error-text: #fca5a5;
160
+ --fgv-status-error-strong: #fecaca;
161
+
162
+ /* --- Status: warning --- */
163
+ --fgv-status-warning-bg: #451a03;
164
+ --fgv-status-warning-surface: #78350f;
165
+ --fgv-status-warning-btn: #d97706;
166
+ --fgv-status-warning-btn-hover: #b45309;
167
+ --fgv-status-warning-icon: #fbbf24;
168
+ --fgv-status-warning-border: #78350f;
169
+ --fgv-status-warning-accent: #f59e0b;
170
+ --fgv-status-warning-text: #fde68a;
171
+ --fgv-status-warning-strong: #fef3c7;
172
+
173
+ /* --- Status: info --- */
174
+ --fgv-status-info-bg: #0c1d3e;
175
+ --fgv-status-info-surface: #1e3a5f;
176
+ --fgv-status-info-btn: #3b82f6;
177
+ --fgv-status-info-btn-hover: #2563eb;
178
+ --fgv-status-info-icon: #60a5fa;
179
+ --fgv-status-info-border: #1e3a5f;
180
+ --fgv-status-info-accent: #93c5fd;
181
+ --fgv-status-info-text: #bfdbfe;
182
+ --fgv-status-info-strong: #dbeafe;
183
+
184
+ /* --- Status: success --- */
185
+ --fgv-status-success-bg: #052e16;
186
+ --fgv-status-success-surface: #14532d;
187
+ --fgv-status-success-btn: #16a34a;
188
+ --fgv-status-success-btn-hover: #15803d;
189
+ --fgv-status-success-icon: #4ade80;
190
+ --fgv-status-success-border: #14532d;
191
+ --fgv-status-success-accent: #86efac;
192
+ --fgv-status-success-text: #bbf7d0;
193
+ --fgv-status-success-strong: #dcfce7;
194
+ }
@@ -0,0 +1,194 @@
1
+ /*
2
+ * @fgv/ts-app-shell — Default theme
3
+ *
4
+ * Defines all semantic design tokens as CSS custom properties.
5
+ * Import this file once at your application entry point:
6
+ *
7
+ * import '@fgv/ts-app-shell/theme.css';
8
+ *
9
+ * Then add the Tailwind preset in your tailwind.config.js:
10
+ *
11
+ * const appShellPreset = require('@fgv/ts-app-shell/tailwind-preset');
12
+ * module.exports = { presets: [appShellPreset], ... };
13
+ *
14
+ * Dark mode: put class="dark" on <html> (or any ancestor).
15
+ * The ThemeProvider component handles this automatically.
16
+ *
17
+ * All variables use the --fgv- prefix and can be overridden by the consumer.
18
+ */
19
+
20
+ /* ============================================================
21
+ * LIGHT MODE (default / :root)
22
+ * ============================================================ */
23
+ :root {
24
+ /* --- Surfaces --- */
25
+ /* Base application surface (page background) */
26
+ --fgv-surface: #ffffff;
27
+ /* Slightly elevated surface (cards, panels) */
28
+ --fgv-surface-raised: #f3f4f6;
29
+ /* Alternate surface (zebra rows, secondary panels) */
30
+ --fgv-surface-alt: #f9fafb;
31
+ /* Modal / dialog backdrop overlay */
32
+ --fgv-backdrop: rgba(0, 0, 0, 0.45);
33
+ /* Hover highlight background (subtle, interactive rows) */
34
+ --fgv-hover: #f3f4f6;
35
+ /* Selected item background (listbox rows, picker items) */
36
+ --fgv-selected: #eff6ff;
37
+
38
+ /* --- Text --- */
39
+ /* High-emphasis body text — minimum 7:1 on --fgv-surface (WCAG AAA) */
40
+ --fgv-text-primary: #111827;
41
+ /* Medium-emphasis secondary text — ~5.4:1 on #fff (WCAG AA) */
42
+ --fgv-text-secondary: #374151;
43
+ /* De-emphasized muted text — ~3.5:1, use only for decorative / supporting copy */
44
+ --fgv-text-muted: #6b7280;
45
+ /* Faintest ghost text — intentionally low contrast, only for truly ornamental content */
46
+ --fgv-text-faint: #9ca3af;
47
+ /* Star / favorite accent (golden) */
48
+ --fgv-text-star: #d97706;
49
+
50
+ /* --- Borders --- */
51
+ /* Default divider / input border */
52
+ --fgv-border: #d1d5db;
53
+ /* Subtle divider (row separators, inner section borders) */
54
+ --fgv-border-subtle: #e5e7eb;
55
+
56
+ /* --- Brand colors --- */
57
+ /* Primary action color (buttons, active nav, links) */
58
+ --fgv-brand-primary: #2563eb;
59
+ /* Darker tonal variant (pressed state, dark top-bar) */
60
+ --fgv-brand-secondary: #1d4ed8;
61
+ /* Accent / highlight (hover text on dark surfaces, checkmarks) */
62
+ --fgv-brand-accent: #3b82f6;
63
+
64
+ /* --- Focus ring --- */
65
+ --fgv-focus-ring: #3b82f6;
66
+ /* Selected item left-border accent */
67
+ --fgv-selected-border: #2563eb;
68
+
69
+ /* --- Status: error / danger --- */
70
+ --fgv-status-error-bg: #fef2f2;
71
+ --fgv-status-error-surface: #fee2e2;
72
+ --fgv-status-error-btn: #dc2626;
73
+ --fgv-status-error-btn-hover: #b91c1c;
74
+ --fgv-status-error-icon: #dc2626;
75
+ --fgv-status-error-border: #fca5a5;
76
+ --fgv-status-error-accent: #ef4444;
77
+ --fgv-status-error-text: #b91c1c;
78
+ --fgv-status-error-strong: #991b1b;
79
+
80
+ /* --- Status: warning --- */
81
+ --fgv-status-warning-bg: #fffbeb;
82
+ --fgv-status-warning-surface: #fef3c7;
83
+ --fgv-status-warning-btn: #d97706;
84
+ --fgv-status-warning-btn-hover: #b45309;
85
+ --fgv-status-warning-icon: #d97706;
86
+ --fgv-status-warning-border: #fcd34d;
87
+ --fgv-status-warning-accent: #f59e0b;
88
+ --fgv-status-warning-text: #92400e;
89
+ --fgv-status-warning-strong: #78350f;
90
+
91
+ /* --- Status: info --- */
92
+ --fgv-status-info-bg: #eff6ff;
93
+ --fgv-status-info-surface: #dbeafe;
94
+ --fgv-status-info-btn: #2563eb;
95
+ --fgv-status-info-btn-hover: #1d4ed8;
96
+ --fgv-status-info-icon: #3b82f6;
97
+ --fgv-status-info-border: #93c5fd;
98
+ --fgv-status-info-accent: #60a5fa;
99
+ --fgv-status-info-text: #1e40af;
100
+ --fgv-status-info-strong: #1e3a8a;
101
+
102
+ /* --- Status: success --- */
103
+ --fgv-status-success-bg: #f0fdf4;
104
+ --fgv-status-success-surface: #dcfce7;
105
+ --fgv-status-success-btn: #16a34a;
106
+ --fgv-status-success-btn-hover: #15803d;
107
+ --fgv-status-success-icon: #16a34a;
108
+ --fgv-status-success-border: #86efac;
109
+ --fgv-status-success-accent: #4ade80;
110
+ --fgv-status-success-text: #166534;
111
+ --fgv-status-success-strong: #14532d;
112
+ }
113
+
114
+ /* ============================================================
115
+ * DARK MODE (.dark on any ancestor element)
116
+ * ============================================================ */
117
+ .dark {
118
+ /* --- Surfaces --- */
119
+ --fgv-surface: #1f2937;
120
+ --fgv-surface-raised: #374151;
121
+ --fgv-surface-alt: #111827;
122
+ --fgv-backdrop: rgba(0, 0, 0, 0.65);
123
+ --fgv-hover: #374151;
124
+ --fgv-selected: #1e3a5f;
125
+
126
+ /* --- Text --- */
127
+ /* ~14:1 on --fgv-surface dark (WCAG AAA) */
128
+ --fgv-text-primary: #f9fafb;
129
+ /* ~8:1 on dark surface (WCAG AA) */
130
+ --fgv-text-secondary: #d1d5db;
131
+ /* ~4.5:1 on dark surface (WCAG AA) */
132
+ --fgv-text-muted: #9ca3af;
133
+ /* Faint ghost text — intentionally low contrast */
134
+ --fgv-text-faint: #6b7280;
135
+ /* Star accent — brightened slightly for dark bg */
136
+ --fgv-text-star: #fbbf24;
137
+
138
+ /* --- Borders --- */
139
+ --fgv-border: #4b5563;
140
+ --fgv-border-subtle: #374151;
141
+
142
+ /* --- Brand colors --- */
143
+ --fgv-brand-primary: #3b82f6;
144
+ --fgv-brand-secondary: #1e3a5f;
145
+ --fgv-brand-accent: #60a5fa;
146
+
147
+ /* --- Focus ring --- */
148
+ --fgv-focus-ring: #60a5fa;
149
+ --fgv-selected-border: #3b82f6;
150
+
151
+ /* --- Status: error / danger --- */
152
+ --fgv-status-error-bg: #450a0a;
153
+ --fgv-status-error-surface: #7f1d1d;
154
+ --fgv-status-error-btn: #dc2626;
155
+ --fgv-status-error-btn-hover: #b91c1c;
156
+ --fgv-status-error-icon: #f87171;
157
+ --fgv-status-error-border: #7f1d1d;
158
+ --fgv-status-error-accent: #ef4444;
159
+ --fgv-status-error-text: #fca5a5;
160
+ --fgv-status-error-strong: #fecaca;
161
+
162
+ /* --- Status: warning --- */
163
+ --fgv-status-warning-bg: #451a03;
164
+ --fgv-status-warning-surface: #78350f;
165
+ --fgv-status-warning-btn: #d97706;
166
+ --fgv-status-warning-btn-hover: #b45309;
167
+ --fgv-status-warning-icon: #fbbf24;
168
+ --fgv-status-warning-border: #78350f;
169
+ --fgv-status-warning-accent: #f59e0b;
170
+ --fgv-status-warning-text: #fde68a;
171
+ --fgv-status-warning-strong: #fef3c7;
172
+
173
+ /* --- Status: info --- */
174
+ --fgv-status-info-bg: #0c1d3e;
175
+ --fgv-status-info-surface: #1e3a5f;
176
+ --fgv-status-info-btn: #3b82f6;
177
+ --fgv-status-info-btn-hover: #2563eb;
178
+ --fgv-status-info-icon: #60a5fa;
179
+ --fgv-status-info-border: #1e3a5f;
180
+ --fgv-status-info-accent: #93c5fd;
181
+ --fgv-status-info-text: #bfdbfe;
182
+ --fgv-status-info-strong: #dbeafe;
183
+
184
+ /* --- Status: success --- */
185
+ --fgv-status-success-bg: #052e16;
186
+ --fgv-status-success-surface: #14532d;
187
+ --fgv-status-success-btn: #16a34a;
188
+ --fgv-status-success-btn-hover: #15803d;
189
+ --fgv-status-success-icon: #4ade80;
190
+ --fgv-status-success-border: #14532d;
191
+ --fgv-status-success-accent: #86efac;
192
+ --fgv-status-success-text: #bbf7d0;
193
+ --fgv-status-success-strong: #dcfce7;
194
+ }
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@fgv/ts-app-shell",
3
- "version": "5.1.0-29",
3
+ "version": "5.1.0-30",
4
4
  "description": "Shared React UI primitives for application shells: column cascade, sidebar, toast/log messages, command palette, keybinding registry",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
- "sideEffects": false,
7
+ "sideEffects": [
8
+ "**/*.css"
9
+ ],
8
10
  "keywords": [
9
11
  "typescript",
10
12
  "react",
@@ -17,8 +19,8 @@
17
19
  "dependencies": {
18
20
  "@heroicons/react": "~2.2.0",
19
21
  "tslib": "^2.8.1",
20
- "@fgv/ts-extras": "5.1.0-29",
21
- "@fgv/ts-utils": "5.1.0-29"
22
+ "@fgv/ts-utils": "5.1.0-30",
23
+ "@fgv/ts-extras": "5.1.0-30"
22
24
  },
23
25
  "peerDependencies": {
24
26
  "react": ">=18.0.0",
@@ -51,8 +53,8 @@
51
53
  "@rushstack/heft-jest-plugin": "1.2.6",
52
54
  "@testing-library/dom": "^10.4.0",
53
55
  "@rushstack/heft-node-rig": "2.11.27",
54
- "@fgv/ts-utils-jest": "5.1.0-29",
55
- "@fgv/heft-dual-rig": "5.1.0-29"
56
+ "@fgv/heft-dual-rig": "5.1.0-30",
57
+ "@fgv/ts-utils-jest": "5.1.0-30"
56
58
  },
57
59
  "exports": {
58
60
  ".": {
@@ -65,7 +67,9 @@
65
67
  "require": "./lib/index.browser.js"
66
68
  },
67
69
  "types": "lib/index.d.ts"
68
- }
70
+ },
71
+ "./tailwind-preset": "./tailwind-preset.cjs",
72
+ "./theme.css": "./lib/styles/theme.css"
69
73
  },
70
74
  "repository": {
71
75
  "type": "git",
@@ -0,0 +1,102 @@
1
+ /**
2
+ * @fgv/ts-app-shell — Tailwind CSS preset
3
+ *
4
+ * Maps the package's semantic design tokens to the CSS custom properties
5
+ * defined in theme.css. Add this preset to your tailwind.config.js and
6
+ * import theme.css once at your application entry point.
7
+ *
8
+ * Usage:
9
+ *
10
+ * // tailwind.config.js
11
+ * const appShellPreset = require('@fgv/ts-app-shell/tailwind-preset');
12
+ * module.exports = {
13
+ * presets: [appShellPreset],
14
+ * content: ['./src/**\/*.{ts,tsx}', './node_modules/@fgv/ts-app-shell/lib/**\/*.{js,jsx}'],
15
+ * // ...your config
16
+ * };
17
+ *
18
+ * // application entry (e.g. main.tsx or index.css)
19
+ * import '@fgv/ts-app-shell/theme.css';
20
+ *
21
+ * // dark mode: add class="dark" to <html> (ThemeProvider does this automatically)
22
+ */
23
+
24
+ /** @type {import('tailwindcss').Config} */
25
+ module.exports = {
26
+ darkMode: 'class',
27
+ theme: {
28
+ extend: {
29
+ colors: {
30
+ /* ----- Surfaces ----- */
31
+ surface: 'var(--fgv-surface)',
32
+ 'surface-raised': 'var(--fgv-surface-raised)',
33
+ 'surface-alt': 'var(--fgv-surface-alt)',
34
+ backdrop: 'var(--fgv-backdrop)',
35
+ hover: 'var(--fgv-hover)',
36
+ selected: 'var(--fgv-selected)',
37
+
38
+ /* ----- Text ----- */
39
+ primary: 'var(--fgv-text-primary)',
40
+ secondary: 'var(--fgv-text-secondary)',
41
+ muted: 'var(--fgv-text-muted)',
42
+ faint: 'var(--fgv-text-faint)',
43
+ star: 'var(--fgv-text-star)',
44
+
45
+ /* ----- Borders ----- */
46
+ border: 'var(--fgv-border)',
47
+ 'border-subtle': 'var(--fgv-border-subtle)',
48
+ 'focus-ring': 'var(--fgv-focus-ring)',
49
+ 'selected-border': 'var(--fgv-selected-border)',
50
+
51
+ /* ----- Brand ----- */
52
+ 'brand-primary': 'var(--fgv-brand-primary)',
53
+ 'brand-secondary': 'var(--fgv-brand-secondary)',
54
+ 'brand-accent': 'var(--fgv-brand-accent)',
55
+
56
+ /* ----- Status: error / danger ----- */
57
+ 'status-error-bg': 'var(--fgv-status-error-bg)',
58
+ 'status-error-surface': 'var(--fgv-status-error-surface)',
59
+ 'status-error-btn': 'var(--fgv-status-error-btn)',
60
+ 'status-error-btn-hover': 'var(--fgv-status-error-btn-hover)',
61
+ 'status-error-icon': 'var(--fgv-status-error-icon)',
62
+ 'status-error-border': 'var(--fgv-status-error-border)',
63
+ 'status-error-accent': 'var(--fgv-status-error-accent)',
64
+ 'status-error-text': 'var(--fgv-status-error-text)',
65
+ 'status-error-strong': 'var(--fgv-status-error-strong)',
66
+
67
+ /* ----- Status: warning ----- */
68
+ 'status-warning-bg': 'var(--fgv-status-warning-bg)',
69
+ 'status-warning-surface': 'var(--fgv-status-warning-surface)',
70
+ 'status-warning-btn': 'var(--fgv-status-warning-btn)',
71
+ 'status-warning-btn-hover': 'var(--fgv-status-warning-btn-hover)',
72
+ 'status-warning-icon': 'var(--fgv-status-warning-icon)',
73
+ 'status-warning-border': 'var(--fgv-status-warning-border)',
74
+ 'status-warning-accent': 'var(--fgv-status-warning-accent)',
75
+ 'status-warning-text': 'var(--fgv-status-warning-text)',
76
+ 'status-warning-strong': 'var(--fgv-status-warning-strong)',
77
+
78
+ /* ----- Status: info ----- */
79
+ 'status-info-bg': 'var(--fgv-status-info-bg)',
80
+ 'status-info-surface': 'var(--fgv-status-info-surface)',
81
+ 'status-info-btn': 'var(--fgv-status-info-btn)',
82
+ 'status-info-btn-hover': 'var(--fgv-status-info-btn-hover)',
83
+ 'status-info-icon': 'var(--fgv-status-info-icon)',
84
+ 'status-info-border': 'var(--fgv-status-info-border)',
85
+ 'status-info-accent': 'var(--fgv-status-info-accent)',
86
+ 'status-info-text': 'var(--fgv-status-info-text)',
87
+ 'status-info-strong': 'var(--fgv-status-info-strong)',
88
+
89
+ /* ----- Status: success ----- */
90
+ 'status-success-bg': 'var(--fgv-status-success-bg)',
91
+ 'status-success-surface': 'var(--fgv-status-success-surface)',
92
+ 'status-success-btn': 'var(--fgv-status-success-btn)',
93
+ 'status-success-btn-hover': 'var(--fgv-status-success-btn-hover)',
94
+ 'status-success-icon': 'var(--fgv-status-success-icon)',
95
+ 'status-success-border': 'var(--fgv-status-success-border)',
96
+ 'status-success-accent': 'var(--fgv-status-success-accent)',
97
+ 'status-success-text': 'var(--fgv-status-success-text)',
98
+ 'status-success-strong': 'var(--fgv-status-success-strong)'
99
+ }
100
+ }
101
+ }
102
+ };