@djangocfg/ui-core 2.1.120 → 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.
- package/package.json +8 -5
- package/src/components/accordion.story.tsx +110 -0
- package/src/components/alert-dialog.story.tsx +104 -0
- package/src/components/alert.story.tsx +77 -0
- package/src/components/aspect-ratio.story.tsx +94 -0
- package/src/components/avatar.story.tsx +115 -0
- package/src/components/badge.story.tsx +56 -0
- package/src/components/button-download.story.tsx +112 -0
- package/src/components/button-group.story.tsx +79 -0
- package/src/components/button.story.tsx +116 -0
- package/src/components/calendar.story.tsx +126 -0
- package/src/components/card.story.tsx +105 -0
- package/src/components/carousel.story.tsx +122 -0
- package/src/components/checkbox.story.tsx +89 -0
- package/src/components/collapsible.story.tsx +133 -0
- package/src/components/combobox.story.tsx +145 -0
- package/src/components/command.story.tsx +121 -0
- package/src/components/context-menu.story.tsx +125 -0
- package/src/components/copy.story.tsx +77 -0
- package/src/components/dialog.story.tsx +137 -0
- package/src/components/drawer.story.tsx +131 -0
- package/src/components/dropdown-menu.story.tsx +208 -0
- package/src/components/empty.story.tsx +115 -0
- package/src/components/hover-card.story.tsx +102 -0
- package/src/components/image-with-fallback.story.tsx +105 -0
- package/src/components/input-group.story.tsx +119 -0
- package/src/components/input-otp.story.tsx +105 -0
- package/src/components/input.story.tsx +77 -0
- package/src/components/kbd.story.tsx +113 -0
- package/src/components/label.story.tsx +52 -0
- package/src/components/menubar.story.tsx +152 -0
- package/src/components/multi-select.story.tsx +122 -0
- package/src/components/navigation-menu.story.tsx +154 -0
- package/src/components/popover.story.tsx +127 -0
- package/src/components/preloader.story.tsx +86 -0
- package/src/components/progress.story.tsx +97 -0
- package/src/components/radio-group.story.tsx +113 -0
- package/src/components/resizable.story.tsx +119 -0
- package/src/components/responsive-sheet.story.tsx +117 -0
- package/src/components/scroll-area.story.tsx +112 -0
- package/src/components/select.story.tsx +112 -0
- package/src/components/separator.story.tsx +69 -0
- package/src/components/sheet.story.tsx +148 -0
- package/src/components/skeleton.story.tsx +101 -0
- package/src/components/slider.story.tsx +113 -0
- package/src/components/spinner.story.tsx +66 -0
- package/src/components/switch.story.tsx +98 -0
- package/src/components/table.story.tsx +148 -0
- package/src/components/tabs.story.tsx +98 -0
- package/src/components/tabs.tsx +1 -1
- package/src/components/textarea.story.tsx +94 -0
- package/src/components/toggle-group.story.tsx +118 -0
- package/src/components/toggle.story.tsx +104 -0
- package/src/components/tooltip.story.tsx +139 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { defineStory, useBoolean } from '@djangocfg/playground';
|
|
2
|
+
import { DownloadButton } from './button-download';
|
|
3
|
+
|
|
4
|
+
export default defineStory({
|
|
5
|
+
title: 'Core/DownloadButton',
|
|
6
|
+
component: DownloadButton,
|
|
7
|
+
description: 'Button with download functionality and status indicators.',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const Interactive = () => {
|
|
11
|
+
const [showStatus] = useBoolean('showStatus', {
|
|
12
|
+
defaultValue: true,
|
|
13
|
+
label: 'Show Status',
|
|
14
|
+
description: 'Show loading/success/error icons',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<DownloadButton
|
|
19
|
+
url="https://example.com/file.pdf"
|
|
20
|
+
filename="document.pdf"
|
|
21
|
+
showStatus={showStatus}
|
|
22
|
+
>
|
|
23
|
+
Download PDF
|
|
24
|
+
</DownloadButton>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Default = () => (
|
|
29
|
+
<DownloadButton
|
|
30
|
+
url="https://example.com/file.pdf"
|
|
31
|
+
filename="document.pdf"
|
|
32
|
+
>
|
|
33
|
+
Download File
|
|
34
|
+
</DownloadButton>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const IconOnly = () => (
|
|
38
|
+
<DownloadButton
|
|
39
|
+
url="https://example.com/file.pdf"
|
|
40
|
+
filename="document.pdf"
|
|
41
|
+
size="icon"
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const Variants = () => (
|
|
46
|
+
<div className="flex gap-4">
|
|
47
|
+
<DownloadButton
|
|
48
|
+
url="https://example.com/file.pdf"
|
|
49
|
+
variant="default"
|
|
50
|
+
>
|
|
51
|
+
Default
|
|
52
|
+
</DownloadButton>
|
|
53
|
+
<DownloadButton
|
|
54
|
+
url="https://example.com/file.pdf"
|
|
55
|
+
variant="outline"
|
|
56
|
+
>
|
|
57
|
+
Outline
|
|
58
|
+
</DownloadButton>
|
|
59
|
+
<DownloadButton
|
|
60
|
+
url="https://example.com/file.pdf"
|
|
61
|
+
variant="ghost"
|
|
62
|
+
>
|
|
63
|
+
Ghost
|
|
64
|
+
</DownloadButton>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export const Sizes = () => (
|
|
69
|
+
<div className="flex items-center gap-4">
|
|
70
|
+
<DownloadButton url="https://example.com/file.pdf" size="sm">
|
|
71
|
+
Small
|
|
72
|
+
</DownloadButton>
|
|
73
|
+
<DownloadButton url="https://example.com/file.pdf" size="default">
|
|
74
|
+
Default
|
|
75
|
+
</DownloadButton>
|
|
76
|
+
<DownloadButton url="https://example.com/file.pdf" size="lg">
|
|
77
|
+
Large
|
|
78
|
+
</DownloadButton>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const WithCallbacks = () => (
|
|
83
|
+
<DownloadButton
|
|
84
|
+
url="https://example.com/file.pdf"
|
|
85
|
+
filename="report.pdf"
|
|
86
|
+
onDownloadStart={() => console.log('Download started')}
|
|
87
|
+
onDownloadComplete={(name) => console.log(`Downloaded: ${name}`)}
|
|
88
|
+
onDownloadError={(err) => console.error('Error:', err)}
|
|
89
|
+
>
|
|
90
|
+
Download with Callbacks
|
|
91
|
+
</DownloadButton>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const PostRequest = () => (
|
|
95
|
+
<DownloadButton
|
|
96
|
+
url="https://api.example.com/export"
|
|
97
|
+
method="POST"
|
|
98
|
+
body={{ format: 'pdf', includeImages: true }}
|
|
99
|
+
filename="export.pdf"
|
|
100
|
+
>
|
|
101
|
+
Export as PDF
|
|
102
|
+
</DownloadButton>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
export const DisabledStatus = () => (
|
|
106
|
+
<DownloadButton
|
|
107
|
+
url="https://example.com/file.pdf"
|
|
108
|
+
showStatus={false}
|
|
109
|
+
>
|
|
110
|
+
No Status Icons
|
|
111
|
+
</DownloadButton>
|
|
112
|
+
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { defineStory } from '@djangocfg/playground';
|
|
2
|
+
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from './button-group';
|
|
3
|
+
import { Button } from './button';
|
|
4
|
+
import { Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight, ChevronDown } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export default defineStory({
|
|
7
|
+
title: 'Core/ButtonGroup',
|
|
8
|
+
component: ButtonGroup,
|
|
9
|
+
description: 'Group of buttons with connected styling.',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const Default = () => (
|
|
13
|
+
<ButtonGroup>
|
|
14
|
+
<Button variant="outline">First</Button>
|
|
15
|
+
<Button variant="outline">Second</Button>
|
|
16
|
+
<Button variant="outline">Third</Button>
|
|
17
|
+
</ButtonGroup>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const TextFormatting = () => (
|
|
21
|
+
<ButtonGroup>
|
|
22
|
+
<Button variant="outline" size="icon">
|
|
23
|
+
<Bold className="h-4 w-4" />
|
|
24
|
+
</Button>
|
|
25
|
+
<Button variant="outline" size="icon">
|
|
26
|
+
<Italic className="h-4 w-4" />
|
|
27
|
+
</Button>
|
|
28
|
+
<Button variant="outline" size="icon">
|
|
29
|
+
<Underline className="h-4 w-4" />
|
|
30
|
+
</Button>
|
|
31
|
+
</ButtonGroup>
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
export const Alignment = () => (
|
|
35
|
+
<ButtonGroup>
|
|
36
|
+
<Button variant="outline" size="icon">
|
|
37
|
+
<AlignLeft className="h-4 w-4" />
|
|
38
|
+
</Button>
|
|
39
|
+
<Button variant="outline" size="icon">
|
|
40
|
+
<AlignCenter className="h-4 w-4" />
|
|
41
|
+
</Button>
|
|
42
|
+
<Button variant="outline" size="icon">
|
|
43
|
+
<AlignRight className="h-4 w-4" />
|
|
44
|
+
</Button>
|
|
45
|
+
</ButtonGroup>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const WithSeparator = () => (
|
|
49
|
+
<ButtonGroup>
|
|
50
|
+
<Button variant="outline">Edit</Button>
|
|
51
|
+
<ButtonGroupSeparator />
|
|
52
|
+
<Button variant="outline" size="icon">
|
|
53
|
+
<ChevronDown className="h-4 w-4" />
|
|
54
|
+
</Button>
|
|
55
|
+
</ButtonGroup>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
export const WithText = () => (
|
|
59
|
+
<ButtonGroup>
|
|
60
|
+
<ButtonGroupText>Label</ButtonGroupText>
|
|
61
|
+
<Button variant="outline">Action</Button>
|
|
62
|
+
</ButtonGroup>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
export const Vertical = () => (
|
|
66
|
+
<ButtonGroup orientation="vertical">
|
|
67
|
+
<Button variant="outline">Top</Button>
|
|
68
|
+
<Button variant="outline">Middle</Button>
|
|
69
|
+
<Button variant="outline">Bottom</Button>
|
|
70
|
+
</ButtonGroup>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const Mixed = () => (
|
|
74
|
+
<ButtonGroup>
|
|
75
|
+
<Button variant="default">Primary</Button>
|
|
76
|
+
<Button variant="outline">Secondary</Button>
|
|
77
|
+
<Button variant="ghost">Tertiary</Button>
|
|
78
|
+
</ButtonGroup>
|
|
79
|
+
);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { defineStory, useSelect, useBoolean } from '@djangocfg/playground';
|
|
2
|
+
import { Mail, Send, Loader2, ChevronRight, Plus, Trash2 } from 'lucide-react';
|
|
3
|
+
import { Button, ButtonLink } from './button';
|
|
4
|
+
|
|
5
|
+
export default defineStory({
|
|
6
|
+
title: 'Core/Button',
|
|
7
|
+
component: Button,
|
|
8
|
+
description: 'Versatile button component with multiple variants and sizes.',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const Interactive = () => {
|
|
12
|
+
const [variant] = useSelect('variant', {
|
|
13
|
+
options: ['default', 'secondary', 'destructive', 'outline', 'ghost', 'link'] as const,
|
|
14
|
+
defaultValue: 'default',
|
|
15
|
+
label: 'Variant',
|
|
16
|
+
description: 'Button style variant',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const [size] = useSelect('size', {
|
|
20
|
+
options: ['xs', 'sm', 'default', 'lg', 'huge', 'icon'] as const,
|
|
21
|
+
defaultValue: 'default',
|
|
22
|
+
label: 'Size',
|
|
23
|
+
description: 'Button size',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const [loading] = useBoolean('loading', {
|
|
27
|
+
defaultValue: false,
|
|
28
|
+
label: 'Loading',
|
|
29
|
+
description: 'Show loading spinner',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const [disabled] = useBoolean('disabled', {
|
|
33
|
+
defaultValue: false,
|
|
34
|
+
label: 'Disabled',
|
|
35
|
+
description: 'Disable button',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
40
|
+
<Button variant={variant} size={size} loading={loading} disabled={disabled}>
|
|
41
|
+
{size === 'icon' ? <Plus /> : 'Button'}
|
|
42
|
+
</Button>
|
|
43
|
+
<Button variant={variant} size={size} loading={loading} disabled={disabled}>
|
|
44
|
+
<Mail />
|
|
45
|
+
{size !== 'icon' && 'With Icon'}
|
|
46
|
+
</Button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Variants = () => (
|
|
52
|
+
<div className="flex flex-wrap gap-4">
|
|
53
|
+
<Button variant="default">Default</Button>
|
|
54
|
+
<Button variant="secondary">Secondary</Button>
|
|
55
|
+
<Button variant="destructive">Destructive</Button>
|
|
56
|
+
<Button variant="outline">Outline</Button>
|
|
57
|
+
<Button variant="ghost">Ghost</Button>
|
|
58
|
+
<Button variant="link">Link</Button>
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export const Sizes = () => (
|
|
63
|
+
<div className="flex flex-wrap gap-4 items-center">
|
|
64
|
+
<Button size="xs">Extra Small</Button>
|
|
65
|
+
<Button size="sm">Small</Button>
|
|
66
|
+
<Button size="default">Default</Button>
|
|
67
|
+
<Button size="lg">Large</Button>
|
|
68
|
+
<Button size="huge">Huge</Button>
|
|
69
|
+
<Button size="icon"><Plus /></Button>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export const WithIcons = () => (
|
|
74
|
+
<div className="flex flex-wrap gap-4">
|
|
75
|
+
<Button>
|
|
76
|
+
<Mail />
|
|
77
|
+
Login with Email
|
|
78
|
+
</Button>
|
|
79
|
+
<Button variant="secondary">
|
|
80
|
+
Send
|
|
81
|
+
<Send />
|
|
82
|
+
</Button>
|
|
83
|
+
<Button variant="outline">
|
|
84
|
+
Next
|
|
85
|
+
<ChevronRight />
|
|
86
|
+
</Button>
|
|
87
|
+
<Button variant="destructive">
|
|
88
|
+
<Trash2 />
|
|
89
|
+
Delete
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
export const Loading = () => (
|
|
95
|
+
<div className="flex flex-wrap gap-4">
|
|
96
|
+
<Button loading>Loading...</Button>
|
|
97
|
+
<Button loading variant="secondary">Processing</Button>
|
|
98
|
+
<Button loading variant="outline">Please wait</Button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
export const Disabled = () => (
|
|
103
|
+
<div className="flex flex-wrap gap-4">
|
|
104
|
+
<Button disabled>Default</Button>
|
|
105
|
+
<Button disabled variant="secondary">Secondary</Button>
|
|
106
|
+
<Button disabled variant="outline">Outline</Button>
|
|
107
|
+
</div>
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const AsLink = () => (
|
|
111
|
+
<div className="flex flex-wrap gap-4">
|
|
112
|
+
<ButtonLink href="https://example.com">External Link</ButtonLink>
|
|
113
|
+
<ButtonLink href="/dashboard" variant="secondary">Dashboard</ButtonLink>
|
|
114
|
+
<ButtonLink href="/settings" variant="outline">Settings</ButtonLink>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { defineStory } from '@djangocfg/playground';
|
|
3
|
+
import { Calendar, DatePicker, DateRangePicker, type DateRange } from './calendar';
|
|
4
|
+
|
|
5
|
+
export default defineStory({
|
|
6
|
+
title: 'Core/Calendar',
|
|
7
|
+
component: Calendar,
|
|
8
|
+
description: 'Calendar and date picker components.',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const Default = () => {
|
|
12
|
+
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Calendar
|
|
16
|
+
mode="single"
|
|
17
|
+
selected={date}
|
|
18
|
+
onSelect={setDate}
|
|
19
|
+
className="rounded-md border"
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const Range = () => {
|
|
25
|
+
const [range, setRange] = useState<DateRange | undefined>({
|
|
26
|
+
from: new Date(),
|
|
27
|
+
to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Calendar
|
|
32
|
+
mode="range"
|
|
33
|
+
selected={range}
|
|
34
|
+
onSelect={setRange}
|
|
35
|
+
className="rounded-md border"
|
|
36
|
+
numberOfMonths={2}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const Multiple = () => {
|
|
42
|
+
const [dates, setDates] = useState<Date[] | undefined>([]);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Calendar
|
|
46
|
+
mode="multiple"
|
|
47
|
+
selected={dates}
|
|
48
|
+
onSelect={setDates}
|
|
49
|
+
className="rounded-md border"
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const Picker = () => {
|
|
55
|
+
const [date, setDate] = useState<Date>();
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="max-w-xs">
|
|
59
|
+
<DatePicker
|
|
60
|
+
value={date}
|
|
61
|
+
onChange={setDate}
|
|
62
|
+
placeholder="Pick a date"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const PickerWithLabel = () => {
|
|
69
|
+
const [date, setDate] = useState<Date>();
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="max-w-xs space-y-2">
|
|
73
|
+
<label className="text-sm font-medium">Date of birth</label>
|
|
74
|
+
<DatePicker
|
|
75
|
+
value={date}
|
|
76
|
+
onChange={setDate}
|
|
77
|
+
placeholder="Select your birth date"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const RangePicker = () => {
|
|
84
|
+
const [range, setRange] = useState<DateRange>();
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="max-w-sm">
|
|
88
|
+
<DateRangePicker
|
|
89
|
+
value={range}
|
|
90
|
+
onChange={setRange}
|
|
91
|
+
placeholder="Select date range"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const RangePickerWithPresets = () => {
|
|
98
|
+
const [range, setRange] = useState<DateRange>();
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<div className="max-w-sm space-y-2">
|
|
102
|
+
<label className="text-sm font-medium">Booking dates</label>
|
|
103
|
+
<DateRangePicker
|
|
104
|
+
value={range}
|
|
105
|
+
onChange={setRange}
|
|
106
|
+
placeholder="Check-in — Check-out"
|
|
107
|
+
numberOfMonths={2}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const Disabled = () => {
|
|
114
|
+
const [date, setDate] = useState<Date>();
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="max-w-xs">
|
|
118
|
+
<DatePicker
|
|
119
|
+
value={date}
|
|
120
|
+
onChange={setDate}
|
|
121
|
+
placeholder="Pick a date"
|
|
122
|
+
disabled
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineStory } from '@djangocfg/playground';
|
|
2
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from './card';
|
|
3
|
+
import { Button } from './button';
|
|
4
|
+
import { Input } from './input';
|
|
5
|
+
import { Label } from './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
|
+
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { defineStory, type StoryMeta } from '@djangocfg/playground';
|
|
2
|
+
import {
|
|
3
|
+
Carousel,
|
|
4
|
+
CarouselContent,
|
|
5
|
+
CarouselItem,
|
|
6
|
+
CarouselNext,
|
|
7
|
+
CarouselPrevious,
|
|
8
|
+
} from './carousel';
|
|
9
|
+
import { Card, CardContent } from './card';
|
|
10
|
+
|
|
11
|
+
const meta: StoryMeta = defineStory({
|
|
12
|
+
title: 'Core/Carousel',
|
|
13
|
+
component: Carousel,
|
|
14
|
+
description: 'Carousel/slider component for cycling through content.',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
|
|
19
|
+
export const Default = () => (
|
|
20
|
+
<Carousel className="w-full max-w-xs">
|
|
21
|
+
<CarouselContent>
|
|
22
|
+
{Array.from({ length: 5 }).map((_, index) => (
|
|
23
|
+
<CarouselItem key={index}>
|
|
24
|
+
<div className="p-1">
|
|
25
|
+
<Card>
|
|
26
|
+
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
27
|
+
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
28
|
+
</CardContent>
|
|
29
|
+
</Card>
|
|
30
|
+
</div>
|
|
31
|
+
</CarouselItem>
|
|
32
|
+
))}
|
|
33
|
+
</CarouselContent>
|
|
34
|
+
<CarouselPrevious />
|
|
35
|
+
<CarouselNext />
|
|
36
|
+
</Carousel>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const Multiple = () => (
|
|
40
|
+
<Carousel
|
|
41
|
+
opts={{ align: 'start' }}
|
|
42
|
+
className="w-full max-w-sm"
|
|
43
|
+
>
|
|
44
|
+
<CarouselContent>
|
|
45
|
+
{Array.from({ length: 5 }).map((_, index) => (
|
|
46
|
+
<CarouselItem key={index} className="basis-1/3">
|
|
47
|
+
<div className="p-1">
|
|
48
|
+
<Card>
|
|
49
|
+
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
50
|
+
<span className="text-3xl font-semibold">{index + 1}</span>
|
|
51
|
+
</CardContent>
|
|
52
|
+
</Card>
|
|
53
|
+
</div>
|
|
54
|
+
</CarouselItem>
|
|
55
|
+
))}
|
|
56
|
+
</CarouselContent>
|
|
57
|
+
<CarouselPrevious />
|
|
58
|
+
<CarouselNext />
|
|
59
|
+
</Carousel>
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
export const Wide = () => (
|
|
63
|
+
<Carousel className="w-full max-w-lg">
|
|
64
|
+
<CarouselContent>
|
|
65
|
+
{Array.from({ length: 3 }).map((_, index) => (
|
|
66
|
+
<CarouselItem key={index}>
|
|
67
|
+
<div className="p-1">
|
|
68
|
+
<Card>
|
|
69
|
+
<CardContent className="flex aspect-video items-center justify-center p-6 bg-muted">
|
|
70
|
+
<span className="text-4xl font-semibold">Slide {index + 1}</span>
|
|
71
|
+
</CardContent>
|
|
72
|
+
</Card>
|
|
73
|
+
</div>
|
|
74
|
+
</CarouselItem>
|
|
75
|
+
))}
|
|
76
|
+
</CarouselContent>
|
|
77
|
+
<CarouselPrevious />
|
|
78
|
+
<CarouselNext />
|
|
79
|
+
</Carousel>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
export const WithImages = () => (
|
|
83
|
+
<Carousel className="w-full max-w-md">
|
|
84
|
+
<CarouselContent>
|
|
85
|
+
{[
|
|
86
|
+
'https://images.unsplash.com/photo-1494976388531-d1058494cdd8?w=400',
|
|
87
|
+
'https://images.unsplash.com/photo-1503376780353-7e6692767b70?w=400',
|
|
88
|
+
'https://images.unsplash.com/photo-1542362567-b07e54358753?w=400',
|
|
89
|
+
].map((src, index) => (
|
|
90
|
+
<CarouselItem key={index}>
|
|
91
|
+
<div className="p-1">
|
|
92
|
+
<div className="aspect-video rounded-lg overflow-hidden">
|
|
93
|
+
<img src={src} alt={`Slide ${index + 1}`} className="w-full h-full object-cover" />
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</CarouselItem>
|
|
97
|
+
))}
|
|
98
|
+
</CarouselContent>
|
|
99
|
+
<CarouselPrevious />
|
|
100
|
+
<CarouselNext />
|
|
101
|
+
</Carousel>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export const Loop = () => (
|
|
105
|
+
<Carousel opts={{ loop: true }} className="w-full max-w-xs">
|
|
106
|
+
<CarouselContent>
|
|
107
|
+
{Array.from({ length: 5 }).map((_, index) => (
|
|
108
|
+
<CarouselItem key={index}>
|
|
109
|
+
<div className="p-1">
|
|
110
|
+
<Card>
|
|
111
|
+
<CardContent className="flex aspect-square items-center justify-center p-6">
|
|
112
|
+
<span className="text-4xl font-semibold">{index + 1}</span>
|
|
113
|
+
</CardContent>
|
|
114
|
+
</Card>
|
|
115
|
+
</div>
|
|
116
|
+
</CarouselItem>
|
|
117
|
+
))}
|
|
118
|
+
</CarouselContent>
|
|
119
|
+
<CarouselPrevious />
|
|
120
|
+
<CarouselNext />
|
|
121
|
+
</Carousel>
|
|
122
|
+
);
|