@djangocfg/ui-core 2.1.119 → 2.1.121

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 (54) hide show
  1. package/package.json +8 -5
  2. package/src/components/accordion.story.tsx +110 -0
  3. package/src/components/alert-dialog.story.tsx +104 -0
  4. package/src/components/alert.story.tsx +77 -0
  5. package/src/components/aspect-ratio.story.tsx +94 -0
  6. package/src/components/avatar.story.tsx +115 -0
  7. package/src/components/badge.story.tsx +56 -0
  8. package/src/components/button-download.story.tsx +112 -0
  9. package/src/components/button-group.story.tsx +79 -0
  10. package/src/components/button.story.tsx +116 -0
  11. package/src/components/calendar.story.tsx +126 -0
  12. package/src/components/card.story.tsx +105 -0
  13. package/src/components/carousel.story.tsx +122 -0
  14. package/src/components/checkbox.story.tsx +89 -0
  15. package/src/components/collapsible.story.tsx +133 -0
  16. package/src/components/combobox.story.tsx +145 -0
  17. package/src/components/command.story.tsx +121 -0
  18. package/src/components/context-menu.story.tsx +125 -0
  19. package/src/components/copy.story.tsx +77 -0
  20. package/src/components/dialog.story.tsx +137 -0
  21. package/src/components/drawer.story.tsx +131 -0
  22. package/src/components/dropdown-menu.story.tsx +208 -0
  23. package/src/components/empty.story.tsx +115 -0
  24. package/src/components/hover-card.story.tsx +102 -0
  25. package/src/components/image-with-fallback.story.tsx +105 -0
  26. package/src/components/input-group.story.tsx +119 -0
  27. package/src/components/input-otp.story.tsx +105 -0
  28. package/src/components/input.story.tsx +77 -0
  29. package/src/components/kbd.story.tsx +113 -0
  30. package/src/components/label.story.tsx +52 -0
  31. package/src/components/menubar.story.tsx +152 -0
  32. package/src/components/multi-select.story.tsx +122 -0
  33. package/src/components/navigation-menu.story.tsx +154 -0
  34. package/src/components/popover.story.tsx +127 -0
  35. package/src/components/preloader.story.tsx +86 -0
  36. package/src/components/progress.story.tsx +97 -0
  37. package/src/components/radio-group.story.tsx +113 -0
  38. package/src/components/resizable.story.tsx +119 -0
  39. package/src/components/responsive-sheet.story.tsx +117 -0
  40. package/src/components/scroll-area.story.tsx +112 -0
  41. package/src/components/select.story.tsx +112 -0
  42. package/src/components/separator.story.tsx +69 -0
  43. package/src/components/sheet.story.tsx +148 -0
  44. package/src/components/skeleton.story.tsx +101 -0
  45. package/src/components/slider.story.tsx +113 -0
  46. package/src/components/spinner.story.tsx +66 -0
  47. package/src/components/switch.story.tsx +98 -0
  48. package/src/components/table.story.tsx +148 -0
  49. package/src/components/tabs.story.tsx +98 -0
  50. package/src/components/tabs.tsx +1 -1
  51. package/src/components/textarea.story.tsx +94 -0
  52. package/src/components/toggle-group.story.tsx +118 -0
  53. package/src/components/toggle.story.tsx +104 -0
  54. package/src/components/tooltip.story.tsx +139 -0
