@ponti-studios/ui 0.1.2

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 (108) hide show
  1. package/README.md +27 -0
  2. package/dist/index.js +2118 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +130 -0
  5. package/src/animations.css +437 -0
  6. package/src/components/data-display/accordion.stories.tsx +57 -0
  7. package/src/components/data-display/accordion.tsx +65 -0
  8. package/src/components/data-display/filter-chip.tsx +41 -0
  9. package/src/components/data-display/index.ts +13 -0
  10. package/src/components/data-display/table.stories.tsx +75 -0
  11. package/src/components/data-display/table.tsx +84 -0
  12. package/src/components/feedback/alert.stories.tsx +51 -0
  13. package/src/components/feedback/alert.tsx +60 -0
  14. package/src/components/feedback/empty-state.tsx +66 -0
  15. package/src/components/feedback/index.ts +6 -0
  16. package/src/components/feedback/progress.stories.tsx +37 -0
  17. package/src/components/feedback/progress.tsx +30 -0
  18. package/src/components/feedback/spinner.stories.tsx +52 -0
  19. package/src/components/feedback/spinner.tsx +48 -0
  20. package/src/components/forms/calendar.stories.tsx +30 -0
  21. package/src/components/forms/calendar.tsx +67 -0
  22. package/src/components/forms/date-picker.stories.tsx +48 -0
  23. package/src/components/forms/date-picker.tsx +60 -0
  24. package/src/components/forms/field.stories.tsx +62 -0
  25. package/src/components/forms/field.tsx +65 -0
  26. package/src/components/forms/field.types.ts +7 -0
  27. package/src/components/forms/index.ts +24 -0
  28. package/src/components/forms/input.stories.tsx +47 -0
  29. package/src/components/forms/input.tsx +20 -0
  30. package/src/components/forms/radio-group.stories.tsx +48 -0
  31. package/src/components/forms/radio-group.tsx +38 -0
  32. package/src/components/forms/select.stories.tsx +88 -0
  33. package/src/components/forms/select.tsx +147 -0
  34. package/src/components/forms/slider.stories.tsx +67 -0
  35. package/src/components/forms/slider.tsx +25 -0
  36. package/src/components/forms/stepper.stories.tsx +111 -0
  37. package/src/components/forms/stepper.tsx +66 -0
  38. package/src/components/forms/switch.stories.tsx +144 -0
  39. package/src/components/forms/switch.tsx +36 -0
  40. package/src/components/forms/text-field.stories.tsx +87 -0
  41. package/src/components/forms/text-field.tsx +40 -0
  42. package/src/components/forms/text-field.types.ts +9 -0
  43. package/src/components/forms/textarea.stories.tsx +150 -0
  44. package/src/components/forms/textarea.tsx +18 -0
  45. package/src/components/layout/index.ts +2 -0
  46. package/src/components/layout/scroll-area.stories.tsx +83 -0
  47. package/src/components/layout/scroll-area.tsx +40 -0
  48. package/src/components/navigation/app-navigation.stories.tsx +81 -0
  49. package/src/components/navigation/app-navigation.tsx +155 -0
  50. package/src/components/navigation/index.ts +3 -0
  51. package/src/components/navigation/tabs.stories.tsx +69 -0
  52. package/src/components/navigation/tabs.tsx +49 -0
  53. package/src/components/overlays/alert-dialog.stories.tsx +45 -0
  54. package/src/components/overlays/alert-dialog.tsx +119 -0
  55. package/src/components/overlays/command.stories.tsx +65 -0
  56. package/src/components/overlays/command.tsx +130 -0
  57. package/src/components/overlays/dialog.stories.tsx +111 -0
  58. package/src/components/overlays/dialog.tsx +162 -0
  59. package/src/components/overlays/dropdown-menu.stories.tsx +79 -0
  60. package/src/components/overlays/dropdown-menu.tsx +214 -0
  61. package/src/components/overlays/index.ts +65 -0
  62. package/src/components/overlays/popover.stories.tsx +39 -0
  63. package/src/components/overlays/popover.tsx +40 -0
  64. package/src/components/overlays/sheet.stories.tsx +68 -0
  65. package/src/components/overlays/sheet.tsx +98 -0
  66. package/src/components/primitives/avatar.stories.tsx +71 -0
  67. package/src/components/primitives/avatar.tsx +132 -0
  68. package/src/components/primitives/badge.stories.tsx +33 -0
  69. package/src/components/primitives/badge.tsx +44 -0
  70. package/src/components/primitives/button.stories.tsx +113 -0
  71. package/src/components/primitives/button.tsx +95 -0
  72. package/src/components/primitives/button.types.ts +10 -0
  73. package/src/components/primitives/card.stories.tsx +59 -0
  74. package/src/components/primitives/card.tsx +69 -0
  75. package/src/components/primitives/index.ts +13 -0
  76. package/src/components/primitives/label.stories.tsx +47 -0
  77. package/src/components/primitives/label.tsx +21 -0
  78. package/src/constants/chart-colors.ts +39 -0
  79. package/src/env.d.ts +9 -0
  80. package/src/hooks/index.ts +6 -0
  81. package/src/hooks/sort.types.ts +12 -0
  82. package/src/hooks/use-api-client.ts +122 -0
  83. package/src/hooks/use-debounced-value.ts +12 -0
  84. package/src/hooks/use-filter-state.ts +68 -0
  85. package/src/hooks/use-media-query.ts +65 -0
  86. package/src/hooks/use-mobile.tsx +19 -0
  87. package/src/index.ts +25 -0
  88. package/src/lib/auth.ts +5 -0
  89. package/src/lib/clipboard.ts +8 -0
  90. package/src/lib/create-memory-storage.ts +24 -0
  91. package/src/lib/index.ts +4 -0
  92. package/src/lib/utils.ts +6 -0
  93. package/src/storybook/controls.ts +117 -0
  94. package/src/storybook/options.ts +52 -0
  95. package/src/styles.css +1052 -0
  96. package/src/tokens/color-systems.stories.tsx +133 -0
  97. package/src/tokens/colors.ts +156 -0
  98. package/src/tokens/foundations.stories.tsx +151 -0
  99. package/src/tokens/index.ts +16 -0
  100. package/src/tokens/layout.stories.tsx +118 -0
  101. package/src/tokens/motion.ts +51 -0
  102. package/src/tokens/radii.ts +10 -0
  103. package/src/tokens/shadows.ts +34 -0
  104. package/src/tokens/spacing.ts +30 -0
  105. package/src/tokens/typography.ts +42 -0
  106. package/src/translations/en.ts +34 -0
  107. package/src/translations/index.ts +32 -0
  108. package/src/types/upload.ts +42 -0
