@stackshift-ui/dropdown-menu 1.0.0-beta.1

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 ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@stackshift-ui/dropdown-menu",
3
+ "description": "Displays a menu to the user — such as a set of actions or functions — triggered by a button.",
4
+ "version": "1.0.0-beta.1",
5
+ "private": false,
6
+ "sideEffects": false,
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist/**",
12
+ "src"
13
+ ],
14
+ "author": "WebriQ <info@webriq.com>",
15
+ "devDependencies": {
16
+ "@testing-library/jest-dom": "^6.5.0",
17
+ "@testing-library/react": "^16.0.1",
18
+ "@testing-library/user-event": "^14.6.1",
19
+ "@types/node": "^22.7.0",
20
+ "@types/react": "^18.3.9",
21
+ "@types/react-dom": "^18.3.0",
22
+ "@vitejs/plugin-react": "^4.3.1",
23
+ "@vitest/coverage-v8": "^2.1.1",
24
+ "esbuild-plugin-rdi": "^0.0.0",
25
+ "esbuild-plugin-react18": "^0.2.5",
26
+ "esbuild-plugin-react18-css": "^0.0.4",
27
+ "jsdom": "^25.0.1",
28
+ "react": "^18.3.1",
29
+ "react-dom": "^18.3.1",
30
+ "tsup": "^8.3.0",
31
+ "typescript": "^5.6.2",
32
+ "vite-tsconfig-paths": "^5.0.1",
33
+ "vitest": "^2.1.1",
34
+ "@stackshift-ui/eslint-config": "6.0.10",
35
+ "@stackshift-ui/typescript-config": "6.0.10"
36
+ },
37
+ "dependencies": {
38
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
39
+ "classnames": "^2.5.1",
40
+ "lucide-react": "^0.468.0",
41
+ "@stackshift-ui/scripts": "6.1.0-beta.0",
42
+ "@stackshift-ui/system": "6.1.0-beta.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@stackshift-ui/system": ">=6.1.0-beta.0",
46
+ "@types/react": "16.8 - 19",
47
+ "next": "10 - 14",
48
+ "react": "16.8 - 19",
49
+ "react-dom": "16.8 - 19"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "next": {
53
+ "optional": true
54
+ }
55
+ },
56
+ "scripts": {
57
+ "build": "tsup && tsc -p tsconfig-build.json",
58
+ "clean": "rm -rf dist",
59
+ "dev": "tsup --watch && tsc -p tsconfig-build.json -w",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "eslint src/",
62
+ "test": "vitest run --coverage"
63
+ }
64
+ }
@@ -0,0 +1,312 @@
1
+ import { cleanup, render, screen, waitFor } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import * as React from "react";
4
+ import { afterEach, beforeEach, describe, test, vi } from "vitest";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuCheckboxItem,
8
+ DropdownMenuContent,
9
+ DropdownMenuGroup,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuRadioGroup,
13
+ DropdownMenuRadioItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuShortcut,
16
+ DropdownMenuSub,
17
+ DropdownMenuSubContent,
18
+ DropdownMenuSubTrigger,
19
+ DropdownMenuTrigger,
20
+ } from "./dropdown-menu";
21
+
22
+ describe("dropdown-menu", () => {
23
+ beforeEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ afterEach(cleanup);
28
+
29
+ test("Common: DropdownMenu - test if renders with proper structure and data attributes", ({
30
+ expect,
31
+ }) => {
32
+ const customClass = "custom-dropdown";
33
+
34
+ const { unmount } = render(
35
+ <DropdownMenu>
36
+ <DropdownMenuTrigger data-testid="dropdown-trigger" className={customClass}>
37
+ Open Menu
38
+ </DropdownMenuTrigger>
39
+ <DropdownMenuContent data-testid="dropdown-content">
40
+ <DropdownMenuItem data-testid="dropdown-item">Item 1</DropdownMenuItem>
41
+ </DropdownMenuContent>
42
+ </DropdownMenu>,
43
+ );
44
+
45
+ const trigger = screen.getByTestId("dropdown-trigger");
46
+
47
+ expect(trigger).toBeInTheDocument();
48
+ expect(trigger).toHaveAttribute("data-slot", "dropdown-menu-trigger");
49
+ expect(trigger.classList).toContain(customClass);
50
+ unmount();
51
+ });
52
+
53
+ test("Common: DropdownMenu - test if trigger interactions and content display work correctly", async ({
54
+ expect,
55
+ }) => {
56
+ const user = userEvent.setup();
57
+
58
+ const { unmount } = render(
59
+ <DropdownMenu>
60
+ <DropdownMenuTrigger data-testid="trigger-test">Open Menu</DropdownMenuTrigger>
61
+ <DropdownMenuContent data-testid="content-test" sideOffset={8}>
62
+ <DropdownMenuItem data-testid="menu-item-1">First Item</DropdownMenuItem>
63
+ <DropdownMenuItem data-testid="menu-item-2">Second Item</DropdownMenuItem>
64
+ </DropdownMenuContent>
65
+ </DropdownMenu>,
66
+ );
67
+
68
+ const trigger = screen.getByTestId("trigger-test");
69
+
70
+ expect(screen.queryByTestId("content-test")).not.toBeInTheDocument();
71
+
72
+ await user.click(trigger);
73
+
74
+ // Wait for content to appear
75
+ const content = await screen.findByTestId("content-test");
76
+ expect(content).toBeInTheDocument();
77
+ expect(content).toHaveAttribute("data-slot", "dropdown-menu-content");
78
+
79
+ expect(screen.getByTestId("menu-item-1")).toBeInTheDocument();
80
+ expect(screen.getByTestId("menu-item-2")).toBeInTheDocument();
81
+ unmount();
82
+ });
83
+
84
+ test("Common: DropdownMenu - test if menu items with variants and states work properly", async ({
85
+ expect,
86
+ }) => {
87
+ const handleClick = vi.fn();
88
+
89
+ const { unmount } = render(
90
+ <DropdownMenu defaultOpen>
91
+ <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
92
+ <DropdownMenuContent data-testid="variants-content">
93
+ <DropdownMenuItem data-testid="default-item" onSelect={handleClick}>
94
+ Default Item
95
+ </DropdownMenuItem>
96
+ <DropdownMenuItem
97
+ data-testid="destructive-item"
98
+ variant="destructive"
99
+ onSelect={handleClick}>
100
+ Delete Item
101
+ </DropdownMenuItem>
102
+ <DropdownMenuItem data-testid="inset-item" inset>
103
+ Inset Item
104
+ </DropdownMenuItem>
105
+ <DropdownMenuItem data-testid="disabled-item" disabled>
106
+ Disabled Item
107
+ </DropdownMenuItem>
108
+ </DropdownMenuContent>
109
+ </DropdownMenu>,
110
+ );
111
+
112
+ const content = await screen.findByTestId("variants-content");
113
+ expect(content).toBeInTheDocument();
114
+
115
+ const defaultItem = screen.getByTestId("default-item");
116
+ const destructiveItem = screen.getByTestId("destructive-item");
117
+ const insetItem = screen.getByTestId("inset-item");
118
+ const disabledItem = screen.getByTestId("disabled-item");
119
+
120
+ // Check data attributes and styling
121
+ expect(defaultItem).toHaveAttribute("data-slot", "dropdown-menu-item");
122
+ expect(defaultItem).toHaveAttribute("data-variant", "default");
123
+ expect(destructiveItem).toHaveAttribute("data-variant", "destructive");
124
+ expect(insetItem).toHaveAttribute("data-inset", "true");
125
+ expect(disabledItem).toHaveAttribute("data-disabled");
126
+ unmount();
127
+ });
128
+
129
+ test("Common: DropdownMenu - test if checkbox and radio item functionality works correctly", async ({
130
+ expect,
131
+ }) => {
132
+ function TestComponent() {
133
+ const [checkboxChecked, setCheckboxChecked] = React.useState(false);
134
+ const [radioValue, setRadioValue] = React.useState("option1");
135
+
136
+ return (
137
+ <DropdownMenu defaultOpen>
138
+ <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
139
+ <DropdownMenuContent data-testid="checkbox-radio-content">
140
+ <DropdownMenuCheckboxItem
141
+ data-testid="checkbox-item"
142
+ checked={checkboxChecked}
143
+ onCheckedChange={setCheckboxChecked}>
144
+ Checkbox Item
145
+ </DropdownMenuCheckboxItem>
146
+ <DropdownMenuRadioGroup value={radioValue} onValueChange={setRadioValue}>
147
+ <DropdownMenuRadioItem data-testid="radio-item-1" value="option1">
148
+ Radio Option 1
149
+ </DropdownMenuRadioItem>
150
+ <DropdownMenuRadioItem data-testid="radio-item-2" value="option2">
151
+ Radio Option 2
152
+ </DropdownMenuRadioItem>
153
+ </DropdownMenuRadioGroup>
154
+ </DropdownMenuContent>
155
+ </DropdownMenu>
156
+ );
157
+ }
158
+
159
+ const { unmount } = render(<TestComponent />);
160
+
161
+ const content = await screen.findByTestId("checkbox-radio-content");
162
+ expect(content).toBeInTheDocument();
163
+
164
+ const checkboxItem = screen.getByTestId("checkbox-item");
165
+ const radioItem1 = screen.getByTestId("radio-item-1");
166
+ const radioItem2 = screen.getByTestId("radio-item-2");
167
+
168
+ expect(checkboxItem).toHaveAttribute("data-slot", "dropdown-menu-checkbox-item");
169
+ expect(radioItem1).toHaveAttribute("data-slot", "dropdown-menu-radio-item");
170
+ expect(radioItem2).toHaveAttribute("data-slot", "dropdown-menu-radio-item");
171
+
172
+ expect(checkboxItem).toHaveAttribute("data-state", "unchecked");
173
+ expect(radioItem1).toHaveAttribute("data-state", "checked");
174
+ expect(radioItem2).toHaveAttribute("data-state", "unchecked");
175
+ unmount();
176
+ });
177
+
178
+ test("Common: DropdownMenu - test if keyboard navigation and accessibility work properly", async ({
179
+ expect,
180
+ }) => {
181
+ const user = userEvent.setup();
182
+
183
+ const { unmount } = render(
184
+ <DropdownMenu>
185
+ <DropdownMenuTrigger data-testid="keyboard-trigger">Open Menu</DropdownMenuTrigger>
186
+ <DropdownMenuContent data-testid="keyboard-content">
187
+ <DropdownMenuItem data-testid="item-1">First Item</DropdownMenuItem>
188
+ <DropdownMenuItem data-testid="item-2">Second Item</DropdownMenuItem>
189
+ <DropdownMenuItem data-testid="item-3">Third Item</DropdownMenuItem>
190
+ </DropdownMenuContent>
191
+ </DropdownMenu>,
192
+ );
193
+
194
+ const trigger = screen.getByTestId("keyboard-trigger");
195
+
196
+ await user.click(trigger);
197
+
198
+ const content = await screen.findByTestId("keyboard-content");
199
+ expect(content).toBeInTheDocument();
200
+ expect(content).toHaveAttribute("role", "menu");
201
+ expect(content).toHaveAttribute("data-slot", "dropdown-menu-content");
202
+
203
+ expect(screen.getByTestId("item-1")).toBeInTheDocument();
204
+ expect(screen.getByTestId("item-2")).toBeInTheDocument();
205
+ expect(screen.getByTestId("item-3")).toBeInTheDocument();
206
+ unmount();
207
+ });
208
+
209
+ test("Common: DropdownMenu - test if sub-menu functionality works correctly", async ({
210
+ expect,
211
+ }) => {
212
+ const { unmount } = render(
213
+ <DropdownMenu defaultOpen>
214
+ <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
215
+ <DropdownMenuContent data-testid="main-content">
216
+ <DropdownMenuItem>Regular Item</DropdownMenuItem>
217
+ <DropdownMenuSub>
218
+ <DropdownMenuSubTrigger data-testid="sub-trigger" inset>
219
+ More Options
220
+ </DropdownMenuSubTrigger>
221
+ <DropdownMenuSubContent data-testid="sub-content">
222
+ <DropdownMenuItem data-testid="sub-item-1">Sub Item 1</DropdownMenuItem>
223
+ <DropdownMenuItem data-testid="sub-item-2">Sub Item 2</DropdownMenuItem>
224
+ </DropdownMenuSubContent>
225
+ </DropdownMenuSub>
226
+ </DropdownMenuContent>
227
+ </DropdownMenu>,
228
+ );
229
+
230
+ const mainContent = await screen.findByTestId("main-content");
231
+ expect(mainContent).toBeInTheDocument();
232
+
233
+ const subTrigger = screen.getByTestId("sub-trigger");
234
+
235
+ expect(subTrigger).toHaveAttribute("data-slot", "dropdown-menu-sub-trigger");
236
+ expect(subTrigger).toHaveAttribute("data-inset", "true");
237
+
238
+ expect(screen.queryByTestId("sub-content")).not.toBeInTheDocument();
239
+ unmount();
240
+ });
241
+
242
+ test("Common: DropdownMenu - test if menu structure components render with proper styling", ({
243
+ expect,
244
+ }) => {
245
+ const { unmount } = render(
246
+ <DropdownMenu defaultOpen>
247
+ <DropdownMenuTrigger>Open Menu</DropdownMenuTrigger>
248
+ <DropdownMenuContent data-testid="structure-content">
249
+ <DropdownMenuLabel data-testid="menu-label" inset>
250
+ Menu Section
251
+ </DropdownMenuLabel>
252
+ <DropdownMenuGroup data-testid="menu-group">
253
+ <DropdownMenuItem>
254
+ Item with shortcut
255
+ <DropdownMenuShortcut data-testid="menu-shortcut">⌘K</DropdownMenuShortcut>
256
+ </DropdownMenuItem>
257
+ </DropdownMenuGroup>
258
+ <DropdownMenuSeparator data-testid="menu-separator" />
259
+ <DropdownMenuItem>Another Item</DropdownMenuItem>
260
+ </DropdownMenuContent>
261
+ </DropdownMenu>,
262
+ );
263
+
264
+ const label = screen.getByTestId("menu-label");
265
+ const group = screen.getByTestId("menu-group");
266
+ const shortcut = screen.getByTestId("menu-shortcut");
267
+ const separator = screen.getByTestId("menu-separator");
268
+
269
+ expect(label).toHaveAttribute("data-slot", "dropdown-menu-label");
270
+ expect(label).toHaveAttribute("data-inset", "true");
271
+ expect(group).toHaveAttribute("data-slot", "dropdown-menu-group");
272
+ expect(shortcut).toHaveAttribute("data-slot", "dropdown-menu-shortcut");
273
+ expect(separator).toHaveAttribute("data-slot", "dropdown-menu-separator");
274
+
275
+ expect(label).toHaveTextContent("Menu Section");
276
+ expect(shortcut).toHaveTextContent("⌘K");
277
+ unmount();
278
+ });
279
+
280
+ test("Common: DropdownMenu - test if portal and positioning behavior work correctly", async ({
281
+ expect,
282
+ }) => {
283
+ const user = userEvent.setup();
284
+
285
+ const { unmount } = render(
286
+ <div data-testid="container">
287
+ <DropdownMenu>
288
+ <DropdownMenuTrigger data-testid="portal-trigger">Open Menu</DropdownMenuTrigger>
289
+ <DropdownMenuContent data-testid="portal-content" sideOffset={12}>
290
+ <DropdownMenuItem>Portal Item</DropdownMenuItem>
291
+ </DropdownMenuContent>
292
+ </DropdownMenu>
293
+ </div>,
294
+ );
295
+
296
+ const trigger = screen.getByTestId("portal-trigger");
297
+ const container = screen.getByTestId("container");
298
+
299
+ expect(screen.queryByTestId("portal-content")).not.toBeInTheDocument();
300
+
301
+ await user.click(trigger);
302
+
303
+ const content = await screen.findByTestId("portal-content");
304
+ expect(content).toBeInTheDocument();
305
+
306
+ expect(container).not.toContainElement(content);
307
+
308
+ expect(content).toHaveAttribute("data-side");
309
+ expect(content).toHaveAttribute("data-align");
310
+ unmount();
311
+ });
312
+ });
@@ -0,0 +1,305 @@
1
+ "use client";
2
+
3
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
4
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
5
+ import * as React from "react";
6
+
7
+ import { cn, DefaultComponent, useStackShiftUIComponents } from "@stackshift-ui/system";
8
+
9
+ const displayName = "DropdownMenu";
10
+ const displayNamePortal = "DropdownMenuPortal";
11
+ const displayNameTrigger = "DropdownMenuTrigger";
12
+ const displayNameContent = "DropdownMenuContent";
13
+ const displayNameGroup = "DropdownMenuGroup";
14
+ const displayNameItem = "DropdownMenuItem";
15
+ const displayNameCheckboxItem = "DropdownMenuCheckboxItem";
16
+ const displayNameRadioGroup = "DropdownMenuRadioGroup";
17
+ const displayNameRadioItem = "DropdownMenuRadioItem";
18
+ const displayNameLabel = "DropdownMenuLabel";
19
+ const displayNameSeparator = "DropdownMenuSeparator";
20
+ const displayNameShortcut = "DropdownMenuShortcut";
21
+ const displayNameSub = "DropdownMenuSub";
22
+ const displayNameSubTrigger = "DropdownMenuSubTrigger";
23
+ const displayNameSubContent = "DropdownMenuSubContent";
24
+
25
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
26
+ const { [displayName]: Component = DefaultComponent } = useStackShiftUIComponents();
27
+
28
+ return <Component as={DropdownMenuPrimitive.Root} data-slot="dropdown-menu" {...props} />;
29
+ }
30
+ DropdownMenu.displayName = displayName;
31
+
32
+ function DropdownMenuPortal({
33
+ ...props
34
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
35
+ const { [displayNamePortal]: Component = DefaultComponent } = useStackShiftUIComponents();
36
+
37
+ return (
38
+ <Component as={DropdownMenuPrimitive.Portal} data-slot="dropdown-menu-portal" {...props} />
39
+ );
40
+ }
41
+ DropdownMenuPortal.displayName = displayNamePortal;
42
+
43
+ function DropdownMenuTrigger({
44
+ ...props
45
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
46
+ const { [displayNameTrigger]: Component = DefaultComponent } = useStackShiftUIComponents();
47
+
48
+ return (
49
+ <Component as={DropdownMenuPrimitive.Trigger} data-slot="dropdown-menu-trigger" {...props} />
50
+ );
51
+ }
52
+ DropdownMenuTrigger.displayName = displayNameTrigger;
53
+
54
+ function DropdownMenuContent({
55
+ className,
56
+ sideOffset = 4,
57
+ ...props
58
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
59
+ const { [displayNameContent]: Component = DefaultComponent } = useStackShiftUIComponents();
60
+
61
+ return (
62
+ <DropdownMenuPortal>
63
+ <Component
64
+ as={DropdownMenuPrimitive.Content}
65
+ data-slot="dropdown-menu-content"
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
69
+ className,
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPortal>
74
+ );
75
+ }
76
+ DropdownMenuContent.displayName = displayNameContent;
77
+
78
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
79
+ const { [displayNameGroup]: Component = DefaultComponent } = useStackShiftUIComponents();
80
+
81
+ return <Component as={DropdownMenuPrimitive.Group} data-slot="dropdown-menu-group" {...props} />;
82
+ }
83
+ DropdownMenuGroup.displayName = displayNameGroup;
84
+
85
+ function DropdownMenuItem({
86
+ className,
87
+ inset,
88
+ variant = "default",
89
+ ...props
90
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
91
+ inset?: boolean;
92
+ variant?: "default" | "destructive";
93
+ }) {
94
+ const { [displayNameItem]: Component = DefaultComponent } = useStackShiftUIComponents();
95
+
96
+ return (
97
+ <Component
98
+ as={DropdownMenuPrimitive.Item}
99
+ data-slot="dropdown-menu-item"
100
+ data-inset={inset}
101
+ data-variant={variant}
102
+ className={cn(
103
+ "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
104
+ className,
105
+ )}
106
+ {...props}
107
+ />
108
+ );
109
+ }
110
+ DropdownMenuItem.displayName = displayNameItem;
111
+
112
+ function DropdownMenuCheckboxItem({
113
+ className,
114
+ children,
115
+ checked,
116
+ ...props
117
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
118
+ const { [displayNameCheckboxItem]: Component = DefaultComponent } = useStackShiftUIComponents();
119
+
120
+ return (
121
+ <Component
122
+ as={DropdownMenuPrimitive.CheckboxItem}
123
+ data-slot="dropdown-menu-checkbox-item"
124
+ className={cn(
125
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
126
+ className,
127
+ )}
128
+ checked={checked}
129
+ {...props}>
130
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
131
+ <DropdownMenuPrimitive.ItemIndicator>
132
+ <CheckIcon className="size-4" />
133
+ </DropdownMenuPrimitive.ItemIndicator>
134
+ </span>
135
+ {children}
136
+ </Component>
137
+ );
138
+ }
139
+ DropdownMenuCheckboxItem.displayName = displayNameCheckboxItem;
140
+
141
+ function DropdownMenuRadioGroup({
142
+ ...props
143
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
144
+ const { [displayNameRadioGroup]: Component = DefaultComponent } = useStackShiftUIComponents();
145
+
146
+ return (
147
+ <Component
148
+ as={DropdownMenuPrimitive.RadioGroup}
149
+ data-slot="dropdown-menu-radio-group"
150
+ {...props}
151
+ />
152
+ );
153
+ }
154
+ DropdownMenuRadioGroup.displayName = displayNameRadioGroup;
155
+
156
+ function DropdownMenuRadioItem({
157
+ className,
158
+ children,
159
+ ...props
160
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
161
+ const { [displayNameRadioItem]: Component = DefaultComponent } = useStackShiftUIComponents();
162
+
163
+ return (
164
+ <Component
165
+ as={DropdownMenuPrimitive.RadioItem}
166
+ data-slot="dropdown-menu-radio-item"
167
+ className={cn(
168
+ "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
169
+ className,
170
+ )}
171
+ {...props}>
172
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
173
+ <DropdownMenuPrimitive.ItemIndicator>
174
+ <CircleIcon className="size-2 fill-current" />
175
+ </DropdownMenuPrimitive.ItemIndicator>
176
+ </span>
177
+ {children}
178
+ </Component>
179
+ );
180
+ }
181
+ DropdownMenuRadioItem.displayName = displayNameRadioItem;
182
+
183
+ function DropdownMenuLabel({
184
+ className,
185
+ inset,
186
+ ...props
187
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
188
+ inset?: boolean;
189
+ }) {
190
+ const { [displayNameLabel]: Component = DefaultComponent } = useStackShiftUIComponents();
191
+
192
+ return (
193
+ <Component
194
+ as={DropdownMenuPrimitive.Label}
195
+ data-slot="dropdown-menu-label"
196
+ data-inset={inset}
197
+ className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
198
+ {...props}
199
+ />
200
+ );
201
+ }
202
+ DropdownMenuLabel.displayName = displayNameLabel;
203
+
204
+ function DropdownMenuSeparator({
205
+ className,
206
+ ...props
207
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
208
+ const { [displayNameSeparator]: Component = DefaultComponent } = useStackShiftUIComponents();
209
+
210
+ return (
211
+ <Component
212
+ as={DropdownMenuPrimitive.Separator}
213
+ data-slot="dropdown-menu-separator"
214
+ className={cn("bg-border -mx-1 my-1 h-px", className)}
215
+ {...props}
216
+ />
217
+ );
218
+ }
219
+ DropdownMenuSeparator.displayName = displayNameSeparator;
220
+
221
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
222
+ const { [displayNameShortcut]: Component = DefaultComponent } = useStackShiftUIComponents();
223
+
224
+ return (
225
+ <Component
226
+ as="span"
227
+ data-slot="dropdown-menu-shortcut"
228
+ className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
229
+ {...props}
230
+ />
231
+ );
232
+ }
233
+ DropdownMenuShortcut.displayName = displayNameShortcut;
234
+
235
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
236
+ const { [displayNameSub]: Component = DefaultComponent } = useStackShiftUIComponents();
237
+
238
+ return <Component as={DropdownMenuPrimitive.Sub} data-slot="dropdown-menu-sub" {...props} />;
239
+ }
240
+ DropdownMenuSub.displayName = displayNameSub;
241
+
242
+ function DropdownMenuSubTrigger({
243
+ className,
244
+ inset,
245
+ children,
246
+ ...props
247
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
248
+ inset?: boolean;
249
+ }) {
250
+ const { [displayNameSubTrigger]: Component = DefaultComponent } = useStackShiftUIComponents();
251
+
252
+ return (
253
+ <Component
254
+ as={DropdownMenuPrimitive.SubTrigger}
255
+ data-slot="dropdown-menu-sub-trigger"
256
+ data-inset={inset}
257
+ className={cn(
258
+ "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
259
+ className,
260
+ )}
261
+ {...props}>
262
+ {children}
263
+ <ChevronRightIcon className="ml-auto size-4" />
264
+ </Component>
265
+ );
266
+ }
267
+ DropdownMenuSubTrigger.displayName = displayNameSubTrigger;
268
+
269
+ function DropdownMenuSubContent({
270
+ className,
271
+ ...props
272
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
273
+ const { [displayNameSubContent]: Component = DefaultComponent } = useStackShiftUIComponents();
274
+
275
+ return (
276
+ <Component
277
+ as={DropdownMenuPrimitive.SubContent}
278
+ data-slot="dropdown-menu-sub-content"
279
+ className={cn(
280
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
281
+ className,
282
+ )}
283
+ {...props}
284
+ />
285
+ );
286
+ }
287
+ DropdownMenuSubContent.displayName = displayNameSubContent;
288
+
289
+ export {
290
+ DropdownMenu,
291
+ DropdownMenuCheckboxItem,
292
+ DropdownMenuContent,
293
+ DropdownMenuGroup,
294
+ DropdownMenuItem,
295
+ DropdownMenuLabel,
296
+ DropdownMenuPortal,
297
+ DropdownMenuRadioGroup,
298
+ DropdownMenuRadioItem,
299
+ DropdownMenuSeparator,
300
+ DropdownMenuShortcut,
301
+ DropdownMenuSub,
302
+ DropdownMenuSubContent,
303
+ DropdownMenuSubTrigger,
304
+ DropdownMenuTrigger,
305
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ "use client";
2
+
3
+ // component exports
4
+ export * from "./dropdown-menu";
@@ -0,0 +1,4 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ export { };
4
+