create-nexst-monorepo 0.1.0

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 (303) hide show
  1. package/dist/index.js +1727 -0
  2. package/package.json +37 -0
  3. package/template/.agents/plugins/marketplace.json +21 -0
  4. package/template/.claude/settings.json +6 -0
  5. package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
  6. package/template/.claude/skills/frontend-design/SKILL.md +154 -0
  7. package/template/.claude/skills/theme-factory/SKILL.md +95 -0
  8. package/template/.claude/skills/theme-factory/references/schema.md +104 -0
  9. package/template/.claude/skills/theme-factory/scripts/theme.mjs +261 -0
  10. package/template/.dockerignore +43 -0
  11. package/template/.editorconfig +10 -0
  12. package/template/.github/workflows/ci.yaml +289 -0
  13. package/template/.github/workflows/publish-app.yaml +66 -0
  14. package/template/.github/workflows/publish.yaml +148 -0
  15. package/template/.github/workflows/snyk.yaml +34 -0
  16. package/template/.vscode/settings.json +8 -0
  17. package/template/AGENTS.md +42 -0
  18. package/template/Dockerfile.base +9 -0
  19. package/template/README.md +54 -0
  20. package/template/_gitignore +10 -0
  21. package/template/apps/api/.env.example +32 -0
  22. package/template/apps/api/AGENTS.md +44 -0
  23. package/template/apps/api/Dockerfile +69 -0
  24. package/template/apps/api/_gitignore +8 -0
  25. package/template/apps/api/knip.config.ts +22 -0
  26. package/template/apps/api/kustomize/base/application.properties +16 -0
  27. package/template/apps/api/kustomize/base/deployment.yaml +94 -0
  28. package/template/apps/api/kustomize/base/kustomization.yaml +17 -0
  29. package/template/apps/api/kustomize/base/service.yaml +10 -0
  30. package/template/apps/api/kustomize/overlays/prod/kustomization.yaml +17 -0
  31. package/template/apps/api/kustomize/overlays/staging/application.properties +1 -0
  32. package/template/apps/api/kustomize/overlays/staging/kustomization.yaml +21 -0
  33. package/template/apps/api/kustomize/secret.keys +14 -0
  34. package/template/apps/api/oxfmt.config.ts +8 -0
  35. package/template/apps/api/oxlint.config.ts +107 -0
  36. package/template/apps/api/package.json +79 -0
  37. package/template/apps/api/prisma/migrations/20260927000000_init/migration.sql +113 -0
  38. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  39. package/template/apps/api/prisma/schema.prisma +113 -0
  40. package/template/apps/api/prisma.config.ts +7 -0
  41. package/template/apps/api/scripts/auth.cli.ts +19 -0
  42. package/template/apps/api/scripts/seal-secret.sh +171 -0
  43. package/template/apps/api/sonar-project.properties +9 -0
  44. package/template/apps/api/src/app-info/app-info.dto.ts +10 -0
  45. package/template/apps/api/src/app-info/app-info.module.ts +7 -0
  46. package/template/apps/api/src/app-info/app-info.resolver.spec.ts +14 -0
  47. package/template/apps/api/src/app-info/app-info.resolver.ts +21 -0
  48. package/template/apps/api/src/app.module.spec.ts +93 -0
  49. package/template/apps/api/src/app.module.ts +77 -0
  50. package/template/apps/api/src/auth/auth.guard.spec.ts +90 -0
  51. package/template/apps/api/src/auth/auth.guard.ts +97 -0
  52. package/template/apps/api/src/auth/auth.ts +129 -0
  53. package/template/apps/api/src/auth/decorator/allow-anonymous.decorator.ts +17 -0
  54. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.spec.ts +41 -0
  55. package/template/apps/api/src/auth/decorator/is-email-verified.decorator.ts +20 -0
  56. package/template/apps/api/src/auth/decorator/roles.decorator.ts +27 -0
  57. package/template/apps/api/src/auth/testing/make-session.ts +31 -0
  58. package/template/apps/api/src/auth/types/auth.types.ts +27 -0
  59. package/template/apps/api/src/bootstrap/register-graphql-enums.ts +12 -0
  60. package/template/apps/api/src/bootstrap/security.ts +24 -0
  61. package/template/apps/api/src/bootstrap/swagger.ts +28 -0
  62. package/template/apps/api/src/config/app.config.spec.ts +22 -0
  63. package/template/apps/api/src/config/app.config.ts +122 -0
  64. package/template/apps/api/src/config/graphql-depth-limit.ts +103 -0
  65. package/template/apps/api/src/config/graphql.config.ts +77 -0
  66. package/template/apps/api/src/config/resources/app.yaml +29 -0
  67. package/template/apps/api/src/errors/app-parse-uuid.pipe.ts +27 -0
  68. package/template/apps/api/src/errors/app.exception.ts +47 -0
  69. package/template/apps/api/src/errors/error-code.enum.ts +54 -0
  70. package/template/apps/api/src/errors/http-status-fallback-code.util.ts +27 -0
  71. package/template/apps/api/src/errors/validation-exception.factory.ts +43 -0
  72. package/template/apps/api/src/health/health.controller.spec.ts +82 -0
  73. package/template/apps/api/src/health/health.controller.ts +57 -0
  74. package/template/apps/api/src/health/health.module.ts +12 -0
  75. package/template/apps/api/src/logging/context-logger.ts +18 -0
  76. package/template/apps/api/src/logging/logger.config.ts +23 -0
  77. package/template/apps/api/src/logging/logging.module.ts +22 -0
  78. package/template/apps/api/src/mail/mail.module.ts +12 -0
  79. package/template/apps/api/src/mail/mail.service.spec.ts +47 -0
  80. package/template/apps/api/src/mail/mail.service.ts +123 -0
  81. package/template/apps/api/src/main.ts +41 -0
  82. package/template/apps/api/src/prisma/prisma.module.ts +12 -0
  83. package/template/apps/api/src/prisma/prisma.service.spec.ts +136 -0
  84. package/template/apps/api/src/prisma/prisma.service.ts +138 -0
  85. package/template/apps/api/src/queue/dto/enqueue-example.input.ts +11 -0
  86. package/template/apps/api/src/queue/example-queue.resolver.ts +14 -0
  87. package/template/apps/api/src/queue/example-queue.service.spec.ts +21 -0
  88. package/template/apps/api/src/queue/example-queue.service.ts +32 -0
  89. package/template/apps/api/src/queue/queue.module.ts +27 -0
  90. package/template/apps/api/src/r2/r2.module.ts +12 -0
  91. package/template/apps/api/src/r2/r2.service.spec.ts +117 -0
  92. package/template/apps/api/src/r2/r2.service.ts +131 -0
  93. package/template/apps/api/src/user/dto/user.dto.ts +26 -0
  94. package/template/apps/api/src/user/user.module.ts +10 -0
  95. package/template/apps/api/src/user/user.resolver.spec.ts +18 -0
  96. package/template/apps/api/src/user/user.resolver.ts +24 -0
  97. package/template/apps/api/src/utils/catch-everything-exception.filter.spec.ts +292 -0
  98. package/template/apps/api/src/utils/catch-everything-exception.filter.ts +70 -0
  99. package/template/apps/api/src/utils/error-handler.util.spec.ts +20 -0
  100. package/template/apps/api/src/utils/error-handler.util.ts +12 -0
  101. package/template/apps/api/src/utils/escape-like-pattern.util.spec.ts +29 -0
  102. package/template/apps/api/src/utils/escape-like-pattern.util.ts +11 -0
  103. package/template/apps/api/src/utils/pagination.input.ts +20 -0
  104. package/template/apps/api/src/utils/prisma-exception.filter.spec.ts +186 -0
  105. package/template/apps/api/src/utils/prisma-exception.filter.ts +45 -0
  106. package/template/apps/api/src/utils/prisma-graphql-exception.filter.spec.ts +216 -0
  107. package/template/apps/api/src/utils/prisma-graphql-exception.filter.ts +43 -0
  108. package/template/apps/api/tests/app.e2e-spec.ts +35 -0
  109. package/template/apps/api/tests/auth.e2e-spec.ts +38 -0
  110. package/template/apps/api/tests/helpers/api.ts +27 -0
  111. package/template/apps/api/tsconfig.json +11 -0
  112. package/template/apps/api/vitest.config.e2e.ts +16 -0
  113. package/template/apps/api/vitest.config.ts +40 -0
  114. package/template/apps/web/.env.example +7 -0
  115. package/template/apps/web/.storybook/decorators.tsx +38 -0
  116. package/template/apps/web/.storybook/main.ts +17 -0
  117. package/template/apps/web/.storybook/preview.ts +38 -0
  118. package/template/apps/web/.storybook/vitest.setup.ts +5 -0
  119. package/template/apps/web/AGENTS.md +29 -0
  120. package/template/apps/web/Dockerfile +49 -0
  121. package/template/apps/web/_gitignore +16 -0
  122. package/template/apps/web/knip.config.ts +15 -0
  123. package/template/apps/web/kustomize/base/application.properties +5 -0
  124. package/template/apps/web/kustomize/base/deployment.yaml +60 -0
  125. package/template/apps/web/kustomize/base/kustomization.yaml +17 -0
  126. package/template/apps/web/kustomize/base/service.yaml +10 -0
  127. package/template/apps/web/kustomize/overlays/prod/kustomization.yaml +12 -0
  128. package/template/apps/web/kustomize/overlays/staging/application.properties +2 -0
  129. package/template/apps/web/kustomize/overlays/staging/kustomization.yaml +18 -0
  130. package/template/apps/web/messages/de.json +124 -0
  131. package/template/apps/web/messages/en.json +124 -0
  132. package/template/apps/web/next-env.d.ts +7 -0
  133. package/template/apps/web/next.config.ts +39 -0
  134. package/template/apps/web/oxfmt.config.ts +9 -0
  135. package/template/apps/web/oxlint.config.ts +36 -0
  136. package/template/apps/web/package.json +80 -0
  137. package/template/apps/web/playwright.config.ts +34 -0
  138. package/template/apps/web/postcss.config.mjs +7 -0
  139. package/template/apps/web/sonar-project.properties +9 -0
  140. package/template/apps/web/src/app/[locale]/(auth)/recovery/page.tsx +22 -0
  141. package/template/apps/web/src/app/[locale]/(auth)/reset-password/page.tsx +29 -0
  142. package/template/apps/web/src/app/[locale]/(auth)/sign-in/page.tsx +24 -0
  143. package/template/apps/web/src/app/[locale]/(auth)/sign-up/page.tsx +24 -0
  144. package/template/apps/web/src/app/[locale]/(auth)/verify/page.tsx +24 -0
  145. package/template/apps/web/src/app/[locale]/dashboard/page.tsx +34 -0
  146. package/template/apps/web/src/app/[locale]/layout.tsx +60 -0
  147. package/template/apps/web/src/app/[locale]/page.tsx +23 -0
  148. package/template/apps/web/src/app/api/health/route.ts +3 -0
  149. package/template/apps/web/src/app/global-error.tsx +19 -0
  150. package/template/apps/web/src/app/globals.css +98 -0
  151. package/template/apps/web/src/app/icon.svg +1 -0
  152. package/template/apps/web/src/app/robots.ts +15 -0
  153. package/template/apps/web/src/app/sitemap.ts +24 -0
  154. package/template/apps/web/src/components/alert/alert.stories.tsx +25 -0
  155. package/template/apps/web/src/components/alert/alert.tsx +50 -0
  156. package/template/apps/web/src/components/avatar/avatar.stories.tsx +116 -0
  157. package/template/apps/web/src/components/avatar/avatar.tsx +88 -0
  158. package/template/apps/web/src/components/button/button-variants.ts +28 -0
  159. package/template/apps/web/src/components/button/button.stories.tsx +33 -0
  160. package/template/apps/web/src/components/button/button.tsx +32 -0
  161. package/template/apps/web/src/components/checkbox/checkbox.stories.tsx +312 -0
  162. package/template/apps/web/src/components/checkbox/checkbox.tsx +38 -0
  163. package/template/apps/web/src/components/dialog/danger-panel.stories.tsx +41 -0
  164. package/template/apps/web/src/components/dialog/danger-panel.tsx +32 -0
  165. package/template/apps/web/src/components/dialog/dialog.stories.tsx +239 -0
  166. package/template/apps/web/src/components/dialog/dialog.tsx +136 -0
  167. package/template/apps/web/src/components/dialog-buttons.stories.tsx +55 -0
  168. package/template/apps/web/src/components/dialog-buttons.tsx +48 -0
  169. package/template/apps/web/src/components/drawer/drawer.stories.tsx +251 -0
  170. package/template/apps/web/src/components/drawer/drawer.tsx +120 -0
  171. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.stories.tsx +329 -0
  172. package/template/apps/web/src/components/dropdown-menu/dropdown-menu.tsx +263 -0
  173. package/template/apps/web/src/components/empty-state.stories.tsx +49 -0
  174. package/template/apps/web/src/components/empty-state.tsx +33 -0
  175. package/template/apps/web/src/components/field-group.stories.tsx +47 -0
  176. package/template/apps/web/src/components/field-group.tsx +20 -0
  177. package/template/apps/web/src/components/field-label.stories.tsx +34 -0
  178. package/template/apps/web/src/components/field-label.tsx +28 -0
  179. package/template/apps/web/src/components/icons.tsx +25 -0
  180. package/template/apps/web/src/components/input/input-otp.stories.tsx +86 -0
  181. package/template/apps/web/src/components/input/input-otp.tsx +184 -0
  182. package/template/apps/web/src/components/input/input.stories.tsx +35 -0
  183. package/template/apps/web/src/components/input/input.tsx +180 -0
  184. package/template/apps/web/src/components/popover/popover.stories.tsx +479 -0
  185. package/template/apps/web/src/components/popover/popover.tsx +45 -0
  186. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.stories.tsx +307 -0
  187. package/template/apps/web/src/components/responsive-dialog/responsive-dialog.tsx +96 -0
  188. package/template/apps/web/src/components/searchbar/searchbar.stories.tsx +59 -0
  189. package/template/apps/web/src/components/searchbar/searchbar.tsx +64 -0
  190. package/template/apps/web/src/components/select/select.stories.tsx +293 -0
  191. package/template/apps/web/src/components/select/select.tsx +445 -0
  192. package/template/apps/web/src/components/skeleton/skeleton.stories.tsx +76 -0
  193. package/template/apps/web/src/components/skeleton/skeleton.tsx +13 -0
  194. package/template/apps/web/src/components/spinner/spinner.tsx +18 -0
  195. package/template/apps/web/src/components/switch/switch.stories.tsx +71 -0
  196. package/template/apps/web/src/components/switch/switch.tsx +35 -0
  197. package/template/apps/web/src/components/tabs/tabs.stories.tsx +209 -0
  198. package/template/apps/web/src/components/tabs/tabs.tsx +69 -0
  199. package/template/apps/web/src/components/tabs/url-tabs.stories.tsx +68 -0
  200. package/template/apps/web/src/components/tabs/url-tabs.tsx +42 -0
  201. package/template/apps/web/src/components/textarea/textarea.stories.tsx +144 -0
  202. package/template/apps/web/src/components/textarea/textarea.tsx +115 -0
  203. package/template/apps/web/src/components/toast/toaster.tsx +38 -0
  204. package/template/apps/web/src/components/tooltip/tooltip.stories.tsx +237 -0
  205. package/template/apps/web/src/components/tooltip/tooltip.tsx +49 -0
  206. package/template/apps/web/src/config/csp.ts +59 -0
  207. package/template/apps/web/src/config/routes.ts +14 -0
  208. package/template/apps/web/src/config/site.ts +5 -0
  209. package/template/apps/web/src/env.ts +15 -0
  210. package/template/apps/web/src/features/auth/auth-card.tsx +20 -0
  211. package/template/apps/web/src/features/auth/auth-divider.tsx +14 -0
  212. package/template/apps/web/src/features/auth/google-auth-button.tsx +49 -0
  213. package/template/apps/web/src/features/auth/google-one-tap.tsx +23 -0
  214. package/template/apps/web/src/features/auth/lib/auth-error.test.ts +22 -0
  215. package/template/apps/web/src/features/auth/lib/auth-error.ts +16 -0
  216. package/template/apps/web/src/features/auth/lib/return-to.test.ts +41 -0
  217. package/template/apps/web/src/features/auth/lib/return-to.ts +19 -0
  218. package/template/apps/web/src/features/auth/lib/validator.test.ts +45 -0
  219. package/template/apps/web/src/features/auth/lib/validator.ts +49 -0
  220. package/template/apps/web/src/features/auth/passkey-settings.tsx +106 -0
  221. package/template/apps/web/src/features/auth/recovery-form.tsx +61 -0
  222. package/template/apps/web/src/features/auth/resend-verification-button.tsx +36 -0
  223. package/template/apps/web/src/features/auth/reset-password-form.tsx +62 -0
  224. package/template/apps/web/src/features/auth/sign-in-form.stories.tsx +22 -0
  225. package/template/apps/web/src/features/auth/sign-in-form.tsx +116 -0
  226. package/template/apps/web/src/features/auth/sign-out-button.tsx +26 -0
  227. package/template/apps/web/src/features/auth/sign-up-form.tsx +107 -0
  228. package/template/apps/web/src/features/shared/provider.tsx +16 -0
  229. package/template/apps/web/src/features/shared/site-header.tsx +48 -0
  230. package/template/apps/web/src/features/shared/theme-toggle.tsx +24 -0
  231. package/template/apps/web/src/i18n/navigation.ts +5 -0
  232. package/template/apps/web/src/i18n/request.ts +21 -0
  233. package/template/apps/web/src/i18n/routing.ts +6 -0
  234. package/template/apps/web/src/lib/auth-client.ts +19 -0
  235. package/template/apps/web/src/lib/auth-server.ts +46 -0
  236. package/template/apps/web/src/lib/auth.ts +8 -0
  237. package/template/apps/web/src/lib/codegen.ts +30 -0
  238. package/template/apps/web/src/lib/graphql-client.ts +29 -0
  239. package/template/apps/web/src/lib/graphql-server.ts +28 -0
  240. package/template/apps/web/src/lib/graphql-utils.test.ts +32 -0
  241. package/template/apps/web/src/lib/graphql-utils.ts +76 -0
  242. package/template/apps/web/src/lib/hooks/use-media-query.ts +21 -0
  243. package/template/apps/web/src/lib/metadata.ts +17 -0
  244. package/template/apps/web/src/lib/network-error.test.ts +30 -0
  245. package/template/apps/web/src/lib/network-error.ts +17 -0
  246. package/template/apps/web/src/lib/proxy-utils.ts +42 -0
  247. package/template/apps/web/src/lib/query-client.ts +29 -0
  248. package/template/apps/web/src/lib/utils.ts +5 -0
  249. package/template/apps/web/src/proxy.ts +57 -0
  250. package/template/apps/web/test-results/.last-run.json +4 -0
  251. package/template/apps/web/tests/auth.spec.ts +17 -0
  252. package/template/apps/web/tests/csp-theme-script.spec.ts +37 -0
  253. package/template/apps/web/tests/home.spec.ts +25 -0
  254. package/template/apps/web/tsconfig.build.json +4 -0
  255. package/template/apps/web/tsconfig.json +28 -0
  256. package/template/apps/web/vitest.config.ts +40 -0
  257. package/template/apps/web/vitest.shims.d.ts +1 -0
  258. package/template/apps/worker/.env.example +4 -0
  259. package/template/apps/worker/AGENTS.md +11 -0
  260. package/template/apps/worker/Dockerfile +35 -0
  261. package/template/apps/worker/knip.config.ts +3 -0
  262. package/template/apps/worker/kustomize/base/application.properties +4 -0
  263. package/template/apps/worker/kustomize/base/deployment.yaml +62 -0
  264. package/template/apps/worker/kustomize/base/kustomization.yaml +16 -0
  265. package/template/apps/worker/kustomize/overlays/prod/kustomization.yaml +12 -0
  266. package/template/apps/worker/kustomize/overlays/staging/application.properties +1 -0
  267. package/template/apps/worker/kustomize/overlays/staging/kustomization.yaml +18 -0
  268. package/template/apps/worker/oxfmt.config.ts +8 -0
  269. package/template/apps/worker/oxlint.config.ts +15 -0
  270. package/template/apps/worker/package.json +33 -0
  271. package/template/apps/worker/sonar-project.properties +8 -0
  272. package/template/apps/worker/src/env.ts +13 -0
  273. package/template/apps/worker/src/lib/graceful-shutdown.ts +27 -0
  274. package/template/apps/worker/src/lib/logger.ts +20 -0
  275. package/template/apps/worker/src/main.ts +17 -0
  276. package/template/apps/worker/src/processors/example-processor.ts +15 -0
  277. package/template/apps/worker/src/worker/redis.ts +19 -0
  278. package/template/apps/worker/src/worker/worker.ts +37 -0
  279. package/template/apps/worker/tests/example-processor.test.ts +13 -0
  280. package/template/apps/worker/tests/graceful-shutdown.test.ts +34 -0
  281. package/template/apps/worker/tsconfig.json +4 -0
  282. package/template/apps/worker/vitest.config.ts +13 -0
  283. package/template/bun.lock +3053 -0
  284. package/template/bunfig.toml +2 -0
  285. package/template/compose.yaml +53 -0
  286. package/template/docker-bake.hcl +86 -0
  287. package/template/package.json +47 -0
  288. package/template/packages/lint/knip.ts +40 -0
  289. package/template/packages/lint/oxfmt.ts +8 -0
  290. package/template/packages/lint/oxlint.ts +136 -0
  291. package/template/packages/lint/package.json +13 -0
  292. package/template/packages/queues/oxfmt.config.ts +8 -0
  293. package/template/packages/queues/oxlint.config.ts +7 -0
  294. package/template/packages/queues/package.json +15 -0
  295. package/template/packages/queues/src/example-job.ts +11 -0
  296. package/template/packages/queues/src/index.ts +3 -0
  297. package/template/packages/queues/src/queue-names.ts +2 -0
  298. package/template/packages/queues/src/redis-connection.ts +27 -0
  299. package/template/packages/queues/tsconfig.json +4 -0
  300. package/template/packages/tsconfig/base.json +23 -0
  301. package/template/packages/tsconfig/node.json +11 -0
  302. package/template/packages/tsconfig/package.json +4 -0
  303. package/template/turbo.json +50 -0