@@ -0,0 +1,65 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Calculator, Calendar, CreditCard, Settings, Smile, User } from "lucide-react";
3
+
4
+ import {
5
+ Command,
6
+ CommandEmpty,
7
+ CommandGroup,
8
+ CommandInput,
9
+ CommandItem,
10
+ CommandList,
11
+ CommandSeparator,
12
+ CommandShortcut,
13
+ } from "./command";
14
+
15
+ const meta = {
16
+ title: "Overlays/Command",
17
+ component: Command,
18
+ tags: ["autodocs"],
19
+ } satisfies Meta<typeof Command>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <Command className="w-[450px] rounded-md border">
27
+ <CommandInput placeholder="Type a command or search..." />
28
+ <CommandList>
29
+ <CommandEmpty>No results found.</CommandEmpty>
30
+ <CommandGroup heading="Suggestions">
31
+ <CommandItem>
32
+ <Calendar className="mr-2 size-4" />
33
+ <span>Calendar</span>
34
+ </CommandItem>
35
+ <CommandItem>
36
+ <Smile className="mr-2 size-4" />
37
+ <span>Search Emoji</span>
38
+ </CommandItem>
39
+ <CommandItem>
40
+ <Calculator className="mr-2 size-4" />
41
+ <span>Calculator</span>
42
+ </CommandItem>
43
+ </CommandGroup>
44
+ <CommandSeparator />
45
+ <CommandGroup heading="Settings">
46
+ <CommandItem>
47
+ <User className="mr-2 size-4" />
48
+ <span>Profile</span>
49
+ <CommandShortcut>⌘P</CommandShortcut>
50
+ </CommandItem>
51
+ <CommandItem>
52
+ <CreditCard className="mr-2 size-4" />
53
+ <span>Billing</span>
54
+ <CommandShortcut>⌘B</CommandShortcut>
55
+ </CommandItem>
56
+ <CommandItem>
57
+ <Settings className="mr-2 size-4" />
58
+ <span>Settings</span>
59
+ <CommandShortcut>⌘S</CommandShortcut>
60
+ </CommandItem>
61
+ </CommandGroup>
62
+ </CommandList>
63
+ </Command>
64
+ ),
65
+ };
@@ -0,0 +1,130 @@
1
+ import { Command as CommandPrimitive } from "cmdk";
2
+ import { SearchIcon } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "./dialog";
6
+
7
+ function Command(props: React.ComponentProps<typeof CommandPrimitive>) {
8
+ return (
9
+ <CommandPrimitive
10
+ data-slot="command"
11
+ className="bg-raised text-primary flex h-full w-full flex-col overflow-hidden rounded-md"
12
+ {...props}
13
+ />
14
+ );
15
+ }
16
+
17
+ function CommandDialog({
18
+ title = "Command Palette",
19
+ description = "Search for a command to run...",
20
+ children,
21
+ showCloseButton = true,
22
+ ...props
23
+ }: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
24
+ title?: string;
25
+ description?: string;
26
+ showCloseButton?: boolean;
27
+ children?: React.ReactNode;
28
+ }) {
29
+ return (
30
+ <Dialog {...props}>
31
+ <DialogHeader className="sr-only">
32
+ <DialogTitle>{title}</DialogTitle>
33
+ <DialogDescription>{description}</DialogDescription>
34
+ </DialogHeader>
35
+ <DialogContent className="overflow-hidden p-0" showCloseButton={showCloseButton}>
36
+ <Command>{children}</Command>
37
+ </DialogContent>
38
+ </Dialog>
39
+ );
40
+ }
41
+
42
+ function CommandInput(props: React.ComponentProps<typeof CommandPrimitive.Input>) {
43
+ return (
44
+ <div data-slot="command-input-wrapper" className="flex h-9 items-center gap-2 border-b px-3">
45
+ <SearchIcon className="size-4 shrink-0 opacity-50" />
46
+ <CommandPrimitive.Input
47
+ data-slot="command-input"
48
+ className="placeholder:text-secondary flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50"
49
+ {...props}
50
+ />
51
+ </div>
52
+ );
53
+ }
54
+
55
+ function CommandList(props: React.ComponentProps<typeof CommandPrimitive.List>) {
56
+ return (
57
+ <CommandPrimitive.List
58
+ data-slot="command-list"
59
+ className="max-h-75 scroll-py-1 overflow-x-hidden overflow-y-auto"
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ function CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {
66
+ return (
67
+ <CommandPrimitive.Empty
68
+ data-slot="command-empty"
69
+ className="py-6 text-center text-sm"
70
+ {...props}
71
+ />
72
+ );
73
+ }
74
+
75
+ function CommandGroup(props: React.ComponentProps<typeof CommandPrimitive.Group>) {
76
+ return (
77
+ <CommandPrimitive.Group
78
+ data-slot="command-group"
79
+ className="text-primary **:[[cmdk-group-heading]]:text-secondary overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium"
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ function CommandSeparator(props: React.ComponentProps<typeof CommandPrimitive.Separator>) {
86
+ return (
87
+ <CommandPrimitive.Separator
88
+ data-slot="command-separator"
89
+ className="bg-border -mx-1 h-px"
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ function CommandItem(props: React.ComponentProps<typeof CommandPrimitive.Item>) {
96
+ return (
97
+ <CommandPrimitive.Item
98
+ data-slot="command-item"
99
+ className="data-[selected=true]:bg-accent data-[selected=true]:text-on-accent [&_svg:not([class*='text-'])]:text-secondary relative flex cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
100
+ {...props}
101
+ />
102
+ );
103
+ }
104
+
105
+ function CommandShortcut(props: React.ComponentProps<"span">) {
106
+ return (
107
+ <span
108
+ data-slot="command-shortcut"
109
+ className="text-secondary ml-auto text-xs tracking-widest"
110
+ {...props}
111
+ />
112
+ );
113
+ }
114
+
115
+ function CommandListLoading(props: React.ComponentProps<"div">) {
116
+ return <div data-slot="command-list-loading" className="py-6 text-center text-sm" {...props} />;
117
+ }
118
+
119
+ export {
120
+ Command,
121
+ CommandDialog,
122
+ CommandEmpty,
123
+ CommandGroup,
124
+ CommandInput,
125
+ CommandItem,
126
+ CommandList,
127
+ CommandListLoading,
128
+ CommandSeparator,
129
+ CommandShortcut,
130
+ };
@@ -0,0 +1,111 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { useEffect, useState } from "react";
3
+
4
+ import { booleanControl, hiddenControl } from "../../storybook/controls";
5
+ import { Button } from "../primitives/button";
6
+ import {
7
+ Dialog,
8
+ DialogContent,
9
+ DialogDescription,
10
+ DialogFooter,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ DialogTrigger,
14
+ } from "./dialog";
15
+
16
+ const meta = {
17
+ title: "Overlays/Dialog",
18
+ component: Dialog,
19
+ tags: ["autodocs"],
20
+ argTypes: {
21
+ open: booleanControl("Controls whether the dialog is open", true),
22
+ modal: booleanControl("Whether interaction outside the dialog is disabled when open", true),
23
+ defaultOpen: hiddenControl,
24
+ onOpenChange: hiddenControl,
25
+ },
26
+ } satisfies Meta<typeof Dialog>;
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ function DialogPreview({
32
+ open,
33
+ modal,
34
+ children,
35
+ }: {
36
+ open: boolean;
37
+ modal: boolean;
38
+ children: React.ReactNode;
39
+ }) {
40
+ const [isOpen, setIsOpen] = useState(open);
41
+
42
+ useEffect(() => {
43
+ setIsOpen(open);
44
+ }, [open]);
45
+
46
+ return (
47
+ <Dialog open={isOpen} onOpenChange={setIsOpen} modal={modal}>
48
+ {children}
49
+ </Dialog>
50
+ );
51
+ }
52
+
53
+ export const Default: Story = {
54
+ args: {
55
+ open: true,
56
+ modal: true,
57
+ },
58
+ render: (args) => (
59
+ <DialogPreview open={Boolean(args.open ?? true)} modal={Boolean(args.modal ?? true)}>
60
+ <DialogTrigger asChild>
61
+ <Button variant="outline">Open Dialog</Button>
62
+ </DialogTrigger>
63
+ <DialogContent>
64
+ <DialogHeader>
65
+ <DialogTitle>Edit Profile</DialogTitle>
66
+ <DialogDescription>
67
+ Make changes to your profile here. Click save when you're done.
68
+ </DialogDescription>
69
+ </DialogHeader>
70
+ <div className="grid gap-4 py-4">
71
+ <div className="grid grid-cols-4 items-center gap-4">
72
+ <label htmlFor="name" className="text-right text-sm font-medium">
73
+ Name
74
+ </label>
75
+ <input
76
+ id="name"
77
+ defaultValue="Pedro Duarte"
78
+ className="col-span-3 h-9 rounded-md border px-3 text-sm"
79
+ />
80
+ </div>
81
+ </div>
82
+ <DialogFooter>
83
+ <Button type="submit">Save changes</Button>
84
+ </DialogFooter>
85
+ </DialogContent>
86
+ </DialogPreview>
87
+ ),
88
+ };
89
+
90
+ export const WithCloseButton: Story = {
91
+ args: {
92
+ open: true,
93
+ modal: true,
94
+ },
95
+ render: (args) => (
96
+ <DialogPreview open={Boolean(args.open ?? true)} modal={Boolean(args.modal ?? true)}>
97
+ <DialogTrigger asChild>
98
+ <Button>Open</Button>
99
+ </DialogTrigger>
100
+ <DialogContent showCloseButton>
101
+ <DialogHeader>
102
+ <DialogTitle>Confirmation</DialogTitle>
103
+ <DialogDescription>Are you sure you want to proceed?</DialogDescription>
104
+ </DialogHeader>
105
+ <DialogFooter>
106
+ <Button>Confirm</Button>
107
+ </DialogFooter>
108
+ </DialogContent>
109
+ </DialogPreview>
110
+ ),
111
+ };
@@ -0,0 +1,162 @@
1
+ import { Dialog as BaseDialog } from "@base-ui/react/dialog";
2
+ import { XIcon } from "lucide-react";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ function Dialog({ ...props }: React.ComponentProps<typeof BaseDialog.Root>) {
8
+ return <BaseDialog.Root {...props} />;
9
+ }
10
+
11
+ function DialogTrigger({
12
+ asChild,
13
+ children,
14
+ ...props
15
+ }: React.ComponentProps<typeof BaseDialog.Trigger> & { asChild?: boolean }) {
16
+ if (asChild && React.isValidElement(children)) {
17
+ return (
18
+ <BaseDialog.Trigger
19
+ data-slot="dialog-trigger"
20
+ render={(triggerProps) =>
21
+ React.cloneElement(children as React.ReactElement<unknown>, triggerProps)
22
+ }
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+ return (
28
+ <BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
29
+ {children}
30
+ </BaseDialog.Trigger>
31
+ );
32
+ }
33
+
34
+ function DialogPortal({ ...props }: React.ComponentProps<typeof BaseDialog.Portal>) {
35
+ return <BaseDialog.Portal data-slot="dialog-portal" {...props} />;
36
+ }
37
+
38
+ function DialogClose({
39
+ asChild,
40
+ children,
41
+ ...props
42
+ }: React.ComponentProps<typeof BaseDialog.Close> & { asChild?: boolean }) {
43
+ if (asChild && React.isValidElement(children)) {
44
+ return (
45
+ <BaseDialog.Close
46
+ data-slot="dialog-close"
47
+ render={(closeProps) =>
48
+ React.cloneElement(children as React.ReactElement<unknown>, closeProps)
49
+ }
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+ return (
55
+ <BaseDialog.Close data-slot="dialog-close" {...props}>
56
+ {children}
57
+ </BaseDialog.Close>
58
+ );
59
+ }
60
+
61
+ function DialogOverlay({ className, ...props }: React.ComponentProps<typeof BaseDialog.Backdrop>) {
62
+ return (
63
+ <BaseDialog.Backdrop
64
+ data-slot="dialog-overlay"
65
+ className={cn(
66
+ "overlay-backdrop data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 transition-opacity",
67
+ className,
68
+ )}
69
+ {...props}
70
+ />
71
+ );
72
+ }
73
+
74
+ function DialogContent({
75
+ className,
76
+ children,
77
+ showCloseButton = true,
78
+ ...props
79
+ }: React.ComponentProps<typeof BaseDialog.Popup> & {
80
+ showCloseButton?: boolean;
81
+ }) {
82
+ return (
83
+ <BaseDialog.Portal data-slot="dialog-portal">
84
+ <DialogOverlay />
85
+ <BaseDialog.Popup
86
+ data-slot="dialog-content"
87
+ className={cn(
88
+ "bg-raised text-primary border-subtle data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid max-h-[90dvh] w-[calc(100%-2rem)] max-w-[30rem] translate-x-[-50%] translate-y-[-50%] gap-6 overflow-y-auto rounded-xl border p-4 duration-200 outline-none sm:p-6",
89
+ className,
90
+ )}
91
+ {...props}
92
+ >
93
+ {children}
94
+ {showCloseButton && (
95
+ <BaseDialog.Close
96
+ data-slot="dialog-close"
97
+ className="text-secondary hover:bg-inset hover:text-primary focus-visible:outline-ring absolute top-3 right-3 inline-flex size-11 items-center justify-center rounded-md border border-transparent opacity-100 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none sm:top-4 sm:right-4 [&_svg]:pointer-events-none [&_svg]:size-4.5 [&_svg]:shrink-0"
98
+ >
99
+ <XIcon />
100
+ <span className="sr-only">Close</span>
101
+ </BaseDialog.Close>
102
+ )}
103
+ </BaseDialog.Popup>
104
+ </BaseDialog.Portal>
105
+ );
106
+ }
107
+
108
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
109
+ return (
110
+ <div
111
+ data-slot="dialog-header"
112
+ className={cn("flex flex-col gap-2 text-left", className)}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
119
+ return (
120
+ <div
121
+ data-slot="dialog-footer"
122
+ className={cn("flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className)}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof BaseDialog.Title>) {
129
+ return (
130
+ <BaseDialog.Title
131
+ data-slot="dialog-title"
132
+ className={cn("heading-2 text-primary", className)}
133
+ {...props}
134
+ />
135
+ );
136
+ }
137
+
138
+ function DialogDescription({
139
+ className,
140
+ ...props
141
+ }: React.ComponentProps<typeof BaseDialog.Description>) {
142
+ return (
143
+ <BaseDialog.Description
144
+ data-slot="dialog-description"
145
+ className={cn("body-2 text-secondary max-w-prose", className)}
146
+ {...props}
147
+ />
148
+ );
149
+ }
150
+
151
+ export {
152
+ Dialog,
153
+ DialogClose,
154
+ DialogContent,
155
+ DialogDescription,
156
+ DialogFooter,
157
+ DialogHeader,
158
+ DialogOverlay,
159
+ DialogPortal,
160
+ DialogTitle,
161
+ DialogTrigger,
162
+ };
@@ -0,0 +1,79 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { CreditCard, LogOut, Settings, User } from "lucide-react";
3
+
4
+ import { Button } from "../primitives/button";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuGroup,
9
+ DropdownMenuItem,
10
+ DropdownMenuLabel,
11
+ DropdownMenuSeparator,
12
+ DropdownMenuShortcut,
13
+ DropdownMenuTrigger,
14
+ } from "./dropdown-menu";
15
+
16
+ const meta = {
17
+ title: "Overlays/DropdownMenu",
18
+ component: DropdownMenu,
19
+ tags: ["autodocs"],
20
+ argTypes: {
21
+ open: {
22
+ control: "boolean",
23
+ description: "Controls whether the dropdown menu is open",
24
+ },
25
+ defaultOpen: {
26
+ control: "boolean",
27
+ description: "Whether the dropdown menu is open by default (uncontrolled)",
28
+ },
29
+ modal: {
30
+ control: "boolean",
31
+ description: "When true, interaction outside the menu is disabled while open",
32
+ table: { defaultValue: { summary: "true" } },
33
+ },
34
+ dir: {
35
+ control: "select",
36
+ options: ["ltr", "rtl"],
37
+ description: "Reading direction of the dropdown menu",
38
+ },
39
+ },
40
+ } satisfies Meta<typeof DropdownMenu>;
41
+
42
+ export default meta;
43
+ type Story = StoryObj<typeof meta>;
44
+
45
+ export const Default: Story = {
46
+ render: () => (
47
+ <DropdownMenu>
48
+ <DropdownMenuTrigger asChild>
49
+ <Button variant="outline">Open Menu</Button>
50
+ </DropdownMenuTrigger>
51
+ <DropdownMenuContent className="w-56">
52
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
53
+ <DropdownMenuSeparator />
54
+ <DropdownMenuGroup>
55
+ <DropdownMenuItem>
56
+ <User className="mr-2 size-4" />
57
+ <span>Profile</span>
58
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
59
+ </DropdownMenuItem>
60
+ <DropdownMenuItem>
61
+ <CreditCard className="mr-2 size-4" />
62
+ <span>Billing</span>
63
+ <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
64
+ </DropdownMenuItem>
65
+ <DropdownMenuItem>
66
+ <Settings className="mr-2 size-4" />
67
+ <span>Settings</span>
68
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
69
+ </DropdownMenuItem>
70
+ </DropdownMenuGroup>
71
+ <DropdownMenuSeparator />
72
+ <DropdownMenuItem variant="destructive">
73
+ <LogOut className="mr-2 size-4" />
74
+ <span>Log out</span>
75
+ </DropdownMenuItem>
76
+ </DropdownMenuContent>
77
+ </DropdownMenu>
78
+ ),
79
+ };