@@ -0,0 +1,105 @@
1
+ import { defineStory, useSelect } from '@djangocfg/playground';
2
+ import { ImageWithFallback } from './image-with-fallback';
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
+ );
@@ -0,0 +1,119 @@
1
+ import { defineStory } from '@djangocfg/playground';
2
+ import {
3
+ InputGroup,
4
+ InputGroupAddon,
5
+ InputGroupButton,
6
+ InputGroupText,
7
+ InputGroupInput,
8
+ InputGroupTextarea,
9
+ } from './input-group';
10
+ import { Search, Mail, Eye, EyeOff, Copy, AtSign } from 'lucide-react';
11
+ import { useState } from 'react';
12
+ import { Kbd } from './kbd';
13
+
14
+ export default defineStory({
15
+ title: 'Core/InputGroup',
16
+ component: InputGroup,
17
+ description: 'Input with addons, buttons, and icons.',
18
+ });
19
+
20
+ export const WithIcon = () => (
21
+ <InputGroup className="max-w-sm">
22
+ <InputGroupAddon>
23
+ <Search className="h-4 w-4" />
24
+ </InputGroupAddon>
25
+ <InputGroupInput placeholder="Search..." />
26
+ </InputGroup>
27
+ );
28
+
29
+ export const WithText = () => (
30
+ <InputGroup className="max-w-sm">
31
+ <InputGroupAddon>
32
+ <InputGroupText>https://</InputGroupText>
33
+ </InputGroupAddon>
34
+ <InputGroupInput placeholder="example.com" />
35
+ </InputGroup>
36
+ );
37
+
38
+ export const WithButton = () => (
39
+ <InputGroup className="max-w-sm">
40
+ <InputGroupInput placeholder="Enter email" />
41
+ <InputGroupAddon align="inline-end">
42
+ <InputGroupButton>Subscribe</InputGroupButton>
43
+ </InputGroupAddon>
44
+ </InputGroup>
45
+ );
46
+
47
+ export const PasswordToggle = () => {
48
+ const [showPassword, setShowPassword] = useState(false);
49
+
50
+ return (
51
+ <InputGroup className="max-w-sm">
52
+ <InputGroupInput
53
+ type={showPassword ? 'text' : 'password'}
54
+ placeholder="Enter password"
55
+ />
56
+ <InputGroupAddon align="inline-end">
57
+ <InputGroupButton
58
+ size="icon-xs"
59
+ onClick={() => setShowPassword(!showPassword)}
60
+ >
61
+ {showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
62
+ </InputGroupButton>
63
+ </InputGroupAddon>
64
+ </InputGroup>
65
+ );
66
+ };
67
+
68
+ export const CopyInput = () => (
69
+ <InputGroup className="max-w-md">
70
+ <InputGroupInput value="npm install @djangocfg/ui-core" readOnly />
71
+ <InputGroupAddon align="inline-end">
72
+ <InputGroupButton size="icon-xs">
73
+ <Copy className="h-4 w-4" />
74
+ </InputGroupButton>
75
+ </InputGroupAddon>
76
+ </InputGroup>
77
+ );
78
+
79
+ export const WithKbd = () => (
80
+ <InputGroup className="max-w-sm">
81
+ <InputGroupAddon>
82
+ <Search className="h-4 w-4" />
83
+ </InputGroupAddon>
84
+ <InputGroupInput placeholder="Search..." />
85
+ <InputGroupAddon align="inline-end">
86
+ <Kbd size="xs">⌘K</Kbd>
87
+ </InputGroupAddon>
88
+ </InputGroup>
89
+ );
90
+
91
+ export const Email = () => (
92
+ <InputGroup className="max-w-sm">
93
+ <InputGroupAddon>
94
+ <AtSign className="h-4 w-4" />
95
+ </InputGroupAddon>
96
+ <InputGroupInput type="email" placeholder="you@example.com" />
97
+ </InputGroup>
98
+ );
99
+
100
+ export const WithTextarea = () => (
101
+ <InputGroup className="max-w-md">
102
+ <InputGroupAddon align="block-start">
103
+ <InputGroupText>Description</InputGroupText>
104
+ </InputGroupAddon>
105
+ <InputGroupTextarea placeholder="Enter description..." rows={4} />
106
+ </InputGroup>
107
+ );
108
+
109
+ export const BothSides = () => (
110
+ <InputGroup className="max-w-sm">
111
+ <InputGroupAddon>
112
+ <Mail className="h-4 w-4" />
113
+ </InputGroupAddon>
114
+ <InputGroupInput placeholder="Enter email" />
115
+ <InputGroupAddon align="inline-end">
116
+ <InputGroupButton>Send</InputGroupButton>
117
+ </InputGroupAddon>
118
+ </InputGroup>
119
+ );
@@ -0,0 +1,105 @@
1
+ import { useState } from 'react';
2
+ import { defineStory } from '@djangocfg/playground';
3
+ import {
4
+ InputOTP,
5
+ InputOTPGroup,
6
+ InputOTPSlot,
7
+ InputOTPSeparator,
8
+ } from './input-otp';
9
+
10
+ export default defineStory({
11
+ title: 'Core/InputOTP',
12
+ component: InputOTP,
13
+ description: 'One-time password input with individual digit slots.',
14
+ });
15
+
16
+ export const Default = () => {
17
+ const [value, setValue] = useState('');
18
+
19
+ return (
20
+ <InputOTP maxLength={6} value={value} onChange={setValue}>
21
+ <InputOTPGroup>
22
+ <InputOTPSlot index={0} />
23
+ <InputOTPSlot index={1} />
24
+ <InputOTPSlot index={2} />
25
+ <InputOTPSlot index={3} />
26
+ <InputOTPSlot index={4} />
27
+ <InputOTPSlot index={5} />
28
+ </InputOTPGroup>
29
+ </InputOTP>
30
+ );
31
+ };
32
+
33
+ export const WithSeparator = () => {
34
+ const [value, setValue] = useState('');
35
+
36
+ return (
37
+ <InputOTP maxLength={6} value={value} onChange={setValue}>
38
+ <InputOTPGroup>
39
+ <InputOTPSlot index={0} />
40
+ <InputOTPSlot index={1} />
41
+ <InputOTPSlot index={2} />
42
+ </InputOTPGroup>
43
+ <InputOTPSeparator />
44
+ <InputOTPGroup>
45
+ <InputOTPSlot index={3} />
46
+ <InputOTPSlot index={4} />
47
+ <InputOTPSlot index={5} />
48
+ </InputOTPGroup>
49
+ </InputOTP>
50
+ );
51
+ };
52
+
53
+ export const FourDigits = () => {
54
+ const [value, setValue] = useState('');
55
+
56
+ return (
57
+ <InputOTP maxLength={4} value={value} onChange={setValue}>
58
+ <InputOTPGroup>
59
+ <InputOTPSlot index={0} />
60
+ <InputOTPSlot index={1} />
61
+ <InputOTPSlot index={2} />
62
+ <InputOTPSlot index={3} />
63
+ </InputOTPGroup>
64
+ </InputOTP>
65
+ );
66
+ };
67
+
68
+ export const Disabled = () => (
69
+ <InputOTP maxLength={6} disabled>
70
+ <InputOTPGroup>
71
+ <InputOTPSlot index={0} />
72
+ <InputOTPSlot index={1} />
73
+ <InputOTPSlot index={2} />
74
+ <InputOTPSlot index={3} />
75
+ <InputOTPSlot index={4} />
76
+ <InputOTPSlot index={5} />
77
+ </InputOTPGroup>
78
+ </InputOTP>
79
+ );
80
+
81
+ export const WithLabel = () => {
82
+ const [value, setValue] = useState('');
83
+
84
+ return (
85
+ <div className="space-y-2">
86
+ <label className="text-sm font-medium">Verification Code</label>
87
+ <InputOTP maxLength={6} value={value} onChange={setValue}>
88
+ <InputOTPGroup>
89
+ <InputOTPSlot index={0} />
90
+ <InputOTPSlot index={1} />
91
+ <InputOTPSlot index={2} />
92
+ </InputOTPGroup>
93
+ <InputOTPSeparator />
94
+ <InputOTPGroup>
95
+ <InputOTPSlot index={3} />
96
+ <InputOTPSlot index={4} />
97
+ <InputOTPSlot index={5} />
98
+ </InputOTPGroup>
99
+ </InputOTP>
100
+ <p className="text-sm text-muted-foreground">
101
+ Enter the 6-digit code sent to your email.
102
+ </p>
103
+ </div>
104
+ );
105
+ };
@@ -0,0 +1,77 @@
1
+ import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
2
+ import { Input } from './input';
3
+ import { Label } from './label';
4
+
5
+ export default defineStory({
6
+ title: 'Core/Input',
7
+ component: Input,
8
+ description: 'Text input component for forms.',
9
+ });
10
+
11
+ export const Interactive = () => {
12
+ const [type] = useSelect('type', {
13
+ options: ['text', 'email', 'password', 'number', 'search', 'tel', 'url'] as const,
14
+ defaultValue: 'text',
15
+ label: 'Type',
16
+ description: 'Input type',
17
+ });
18
+
19
+ const [disabled] = useBoolean('disabled', {
20
+ defaultValue: false,
21
+ label: 'Disabled',
22
+ description: 'Disable input',
23
+ });
24
+
25
+ return (
26
+ <div className="max-w-sm space-y-4">
27
+ <div className="space-y-2">
28
+ <Label htmlFor="demo">Label</Label>
29
+ <Input
30
+ id="demo"
31
+ type={type}
32
+ placeholder={`Enter ${type}...`}
33
+ disabled={disabled}
34
+ />
35
+ </div>
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export const Types = () => (
41
+ <div className="max-w-sm space-y-4">
42
+ <Input type="text" placeholder="Text input" />
43
+ <Input type="email" placeholder="Email input" />
44
+ <Input type="password" placeholder="Password input" />
45
+ <Input type="number" placeholder="Number input" />
46
+ <Input type="search" placeholder="Search input" />
47
+ </div>
48
+ );
49
+
50
+ export const WithLabels = () => (
51
+ <div className="max-w-sm space-y-4">
52
+ <div className="space-y-2">
53
+ <Label htmlFor="email">Email</Label>
54
+ <Input id="email" type="email" placeholder="Enter your email" />
55
+ </div>
56
+ <div className="space-y-2">
57
+ <Label htmlFor="password">Password</Label>
58
+ <Input id="password" type="password" placeholder="Enter password" />
59
+ </div>
60
+ </div>
61
+ );
62
+
63
+ export const States = () => (
64
+ <div className="max-w-sm space-y-4">
65
+ <Input placeholder="Default" />
66
+ <Input placeholder="Disabled" disabled />
67
+ <Input placeholder="With value" defaultValue="Hello World" />
68
+ <Input placeholder="Read only" readOnly defaultValue="Read only value" />
69
+ </div>
70
+ );
71
+
72
+ export const File = () => (
73
+ <div className="max-w-sm space-y-2">
74
+ <Label htmlFor="file">Upload file</Label>
75
+ <Input id="file" type="file" />
76
+ </div>
77
+ );
@@ -0,0 +1,113 @@
1
+ import { defineStory, useSelect } from '@djangocfg/playground';
2
+ import { Kbd, KbdGroup } from './kbd';
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
+ );
@@ -0,0 +1,52 @@
1
+ import { defineStory } from '@djangocfg/playground';
2
+ import { Label } from './label';
3
+ import { Input } from './input';
4
+ import { Checkbox } from './checkbox';
5
+
6
+ export default defineStory({
7
+ title: 'Core/Label',
8
+ component: Label,
9
+ description: 'Accessible label for form controls.',
10
+ });
11
+
12
+ export const Default = () => <Label>Email address</Label>;
13
+
14
+ export const WithInput = () => (
15
+ <div className="max-w-sm space-y-2">
16
+ <Label htmlFor="email">Email</Label>
17
+ <Input id="email" type="email" placeholder="you@example.com" />
18
+ </div>
19
+ );
20
+
21
+ export const WithCheckbox = () => (
22
+ <div className="flex items-center space-x-2">
23
+ <Checkbox id="terms" />
24
+ <Label htmlFor="terms">Accept terms and conditions</Label>
25
+ </div>
26
+ );
27
+
28
+ export const Required = () => (
29
+ <div className="max-w-sm space-y-2">
30
+ <Label htmlFor="name">
31
+ Name <span className="text-destructive">*</span>
32
+ </Label>
33
+ <Input id="name" placeholder="Enter your name" />
34
+ </div>
35
+ );
36
+
37
+ export const WithDescription = () => (
38
+ <div className="max-w-sm space-y-1">
39
+ <Label htmlFor="bio">Bio</Label>
40
+ <p className="text-sm text-muted-foreground">
41
+ Tell us a little about yourself.
42
+ </p>
43
+ <Input id="bio" placeholder="I am..." />
44
+ </div>
45
+ );
46
+
47
+ export const Disabled = () => (
48
+ <div className="max-w-sm space-y-2">
49
+ <Label htmlFor="disabled" className="opacity-50">Disabled field</Label>
50
+ <Input id="disabled" disabled placeholder="Cannot edit" />
51
+ </div>
52
+ );