@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
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+
5
+ export interface UseHotkeyChordOptions {
6
+ /** Whether the chord is active. @default true */
7
+ enabled?: boolean;
8
+ /** Max delay between consecutive keys, in ms. @default 800 */
9
+ window?: number;
10
+ /**
11
+ * Fire even when focus is inside an input / textarea / contenteditable.
12
+ * @default false — chord sequences shouldn't hijack typing
13
+ */
14
+ enableOnFormTags?: boolean;
15
+ /** Prevent default browser behaviour on each key. @default false */
16
+ preventDefault?: boolean;
17
+ }
18
+
19
+ const FORM_TAGS = new Set(['INPUT', 'TEXTAREA', 'SELECT']);
20
+
21
+ function isEditableTarget(el: EventTarget | null): boolean {
22
+ if (!(el instanceof HTMLElement)) return false;
23
+ if (FORM_TAGS.has(el.tagName)) return true;
24
+ if (el.isContentEditable) return true;
25
+ return false;
26
+ }
27
+
28
+ /**
29
+ * Linear-style chord shortcuts — fire `callback` when the user presses
30
+ * a sequence of bare keys within a time window.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * useHotkeyChord(['g', 't'], () => navigate('/tasks'));
35
+ * useHotkeyChord(['g', 'i'], () => navigate('/inbox'), { window: 1200 });
36
+ * ```
37
+ *
38
+ * Each step is a single bare key (no modifiers). Pressing any
39
+ * non-sequence key resets progress, so partially-typed sequences don't
40
+ * fire accidentally.
41
+ */
42
+ export function useHotkeyChord(
43
+ keys: readonly string[],
44
+ callback: (event: KeyboardEvent) => void,
45
+ options: UseHotkeyChordOptions = {},
46
+ ): void {
47
+ const { enabled = true, window: chordWindow = 800, enableOnFormTags = false, preventDefault = false } = options;
48
+ const callbackRef = useRef(callback);
49
+ callbackRef.current = callback;
50
+
51
+ useEffect(() => {
52
+ if (!enabled) return;
53
+ if (typeof window === 'undefined') return;
54
+ if (!keys.length) return;
55
+
56
+ const sequence = keys.map((k) => k.toLowerCase());
57
+ let progress = 0;
58
+ let timer: ReturnType<typeof setTimeout> | null = null;
59
+
60
+ const reset = () => {
61
+ progress = 0;
62
+ if (timer) {
63
+ clearTimeout(timer);
64
+ timer = null;
65
+ }
66
+ };
67
+
68
+ const onKey = (e: KeyboardEvent) => {
69
+ if (!enableOnFormTags && isEditableTarget(e.target)) return;
70
+ // Chords don't carry modifiers.
71
+ if (e.metaKey || e.ctrlKey || e.altKey) return reset();
72
+
73
+ const expected = sequence[progress];
74
+ if (!expected) return;
75
+ const pressed = e.key.toLowerCase();
76
+ if (pressed !== expected) return reset();
77
+
78
+ if (preventDefault) e.preventDefault();
79
+ progress++;
80
+ if (progress >= sequence.length) {
81
+ callbackRef.current(e);
82
+ reset();
83
+ return;
84
+ }
85
+
86
+ if (timer) clearTimeout(timer);
87
+ timer = setTimeout(reset, chordWindow);
88
+ };
89
+
90
+ window.addEventListener('keydown', onKey);
91
+ return () => {
92
+ window.removeEventListener('keydown', onKey);
93
+ if (timer) clearTimeout(timer);
94
+ };
95
+ }, [keys.join('|'), enabled, chordWindow, enableOnFormTags, preventDefault]);
96
+ }
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useSyncExternalStore } from 'react';
4
+
5
+ /**
6
+ * Module-level store of every hotkey that registered itself with a
7
+ * `description`. Lets you render a `?` cheat-sheet without prop-drilling.
8
+ */
9
+ export interface RegisteredHotkey {
10
+ /** Key combo (raw string passed to `useHotkey` — format with `formatHotkey()` for display). */
11
+ combo: string;
12
+ /** Human-readable purpose. */
13
+ description: string;
14
+ /** Optional scope label for grouping (e.g. `'chat'`, `'global'`). */
15
+ scope?: string;
16
+ }
17
+
18
+ const registry = new Map<symbol, RegisteredHotkey>();
19
+ const listeners = new Set<() => void>();
20
+
21
+ function notify() {
22
+ for (const cb of listeners) cb();
23
+ }
24
+
25
+ /**
26
+ * Register a hotkey entry while a component is mounted. Internal —
27
+ * called by `useHotkey` when a `description` is provided.
28
+ */
29
+ export function registerHotkey(entry: RegisteredHotkey): () => void {
30
+ const key = Symbol(entry.combo);
31
+ registry.set(key, entry);
32
+ notify();
33
+ return () => {
34
+ registry.delete(key);
35
+ notify();
36
+ };
37
+ }
38
+
39
+ /** Read the live list of registered hotkeys. SSR-safe. */
40
+ export function useHotkeyHelp(): RegisteredHotkey[] {
41
+ return useSyncExternalStore(
42
+ (cb) => {
43
+ listeners.add(cb);
44
+ return () => {
45
+ listeners.delete(cb);
46
+ };
47
+ },
48
+ () => Array.from(registry.values()),
49
+ () => [],
50
+ );
51
+ }
52
+
53
+ /** Imperative read for non-React consumers (debug panels, etc.). */
54
+ export function getRegisteredHotkeys(): RegisteredHotkey[] {
55
+ return Array.from(registry.values());
56
+ }
57
+
58
+ /**
59
+ * Hook variant — call alongside `useHotkey` when the binding lives
60
+ * outside the hook (e.g. raw `window.addEventListener` consumers that
61
+ * still want to appear in the cheat sheet).
62
+ */
63
+ export function useRegisterHotkey(entry: RegisteredHotkey | null): void {
64
+ useEffect(() => {
65
+ if (!entry) return;
66
+ return registerHotkey(entry);
67
+ }, [entry?.combo, entry?.description, entry?.scope]);
68
+ }
@@ -19,6 +19,7 @@ export * from './theme';
19
19
  export * from './time';
