@djangocfg/ui-core 2.1.381 → 2.1.383

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 (78) hide show
  1. package/README.md +85 -21
  2. package/package.json +5 -12
  3. package/src/components/boundary/Boundary.tsx +204 -33
  4. package/src/components/boundary/README.md +249 -0
  5. package/src/components/boundary/index.ts +9 -2
  6. package/src/components/index.ts +9 -2
  7. package/src/components/select/combobox.tsx +47 -19
  8. package/src/hooks/audio/createSoundBus.ts +172 -0
  9. package/src/hooks/audio/index.ts +21 -0
  10. package/src/hooks/audio/useAudioPrefs.ts +91 -0
  11. package/src/hooks/audio/useNotificationSounds.ts +271 -0
  12. package/src/hooks/audio/useSoundEffect.ts +78 -0
  13. package/src/hooks/hotkey/formatHotkey.ts +96 -0
  14. package/src/hooks/hotkey/index.ts +10 -0
  15. package/src/hooks/hotkey/useHotkey.ts +106 -34
  16. package/src/hooks/hotkey/useHotkeyChord.ts +96 -0
  17. package/src/hooks/hotkey/useHotkeyHelp.ts +68 -0
  18. package/src/hooks/index.ts +1 -0
  19. package/src/components/boundary/boundary.story.tsx +0 -109
  20. package/src/components/data/avatar/avatar.story.tsx +0 -115
  21. package/src/components/data/badge/badge.story.tsx +0 -56
  22. package/src/components/data/calendar/calendar.story.tsx +0 -127
  23. package/src/components/data/carousel/carousel.story.tsx +0 -122
  24. package/src/components/data/progress/progress.story.tsx +0 -97
  25. package/src/components/data/table/table.story.tsx +0 -148
  26. package/src/components/data/toggle/toggle.story.tsx +0 -104
  27. package/src/components/data/toggle-group/toggle-group.story.tsx +0 -118
  28. package/src/components/feedback/alert/alert.story.tsx +0 -77
  29. package/src/components/feedback/empty/empty.story.tsx +0 -115
  30. package/src/components/feedback/preloader/preloader.story.tsx +0 -86
  31. package/src/components/feedback/spinner/spinner.story.tsx +0 -66
  32. package/src/components/forms/button/button.story.tsx +0 -116
  33. package/src/components/forms/button-download/button-download.story.tsx +0 -112
  34. package/src/components/forms/button-group/button-group.story.tsx +0 -79
  35. package/src/components/forms/checkbox/checkbox.story.tsx +0 -89
  36. package/src/components/forms/input/input.story.tsx +0 -77
  37. package/src/components/forms/input-group/input-group.story.tsx +0 -119
  38. package/src/components/forms/input-otp/input-otp.story.tsx +0 -105
  39. package/src/components/forms/label/label.story.tsx +0 -52
  40. package/src/components/forms/radio-group/radio-group.story.tsx +0 -113
  41. package/src/components/forms/slider/slider.story.tsx +0 -134
  42. package/src/components/forms/switch/switch.story.tsx +0 -98
  43. package/src/components/forms/textarea/textarea.story.tsx +0 -94
  44. package/src/components/layout/aspect-ratio/aspect-ratio.story.tsx +0 -94
  45. package/src/components/layout/card/card.story.tsx +0 -105
  46. package/src/components/layout/resizable/resizable.story.tsx +0 -119
  47. package/src/components/layout/scroll-area/scroll-area.story.tsx +0 -172
  48. package/src/components/layout/separator/separator.story.tsx +0 -69
  49. package/src/components/layout/skeleton/skeleton.story.tsx +0 -101
  50. package/src/components/navigation/accordion/accordion.story.tsx +0 -110
  51. package/src/components/navigation/collapsible/collapsible.story.tsx +0 -133
  52. package/src/components/navigation/command/command.story.tsx +0 -121
  53. package/src/components/navigation/context-menu/context-menu.story.tsx +0 -125
  54. package/src/components/navigation/dropdown-menu/dropdown-menu.story.tsx +0 -208
  55. package/src/components/navigation/menubar/menubar.story.tsx +0 -152
  56. package/src/components/navigation/navigation-menu/navigation-menu.story.tsx +0 -154
  57. package/src/components/navigation/tabs/tabs.story.tsx +0 -98
  58. package/src/components/overlay/alert-dialog/alert-dialog.story.tsx +0 -104
  59. package/src/components/overlay/dialog/dialog.story.tsx +0 -212
  60. package/src/components/overlay/drawer/drawer.story.tsx +0 -359
  61. package/src/components/overlay/hover-card/hover-card.story.tsx +0 -102
  62. package/src/components/overlay/popover/popover.story.tsx +0 -127
  63. package/src/components/overlay/responsive-sheet/responsive-sheet.story.tsx +0 -117
  64. package/src/components/overlay/sheet/sheet.story.tsx +0 -148
  65. package/src/components/overlay/tooltip/tooltip.story.tsx +0 -139
  66. package/src/components/select/combobox-async.story.tsx +0 -215
  67. package/src/components/select/combobox.story.tsx +0 -226
  68. package/src/components/select/country-select.story.tsx +0 -261
  69. package/src/components/select/language-select.story.tsx +0 -264
  70. package/src/components/select/multi-select.story.tsx +0 -122
  71. package/src/components/select/select.story.tsx +0 -112
  72. package/src/components/specialized/copy/copy.story.tsx +0 -77
  73. package/src/components/specialized/flag/flag.story.tsx +0 -82
  74. package/src/components/specialized/image-with-fallback/image-with-fallback.story.tsx +0 -105
  75. package/src/components/specialized/kbd/kbd.story.tsx +0 -113
  76. package/src/lib/dialog-service/dialog-service.story.tsx +0 -263
  77. package/src/stories/index.ts +0 -28
  78. package/src/styles/theme/theme-tokens.story.tsx +0 -157
