@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,98 +0,0 @@
1
- import { useState } from 'react';
2
- import { defineStory, useBoolean } from '@djangocfg/playground';
3
- import { Switch } from '.';
4
- import { Label } from '../../forms/label';
5
-
6
- export default defineStory({
7
- title: 'Core/Switch',
8
- component: Switch,
9
- description: 'Toggle switch for on/off states.',
10
- });
11
-
12
- export const Interactive = () => {
13
- const [disabled] = useBoolean('disabled', {
14
- defaultValue: false,
15
- label: 'Disabled',
16
- description: 'Disable switch',
17
- });
18
-
19
- return (
20
- <div className="flex items-center space-x-2">
21
- <Switch id="airplane-mode" disabled={disabled} />
22
- <Label htmlFor="airplane-mode">Airplane Mode</Label>
23
- </div>
24
- );
25
- };
26
-
27
- export const Default = () => (
28
- <div className="flex items-center space-x-2">
29
- <Switch id="default" />
30
- <Label htmlFor="default">Default switch</Label>
31
- </div>
32
- );
33
-
34
- export const Checked = () => (
35
- <div className="flex items-center space-x-2">
36
- <Switch id="checked" defaultChecked />
37
- <Label htmlFor="checked">Checked by default</Label>
38
- </div>
39
- );
40
-
41
- export const Disabled = () => (
42
- <div className="space-y-4">
43
- <div className="flex items-center space-x-2">
44
- <Switch id="disabled-off" disabled />
45
- <Label htmlFor="disabled-off">Disabled off</Label>
46
- </div>
47
- <div className="flex items-center space-x-2">
48
- <Switch id="disabled-on" disabled defaultChecked />
49
- <Label htmlFor="disabled-on">Disabled on</Label>
50
- </div>
51
- </div>
52
- );
53
-
54
- export const Settings = () => {
55
- const [settings, setSettings] = useState({
56
- notifications: true,
57
- darkMode: false,
58
- autoSave: true,
59
- });
60
-
61
- return (
62
- <div className="space-y-6 max-w-sm">
63
- <div className="flex items-center justify-between">
64
- <div>
65
- <Label htmlFor="notifications" className="block">Notifications</Label>
66
- <span className="text-sm text-muted-foreground">Receive push notifications</span>
67
- </div>
68
- <Switch
69
- id="notifications"
70
- checked={settings.notifications}
71
- onCheckedChange={(checked) => setSettings({ ...settings, notifications: checked })}
72
- />
73
- </div>
74
- <div className="flex items-center justify-between">
75
- <div>
76
- <Label htmlFor="darkMode" className="block">Dark Mode</Label>
77
- <span className="text-sm text-muted-foreground">Use dark theme</span>
78
- </div>
79
- <Switch
80
- id="darkMode"
81
- checked={settings.darkMode}
82
- onCheckedChange={(checked) => setSettings({ ...settings, darkMode: checked })}
83
- />
84
- </div>
85
- <div className="flex items-center justify-between">
86
- <div>
87
- <Label htmlFor="autoSave" className="block">Auto Save</Label>
88
- <span className="text-sm text-muted-foreground">Save changes automatically</span>
89
- </div>
90
- <Switch
91
- id="autoSave"
92
- checked={settings.autoSave}
93
- onCheckedChange={(checked) => setSettings({ ...settings, autoSave: checked })}
94
- />
95
- </div>
96
- </div>
97
- );
98
- };
@@ -1,94 +0,0 @@
1
- import { defineStory, useBoolean, useNumber } from '@djangocfg/playground';
2
- import { Textarea } from '.';
3
- import { Label } from '../../forms/label';
4
-
5
- export default defineStory({
6
- title: 'Core/Textarea',
7
- component: Textarea,
8
- description: 'Multi-line text input.',
9
- });
10
-
11
- export const Interactive = () => {
12
- const [disabled] = useBoolean('disabled', {
13
- defaultValue: false,
14
- label: 'Disabled',
15
- description: 'Disable textarea',
16
- });
17
-
18
- const [rows] = useNumber('rows', {
19
- defaultValue: 4,
20
- min: 2,
21
- max: 10,
22
- label: 'Rows',
23
- description: 'Number of visible rows',
24
- });
25
-
26
- return (
27
- <div className="max-w-md space-y-2">
28
- <Label htmlFor="message">Message</Label>
29
- <Textarea
30
- id="message"
31
- placeholder="Type your message here..."
32
- disabled={disabled}
33
- rows={rows}
34
- />
35
- </div>
36
- );
37
- };
38
-
39
- export const Default = () => (
40
- <div className="max-w-md">
41
- <Textarea placeholder="Type your message here..." />
42
- </div>
43
- );
44
-
45
- export const WithLabel = () => (
46
- <div className="max-w-md space-y-2">
47
- <Label htmlFor="bio">Bio</Label>
48
- <Textarea id="bio" placeholder="Tell us about yourself..." />
49
- <p className="text-sm text-muted-foreground">
50
- Write a short introduction about yourself.
51
- </p>
52
- </div>
53
- );
54
-
55
- export const Disabled = () => (
56
- <div className="max-w-md">
57
- <Textarea placeholder="Disabled textarea" disabled />
58
- </div>
59
- );
60
-
61
- export const WithDefaultValue = () => (
62
- <div className="max-w-md space-y-2">
63
- <Label htmlFor="notes">Notes</Label>
64
- <Textarea
65
- id="notes"
66
- defaultValue="This is some default text that was pre-filled in the textarea."
67
- />
68
- </div>
69
- );
70
-
71
- export const Sizes = () => (
72
- <div className="max-w-md space-y-4">
73
- <div className="space-y-2">
74
- <Label>Small (2 rows)</Label>
75
- <Textarea placeholder="Small textarea" rows={2} />
76
- </div>
77
- <div className="space-y-2">
78
- <Label>Medium (4 rows)</Label>
79
- <Textarea placeholder="Medium textarea" rows={4} />
80
- </div>
81
- <div className="space-y-2">
82
- <Label>Large (8 rows)</Label>
83
- <Textarea placeholder="Large textarea" rows={8} />
84
- </div>
85
- </div>
86
- );
87
-
88
- export const WithCharacterCount = () => (
89
- <div className="max-w-md space-y-2">
90
- <Label htmlFor="limited">Description</Label>
91
- <Textarea id="limited" placeholder="Enter description..." maxLength={200} />
92
- <p className="text-sm text-muted-foreground text-right">0/200</p>
93
- </div>
94
- );
@@ -1,94 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { AspectRatio } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/Aspect Ratio',
6
- component: AspectRatio,
7
- description: 'Container that maintains a specific aspect ratio.',
8
- });
9
-
10
- export const Interactive = () => {
11
- const [ratio] = useSelect('ratio', {
12
- options: ['16/9', '4/3', '1/1', '21/9'] as const,
13
- defaultValue: '16/9',
14
- label: 'Ratio',
15
- description: 'Aspect ratio',
16
- });
17
-
18
- const ratioMap = {
19
- '16/9': 16 / 9,
20
- '4/3': 4 / 3,
21
- '1/1': 1,
22
- '21/9': 21 / 9,
23
- };
24
-
25
- return (
26
- <div className="w-[450px]">
27
- <AspectRatio ratio={ratioMap[ratio]} className="bg-muted rounded-lg overflow-hidden">
28
- <img
29
- src="https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=800"
30
- alt="Car"
31
- className="h-full w-full object-cover"
32
- />
33
- </AspectRatio>
34
- </div>
35
- );
36
- };
37
-
38
- export const Ratio16By9 = () => (
39
- <div className="w-[450px]">
40
- <AspectRatio ratio={16 / 9} className="bg-muted rounded-lg overflow-hidden">
41
- <img
42
- src="https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=800"
43
- alt="Car"
44
- className="h-full w-full object-cover"
45
- />
46
- </AspectRatio>
47
- </div>
48
- );
49
-
50
- export const Ratio4By3 = () => (
51
- <div className="w-[300px]">
52
- <AspectRatio ratio={4 / 3} className="bg-muted rounded-lg overflow-hidden">
53
- <img
54
- src="https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=600"
55
- alt="Porsche"
56
- className="h-full w-full object-cover"
57
- />
58
- </AspectRatio>
59
- </div>
60
- );
61
-
62
- export const Square = () => (
63
- <div className="w-[200px]">
64
- <AspectRatio ratio={1} className="bg-muted rounded-lg overflow-hidden">
65
- <img
66
- src="https://images.unsplash.com/photo-1542362567-b07e54358753?w=400"
67
- alt="BMW"
68
- className="h-full w-full object-cover"
69
- />
70
- </AspectRatio>
71
- </div>
72
- );
73
-
74
- export const WithPlaceholder = () => (
75
- <div className="w-[400px]">
76
- <AspectRatio ratio={16 / 9} className="bg-muted rounded-lg flex items-center justify-center">
77
- <span className="text-muted-foreground">No image available</span>
78
- </AspectRatio>
79
- </div>
80
- );
81
-
82
- export const Video = () => (
83
- <div className="w-[560px]">
84
- <AspectRatio ratio={16 / 9} className="bg-black rounded-lg overflow-hidden">
85
- <iframe
86
- src="https://www.youtube.com/embed/dQw4w9WgXcQ"
87
- title="Video"
88
- className="h-full w-full"
89
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
90
- allowFullScreen
91
- />
92
- </AspectRatio>
93
- </div>
94
- );
@@ -1,105 +0,0 @@
1
- import { defineStory } from '@djangocfg/playground';
2
- import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '.';
3
- import { Button } from '../../forms/button';
4
- import { Input } from '../../forms/input';
5
- import { Label } from '../../forms/label';
6
-
7
- export default defineStory({
8
- title: 'Core/Card',
9
- component: Card,
10
- description: 'Card container for grouping related content.',
11
- });
12
-
13
- export const Default = () => (
14
- <Card className="max-w-sm">
15
- <CardHeader>
16
- <CardTitle>Card Title</CardTitle>
17
- <CardDescription>Card description goes here.</CardDescription>
18
- </CardHeader>
19
- <CardContent>
20
- <p>This is the card content. You can put any content here.</p>
21
- </CardContent>
22
- <CardFooter>
23
- <Button>Action</Button>
24
- </CardFooter>
25
- </Card>
26
- );
27
-
28
- export const LoginCard = () => (
29
- <Card className="max-w-sm">
30
- <CardHeader>
31
- <CardTitle>Login</CardTitle>
32
- <CardDescription>Enter your credentials to access your account.</CardDescription>
33
- </CardHeader>
34
- <CardContent className="space-y-4">
35
- <div className="space-y-2">
36
- <Label htmlFor="email">Email</Label>
37
- <Input id="email" type="email" placeholder="Enter your email" />
38
- </div>
39
- <div className="space-y-2">
40
- <Label htmlFor="password">Password</Label>
41
- <Input id="password" type="password" placeholder="Enter password" />
42
- </div>
43
- </CardContent>
44
- <CardFooter className="flex justify-between">
45
- <Button variant="outline">Cancel</Button>
46
- <Button>Login</Button>
47
- </CardFooter>
48
- </Card>
49
- );
50
-
51
- export const ProductCard = () => (
52
- <Card className="max-w-xs overflow-hidden">
53
- <div className="aspect-video bg-muted" />
54
- <CardHeader>
55
- <CardTitle>Product Name</CardTitle>
56
- <CardDescription>$99.99</CardDescription>
57
- </CardHeader>
58
- <CardContent>
59
- <p className="text-sm text-muted-foreground">
60
- This is a brief description of the product with key features.
61
- </p>
62
- </CardContent>
63
- <CardFooter>
64
- <Button className="w-full">Add to Cart</Button>
65
- </CardFooter>
66
- </Card>
67
- );
68
-
69
- export const StatsCards = () => (
70
- <div className="grid grid-cols-3 gap-4">
71
- <Card>
72
- <CardHeader className="pb-2">
73
- <CardDescription>Total Revenue</CardDescription>
74
- <CardTitle className="text-2xl">$45,231.89</CardTitle>
75
- </CardHeader>
76
- <CardContent>
77
- <p className="text-xs text-muted-foreground">+20.1% from last month</p>
78
- </CardContent>
79
- </Card>
80
- <Card>
81
- <CardHeader className="pb-2">
82
- <CardDescription>Subscriptions</CardDescription>
83
- <CardTitle className="text-2xl">+2,350</CardTitle>
84
- </CardHeader>
85
- <CardContent>
86
- <p className="text-xs text-muted-foreground">+180.1% from last month</p>
87
- </CardContent>
88
- </Card>
89
- <Card>
90
- <CardHeader className="pb-2">
91
- <CardDescription>Active Now</CardDescription>
92
- <CardTitle className="text-2xl">+573</CardTitle>
93
- </CardHeader>
94
- <CardContent>
95
- <p className="text-xs text-muted-foreground">+201 since last hour</p>
96
- </CardContent>
97
- </Card>
98
- </div>
99
- );
100
-
101
- export const SimpleCard = () => (
102
- <Card className="max-w-md p-6">
103
- <p>A simple card with just content and padding.</p>
104
- </Card>
105
- );
@@ -1,119 +0,0 @@
1
- import { defineStory } from '@djangocfg/playground';
2
- import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/Resizable',
6
- component: ResizablePanelGroup,
7
- description: 'Resizable panels with drag handles.',
8
- });
9
-
10
- export const Horizontal = () => (
11
- <ResizablePanelGroup
12
- direction="horizontal"
13
- className="max-w-md rounded-lg border"
14
- >
15
- <ResizablePanel defaultSize={50}>
16
- <div className="flex h-[200px] items-center justify-center p-6">
17
- <span className="font-semibold">One</span>
18
- </div>
19
- </ResizablePanel>
20
- <ResizableHandle />
21
- <ResizablePanel defaultSize={50}>
22
- <div className="flex h-[200px] items-center justify-center p-6">
23
- <span className="font-semibold">Two</span>
24
- </div>
25
- </ResizablePanel>
26
- </ResizablePanelGroup>
27
- );
28
-
29
- export const Vertical = () => (
30
- <ResizablePanelGroup
31
- direction="vertical"
32
- className="max-w-md rounded-lg border"
33
- >
34
- <ResizablePanel defaultSize={25}>
35
- <div className="flex h-full items-center justify-center p-6">
36
- <span className="font-semibold">Header</span>
37
- </div>
38
- </ResizablePanel>
39
- <ResizableHandle />
40
- <ResizablePanel defaultSize={75}>
41
- <div className="flex h-full items-center justify-center p-6">
42
- <span className="font-semibold">Content</span>
43
- </div>
44
- </ResizablePanel>
45
- </ResizablePanelGroup>
46
- );
47
-
48
- export const ThreePanels = () => (
49
- <ResizablePanelGroup
50
- direction="horizontal"
51
- className="max-w-lg rounded-lg border"
52
- >
53
- <ResizablePanel defaultSize={25} minSize={15}>
54
- <div className="flex h-[200px] items-center justify-center p-6">
55
- <span className="font-semibold">Sidebar</span>
56
- </div>
57
- </ResizablePanel>
58
- <ResizableHandle />
59
- <ResizablePanel defaultSize={50}>
60
- <div className="flex h-[200px] items-center justify-center p-6">
61
- <span className="font-semibold">Content</span>
62
- </div>
63
- </ResizablePanel>
64
- <ResizableHandle />
65
- <ResizablePanel defaultSize={25} minSize={15}>
66
- <div className="flex h-[200px] items-center justify-center p-6">
67
- <span className="font-semibold">Details</span>
68
- </div>
69
- </ResizablePanel>
70
- </ResizablePanelGroup>
71
- );
72
-
73
- export const Nested = () => (
74
- <ResizablePanelGroup
75
- direction="horizontal"
76
- className="max-w-lg rounded-lg border"
77
- >
78
- <ResizablePanel defaultSize={25}>
79
- <div className="flex h-[300px] items-center justify-center p-6">
80
- <span className="font-semibold">Sidebar</span>
81
- </div>
82
- </ResizablePanel>
83
- <ResizableHandle />
84
- <ResizablePanel defaultSize={75}>
85
- <ResizablePanelGroup direction="vertical">
86
- <ResizablePanel defaultSize={50}>
87
- <div className="flex h-full items-center justify-center p-6">
88
- <span className="font-semibold">Top</span>
89
- </div>
90
- </ResizablePanel>
91
- <ResizableHandle />
92
- <ResizablePanel defaultSize={50}>
93
- <div className="flex h-full items-center justify-center p-6">
94
- <span className="font-semibold">Bottom</span>
95
- </div>
96
- </ResizablePanel>
97
- </ResizablePanelGroup>
98
- </ResizablePanel>
99
- </ResizablePanelGroup>
100
- );
101
-
102
- export const WithHandle = () => (
103
- <ResizablePanelGroup
104
- direction="horizontal"
105
- className="max-w-md rounded-lg border"
106
- >
107
- <ResizablePanel defaultSize={50}>
108
- <div className="flex h-[200px] items-center justify-center p-6">
109
- <span className="font-semibold">Left</span>
110
- </div>
111
- </ResizablePanel>
112
- <ResizableHandle withHandle />
113
- <ResizablePanel defaultSize={50}>
114
- <div className="flex h-[200px] items-center justify-center p-6">
115
- <span className="font-semibold">Right</span>
116
- </div>
117
- </ResizablePanel>
118
- </ResizablePanelGroup>
119
- );
@@ -1,172 +0,0 @@
1
- import { defineStory } from '@djangocfg/playground';
2
- import { ScrollArea, ScrollBar } from '.';
3
- import { Separator } from '../../layout/separator';
4
-
5
- export default defineStory({
6
- title: 'Core/Scroll Area',
7
- component: ScrollArea,
8
- description: 'Custom scrollable area with styled scrollbars.',
9
- });
10
-
11
- const tags = Array.from({ length: 50 }).map(
12
- (_, i, a) => `v1.2.0-beta.${a.length - i}`
13
- );
14
-
15
- const works = [
16
- { artist: 'Ornella Binni', art: 'Sunset Dreams' },
17
- { artist: 'Tom Byrom', art: 'Mountain Peak' },
18
- { artist: 'Vladimir Malyavko', art: 'City Lights' },
19
- { artist: 'Fabio Fistarol', art: 'Ocean Blue' },
20
- { artist: 'Anthony Intraversato', art: 'Forest Path' },
21
- { artist: 'Ash Edmonds', art: 'Desert Storm' },
22
- ];
23
-
24
- export const Vertical = () => (
25
- <ScrollArea className="h-72 w-48 rounded-md border">
26
- <div className="p-4">
27
- <h4 className="mb-4 text-sm font-medium leading-none">Tags</h4>
28
- {tags.map((tag) => (
29
- <div key={tag}>
30
- <div className="text-sm">{tag}</div>
31
- <Separator className="my-2" />
32
- </div>
33
- ))}
34
- </div>
35
- </ScrollArea>
36
- );
37
-
38
- export const Horizontal = () => (
39
- <ScrollArea className="w-96 whitespace-nowrap rounded-md border" orientation="horizontal">
40
- <div className="flex w-max space-x-4 p-4">
41
- {works.map((work) => (
42
- <figure key={work.artist} className="shrink-0">
43
- <div className="overflow-hidden rounded-md">
44
- <div className="h-[150px] w-[150px] bg-muted flex items-center justify-center">
45
- <span className="text-muted-foreground text-sm">{work.art}</span>
46
- </div>
47
- </div>
48
- <figcaption className="pt-2 text-xs text-muted-foreground">
49
- Photo by{' '}
50
- <span className="font-semibold text-foreground">{work.artist}</span>
51
- </figcaption>
52
- </figure>
53
- ))}
54
- </div>
55
- </ScrollArea>
56
- );
57
-
58
- export const Both = () => (
59
- <ScrollArea className="h-72 w-72 rounded-md border" orientation="both">
60
- <div className="p-4">
61
- {Array.from({ length: 20 }).map((_, i) => (
62
- <div key={i} className="whitespace-nowrap py-2">
63
- This is a very long line of text that will require horizontal scrolling. Item {i + 1}
64
- </div>
65
- ))}
66
- </div>
67
- </ScrollArea>
68
- );
69
-
70
- export const InCard = () => (
71
- <div className="rounded-lg border p-4 max-w-sm">
72
- <h4 className="font-medium mb-2">Notifications</h4>
73
- <ScrollArea className="h-48">
74
- <div className="space-y-2">
75
- {Array.from({ length: 10 }).map((_, i) => (
76
- <div key={i} className="rounded-md border p-3">
77
- <p className="text-sm font-medium">Notification {i + 1}</p>
78
- <p className="text-xs text-muted-foreground">Just now</p>
79
- </div>
80
- ))}
81
- </div>
82
- </ScrollArea>
83
- </div>
84
- );
85
-
86
- export const Chat = () => (
87
- <div className="rounded-lg border max-w-md">
88
- <div className="p-4 border-b">
89
- <h4 className="font-medium">Chat</h4>
90
- </div>
91
- <ScrollArea className="h-64 p-4">
92
- <div className="space-y-4">
93
- {Array.from({ length: 15 }).map((_, i) => (
94
- <div
95
- key={i}
96
- className={`flex ${i % 2 === 0 ? 'justify-start' : 'justify-end'}`}
97
- >
98
- <div
99
- className={`rounded-lg px-3 py-2 max-w-[80%] ${
100
- i % 2 === 0 ? 'bg-muted' : 'bg-primary text-primary-foreground'
101
- }`}
102
- >
103
- <p className="text-sm">Message {i + 1}</p>
104
- </div>
105
- </div>
106
- ))}
107
- </div>
108
- </ScrollArea>
109
- </div>
110
- );
111
-
112
- const filters = [
113
- 'All', 'Electronics', 'Clothing', 'Home & Garden', 'Sports', 'Books',
114
- 'Toys', 'Beauty', 'Automotive', 'Health', 'Food', 'Music', 'Movies'
115
- ];
116
-
117
- export const HorizontalFilterTabs = () => (
118
- <div className="max-w-sm">
119
- <ScrollArea className="w-full" orientation="horizontal">
120
- <div className="flex gap-2 pb-2">
121
- {filters.map((filter) => (
122
- <button
123
- key={filter}
124
- className="px-3 py-1.5 rounded-md text-xs font-medium whitespace-nowrap bg-muted text-muted-foreground hover:text-foreground transition-colors"
125
- >
126
- {filter}
127
- </button>
128
- ))}
129
- </div>
130
- </ScrollArea>
131
- <p className="text-xs text-muted-foreground mt-2">
132
- Scroll horizontally to see more filters
133
- </p>
134
- </div>
135
- );
136
-
137
- export const VerticalNoHorizontalScroll = () => (
138
- <div className="max-w-xs border rounded-lg">
139
- <p className="text-sm font-medium p-3 border-b">overflowX="hidden" — no horizontal bleed</p>
140
- <ScrollArea className="h-48" overflowX="hidden">
141
- <div className="p-3 space-y-2">
142
- {Array.from({ length: 10 }).map((_, i) => (
143
- <div key={i} className="flex items-center gap-2 p-2 rounded-md bg-muted/40">
144
- <div className="w-2 h-8 rounded-full bg-primary/50 flex-shrink-0" />
145
- <span className="text-sm truncate flex-1 min-w-0">
146
- Track name that is very long and should truncate gracefully {i + 1}
147
- </span>
148
- </div>
149
- ))}
150
- </div>
151
- </ScrollArea>
152
- </div>
153
- );
154
-
155
- export const HorizontalChips = () => (
156
- <div className="max-w-xs">
157
- <p className="text-sm font-medium mb-2">Selected tags:</p>
158
- <ScrollArea className="w-full" orientation="horizontal">
159
- <div className="flex gap-1 pb-2">
160
- {['React', 'TypeScript', 'Tailwind', 'Next.js', 'Radix UI', 'Zustand', 'TanStack Query'].map((tag) => (
161
- <span
162
- key={tag}
163
- className="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs bg-primary/10 text-primary whitespace-nowrap"
164
- >
165
- {tag}
166
- <button className="hover:bg-primary/20 rounded-full p-0.5">×</button>
167
- </span>
168
- ))}
169
- </div>
170
- </ScrollArea>
171
- </div>
172
- );