@@ -0,0 +1,251 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import { Filter, Menu, Settings, User } from "lucide-react";
3
+ import { useState } from "react";
4
+ import { expect, fn, userEvent, within } from "storybook/test";
5
+ import { Button } from "../button/button";
6
+ import {
7
+ Drawer,
8
+ DrawerClose,
9
+ DrawerContent,
10
+ DrawerDescription,
11
+ DrawerFooter,
12
+ DrawerHeader,
13
+ DrawerTitle,
14
+ DrawerTrigger,
15
+ } from "./drawer";
16
+
17
+ const meta = {
18
+ component: Drawer,
19
+ parameters: {
20
+ layout: "centered",
21
+ viewport: {
22
+ defaultViewport: "mobile1",
23
+ },
24
+ },
25
+ tags: ["autodocs"],
26
+ } satisfies Meta<typeof Drawer>;
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ export const Default: Story = {
32
+ args: {},
33
+ play: async ({ canvasElement }) => {
34
+ const canvas = within(canvasElement);
35
+ const trigger = canvas.getByRole("button", { name: /open drawer/i });
36
+
37
+ await expect(trigger).toBeInTheDocument();
38
+
39
+ await userEvent.click(trigger);
40
+ },
41
+ render: () => (
42
+ <Drawer>
43
+ <DrawerTrigger asChild>
44
+ <Button>
45
+ <Menu size={16} />
46
+ Open Drawer
47
+ </Button>
48
+ </DrawerTrigger>
49
+ <DrawerContent>
50
+ <DrawerHeader>
51
+ <DrawerTitle>Drawer Title</DrawerTitle>
52
+ <DrawerDescription>
53
+ This is a drawer component that slides up from the bottom of the screen.
54
+ </DrawerDescription>
55
+ </DrawerHeader>
56
+ <div className="px-4 py-6">
57
+ <p className="text-sm">
58
+ Drawers are commonly used on mobile devices to show additional options or forms.
59
+ </p>
60
+ </div>
61
+ <DrawerFooter>
62
+ <Button onClick={fn()}>Confirm</Button>
63
+ <DrawerClose asChild>
64
+ <Button variant="secondary">Cancel</Button>
65
+ </DrawerClose>
66
+ </DrawerFooter>
67
+ </DrawerContent>
68
+ </Drawer>
69
+ ),
70
+ };
71
+
72
+ export const WithForm: Story = {
73
+ args: {},
74
+ render: () => {
75
+ const [open, setOpen] = useState(false);
76
+
77
+ return (
78
+ <Drawer onOpenChange={setOpen} open={open}>
79
+ <DrawerTrigger asChild>
80
+ <Button>
81
+ <User size={16} />
82
+ Edit Profile
83
+ </Button>
84
+ </DrawerTrigger>
85
+ <DrawerContent>
86
+ <DrawerHeader className="text-left">
87
+ <DrawerTitle>Edit Profile</DrawerTitle>
88
+ <DrawerDescription>Make changes to your profile information.</DrawerDescription>
89
+ </DrawerHeader>
90
+ <div className="space-y-4 px-4 py-4">
91
+ <div className="space-y-2">
92
+ <label className="text-sm font-medium" htmlFor="name">
93
+ Name
94
+ </label>
95
+ <input
96
+ aria-label="Name"
97
+ className="border-input bg-background ring-offset-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none"
98
+ defaultValue="John Doe"
99
+ id="name"
100
+ placeholder="Enter your name"
101
+ type="text"
102
+ />
103
+ </div>
104
+ <div className="space-y-2">
105
+ <label className="text-sm font-medium" htmlFor="email">
106
+ Email
107
+ </label>
108
+ <input
109
+ aria-label="Email"
110
+ className="border-input bg-background ring-offset-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none"
111
+ defaultValue="john@example.com"
112
+ id="email"
113
+ placeholder="your.email@example.com"
114
+ type="email"
115
+ />
116
+ </div>
117
+ </div>
118
+ <DrawerFooter>
119
+ <Button onClick={() => setOpen(false)}>Save Changes</Button>
120
+ <DrawerClose asChild>
121
+ <Button variant="secondary">Cancel</Button>
122
+ </DrawerClose>
123
+ </DrawerFooter>
124
+ </DrawerContent>
125
+ </Drawer>
126
+ );
127
+ },
128
+ };
129
+
130
+ export const FilterDrawer: Story = {
131
+ args: {},
132
+ render: () => (
133
+ <Drawer>
134
+ <DrawerTrigger asChild>
135
+ <Button variant="outline">
136
+ <Filter size={16} />
137
+ Filters
138
+ </Button>
139
+ </DrawerTrigger>
140
+ <DrawerContent>
141
+ <DrawerHeader className="text-left">
142
+ <DrawerTitle>Filter Options</DrawerTitle>
143
+ <DrawerDescription>Customize your search with these filter options.</DrawerDescription>
144
+ </DrawerHeader>
145
+ <div className="space-y-4 px-4 py-4">
146
+ <div className="space-y-2">
147
+ <p className="text-sm font-medium">Category</p>
148
+ <select className="border-input bg-background ring-offset-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none">
149
+ <option>All Categories</option>
150
+ <option>Documents</option>
151
+ <option>Images</option>
152
+ <option>Videos</option>
153
+ </select>
154
+ </div>
155
+ <div className="space-y-2">
156
+ <p className="text-sm font-medium">Date Range</p>
157
+ <select className="border-input bg-background ring-offset-background focus-visible:ring-ring w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:outline-none">
158
+ <option>Any Time</option>
159
+ <option>Last 7 days</option>
160
+ <option>Last 30 days</option>
161
+ <option>Last Year</option>
162
+ </select>
163
+ </div>
164
+ </div>
165
+ <DrawerFooter>
166
+ <Button>Apply Filters</Button>
167
+ <DrawerClose asChild>
168
+ <Button variant="secondary">Reset</Button>
169
+ </DrawerClose>
170
+ </DrawerFooter>
171
+ </DrawerContent>
172
+ </Drawer>
173
+ ),
174
+ };
175
+
176
+ export const SettingsDrawer: Story = {
177
+ args: {},
178
+ render: () => (
179
+ <Drawer>
180
+ <DrawerTrigger asChild>
181
+ <Button variant="ghost">
182
+ <Settings size={16} />
183
+ Settings
184
+ </Button>
185
+ </DrawerTrigger>
186
+ <DrawerContent>
187
+ <DrawerHeader className="text-left">
188
+ <DrawerTitle>Settings</DrawerTitle>
189
+ <DrawerDescription>Manage your application preferences.</DrawerDescription>
190
+ </DrawerHeader>
191
+ <div className="space-y-3 px-4 py-4">
192
+ <div className="border-border bg-accent/50 flex items-center justify-between rounded-lg border p-3">
193
+ <div>
194
+ <p className="text-sm font-medium">Enable Notifications</p>
195
+ <p className="text-muted-foreground text-xs">Receive updates about your activity</p>
196
+ </div>
197
+ <input
198
+ aria-label="Enable Notifications"
199
+ className="h-4 w-4"
200
+ defaultChecked
201
+ type="checkbox"
202
+ />
203
+ </div>
204
+ <div className="border-border bg-accent/50 flex items-center justify-between rounded-lg border p-3">
205
+ <div>
206
+ <p className="text-sm font-medium">Dark Mode</p>
207
+ <p className="text-muted-foreground text-xs">Switch to dark theme</p>
208
+ </div>
209
+ <input aria-label="Dark Mode" className="h-4 w-4" type="checkbox" />
210
+ </div>
211
+ <div className="border-border bg-accent/50 flex items-center justify-between rounded-lg border p-3">
212
+ <div>
213
+ <p className="text-sm font-medium">Auto-Save</p>
214
+ <p className="text-muted-foreground text-xs">Automatically save changes</p>
215
+ </div>
216
+ <input aria-label="Auto-Save" className="h-4 w-4" defaultChecked type="checkbox" />
217
+ </div>
218
+ </div>
219
+ <DrawerFooter>
220
+ <DrawerClose asChild>
221
+ <Button>Close</Button>
222
+ </DrawerClose>
223
+ </DrawerFooter>
224
+ </DrawerContent>
225
+ </Drawer>
226
+ ),
227
+ };
228
+
229
+ export const NoDescription: Story = {
230
+ args: {},
231
+ render: () => (
232
+ <Drawer>
233
+ <DrawerTrigger asChild>
234
+ <Button variant="outline">Open Simple Drawer</Button>
235
+ </DrawerTrigger>
236
+ <DrawerContent>
237
+ <DrawerHeader className="text-left">
238
+ <DrawerTitle>Simple Drawer</DrawerTitle>
239
+ </DrawerHeader>
240
+ <div className="px-4 py-6">
241
+ <p className="text-sm">This drawer doesn't have a description.</p>
242
+ </div>
243
+ <DrawerFooter>
244
+ <DrawerClose asChild>
245
+ <Button>Close</Button>
246
+ </DrawerClose>
247
+ </DrawerFooter>
248
+ </DrawerContent>
249
+ </Drawer>
250
+ ),
251
+ };
@@ -0,0 +1,120 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Drawer as DrawerPrimitive } from "vaul";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ type DrawerProps = React.ComponentProps<typeof DrawerPrimitive.Root>;
8
+
9
+ const Drawer: React.FC<DrawerProps> = ({ shouldScaleBackground = true, ...props }) => {
10
+ return <DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />;
11
+ };
12
+ Drawer.displayName = "Drawer";
13
+
14
+ const DrawerTrigger = DrawerPrimitive.Trigger;
15
+
16
+ const DrawerPortal = DrawerPrimitive.Portal;
17
+
18
+ const DrawerClose = DrawerPrimitive.Close;
19
+
20
+ function DrawerOverlay({
21
+ className,
22
+ ref,
23
+ ...props
24
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay> & {
25
+ ref?: React.RefObject<null | React.ComponentRef<typeof DrawerPrimitive.Overlay>>;
26
+ }) {
27
+ return (
28
+ <DrawerPrimitive.Overlay
29
+ className={cn("fixed inset-0 z-50 bg-black/60 backdrop-blur-sm", className)}
30
+ ref={ref}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
36
+
37
+ function DrawerContent({
38
+ children,
39
+ className,
40
+ ref,
41
+ ...props
42
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & {
43
+ ref?: React.RefObject<null | React.ComponentRef<typeof DrawerPrimitive.Content>>;
44
+ }) {
45
+ return (
46
+ <DrawerPortal>
47
+ <DrawerOverlay />
48
+ <DrawerPrimitive.Content
49
+ className={cn(
50
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-3xl border border-border/70 bg-popover shadow-2xl",
51
+ className,
52
+ )}
53
+ ref={ref}
54
+ {...props}
55
+ >
56
+ <div className="bg-border mx-auto mt-4 h-1.5 w-12 rounded-full" />
57
+ {children}
58
+ </DrawerPrimitive.Content>
59
+ </DrawerPortal>
60
+ );
61
+ }
62
+ DrawerContent.displayName = "DrawerContent";
63
+
64
+ function DrawerHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
65
+ return <div className={cn("grid gap-2 p-4 text-center sm:text-left", className)} {...props} />;
66
+ }
67
+ DrawerHeader.displayName = "DrawerHeader";
68
+
69
+ function DrawerFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
70
+ return <div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />;
71
+ }
72
+ DrawerFooter.displayName = "DrawerFooter";
73
+
74
+ function DrawerTitle({
75
+ className,
76
+ ref,
77
+ ...props
78
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title> & {
79
+ ref?: React.RefObject<null | React.ComponentRef<typeof DrawerPrimitive.Title>>;
80
+ }) {
81
+ return (
82
+ <DrawerPrimitive.Title
83
+ className={cn(
84
+ "text-xl font-semibold leading-tight tracking-tight text-foreground",
85
+ className,
86
+ )}
87
+ ref={ref}
88
+ {...props}
89
+ />
90
+ );
91
+ }
92
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
93
+
94
+ function DrawerDescription({
95
+ className,
96
+ ref,
97
+ ...props
98
+ }: React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description> & {
99
+ ref?: React.RefObject<null | React.ComponentRef<typeof DrawerPrimitive.Description>>;
100
+ }) {
101
+ return (
102
+ <DrawerPrimitive.Description
103
+ className={cn("text-sm leading-relaxed text-muted-foreground", className)}
104
+ ref={ref}
105
+ {...props}
106
+ />
107
+ );
108
+ }
109
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
110
+
111
+ export {
112
+ Drawer,
113
+ DrawerClose,
114
+ DrawerContent,
115
+ DrawerDescription,
116
+ DrawerFooter,
117
+ DrawerHeader,
118
+ DrawerTitle,
119
+ DrawerTrigger,
120
+ };
@@ -0,0 +1,329 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import {
3
+ Cloud,
4
+ CreditCard,
5
+ Globe,
6
+ Keyboard,
7
+ LifeBuoy,
8
+ LogOut,
9
+ Mail,
10
+ MessageSquare,
11
+ Moon,
12
+ Plus,
13
+ PlusCircle,
14
+ Settings,
15
+ Sun,
16
+ Trash2,
17
+ User,
18
+ UserPlus,
19
+ Users,
20
+ } from "lucide-react";
21
+ import { useState } from "react";
22
+ import { expect, userEvent, within } from "storybook/test";
23
+ import { Avatar } from "@/components/avatar/avatar";
24
+ import { Button } from "@/components/button/button";
25
+ import {
26
+ DropdownMenu,
27
+ DropdownMenuCheckboxItem,
28
+ DropdownMenuContent,
29
+ DropdownMenuGroup,
30
+ DropdownMenuItem,
31
+ DropdownMenuLabel,
32
+ DropdownMenuPortal,
33
+ DropdownMenuRadioGroup,
34
+ DropdownMenuRadioItem,
35
+ DropdownMenuSeparator,
36
+ DropdownMenuShortcut,
37
+ DropdownMenuSub,
38
+ DropdownMenuSubContent,
39
+ DropdownMenuSubTrigger,
40
+ DropdownMenuTrigger,
41
+ } from "./dropdown-menu";
42
+
43
+ const meta: Meta<typeof DropdownMenu> = {
44
+ component: DropdownMenu,
45
+ parameters: {
46
+ // Radix hides the rest of the page (aria-hidden) while the popup is open; axe then flags
47
+ // the story root's focusable elements. Focus is trapped in the popup, so nothing is reachable.
48
+ a11y: { config: { rules: [{ enabled: false, id: "aria-hidden-focus" }] } },
49
+ layout: "centered",
50
+ },
51
+ tags: ["autodocs"],
52
+ } satisfies Meta<typeof DropdownMenu>;
53
+
54
+ export default meta;
55
+ type Story = StoryObj<typeof meta>;
56
+
57
+ export const Default: Story = {
58
+ play: async ({ canvasElement }) => {
59
+ await userEvent.click(within(canvasElement).getByRole("button", { name: "Open Menu" }));
60
+
61
+ const menu = await within(document.body).findByRole("menu");
62
+ await expect(within(menu).getByText("Profile")).toBeInTheDocument();
63
+ await expect(within(menu).getByText("Billing")).toBeInTheDocument();
64
+ },
65
+ render: () => (
66
+ <DropdownMenu>
67
+ <DropdownMenuTrigger asChild>
68
+ <Button variant="outline">Open Menu</Button>
69
+ </DropdownMenuTrigger>
70
+ <DropdownMenuContent className="w-56">
71
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
72
+ <DropdownMenuSeparator />
73
+ <DropdownMenuGroup>
74
+ <DropdownMenuItem>
75
+ <User />
76
+ Profile
77
+ <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
78
+ </DropdownMenuItem>
79
+ <DropdownMenuItem>
80
+ <CreditCard />
81
+ Billing
82
+ <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
83
+ </DropdownMenuItem>
84
+ <DropdownMenuItem>
85
+ <Settings />
86
+ Settings
87
+ <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
88
+ </DropdownMenuItem>
89
+ <DropdownMenuItem>
90
+ <Keyboard />
91
+ Keyboard shortcuts
92
+ <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
93
+ </DropdownMenuItem>
94
+ </DropdownMenuGroup>
95
+ <DropdownMenuSeparator />
96
+ <DropdownMenuGroup>
97
+ <DropdownMenuItem>
98
+ <Users />
99
+ Team
100
+ </DropdownMenuItem>
101
+ <DropdownMenuSub>
102
+ <DropdownMenuSubTrigger>
103
+ <UserPlus />
104
+ Invite users
105
+ </DropdownMenuSubTrigger>
106
+ <DropdownMenuPortal>
107
+ <DropdownMenuSubContent>
108
+ <DropdownMenuItem>
109
+ <Mail />
110
+ Email
111
+ </DropdownMenuItem>
112
+ <DropdownMenuItem>
113
+ <MessageSquare />
114
+ Message
115
+ </DropdownMenuItem>
116
+ <DropdownMenuSeparator />
117
+ <DropdownMenuItem>
118
+ <PlusCircle />
119
+ More...
120
+ </DropdownMenuItem>
121
+ </DropdownMenuSubContent>
122
+ </DropdownMenuPortal>
123
+ </DropdownMenuSub>
124
+ <DropdownMenuItem>
125
+ <Plus />
126
+ New Team
127
+ <DropdownMenuShortcut>⌘+T</DropdownMenuShortcut>
128
+ </DropdownMenuItem>
129
+ </DropdownMenuGroup>
130
+ <DropdownMenuSeparator />
131
+ <DropdownMenuItem>
132
+ <Globe />
133
+ GitHub
134
+ </DropdownMenuItem>
135
+ <DropdownMenuItem>
136
+ <LifeBuoy />
137
+ Support
138
+ </DropdownMenuItem>
139
+ <DropdownMenuItem disabled>
140
+ <Cloud />
141
+ API
142
+ </DropdownMenuItem>
143
+ <DropdownMenuSeparator />
144
+ <DropdownMenuItem>
145
+ <LogOut />
146
+ Log out
147
+ <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
148
+ </DropdownMenuItem>
149
+ </DropdownMenuContent>
150
+ </DropdownMenu>
151
+ ),
152
+ };
153
+
154
+ export const UserProfile: Story = {
155
+ render: () => (
156
+ <DropdownMenu>
157
+ <DropdownMenuTrigger asChild>
158
+ <button
159
+ className="focus-visible:ring-brand-400 rounded-full outline-none focus-visible:ring-2"
160
+ type="button"
161
+ >
162
+ <Avatar color="blue" fallback="JD" size="md" />
163
+ </button>
164
+ </DropdownMenuTrigger>
165
+ <DropdownMenuContent align="end" className="w-56">
166
+ <DropdownMenuLabel>
167
+ <div className="flex flex-col space-y-1">
168
+ <p className="text-sm leading-none font-medium">John Doe</p>
169
+ <p className="text-muted-foreground text-xs leading-none">john.doe@example.com</p>
170
+ </div>
171
+ </DropdownMenuLabel>
172
+ <DropdownMenuSeparator />
173
+ <DropdownMenuGroup>
174
+ <DropdownMenuItem>
175
+ <User />
176
+ My Profile
177
+ </DropdownMenuItem>
178
+ <DropdownMenuItem>
179
+ <Settings />
180
+ Settings
181
+ </DropdownMenuItem>
182
+ </DropdownMenuGroup>
183
+ <DropdownMenuSeparator />
184
+ <DropdownMenuSub>
185
+ <DropdownMenuSubTrigger>
186
+ <Sun className="scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
187
+ <Moon className="absolute scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
188
+ Theme
189
+ </DropdownMenuSubTrigger>
190
+ <DropdownMenuPortal>
191
+ <DropdownMenuSubContent>
192
+ <DropdownMenuItem>
193
+ <Sun />
194
+ Light
195
+ </DropdownMenuItem>
196
+ <DropdownMenuItem>
197
+ <Moon />
198
+ Dark
199
+ </DropdownMenuItem>
200
+ <DropdownMenuItem>
201
+ <Settings />
202
+ System
203
+ </DropdownMenuItem>
204
+ </DropdownMenuSubContent>
205
+ </DropdownMenuPortal>
206
+ </DropdownMenuSub>
207
+ <DropdownMenuSeparator />
208
+ <DropdownMenuItem>
209
+ <LogOut />
210
+ Log out
211
+ </DropdownMenuItem>
212
+ </DropdownMenuContent>
213
+ </DropdownMenu>
214
+ ),
215
+ };
216
+
217
+ export const ContextMenu: Story = {
218
+ render: () => (
219
+ <div className="flex flex-col items-center gap-4">
220
+ <p className="text-muted-foreground mb-2 text-sm">Right-click on the card</p>
221
+ <DropdownMenu>
222
+ <DropdownMenuTrigger asChild>
223
+ <button
224
+ aria-label="Document.pdf actions"
225
+ className="focus-visible:ring-ring/40 w-64 cursor-context-menu rounded-[18px] border border-white/50 bg-white/30 p-6 text-left backdrop-blur-[26px] outline-none focus-visible:ring-2 dark:border-white/20 dark:bg-white/5"
226
+ type="button"
227
+ >
228
+ <span className="mb-2 block font-semibold">Document.pdf</span>
229
+ <span className="text-muted-foreground block text-sm">Click to open context menu</span>
230
+ </button>
231
+ </DropdownMenuTrigger>
232
+ <DropdownMenuContent className="w-48">
233
+ <DropdownMenuItem>
234
+ <User />
235
+ Open
236
+ </DropdownMenuItem>
237
+ <DropdownMenuItem>
238
+ <Settings />
239
+ Rename
240
+ </DropdownMenuItem>
241
+ <DropdownMenuSeparator />
242
+ <DropdownMenuItem>
243
+ <CreditCard />
244
+ Share
245
+ </DropdownMenuItem>
246
+ <DropdownMenuItem>
247
+ <Users />
248
+ Collaborate
249
+ </DropdownMenuItem>
250
+ <DropdownMenuSeparator />
251
+ <DropdownMenuItem destructive>
252
+ <Trash2 />
253
+ Delete
254
+ </DropdownMenuItem>
255
+ </DropdownMenuContent>
256
+ </DropdownMenu>
257
+ </div>
258
+ ),
259
+ };
260
+
261
+ export const WithCheckboxes: Story = {
262
+ render: () => {
263
+ const [showStatusBar, setShowStatusBar] = useState(true);
264
+ const [showActivityBar, setShowActivityBar] = useState(false);
265
+ const [showPanel, setShowPanel] = useState(false);
266
+
267
+ return (
268
+ <DropdownMenu>
269
+ <DropdownMenuTrigger asChild>
270
+ <Button variant="outline">View Options</Button>
271
+ </DropdownMenuTrigger>
272
+ <DropdownMenuContent className="w-56">
273
+ <DropdownMenuLabel>Appearance</DropdownMenuLabel>
274
+ <DropdownMenuSeparator />
275
+ <DropdownMenuCheckboxItem checked={showStatusBar} onCheckedChange={setShowStatusBar}>
276
+ Status Bar
277
+ </DropdownMenuCheckboxItem>
278
+ <DropdownMenuCheckboxItem checked={showActivityBar} onCheckedChange={setShowActivityBar}>
279
+ Activity Bar
280
+ </DropdownMenuCheckboxItem>
281
+ <DropdownMenuCheckboxItem checked={showPanel} onCheckedChange={setShowPanel}>
282
+ Panel
283
+ </DropdownMenuCheckboxItem>
284
+ </DropdownMenuContent>
285
+ </DropdownMenu>
286
+ );
287
+ },
288
+ };
289
+
290
+ export const WithRadioGroup: Story = {
291
+ render: () => {
292
+ const [position, setPosition] = useState("bottom");
293
+
294
+ return (
295
+ <DropdownMenu>
296
+ <DropdownMenuTrigger asChild>
297
+ <Button variant="outline">Position: {position}</Button>
298
+ </DropdownMenuTrigger>
299
+ <DropdownMenuContent className="w-56">
300
+ <DropdownMenuLabel>Panel Position</DropdownMenuLabel>
301
+ <DropdownMenuSeparator />
302
+ <DropdownMenuRadioGroup onValueChange={setPosition} value={position}>
303
+ <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
304
+ <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
305
+ <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
306
+ </DropdownMenuRadioGroup>
307
+ </DropdownMenuContent>
308
+ </DropdownMenu>
309
+ );
310
+ },
311
+ };
312
+
313
+ export const Simple: Story = {
314
+ render: () => (
315
+ <DropdownMenu>
316
+ <DropdownMenuTrigger asChild>
317
+ <Button size="sm" variant="outline">
318
+ Actions
319
+ </Button>
320
+ </DropdownMenuTrigger>
321
+ <DropdownMenuContent>
322
+ <DropdownMenuItem>Edit</DropdownMenuItem>
323
+ <DropdownMenuItem>Duplicate</DropdownMenuItem>
324
+ <DropdownMenuSeparator />
325
+ <DropdownMenuItem destructive>Delete</DropdownMenuItem>
326
+ </DropdownMenuContent>
327
+ </DropdownMenu>
328
+ ),
329
+ };