@@ -1,105 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { ImageWithFallback } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/ImageWithFallback',
6
- component: ImageWithFallback,
7
- description: 'Image component with loading states and fallbacks.',
8
- });
9
-
10
- export const Interactive = () => {
11
- const [fallbackIcon] = useSelect('fallbackIcon', {
12
- options: ['car', 'image', 'user', 'package', 'location'] as const,
13
- defaultValue: 'image',
14
- label: 'Fallback Icon',
15
- description: 'Icon shown on error or missing src',
16
- });
17
-
18
- return (
19
- <ImageWithFallback
20
- src="https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=400"
21
- alt="Car"
22
- fallbackIcon={fallbackIcon}
23
- className="h-48 w-72 rounded-lg"
24
- />
25
- );
26
- };
27
-
28
- export const Default = () => (
29
- <ImageWithFallback
30
- src="https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=400"
31
- alt="Car"
32
- className="h-48 w-72 rounded-lg"
33
- />
34
- );
35
-
36
- export const WithFallbackCar = () => (
37
- <ImageWithFallback
38
- src=""
39
- alt="Missing car image"
40
- fallbackIcon="car"
41
- className="h-48 w-72 rounded-lg"
42
- />
43
- );
44
-
45
- export const WithFallbackUser = () => (
46
- <ImageWithFallback
47
- src=""
48
- alt="Missing user avatar"
49
- fallbackIcon="user"
50
- className="h-24 w-24 rounded-full"
51
- />
52
- );
53
-
54
- export const WithFallbackLocation = () => (
55
- <ImageWithFallback
56
- src=""
57
- alt="Missing location"
58
- fallbackIcon="location"
59
- className="h-32 w-32 rounded-lg"
60
- />
61
- );
62
-
63
- export const BrokenImage = () => (
64
- <ImageWithFallback
65
- src="https://invalid-url-that-does-not-exist.com/image.jpg"
66
- alt="Broken image"
67
- fallbackIcon="image"
68
- className="h-48 w-72 rounded-lg"
69
- />
70
- );
71
-
72
- export const CustomFallback = () => (
73
- <ImageWithFallback
74
- src=""
75
- alt="Custom fallback"
76
- fallbackContent={
77
- <div className="flex flex-col items-center gap-2 text-muted-foreground">
78
- <span className="text-4xl">🚗</span>
79
- <span className="text-sm">No image available</span>
80
- </div>
81
- }
82
- className="h-48 w-72 rounded-lg border bg-muted/20"
83
- />
84
- );
85
-
86
- export const Gallery = () => (
87
- <div className="grid grid-cols-3 gap-4">
88
- <ImageWithFallback
89
- src="https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=200"
90
- alt="Car 1"
91
- className="h-32 w-full rounded-lg"
92
- />
93
- <ImageWithFallback
94
- src=""
95
- alt="Missing"
96
- fallbackIcon="car"
97
- className="h-32 w-full rounded-lg"
98
- />
99
- <ImageWithFallback
100
- src="https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=200"
101
- alt="Car 2"
102
- className="h-32 w-full rounded-lg"
103
- />
104
- </div>
105
- );
@@ -1,113 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { Kbd, KbdGroup } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/Kbd',
6
- component: Kbd,
7
- description: 'Keyboard key indicator for shortcuts.',
8
- });
9
-
10
- export const Interactive = () => {
11
- const [size] = useSelect('size', {
12
- options: ['xs', 'sm', 'default', 'lg'] as const,
13
- defaultValue: 'default',
14
- label: 'Size',
15
- description: 'Kbd size',
16
- });
17
-
18
- return <Kbd size={size}>⌘</Kbd>;
19
- };
20
-
21
- export const Default = () => <Kbd>⌘</Kbd>;
22
-
23
- export const Sizes = () => (
24
- <div className="flex items-center gap-4">
25
- <Kbd size="xs">⌘</Kbd>
26
- <Kbd size="sm">⌘</Kbd>
27
- <Kbd size="default">⌘</Kbd>
28
- <Kbd size="lg">⌘</Kbd>
29
- </div>
30
- );
31
-
32
- export const Shortcuts = () => (
33
- <div className="space-y-4">
34
- <div className="flex items-center gap-2">
35
- <span className="text-sm w-32">Copy</span>
36
- <KbdGroup>
37
- <Kbd>⌘</Kbd>
38
- <Kbd>C</Kbd>
39
- </KbdGroup>
40
- </div>
41
- <div className="flex items-center gap-2">
42
- <span className="text-sm w-32">Paste</span>
43
- <KbdGroup>
44
- <Kbd>⌘</Kbd>
45
- <Kbd>V</Kbd>
46
- </KbdGroup>
47
- </div>
48
- <div className="flex items-center gap-2">
49
- <span className="text-sm w-32">Undo</span>
50
- <KbdGroup>
51
- <Kbd>⌘</Kbd>
52
- <Kbd>Z</Kbd>
53
- </KbdGroup>
54
- </div>
55
- <div className="flex items-center gap-2">
56
- <span className="text-sm w-32">Save</span>
57
- <KbdGroup>
58
- <Kbd>⌘</Kbd>
59
- <Kbd>S</Kbd>
60
- </KbdGroup>
61
- </div>
62
- </div>
63
- );
64
-
65
- export const ComplexShortcuts = () => (
66
- <div className="space-y-4">
67
- <div className="flex items-center gap-2">
68
- <span className="text-sm w-40">Command Palette</span>
69
- <KbdGroup>
70
- <Kbd>⌘</Kbd>
71
- <Kbd>⇧</Kbd>
72
- <Kbd>P</Kbd>
73
- </KbdGroup>
74
- </div>
75
- <div className="flex items-center gap-2">
76
- <span className="text-sm w-40">Go to Definition</span>
77
- <KbdGroup>
78
- <Kbd>⌘</Kbd>
79
- <Kbd>Click</Kbd>
80
- </KbdGroup>
81
- </div>
82
- <div className="flex items-center gap-2">
83
- <span className="text-sm w-40">Toggle Sidebar</span>
84
- <KbdGroup>
85
- <Kbd>⌘</Kbd>
86
- <Kbd>B</Kbd>
87
- </KbdGroup>
88
- </div>
89
- </div>
90
- );
91
-
92
- export const Keys = () => (
93
- <div className="flex flex-wrap gap-2">
94
- <Kbd>⌘</Kbd>
95
- <Kbd>⌥</Kbd>
96
- <Kbd>⇧</Kbd>
97
- <Kbd>⌃</Kbd>
98
- <Kbd>⏎</Kbd>
99
- <Kbd>⌫</Kbd>
100
- <Kbd>⇥</Kbd>
101
- <Kbd>⎋</Kbd>
102
- <Kbd>↑</Kbd>
103
- <Kbd>↓</Kbd>
104
- <Kbd>←</Kbd>
105
- <Kbd>→</Kbd>
106
- </div>
107
- );
108
-
109
- export const InText = () => (
110
- <p className="text-sm text-muted-foreground">
111
- Press <Kbd size="sm">⌘</Kbd> + <Kbd size="sm">K</Kbd> to open the command palette.
112
- </p>
113
- );
@@ -1,263 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { defineStory } from '@djangocfg/playground';
3
- import { Button } from '../../components/forms/button';
4
- import { DialogProvider } from './DialogProvider';
5
- import { useDialog } from './hooks';
6
-
7
- export default defineStory({
8
- title: 'Lib/DialogService',
9
- component: DialogProvider,
10
- description: 'Global dialog service for alert, confirm, and prompt dialogs. Uses CustomEvents to work from anywhere in the app. Supports i18n and keyboard shortcuts.',
11
- });
12
-
13
- // Wrapper component to provide DialogProvider context
14
- function StoryWrapper({ children }: { children: React.ReactNode }) {
15
- return <DialogProvider>{children}</DialogProvider>;
16
- }
17
-
18
- // Demo component using the hook
19
- function AlertDemo() {
20
- const { alert } = useDialog();
21
-
22
- const handleSimpleAlert = useCallback(async () => {
23
- await alert('This is a simple alert message!');
24
- console.log('Alert closed');
25
- }, [alert]);
26
-
27
- const handleAlertWithTitle = useCallback(async () => {
28
- await alert({
29
- title: 'Success!',
30
- message: 'Your changes have been saved successfully.',
31
- });
32
- console.log('Alert with title closed');
33
- }, [alert]);
34
-
35
- return (
36
- <div className="flex gap-2">
37
- <Button onClick={handleSimpleAlert}>Simple Alert</Button>
38
- <Button variant="outline" onClick={handleAlertWithTitle}>
39
- Alert with Title
40
- </Button>
41
- </div>
42
- );
43
- }
44
-
45
- export const Alert = () => (
46
- <StoryWrapper>
47
- <AlertDemo />
48
- </StoryWrapper>
49
- );
50
-
51
- // Confirm demo
52
- function ConfirmDemo() {
53
- const { confirm, alert } = useDialog();
54
-
55
- const handleSimpleConfirm = useCallback(async () => {
56
- const result = await confirm('Are you sure you want to proceed?');
57
- await alert(`You clicked: ${result ? 'Confirm' : 'Cancel'}`);
58
- }, [confirm, alert]);
59
-
60
- const handleDestructiveConfirm = useCallback(async () => {
61
- const result = await confirm({
62
- title: 'Delete Item',
63
- message: 'This action cannot be undone. Are you sure you want to delete this item?',
64
- confirmText: 'Yes, Delete',
65
- cancelText: 'Keep It',
66
- variant: 'destructive',
67
- });
68
- await alert(`You clicked: ${result ? 'Delete' : 'Keep'}`);
69
- }, [confirm, alert]);
70
-
71
- return (
72
- <div className="flex gap-2">
73
- <Button onClick={handleSimpleConfirm}>Simple Confirm</Button>
74
- <Button variant="destructive" onClick={handleDestructiveConfirm}>
75
- Destructive Confirm
76
- </Button>
77
- </div>
78
- );
79
- }
80
-
81
- export const Confirm = () => (
82
- <StoryWrapper>
83
- <ConfirmDemo />
84
- </StoryWrapper>
85
- );
86
-
87
- // Prompt demo
88
- function PromptDemo() {
89
- const { prompt, alert } = useDialog();
90
-
91
- const handleSimplePrompt = useCallback(async () => {
92
- const result = await prompt('What is your name?');
93
- if (result) {
94
- await alert(`Hello, ${result}!`);
95
- } else {
96
- await alert('You cancelled the prompt.');
97
- }
98
- }, [prompt, alert]);
99
-
100
- const handlePromptWithDefault = useCallback(async () => {
101
- const result = await prompt({
102
- title: 'Edit Name',
103
- message: 'Enter your display name:',
104
- defaultValue: 'John Doe',
105
- placeholder: 'Enter name...',
106
- confirmText: 'Save',
107
- cancelText: 'Cancel',
108
- });
109
- if (result) {
110
- await alert(`Name updated to: ${result}`);
111
- }
112
- }, [prompt, alert]);
113
-
114
- const handleEmailPrompt = useCallback(async () => {
115
- const result = await prompt({
116
- title: 'Subscribe',
117
- message: 'Enter your email to subscribe to our newsletter:',
118
- placeholder: 'email@example.com',
119
- inputType: 'email',
120
- confirmText: 'Subscribe',
121
- });
122
- if (result) {
123
- await alert(`Subscribed with: ${result}`);
124
- }
125
- }, [prompt, alert]);
126
-
127
- return (
128
- <div className="flex gap-2 flex-wrap">
129
- <Button onClick={handleSimplePrompt}>Simple Prompt</Button>
130
- <Button variant="outline" onClick={handlePromptWithDefault}>
131
- Prompt with Default
132
- </Button>
133
- <Button variant="secondary" onClick={handleEmailPrompt}>
134
- Email Prompt
135
- </Button>
136
- </div>
137
- );
138
- }
139
-
140
- export const Prompt = () => (
141
- <StoryWrapper>
142
- <PromptDemo />
143
- </StoryWrapper>
144
- );
145
-
146
- // Queue demo - multiple dialogs in sequence
147
- function QueueDemo() {
148
- const { alert, confirm, prompt } = useDialog();
149
-
150
- const handleQueue = useCallback(async () => {
151
- await alert('This is step 1 of 3');
152
- const proceed = await confirm('Do you want to continue to step 2?');
153
- if (!proceed) {
154
- await alert('You cancelled the sequence.');
155
- return;
156
- }
157
- const name = await prompt({
158
- title: 'Step 3',
159
- message: 'Enter your name to complete:',
160
- });
161
- if (name) {
162
- await alert(`Sequence complete! Hello, ${name}!`);
163
- } else {
164
- await alert('Sequence cancelled at step 3.');
165
- }
166
- }, [alert, confirm, prompt]);
167
-
168
- return (
169
- <Button onClick={handleQueue}>
170
- Start Dialog Sequence
171
- </Button>
172
- );
173
- }
174
-
175
- export const Queue = () => (
176
- <StoryWrapper>
177
- <QueueDemo />
178
- </StoryWrapper>
179
- );
180
-
181
- // Window.dialog API demo (vanilla JS style)
182
- function WindowDialogDemo() {
183
- const handleWindowAlert = useCallback(() => {
184
- window.dialog.alert('Called via window.dialog.alert()');
185
- }, []);
186
-
187
- const handleWindowConfirm = useCallback(async () => {
188
- const result = await window.dialog.confirm('Called via window.dialog.confirm()');
189
- window.dialog.alert(`Result: ${result}`);
190
- }, []);
191
-
192
- const handleWindowPrompt = useCallback(async () => {
193
- const result = await window.dialog.prompt({
194
- title: 'window.dialog.prompt()',
195
- message: 'This works from vanilla JS too!',
196
- defaultValue: 'Hello',
197
- });
198
- if (result) {
199
- window.dialog.alert(`You entered: ${result}`);
200
- }
201
- }, []);
202
-
203
- return (
204
- <div className="space-y-4">
205
- <p className="text-sm text-muted-foreground">
206
- These buttons use <code>window.dialog.*</code> directly - works from any JS code!
207
- </p>
208
- <div className="flex gap-2">
209
- <Button variant="outline" onClick={handleWindowAlert}>
210
- window.dialog.alert()
211
- </Button>
212
- <Button variant="outline" onClick={handleWindowConfirm}>
213
- window.dialog.confirm()
214
- </Button>
215
- <Button variant="outline" onClick={handleWindowPrompt}>
216
- window.dialog.prompt()
217
- </Button>
218
- </div>
219
- </div>
220
- );
221
- }
222
-
223
- export const WindowDialogAPI = () => (
224
- <StoryWrapper>
225
- <WindowDialogDemo />
226
- </StoryWrapper>
227
- );
228
-
229
- // Keyboard shortcuts demo
230
- function HotkeysDemo() {
231
- const { alert, confirm } = useDialog();
232
-
233
- const handleHotkeysDemo = useCallback(async () => {
234
- await alert({
235
- title: 'Keyboard Shortcuts',
236
- message: 'Press Enter to close this alert, or wait and click OK.',
237
- });
238
-
239
- const result = await confirm({
240
- title: 'Confirm with Keyboard',
241
- message: 'Press Enter to confirm, Escape to cancel.',
242
- });
243
-
244
- await alert(`You pressed: ${result ? 'Enter (Confirm)' : 'Escape (Cancel)'}`);
245
- }, [alert, confirm]);
246
-
247
- return (
248
- <div className="space-y-4">
249
- <p className="text-sm text-muted-foreground">
250
- Dialogs support keyboard shortcuts: <kbd className="px-1.5 py-0.5 bg-muted rounded text-xs">Enter</kbd> to confirm, <kbd className="px-1.5 py-0.5 bg-muted rounded text-xs">Escape</kbd> to cancel.
251
- </p>
252
- <Button onClick={handleHotkeysDemo}>
253
- Test Keyboard Shortcuts
254
- </Button>
255
- </div>
256
- );
257
- }
258
-
259
- export const KeyboardShortcuts = () => (
260
- <StoryWrapper>
261
- <HotkeysDemo />
262
- </StoryWrapper>
263
- );
@@ -1,28 +0,0 @@
1
- /**
2
- * UI-Core story modules registry.
3
- *
4
- * Re-export stories through a package subpath instead of deep importing files from apps.
5
- */
6
-
7
- import type { StoryModule } from '@djangocfg/playground';
8
-
9
- import * as Button from '../components/forms/button/button.story';
10
- import * as Input from '../components/forms/input/input.story';
11
- import * as Select from '../components/select/select.story';
12
- import * as Slider from '../components/forms/slider/slider.story';
13
- import * as Dialog from '../components/overlay/dialog/dialog.story';
14
- import * as Tabs from '../components/navigation/tabs/tabs.story';
15
- import * as Popover from '../components/overlay/popover/popover.story';
16
- import * as Tooltip from '../components/overlay/tooltip/tooltip.story';
17
-
18
- export const uiCoreStoryModules: Record<string, StoryModule> = {
19
- 'button.story.tsx': Button,
20
- 'input.story.tsx': Input,
21
- 'select.story.tsx': Select,
22
- 'slider.story.tsx': Slider,
23
- 'dialog.story.tsx': Dialog,
24
- 'tabs.story.tsx': Tabs,
25
- 'popover.story.tsx': Popover,
26
- 'tooltip.story.tsx': Tooltip,
27
- };
28
-
@@ -1,157 +0,0 @@
1
- import { defineStory } from '@djangocfg/playground';
2
-
3
- export default defineStory({
4
- title: 'Theme/Tokens',
5
- description: 'Visual reference for all semantic CSS color tokens. Use these classes — never raw Tailwind color-scale classes (amber-500 etc).',
6
- });
7
-
8
- // ─── Helpers ──────────────────────────────────────────────────────────────────
9
-
10
- function TokenRow({ label, bg, text, border }: { label: string; bg: string; text: string; border: string }) {
11
- return (
12
- <div className={`flex items-center justify-between rounded-md border px-4 py-3 ${bg} ${border}`}>
13
- <span className={`text-sm font-medium ${text}`}>{label}</span>
14
- <div className="flex gap-2 font-mono text-xs opacity-70">
15
- <code className={text}>{bg}</code>
16
- <code className={text}>{text}</code>
17
- <code className={text}>{border}</code>
18
- </div>
19
- </div>
20
- );
21
- }
22
-
23
- function Section({ title, children }: { title: string; children: React.ReactNode }) {
24
- return (
25
- <div className="space-y-2">
26
- <h3 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">{title}</h3>
27
- <div className="space-y-2">{children}</div>
28
- </div>
29
- );
30
- }
31
-
32
- function SwatchRow({ name, bgClass }: { name: string; bgClass: string }) {
33
- return (
34
- <div className="flex items-center gap-3">
35
- <div className={`h-8 w-16 rounded border border-border ${bgClass}`} />
36
- <code className="text-xs text-foreground">{name}</code>
37
- </div>
38
- );
39
- }
40
-
41
- // ─── Stories ──────────────────────────────────────────────────────────────────
42
-
43
- export const StatusSurfaces = () => (
44
- <div className="space-y-6 max-w-2xl">
45
- <p className="text-sm text-muted-foreground">
46
- Status banners — use <code className="text-xs bg-code text-code-foreground rounded px-1">bg-*-background</code>,{' '}
47
- <code className="text-xs bg-code text-code-foreground rounded px-1">text-*-foreground</code>,{' '}
48
- <code className="text-xs bg-code text-code-foreground rounded px-1">border-*-border</code>
49
- </p>
50
-
51
- <Section title="Warning">
52
- <TokenRow
53
- label="⚠ You're seeing a preview. Subscribe for full access."
54
- bg="bg-warning-background"
55
- text="text-warning-foreground"
56
- border="border border-warning-border/50"
57
- />
58
- </Section>
59
-
60
- <Section title="Success">
61
- <TokenRow
62
- label="✓ Operation completed successfully."
63
- bg="bg-success-background"
64
- text="text-success-foreground"
65
- border="border border-success-border/50"
66
- />
67
- </Section>
68
-
69
- <Section title="Destructive">
70
- <TokenRow
71
- label="✕ Something went wrong. Please try again."
72
- bg="bg-destructive-background"
73
- text="text-destructive"
74
- border="border border-destructive-border/50"
75
- />
76
- </Section>
77
-
78
- <Section title="Info">
79
- <TokenRow
80
- label="ℹ New features are available in your plan."
81
- bg="bg-info-background"
82
- text="text-info-foreground"
83
- border="border border-info-border/50"
84
- />
85
- </Section>
86
- </div>
87
- );
88
-
89
- export const BaseColors = () => (
90
- <div className="space-y-6 max-w-xl">
91
- <Section title="Base">
92
- <div className="grid grid-cols-2 gap-3">
93
- <SwatchRow name="bg-background" bgClass="bg-background" />
94
- <SwatchRow name="bg-foreground" bgClass="bg-foreground" />
95
- <SwatchRow name="bg-card" bgClass="bg-card" />
96
- <SwatchRow name="bg-muted" bgClass="bg-muted" />
97
- <SwatchRow name="bg-accent" bgClass="bg-accent" />
98
- <SwatchRow name="bg-border" bgClass="bg-border" />
99
- </div>
100
- </Section>
101
-
102
- <Section title="Brand">
103
- <div className="grid grid-cols-2 gap-3">
104
- <SwatchRow name="bg-primary" bgClass="bg-primary" />
105
- <SwatchRow name="bg-secondary" bgClass="bg-secondary" />
106
- </div>
107
- </Section>
108
-
109
- <Section title="Status accents (icon color)">
110
- <div className="grid grid-cols-2 gap-3">
111
- <SwatchRow name="bg-warning" bgClass="bg-warning" />
112
- <SwatchRow name="bg-success" bgClass="bg-success" />
113
- <SwatchRow name="bg-destructive" bgClass="bg-destructive" />
114
- <SwatchRow name="bg-info" bgClass="bg-info" />
115
- </div>
116
- </Section>
117
- </div>
118
- );
119
-
120
- export const CodeSurface = () => (
121
- <div className="space-y-4 max-w-xl">
122
- <Section title="Code tokens">
123
- <div className="rounded-md border border-code-border bg-code text-code-foreground p-4 font-mono text-sm">
124
- <div>bg-code / text-code-foreground / border-code-border</div>
125
- <div className="mt-2 text-xs opacity-60">Used by PrettyCode, MarkdownMessage code fences</div>
126
- </div>
127
- <div className="text-sm">
128
- Inline:{' '}
129
- <code className="rounded bg-code-inline px-1.5 py-0.5 text-code-inline-foreground text-xs">
130
- const x = 42
131
- </code>
132
- </div>
133
- </Section>
134
-
135
- <Section title="Sidebar tokens">
136
- <div className="rounded-md border border-sidebar-border bg-sidebar p-4 space-y-1 text-sidebar-foreground text-sm">
137
- <div className="font-semibold">bg-sidebar / text-sidebar-foreground</div>
138
- <div className="text-xs opacity-60">Used by the app sidebar chrome</div>
139
- <div className="mt-2 rounded bg-sidebar-accent px-2 py-1 text-sidebar-accent-foreground text-xs">
140
- bg-sidebar-accent — hover state
141
- </div>
142
- </div>
143
- </Section>
144
- </div>
145
- );
146
-
147
- export const TypographyTokens = () => (
148
- <div className="space-y-3 max-w-md">
149
- <p className="text-foreground text-sm font-medium">text-foreground — primary body</p>
150
- <p className="text-muted-foreground text-sm">text-muted-foreground — secondary / hints</p>
151
- <p className="text-primary text-sm">text-primary — brand links / accents</p>
152
- <p className="text-destructive text-sm">text-destructive — error messages</p>
153
- <p className="text-warning text-sm">text-warning — warning labels</p>
154
- <p className="text-success text-sm">text-success — success labels</p>
155
- <p className="text-info text-sm">text-info — info labels</p>
156
- </div>
157
- );