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,239 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import { AlertCircle, Info, Mail, Trash2 } 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
+ Dialog,
8
+ DialogClose,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ DialogTrigger,
15
+ } from "./dialog";
16
+
17
+ const meta = {
18
+ component: Dialog,
19
+ parameters: {
20
+ layout: "centered",
21
+ },
22
+ tags: ["autodocs"],
23
+ } satisfies Meta<typeof Dialog>;
24
+
25
+ export default meta;
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ args: {},
30
+ play: async ({ canvasElement }) => {
31
+ const canvas = within(canvasElement);
32
+ const trigger = canvas.getByRole("button", { name: /open dialog/i });
33
+
34
+ await expect(trigger).toBeInTheDocument();
35
+
36
+ await userEvent.click(trigger);
37
+
38
+ const dialog = await within(document.body).findByRole("dialog");
39
+
40
+ await expect(dialog).toBeInTheDocument();
41
+
42
+ const title = within(dialog).getByText(/dialog title/i);
43
+
44
+ await expect(title).toBeInTheDocument();
45
+ },
46
+ render: () => (
47
+ <Dialog>
48
+ <DialogTrigger asChild>
49
+ <Button>Open Dialog</Button>
50
+ </DialogTrigger>
51
+ <DialogContent>
52
+ <DialogHeader>
53
+ <DialogTitle>Dialog Title</DialogTitle>
54
+ <DialogDescription>
55
+ This is a description of what this dialog does. It provides context to the user.
56
+ </DialogDescription>
57
+ </DialogHeader>
58
+ <div className="py-4">
59
+ <p className="text-sm">
60
+ This is the main content of the dialog. You can put any content here.
61
+ </p>
62
+ </div>
63
+ <DialogFooter>
64
+ <DialogClose asChild>
65
+ <Button variant="secondary">Cancel</Button>
66
+ </DialogClose>
67
+ <Button onClick={fn()}>Confirm</Button>
68
+ </DialogFooter>
69
+ </DialogContent>
70
+ </Dialog>
71
+ ),
72
+ };
73
+
74
+ export const WithForm: Story = {
75
+ args: {},
76
+ render: () => {
77
+ const [open, setOpen] = useState(false);
78
+
79
+ return (
80
+ <Dialog onOpenChange={setOpen} open={open}>
81
+ <DialogTrigger asChild>
82
+ <Button>
83
+ <Mail size={16} />
84
+ Send Email
85
+ </Button>
86
+ </DialogTrigger>
87
+ <DialogContent>
88
+ <DialogHeader>
89
+ <DialogTitle>Send Email</DialogTitle>
90
+ <DialogDescription>
91
+ Compose and send an email to the selected recipients.
92
+ </DialogDescription>
93
+ </DialogHeader>
94
+ <div className="space-y-4 py-4">
95
+ <div className="space-y-2">
96
+ <label className="text-sm font-medium" htmlFor="to">
97
+ To
98
+ </label>
99
+ <input
100
+ aria-label="To"
101
+ 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"
102
+ id="to"
103
+ placeholder="recipient@example.com"
104
+ type="email"
105
+ />
106
+ </div>
107
+ <div className="space-y-2">
108
+ <label className="text-sm font-medium" htmlFor="subject">
109
+ Subject
110
+ </label>
111
+ <input
112
+ aria-label="Subject"
113
+ 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"
114
+ id="subject"
115
+ placeholder="Email subject"
116
+ type="text"
117
+ />
118
+ </div>
119
+ <div className="space-y-2">
120
+ <label className="text-sm font-medium" htmlFor="message">
121
+ Message
122
+ </label>
123
+ <textarea
124
+ aria-label="Message"
125
+ 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"
126
+ id="message"
127
+ placeholder="Type your message here..."
128
+ rows={4}
129
+ />
130
+ </div>
131
+ </div>
132
+ <DialogFooter>
133
+ <DialogClose asChild>
134
+ <Button variant="secondary">Cancel</Button>
135
+ </DialogClose>
136
+ <Button onClick={() => setOpen(false)}>Send</Button>
137
+ </DialogFooter>
138
+ </DialogContent>
139
+ </Dialog>
140
+ );
141
+ },
142
+ };
143
+
144
+ export const Destructive: Story = {
145
+ args: {},
146
+ render: () => (
147
+ <Dialog>
148
+ <DialogTrigger asChild>
149
+ <Button variant="destructive">
150
+ <Trash2 size={16} />
151
+ Delete Account
152
+ </Button>
153
+ </DialogTrigger>
154
+ <DialogContent>
155
+ <DialogHeader>
156
+ <DialogTitle className="flex items-center gap-2">
157
+ <AlertCircle className="text-destructive h-5 w-5" />
158
+ Are you absolutely sure?
159
+ </DialogTitle>
160
+ <DialogDescription>
161
+ This action cannot be undone. This will permanently delete your account and remove your
162
+ data from our servers.
163
+ </DialogDescription>
164
+ </DialogHeader>
165
+ <DialogFooter>
166
+ <DialogClose asChild>
167
+ <Button variant="secondary">Cancel</Button>
168
+ </DialogClose>
169
+ <Button onClick={fn()} variant="destructive">
170
+ Delete Account
171
+ </Button>
172
+ </DialogFooter>
173
+ </DialogContent>
174
+ </Dialog>
175
+ ),
176
+ };
177
+
178
+ export const Information: Story = {
179
+ args: {},
180
+ render: () => (
181
+ <Dialog>
182
+ <DialogTrigger asChild>
183
+ <Button variant="outline">
184
+ <Info size={16} />
185
+ Learn More
186
+ </Button>
187
+ </DialogTrigger>
188
+ <DialogContent>
189
+ <DialogHeader>
190
+ <DialogTitle>About This Feature</DialogTitle>
191
+ <DialogDescription>
192
+ Learn more about how this feature works and how it can benefit you.
193
+ </DialogDescription>
194
+ </DialogHeader>
195
+ <div className="space-y-4 py-4 text-sm">
196
+ <p>
197
+ This feature allows you to manage your account settings efficiently. You can update your
198
+ profile information, change your password, and configure notification preferences.
199
+ </p>
200
+ <p>
201
+ All changes are saved automatically and synchronized across all your devices in
202
+ real-time.
203
+ </p>
204
+ </div>
205
+ <DialogFooter>
206
+ <DialogClose asChild>
207
+ <Button>Got it</Button>
208
+ </DialogClose>
209
+ </DialogFooter>
210
+ </DialogContent>
211
+ </Dialog>
212
+ ),
213
+ };
214
+
215
+ export const NoDescription: Story = {
216
+ args: {},
217
+ render: () => (
218
+ <Dialog>
219
+ <DialogTrigger asChild>
220
+ <Button variant="outline">Open Simple Dialog</Button>
221
+ </DialogTrigger>
222
+ <DialogContent aria-describedby={undefined}>
223
+ <DialogHeader>
224
+ <DialogTitle>Simple Dialog</DialogTitle>
225
+ </DialogHeader>
226
+ <div className="py-4">
227
+ <p className="text-sm">
228
+ This dialog doesn't have a description, just a title and content.
229
+ </p>
230
+ </div>
231
+ <DialogFooter>
232
+ <DialogClose asChild>
233
+ <Button>Close</Button>
234
+ </DialogClose>
235
+ </DialogFooter>
236
+ </DialogContent>
237
+ </Dialog>
238
+ ),
239
+ };
@@ -0,0 +1,136 @@
1
+ "use client";
2
+
3
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
+ import { X } from "lucide-react";
5
+ import { useTranslations } from "next-intl";
6
+ import * as React from "react";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const Dialog = DialogPrimitive.Root;
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger;
12
+
13
+ const DialogPortal = DialogPrimitive.Portal;
14
+
15
+ const DialogClose = DialogPrimitive.Close;
16
+
17
+ function DialogOverlay({
18
+ className,
19
+ ref,
20
+ ...props
21
+ }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & {
22
+ ref?: React.RefObject<null | React.ComponentRef<typeof DialogPrimitive.Overlay>>;
23
+ }) {
24
+ return (
25
+ <DialogPrimitive.Overlay
26
+ className={cn(
27
+ "fixed inset-0 z-50 bg-black/70 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
28
+ className,
29
+ )}
30
+ ref={ref}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
36
+
37
+ function DialogContent({
38
+ children,
39
+ className,
40
+ hideClose = false,
41
+ ref,
42
+ ...props
43
+ }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
44
+ hideClose?: boolean;
45
+ ref?: React.RefObject<null | React.ComponentRef<typeof DialogPrimitive.Content>>;
46
+ }) {
47
+ const t = useTranslations("Common");
48
+
49
+ return (
50
+ <DialogPortal>
51
+ <DialogOverlay />
52
+ <DialogPrimitive.Content
53
+ className={cn(
54
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-3xl translate-x-[-50%] translate-y-[-50%] gap-4 rounded-3xl border border-border/70 bg-popover p-6 shadow-2xl backdrop-blur-xl duration-200 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-[state=closed]:slide-out-to-top-[2%] data-[state=open]:slide-in-from-top-[2%]",
55
+ className,
56
+ )}
57
+ ref={ref}
58
+ {...props}
59
+ >
60
+ {children}
61
+ {!hideClose && (
62
+ <DialogPrimitive.Close className="ring-offset-background hover:bg-accent focus:ring-ring absolute top-4 right-4 rounded-full p-1.5 opacity-70 transition-all hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
63
+ <X className="h-4 w-4" />
64
+ <span className="sr-only">{t("a11y.close")}</span>
65
+ </DialogPrimitive.Close>
66
+ )}
67
+ </DialogPrimitive.Content>
68
+ </DialogPortal>
69
+ );
70
+ }
71
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
72
+
73
+ function DialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
74
+ return (
75
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
76
+ );
77
+ }
78
+ DialogHeader.displayName = "DialogHeader";
79
+
80
+ function DialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
81
+ return (
82
+ <div
83
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
84
+ {...props}
85
+ />
86
+ );
87
+ }
88
+ DialogFooter.displayName = "DialogFooter";
89
+
90
+ function DialogTitle({
91
+ className,
92
+ ref,
93
+ ...props
94
+ }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> & {
95
+ ref?: React.RefObject<null | React.ComponentRef<typeof DialogPrimitive.Title>>;
96
+ }) {
97
+ return (
98
+ <DialogPrimitive.Title
99
+ className={cn(
100
+ "text-xl font-semibold leading-tight tracking-tight text-foreground",
101
+ className,
102
+ )}
103
+ ref={ref}
104
+ {...props}
105
+ />
106
+ );
107
+ }
108
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
109
+
110
+ function DialogDescription({
111
+ className,
112
+ ref,
113
+ ...props
114
+ }: React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> & {
115
+ ref?: React.RefObject<null | React.ComponentRef<typeof DialogPrimitive.Description>>;
116
+ }) {
117
+ return (
118
+ <DialogPrimitive.Description
119
+ className={cn("text-sm leading-relaxed text-muted-foreground", className)}
120
+ ref={ref}
121
+ {...props}
122
+ />
123
+ );
124
+ }
125
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
126
+
127
+ export {
128
+ Dialog,
129
+ DialogClose,
130
+ DialogContent,
131
+ DialogDescription,
132
+ DialogFooter,
133
+ DialogHeader,
134
+ DialogTitle,
135
+ DialogTrigger,
136
+ };
@@ -0,0 +1,55 @@
1
+ import type { Meta, StoryObj } from "@storybook/nextjs-vite";
2
+ import { expect, within } from "storybook/test";
3
+ import { DialogButtons } from "./dialog-buttons";
4
+
5
+ /** Cancel / submit row shared by every `ResponsiveDialog` form. */
6
+ const meta = {
7
+ args: {
8
+ buttonLoadingText: "Saving…",
9
+ buttonText: "Save",
10
+ isPending: false,
11
+ setOpen: () => {},
12
+ },
13
+ component: DialogButtons,
14
+ decorators: [
15
+ (Story) => (
16
+ <div className="w-96">
17
+ <Story />
18
+ </div>
19
+ ),
20
+ ],
21
+ parameters: { layout: "centered" },
22
+ tags: ["autodocs"],
23
+ } satisfies Meta<typeof DialogButtons>;
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Primary: Story = {
30
+ play: async ({ canvasElement }) => {
31
+ await expect(within(canvasElement).getByRole("button", { name: "Save" })).toBeEnabled();
32
+ },
33
+ };
34
+
35
+ export const Destructive: Story = {
36
+ args: {
37
+ buttonLoadingText: "Deleting…",
38
+ buttonText: "Delete",
39
+ buttonVariant: "destructive",
40
+ },
41
+ };
42
+
43
+ export const Pending: Story = {
44
+ args: { isPending: true },
45
+ play: async ({ canvasElement }) => {
46
+ await expect(within(canvasElement).getByRole("button", { name: /saving/i })).toBeDisabled();
47
+ },
48
+ };
49
+
50
+ export const SubmitDisabled: Story = {
51
+ args: { buttonDisabled: true },
52
+ play: async ({ canvasElement }) => {
53
+ await expect(within(canvasElement).getByRole("button", { name: "Save" })).toBeDisabled();
54
+ },
55
+ };
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import type { Dispatch, SetStateAction } from "react";
5
+ import { Button } from "./button/button";
6
+
7
+ type Props = {
8
+ buttonDisabled?: boolean;
9
+ buttonLoadingText: string;
10
+ buttonText: string;
11
+ buttonVariant?: "destructive" | "primary";
12
+ isPending: boolean;
13
+ setOpen: Dispatch<SetStateAction<boolean>>;
14
+ };
15
+
16
+ export function DialogButtons({
17
+ buttonDisabled,
18
+ buttonLoadingText,
19
+ buttonText,
20
+ buttonVariant,
21
+ isPending,
22
+ setOpen,
23
+ }: Props) {
24
+ const t = useTranslations("Common");
25
+
26
+ return (
27
+ <section className="w-full gap-2.5 md:flex md:justify-end">
28
+ <Button
29
+ className="hidden md:flex"
30
+ disabled={isPending}
31
+ onClick={() => setOpen(false)}
32
+ type="button"
33
+ variant="secondary"
34
+ >
35
+ {t("cancel")}
36
+ </Button>
37
+ <Button
38
+ className="mt-7 w-full md:mt-0 md:w-auto"
39
+ disabled={buttonDisabled}
40
+ isLoading={isPending}
41
+ type="submit"
42
+ variant={buttonVariant || "primary"}
43
+ >
44
+ {isPending ? buttonLoadingText : buttonText}
45
+ </Button>
46
+ </section>
47
+ );
48
+ }