20
20
  export * from './events';
21
21
  export * from './hotkey';
22
+ export * from './audio';
22
23
  export * from './debug';
23
24
 
24
25
  // ----------------------------------------------------------------------------
@@ -1,109 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { useState } from 'react';
3
-
4
- import { Button } from '../forms/button';
5
- import { Boundary } from '.';
6
- import type { BoundaryVariant } from '.';
7
-
8
- export default defineStory({
9
- title: 'Core/Boundary',
10
- component: Boundary,
11
- description:
12
- 'React error boundary with multiple visual variants. Wrap untrusted subtrees (widgets, third-party iframes, dynamic renderers) so a single render error does not crash the whole page.',
13
- });
14
-
15
- function BoomButton({ label = 'Throw error' }: { label?: string }) {
16
- const [boom, setBoom] = useState(false);
17
- if (boom) throw new Error('Demo crash from BoomButton');
18
- return (
19
- <Button variant="outline" size="sm" onClick={() => setBoom(true)}>
20
- {label}
21
- </Button>
22
- );
23
- }
24
-
25
- export const Interactive = () => {
26
- const [variant] = useSelect('variant', {
27
- options: ['silent', 'inline', 'card', 'fullscreen'] as const,
28
- defaultValue: 'card',
29
- label: 'Variant',
30
- description: 'Fallback visual style',
31
- });
32
-
33
- return (
34
- <div className="max-w-lg space-y-3">
35
- <p className="text-sm text-muted-foreground">
36
- Click the button to throw — the surrounding page stays alive, only this block swaps to the fallback.
37
- </p>
38
- <Boundary variant={variant as BoundaryVariant} name="story">
39
- <div className="rounded-md border p-4">
40
- <BoomButton />
41
- </div>
42
- </Boundary>
43
- </div>
44
- );
45
- };
46
-
47
- export const Silent = () => (
48
- <div className="max-w-lg space-y-2">
49
- <p className="text-sm text-muted-foreground">
50
- variant=&quot;silent&quot; renders nothing on error. Use for non-critical widgets (chat launcher, embeds).
51
- </p>
52
- <Boundary variant="silent" name="silent-demo">
53
- <BoomButton label="Crash silently" />
54
- </Boundary>
55
- <p className="text-xs text-muted-foreground">↑ button disappears after click. Page is fine.</p>
56
- </div>
57
- );
58
-
59
- export const Inline = () => (
60
- <div className="max-w-lg">
61
- <Boundary variant="inline" name="inline-demo">
62
- <BoomButton />
63
- </Boundary>
64
- </div>
65
- );
66
-
67
- export const Card = () => (
68
- <div className="max-w-lg">
69
- <Boundary variant="card" name="card-demo">
70
- <BoomButton />
71
- </Boundary>
72
- </div>
73
- );
74
-
75
- export const CustomFallback = () => (
76
- <div className="max-w-lg">
77
- <Boundary
78
- fallback={({ error, reset }) => (
79
- <div className="rounded-md border-2 border-dashed border-amber-500 bg-amber-500/5 p-4">
80
- <p className="text-sm font-semibold text-amber-700">Custom fallback</p>
81
- <p className="mt-1 text-xs text-amber-700/80">{error.message}</p>
82
- <Button size="sm" variant="outline" className="mt-2" onClick={reset}>
83
- Reset boundary
84
- </Button>
85
- </div>
86
- )}
87
- >
88
- <BoomButton />
89
- </Boundary>
90
- </div>
91
- );
92
-
93
- export const ResetKeys = () => {
94
- const [key, setKey] = useState(0);
95
- return (
96
- <div className="max-w-lg space-y-3">
97
- <p className="text-sm text-muted-foreground">
98
- Pass <code className="text-xs">resetKeys</code> — when any value in the array changes, the boundary auto-resets.
99
- Good for clearing errors on route change.
100
- </p>
101
- <Button size="sm" onClick={() => setKey((k) => k + 1)}>
102
- Bump resetKey ({key})
103
- </Button>
104
- <Boundary variant="card" resetKeys={[key]} name="resetkeys-demo">
105
- <BoomButton />
106
- </Boundary>
107
- </div>
108
- );
109
- };
@@ -1,115 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { Avatar, AvatarImage, AvatarFallback } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/Avatar',
6
- component: Avatar,
7
- description: 'User avatar with image and fallback support.',
8
- });
9
-
10
- const AVATARS = [
11
- { src: 'https://github.com/shadcn.png', fallback: 'CN' },
12
- { src: 'https://github.com/vercel.png', fallback: 'VC' },
13
- { src: 'https://github.com/radix-ui.png', fallback: 'RX' },
14
- ];
15
-
16
- export const Interactive = () => {
17
- const [size] = useSelect('size', {
18
- options: ['sm', 'md', 'lg'] as const,
19
- defaultValue: 'md',
20
- label: 'Size',
21
- description: 'Avatar size',
22
- });
23
-
24
- const sizeClasses = {
25
- sm: 'h-8 w-8',
26
- md: 'h-10 w-10',
27
- lg: 'h-14 w-14',
28
- };
29
-
30
- return (
31
- <Avatar className={sizeClasses[size]}>
32
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
33
- <AvatarFallback>CN</AvatarFallback>
34
- </Avatar>
35
- );
36
- };
37
-
38
- export const Default = () => (
39
- <Avatar>
40
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
41
- <AvatarFallback>CN</AvatarFallback>
42
- </Avatar>
43
- );
44
-
45
- export const Fallback = () => (
46
- <Avatar>
47
- <AvatarImage src="/broken-image.jpg" alt="User" />
48
- <AvatarFallback>JD</AvatarFallback>
49
- </Avatar>
50
- );
51
-
52
- export const Sizes = () => (
53
- <div className="flex items-center gap-4">
54
- <Avatar className="h-6 w-6">
55
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
56
- <AvatarFallback className="text-xs">CN</AvatarFallback>
57
- </Avatar>
58
- <Avatar className="h-8 w-8">
59
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
60
- <AvatarFallback>CN</AvatarFallback>
61
- </Avatar>
62
- <Avatar className="h-10 w-10">
63
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
64
- <AvatarFallback>CN</AvatarFallback>
65
- </Avatar>
66
- <Avatar className="h-14 w-14">
67
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
68
- <AvatarFallback>CN</AvatarFallback>
69
- </Avatar>
70
- <Avatar className="h-20 w-20">
71
- <AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
72
- <AvatarFallback className="text-xl">CN</AvatarFallback>
73
- </Avatar>
74
- </div>
75
- );
76
-
77
- export const Group = () => (
78
- <div className="flex -space-x-4">
79
- {AVATARS.map((avatar, i) => (
80
- <Avatar key={i} className="border-2 border-background">
81
- <AvatarImage src={avatar.src} />
82
- <AvatarFallback>{avatar.fallback}</AvatarFallback>
83
- </Avatar>
84
- ))}
85
- <Avatar className="border-2 border-background">
86
- <AvatarFallback>+5</AvatarFallback>
87
- </Avatar>
88
- </div>
89
- );
90
-
91
- export const WithStatus = () => (
92
- <div className="flex gap-4">
93
- <div className="relative">
94
- <Avatar>
95
- <AvatarImage src="https://github.com/shadcn.png" />
96
- <AvatarFallback>CN</AvatarFallback>
97
- </Avatar>
98
- <span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-green-500 border-2 border-background" />
99
- </div>
100
- <div className="relative">
101
- <Avatar>
102
- <AvatarImage src="https://github.com/vercel.png" />
103
- <AvatarFallback>VC</AvatarFallback>
104
- </Avatar>
105
- <span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-yellow-500 border-2 border-background" />
106
- </div>
107
- <div className="relative">
108
- <Avatar>
109
- <AvatarImage src="https://github.com/radix-ui.png" />
110
- <AvatarFallback>RX</AvatarFallback>
111
- </Avatar>
112
- <span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-gray-400 border-2 border-background" />
113
- </div>
114
- </div>
115
- );
@@ -1,56 +0,0 @@
1
- import { defineStory, useSelect } from '@djangocfg/playground';
2
- import { Badge } from '.';
3
-
4
- export default defineStory({
5
- title: 'Core/Badge',
6
- component: Badge,
7
- description: 'Small status indicators and labels.',
8
- });
9
-
10
- export const Interactive = () => {
11
- const [variant] = useSelect('variant', {
12
- options: ['default', 'secondary', 'destructive', 'outline'] as const,
13
- defaultValue: 'default',
14
- label: 'Variant',
15
- description: 'Badge style variant',
16
- });
17
-
18
- return (
19
- <div className="flex gap-4">
20
- <Badge variant={variant}>Badge</Badge>
21
- </div>
22
- );
23
- };
24
-
25
- export const Variants = () => (
26
- <div className="flex flex-wrap gap-4">
27
- <Badge variant="default">Default</Badge>
28
- <Badge variant="secondary">Secondary</Badge>
29
- <Badge variant="destructive">Destructive</Badge>
30
- <Badge variant="outline">Outline</Badge>
31
- </div>
32
- );
33
-
34
- export const StatusBadges = () => (
35
- <div className="flex flex-wrap gap-4">
36
- <Badge variant="default">Active</Badge>
37
- <Badge variant="secondary">Pending</Badge>
38
- <Badge variant="destructive">Expired</Badge>
39
- <Badge variant="outline">Draft</Badge>
40
- </div>
41
- );
42
-
43
- export const InContext = () => (
44
- <div className="space-y-4">
45
- <div className="flex items-center gap-2">
46
- <span className="font-medium">Status:</span>
47
- <Badge variant="default">Published</Badge>
48
- </div>
49
- <div className="flex items-center gap-2">
50
- <span className="font-medium">Tags:</span>
51
- <Badge variant="secondary">React</Badge>
52
- <Badge variant="secondary">TypeScript</Badge>
53
- <Badge variant="secondary">UI</Badge>
54
- </div>
55
- </div>
56
- );
@@ -1,127 +0,0 @@
1
- import { useState } from 'react';
2
- import { defineStory } from '@djangocfg/playground';
3
- import { Calendar } from '.';
4
- import { DatePicker, DateRangePicker, type DateRange } from './date-picker';
5
-
6
- export default defineStory({
7
- title: 'Core/Calendar',
8
- component: Calendar,
9
- description: 'Calendar and date picker components.',
10
- });
11
-
12
- export const Default = () => {
13
- const [date, setDate] = useState<Date | undefined>(new Date());
14
-
15
- return (
16
- <Calendar
17
- mode="single"
18
- selected={date}
19
- onSelect={setDate}
20
- className="rounded-md border"
21
- />
22
- );
23
- };
24
-
25
- export const Range = () => {
26
- const [range, setRange] = useState<DateRange | undefined>({
27
- from: new Date(),
28
- to: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
29
- });
30
-
31
- return (
32
- <Calendar
33
- mode="range"
34
- selected={range}
35
- onSelect={setRange}
36
- className="rounded-md border"
37
- numberOfMonths={2}
38
- />
39
- );
40
- };
41
-
42
- export const Multiple = () => {
43
- const [dates, setDates] = useState<Date[] | undefined>([]);
44
-
45
- return (
46
- <Calendar
47
- mode="multiple"
48
- selected={dates}
49
- onSelect={setDates}
50
- className="rounded-md border"
51
- />
52
- );
53
- };
54
-
55
- export const Picker = () => {
56
- const [date, setDate] = useState<Date>();
57
-
58
- return (
59
- <div className="max-w-xs">
60
- <DatePicker
61
- value={date}
62
- onChange={setDate}
63
- placeholder="Pick a date"
64
- />
65
- </div>
66
- );
67
- };
68
-
69
- export const PickerWithLabel = () => {
70
- const [date, setDate] = useState<Date>();
71
-
72
- return (
73
- <div className="max-w-xs space-y-2">
74
- <label className="text-sm font-medium">Date of birth</label>
75
- <DatePicker
76
- value={date}
77
- onChange={setDate}
78
- placeholder="Select your birth date"
79
- />
80
- </div>
81
- );
82
- };
83
-
84
- export const RangePicker = () => {
85
- const [range, setRange] = useState<DateRange>();
86
-
87
- return (
88
- <div className="max-w-sm">
89
- <DateRangePicker
90
- value={range}
91
- onChange={setRange}
92
- placeholder="Select date range"
93
- />
94
- </div>
95
- );
96
- };
97
-
98
- export const RangePickerWithPresets = () => {
99
- const [range, setRange] = useState<DateRange>();
100
-
101
- return (
102
- <div className="max-w-sm space-y-2">
103
- <label className="text-sm font-medium">Booking dates</label>
104
- <DateRangePicker
105
- value={range}
106
- onChange={setRange}
107
- placeholder="Check-in — Check-out"
108
- numberOfMonths={2}
109
- />
110
- </div>
111
- );
112
- };
113
-
114
- export const Disabled = () => {
115
- const [date, setDate] = useState<Date>();
116
-
117
- return (
118
- <div className="max-w-xs">
119
- <DatePicker
120
- value={date}
121
- onChange={setDate}
122
- placeholder="Pick a date"
123
- disabled
124
- />
125
- </div>
126
- );
127
- };