@alfredmouelle/create-stack 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 (206) hide show
  1. package/README.md +56 -0
  2. package/_stack/apps/next-base/.dockerignore +10 -0
  3. package/_stack/apps/next-base/Dockerfile +34 -0
  4. package/_stack/apps/next-base/README.md +32 -0
  5. package/_stack/apps/next-base/components.json +25 -0
  6. package/_stack/apps/next-base/drizzle.config.ts +16 -0
  7. package/_stack/apps/next-base/next.config.ts +8 -0
  8. package/_stack/apps/next-base/package.json +70 -0
  9. package/_stack/apps/next-base/postcss.config.mjs +7 -0
  10. package/_stack/apps/next-base/src/app/api/auth/[...all]/route.ts +4 -0
  11. package/_stack/apps/next-base/src/app/api/trpc/[trpc]/route.ts +23 -0
  12. package/_stack/apps/next-base/src/app/auth/_components/forgot-password-form.tsx +92 -0
  13. package/_stack/apps/next-base/src/app/auth/_components/reset-password-form.tsx +105 -0
  14. package/_stack/apps/next-base/src/app/auth/_components/sign-in-form.tsx +126 -0
  15. package/_stack/apps/next-base/src/app/auth/_components/sign-up-form.tsx +139 -0
  16. package/_stack/apps/next-base/src/app/auth/_components/verify-email-actions.tsx +45 -0
  17. package/_stack/apps/next-base/src/app/auth/forgot-password/page.tsx +19 -0
  18. package/_stack/apps/next-base/src/app/auth/layout.tsx +9 -0
  19. package/_stack/apps/next-base/src/app/auth/reset-password/page.tsx +26 -0
  20. package/_stack/apps/next-base/src/app/auth/sign-in/page.tsx +27 -0
  21. package/_stack/apps/next-base/src/app/auth/sign-up/page.tsx +27 -0
  22. package/_stack/apps/next-base/src/app/auth/verify-email/page.tsx +30 -0
  23. package/_stack/apps/next-base/src/app/dashboard/page.tsx +12 -0
  24. package/_stack/apps/next-base/src/app/globals.css +171 -0
  25. package/_stack/apps/next-base/src/app/layout.tsx +23 -0
  26. package/_stack/apps/next-base/src/app/page.tsx +15 -0
  27. package/_stack/apps/next-base/src/components/data-table.tsx +77 -0
  28. package/_stack/apps/next-base/src/components/infinite-data-table.tsx +102 -0
  29. package/_stack/apps/next-base/src/components/sortable-header.tsx +37 -0
  30. package/_stack/apps/next-base/src/components/theme-provider.tsx +8 -0
  31. package/_stack/apps/next-base/src/components/theme-toggle.tsx +37 -0
  32. package/_stack/apps/next-base/src/components/ui/button.tsx +64 -0
  33. package/_stack/apps/next-base/src/components/ui/calendar.tsx +185 -0
  34. package/_stack/apps/next-base/src/components/ui/card.tsx +84 -0
  35. package/_stack/apps/next-base/src/components/ui/date-picker.tsx +85 -0
  36. package/_stack/apps/next-base/src/components/ui/date-range-picker.tsx +138 -0
  37. package/_stack/apps/next-base/src/components/ui/dropdown-menu.tsx +246 -0
  38. package/_stack/apps/next-base/src/components/ui/form.tsx +149 -0
  39. package/_stack/apps/next-base/src/components/ui/input-group.tsx +97 -0
  40. package/_stack/apps/next-base/src/components/ui/input.tsx +18 -0
  41. package/_stack/apps/next-base/src/components/ui/label.tsx +18 -0
  42. package/_stack/apps/next-base/src/components/ui/popover.tsx +76 -0
  43. package/_stack/apps/next-base/src/components/ui/skeleton.tsx +13 -0
  44. package/_stack/apps/next-base/src/components/ui/spinner.tsx +8 -0
  45. package/_stack/apps/next-base/src/components/ui/table.tsx +87 -0
  46. package/_stack/apps/next-base/src/emails/components/components.tsx +199 -0
  47. package/_stack/apps/next-base/src/emails/components/context.tsx +18 -0
  48. package/_stack/apps/next-base/src/emails/components/index.ts +23 -0
  49. package/_stack/apps/next-base/src/emails/components/theme.ts +65 -0
  50. package/_stack/apps/next-base/src/emails/reset-password.tsx +16 -0
  51. package/_stack/apps/next-base/src/emails/verify-email.tsx +15 -0
  52. package/_stack/apps/next-base/src/env.ts +41 -0
  53. package/_stack/apps/next-base/src/features/auth/auth-card.tsx +30 -0
  54. package/_stack/apps/next-base/src/features/auth/form-alert.tsx +27 -0
  55. package/_stack/apps/next-base/src/features/auth/google-button.tsx +66 -0
  56. package/_stack/apps/next-base/src/features/auth/schemas.ts +35 -0
  57. package/_stack/apps/next-base/src/lib/date.ts +4 -0
  58. package/_stack/apps/next-base/src/lib/utils.ts +6 -0
  59. package/_stack/apps/next-base/src/server/api/root.ts +10 -0
  60. package/_stack/apps/next-base/src/server/api/routers/health.router.ts +8 -0
  61. package/_stack/apps/next-base/src/server/api/trpc.ts +56 -0
  62. package/_stack/apps/next-base/src/server/auth/guards.ts +10 -0
  63. package/_stack/apps/next-base/src/server/better-auth/client.ts +9 -0
  64. package/_stack/apps/next-base/src/server/better-auth/config.ts +60 -0
  65. package/_stack/apps/next-base/src/server/better-auth/emails.tsx +25 -0
  66. package/_stack/apps/next-base/src/server/better-auth/index.ts +1 -0
  67. package/_stack/apps/next-base/src/server/better-auth/server.ts +14 -0
  68. package/_stack/apps/next-base/src/server/db/index.ts +6 -0
  69. package/_stack/apps/next-base/src/server/db/keyset.ts +63 -0
  70. package/_stack/apps/next-base/src/server/db/schemas/auth.schema.ts +71 -0
  71. package/_stack/apps/next-base/src/server/db/schemas/index.ts +2 -0
  72. package/_stack/apps/next-base/src/server/db/seed.ts +27 -0
  73. package/_stack/apps/next-base/src/server/email/adapters/resend/config.ts +7 -0
  74. package/_stack/apps/next-base/src/server/email/adapters/resend/index.ts +75 -0
  75. package/_stack/apps/next-base/src/server/email/core/address.ts +21 -0
  76. package/_stack/apps/next-base/src/server/email/core/port.ts +89 -0
  77. package/_stack/apps/next-base/src/server/email/core/render.ts +16 -0
  78. package/_stack/apps/next-base/src/server/email/factory.ts +47 -0
  79. package/_stack/apps/next-base/src/server/email/index.ts +36 -0
  80. package/_stack/apps/next-base/src/trpc/query-client.ts +19 -0
  81. package/_stack/apps/next-base/src/trpc/react.tsx +62 -0
  82. package/_stack/apps/next-base/src/trpc/server.ts +23 -0
  83. package/_stack/apps/next-base/tsconfig.json +37 -0
  84. package/_stack/apps/tanstack-base/.dockerignore +13 -0
  85. package/_stack/apps/tanstack-base/Dockerfile +28 -0
  86. package/_stack/apps/tanstack-base/README.md +31 -0
  87. package/_stack/apps/tanstack-base/components.json +25 -0
  88. package/_stack/apps/tanstack-base/drizzle.config.ts +16 -0
  89. package/_stack/apps/tanstack-base/package.json +85 -0
  90. package/_stack/apps/tanstack-base/public/favicon.ico +0 -0
  91. package/_stack/apps/tanstack-base/public/logo192.png +0 -0
  92. package/_stack/apps/tanstack-base/public/logo512.png +0 -0
  93. package/_stack/apps/tanstack-base/public/manifest.json +25 -0
  94. package/_stack/apps/tanstack-base/public/robots.txt +3 -0
  95. package/_stack/apps/tanstack-base/src/components/data-table.tsx +77 -0
  96. package/_stack/apps/tanstack-base/src/components/form/field-error.tsx +18 -0
  97. package/_stack/apps/tanstack-base/src/components/form/text-field.tsx +47 -0
  98. package/_stack/apps/tanstack-base/src/components/infinite-data-table.tsx +102 -0
  99. package/_stack/apps/tanstack-base/src/components/sortable-header.tsx +37 -0
  100. package/_stack/apps/tanstack-base/src/components/theme-provider.tsx +69 -0
  101. package/_stack/apps/tanstack-base/src/components/theme-toggle.tsx +35 -0
  102. package/_stack/apps/tanstack-base/src/components/ui/button.tsx +64 -0
  103. package/_stack/apps/tanstack-base/src/components/ui/calendar.tsx +185 -0
  104. package/_stack/apps/tanstack-base/src/components/ui/card.tsx +84 -0
  105. package/_stack/apps/tanstack-base/src/components/ui/date-picker.tsx +83 -0
  106. package/_stack/apps/tanstack-base/src/components/ui/date-range-picker.tsx +136 -0
  107. package/_stack/apps/tanstack-base/src/components/ui/dropdown-menu.tsx +246 -0
  108. package/_stack/apps/tanstack-base/src/components/ui/input-group.tsx +97 -0
  109. package/_stack/apps/tanstack-base/src/components/ui/input.tsx +18 -0
  110. package/_stack/apps/tanstack-base/src/components/ui/label.tsx +18 -0
  111. package/_stack/apps/tanstack-base/src/components/ui/popover.tsx +74 -0
  112. package/_stack/apps/tanstack-base/src/components/ui/skeleton.tsx +13 -0
  113. package/_stack/apps/tanstack-base/src/components/ui/spinner.tsx +8 -0
  114. package/_stack/apps/tanstack-base/src/components/ui/table.tsx +87 -0
  115. package/_stack/apps/tanstack-base/src/emails/components/components.tsx +199 -0
  116. package/_stack/apps/tanstack-base/src/emails/components/context.tsx +18 -0
  117. package/_stack/apps/tanstack-base/src/emails/components/index.ts +23 -0
  118. package/_stack/apps/tanstack-base/src/emails/components/theme.ts +65 -0
  119. package/_stack/apps/tanstack-base/src/emails/reset-password.tsx +16 -0
  120. package/_stack/apps/tanstack-base/src/emails/verify-email.tsx +15 -0
  121. package/_stack/apps/tanstack-base/src/env.ts +41 -0
  122. package/_stack/apps/tanstack-base/src/features/auth/auth-card.tsx +30 -0
  123. package/_stack/apps/tanstack-base/src/features/auth/form-alert.tsx +27 -0
  124. package/_stack/apps/tanstack-base/src/features/auth/google-button.tsx +64 -0
  125. package/_stack/apps/tanstack-base/src/features/auth/schemas.ts +35 -0
  126. package/_stack/apps/tanstack-base/src/lib/date.ts +4 -0
  127. package/_stack/apps/tanstack-base/src/lib/utils.ts +6 -0
  128. package/_stack/apps/tanstack-base/src/router.tsx +40 -0
  129. package/_stack/apps/tanstack-base/src/routes/__root.tsx +73 -0
  130. package/_stack/apps/tanstack-base/src/routes/_authed/dashboard.tsx +12 -0
  131. package/_stack/apps/tanstack-base/src/routes/_authed.tsx +21 -0
  132. package/_stack/apps/tanstack-base/src/routes/api/auth/$.ts +14 -0
  133. package/_stack/apps/tanstack-base/src/routes/api.trpc.$.tsx +31 -0
  134. package/_stack/apps/tanstack-base/src/routes/auth/forgot-password.tsx +89 -0
  135. package/_stack/apps/tanstack-base/src/routes/auth/reset-password.tsx +111 -0
  136. package/_stack/apps/tanstack-base/src/routes/auth/sign-in.tsx +117 -0
  137. package/_stack/apps/tanstack-base/src/routes/auth/sign-up.tsx +119 -0
  138. package/_stack/apps/tanstack-base/src/routes/auth/verify-email.tsx +72 -0
  139. package/_stack/apps/tanstack-base/src/routes/auth.tsx +22 -0
  140. package/_stack/apps/tanstack-base/src/routes/index.tsx +18 -0
  141. package/_stack/apps/tanstack-base/src/server/api/root.ts +10 -0
  142. package/_stack/apps/tanstack-base/src/server/api/routers/health.router.ts +8 -0
  143. package/_stack/apps/tanstack-base/src/server/api/trpc.ts +61 -0
  144. package/_stack/apps/tanstack-base/src/server/better-auth/client.ts +9 -0
  145. package/_stack/apps/tanstack-base/src/server/better-auth/config.ts +68 -0
  146. package/_stack/apps/tanstack-base/src/server/better-auth/emails.tsx +25 -0
  147. package/_stack/apps/tanstack-base/src/server/better-auth/index.ts +1 -0
  148. package/_stack/apps/tanstack-base/src/server/better-auth/session.ts +9 -0
  149. package/_stack/apps/tanstack-base/src/server/db/index.ts +6 -0
  150. package/_stack/apps/tanstack-base/src/server/db/keyset.ts +63 -0
  151. package/_stack/apps/tanstack-base/src/server/db/schemas/auth.schema.ts +71 -0
  152. package/_stack/apps/tanstack-base/src/server/db/schemas/index.ts +2 -0
  153. package/_stack/apps/tanstack-base/src/server/db/seed.ts +27 -0
  154. package/_stack/apps/tanstack-base/src/server/email/adapters/resend/config.ts +7 -0
  155. package/_stack/apps/tanstack-base/src/server/email/adapters/resend/index.ts +75 -0
  156. package/_stack/apps/tanstack-base/src/server/email/core/address.ts +21 -0
  157. package/_stack/apps/tanstack-base/src/server/email/core/port.ts +89 -0
  158. package/_stack/apps/tanstack-base/src/server/email/core/render.ts +16 -0
  159. package/_stack/apps/tanstack-base/src/server/email/factory.ts +47 -0
  160. package/_stack/apps/tanstack-base/src/server/email/index.ts +36 -0
  161. package/_stack/apps/tanstack-base/src/styles.css +171 -0
  162. package/_stack/apps/tanstack-base/src/trpc/devtools.tsx +6 -0
  163. package/_stack/apps/tanstack-base/src/trpc/query-client.ts +19 -0
  164. package/_stack/apps/tanstack-base/src/trpc/react.tsx +49 -0
  165. package/_stack/apps/tanstack-base/src/trpc/server.ts +11 -0
  166. package/_stack/apps/tanstack-base/tsconfig.json +27 -0
  167. package/_stack/apps/tanstack-base/tsr.config.json +3 -0
  168. package/_stack/apps/tanstack-base/vite.config.ts +15 -0
  169. package/_stack/packages/analytics/capability.json +26 -0
  170. package/_stack/packages/cache/capability.json +21 -0
  171. package/_stack/packages/error-tracking/capability.json +21 -0
  172. package/_stack/packages/jobs/capability.json +26 -0
  173. package/_stack/packages/logger/capability.json +21 -0
  174. package/_stack/packages/mailer/capability.json +28 -0
  175. package/_stack/packages/mailer/package.json +37 -0
  176. package/_stack/packages/mailer/src/adapters/brevo/config.ts +7 -0
  177. package/_stack/packages/mailer/src/adapters/brevo/index.ts +90 -0
  178. package/_stack/packages/mailer/src/adapters/resend/config.ts +7 -0
  179. package/_stack/packages/mailer/src/adapters/resend/index.ts +75 -0
  180. package/_stack/packages/mailer/src/adapters/ses/config.ts +13 -0
  181. package/_stack/packages/mailer/src/adapters/ses/index.ts +103 -0
  182. package/_stack/packages/storage/capability.json +32 -0
  183. package/_stack/patterns/README.md +58 -0
  184. package/_stack/patterns/_baseline/README-author.md +10 -0
  185. package/_stack/patterns/_baseline/biome.jsonc +119 -0
  186. package/_stack/patterns/_baseline/env.ts +31 -0
  187. package/_stack/patterns/_baseline/tsconfig.json +27 -0
  188. package/_stack/patterns/better-auth/pattern.json +73 -0
  189. package/_stack/patterns/better-auth-next/pattern.json +76 -0
  190. package/_stack/patterns/data-table/pattern.json +43 -0
  191. package/_stack/patterns/drizzle/pattern.json +61 -0
  192. package/_stack/patterns/trpc/pattern.json +61 -0
  193. package/_stack/patterns/trpc-next/pattern.json +64 -0
  194. package/index.mjs +216 -0
  195. package/lib/build.mjs +64 -0
  196. package/lib/env.mjs +56 -0
  197. package/lib/identity.mjs +33 -0
  198. package/lib/mailer.mjs +95 -0
  199. package/lib/manifests.mjs +61 -0
  200. package/lib/scaffold.mjs +49 -0
  201. package/lib/strip.mjs +132 -0
  202. package/lib/util.mjs +82 -0
  203. package/package.json +51 -0
  204. package/templates/next/layout.no-trpc.tsx +22 -0
  205. package/templates/tanstack/__root.no-trpc.tsx +63 -0
  206. package/templates/tanstack/router.no-trpc.tsx +24 -0
@@ -0,0 +1,83 @@
1
+ import { format, isValid, parseISO } from 'date-fns'
2
+ import { CalendarIcon, X } from 'lucide-react'
3
+ import { useState } from 'react'
4
+ import { Button } from '~/components/ui/button'
5
+ import { Calendar } from '~/components/ui/calendar'
6
+ import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'
7
+ import { toISODate } from '~/lib/date'
8
+ import { cn } from '~/lib/utils'
9
+
10
+ interface DatePickerProps {
11
+ value: string
12
+ onChange: (value: string) => void
13
+ placeholder?: string
14
+ disabled?: boolean
15
+ className?: string
16
+ }
17
+
18
+ export function DatePicker({
19
+ value,
20
+ onChange,
21
+ placeholder = 'Pick a date',
22
+ disabled,
23
+ className,
24
+ }: DatePickerProps) {
25
+ const [open, setOpen] = useState(false)
26
+ const parsed = value ? parseISO(value) : undefined
27
+ const date = parsed && isValid(parsed) ? parsed : undefined
28
+
29
+ const clear = () => {
30
+ onChange('')
31
+ setOpen(false)
32
+ }
33
+
34
+ return (
35
+ <Popover onOpenChange={setOpen} open={open}>
36
+ <div className={cn('group relative w-full', className)}>
37
+ <PopoverTrigger asChild>
38
+ <Button
39
+ className={cn(
40
+ 'w-full cursor-pointer justify-start px-3 text-left font-normal',
41
+ !date && 'text-muted-foreground',
42
+ )}
43
+ disabled={disabled}
44
+ variant="outline"
45
+ >
46
+ <CalendarIcon
47
+ className={cn(
48
+ 'mr-2 size-4 opacity-50 transition-opacity',
49
+ date && !disabled && 'group-hover:opacity-0',
50
+ )}
51
+ />
52
+ {date ? format(date, 'PPP') : <span>{placeholder}</span>}
53
+ </Button>
54
+ </PopoverTrigger>
55
+
56
+ {date && !disabled && (
57
+ <button
58
+ aria-label="Clear date"
59
+ className="absolute top-1/2 left-3 z-10 flex size-4 -translate-y-1/2 cursor-pointer items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100"
60
+ onClick={clear}
61
+ type="button"
62
+ >
63
+ <X className="size-4" />
64
+ </button>
65
+ )}
66
+ </div>
67
+
68
+ <PopoverContent align="start" className="w-auto p-0">
69
+ <Calendar
70
+ autoFocus
71
+ mode="single"
72
+ onSelect={(selected) => {
73
+ if (selected) {
74
+ onChange(toISODate(selected))
75
+ setOpen(false)
76
+ }
77
+ }}
78
+ selected={date}
79
+ />
80
+ </PopoverContent>
81
+ </Popover>
82
+ )
83
+ }
@@ -0,0 +1,136 @@
1
+ import { format, parseISO } from 'date-fns'
2
+ import { CalendarRange, X } from 'lucide-react'
3
+ import { type ComponentProps, useState } from 'react'
4
+ import type { DateRange } from 'react-day-picker'
5
+ import { Button } from '~/components/ui/button'
6
+ import { Calendar } from '~/components/ui/calendar'
7
+ import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'
8
+ import { toISODate } from '~/lib/date'
9
+ import { cn } from '~/lib/utils'
10
+
11
+ export interface DateRangeValue {
12
+ from: string
13
+ to: string
14
+ }
15
+
16
+ interface DateRangePickerProps {
17
+ value: DateRangeValue | null
18
+ onChange: (value: DateRangeValue | null) => void
19
+ placeholder?: string
20
+ numberOfMonths?: number
21
+ align?: ComponentProps<typeof PopoverContent>['align']
22
+ triggerVariant?: ComponentProps<typeof Button>['variant']
23
+ triggerClassName?: string
24
+ formatLabel?: (value: DateRangeValue) => string
25
+ }
26
+
27
+ const defaultFormatLabel = (value: DateRangeValue) =>
28
+ `${format(parseISO(value.from), 'd MMM')} – ${format(parseISO(value.to), 'd MMM')}`
29
+
30
+ export function DateRangePicker({
31
+ value,
32
+ onChange,
33
+ placeholder = 'Plage de dates',
34
+ numberOfMonths = 2,
35
+ align = 'end',
36
+ triggerVariant = 'outline',
37
+ triggerClassName,
38
+ formatLabel = defaultFormatLabel,
39
+ }: DateRangePickerProps) {
40
+ const [open, setOpen] = useState(false)
41
+ const [draft, setDraft] = useState<DateRange | undefined>()
42
+ const hasValue = value !== null
43
+
44
+ const handleOpenChange = (next: boolean) => {
45
+ if (next) {
46
+ setDraft(value ? { from: parseISO(value.from), to: parseISO(value.to) } : undefined)
47
+ }
48
+ setOpen(next)
49
+ }
50
+
51
+ const applyDraft = () => {
52
+ if (!(draft?.from && draft.to)) return
53
+ onChange({
54
+ from: toISODate(draft.from),
55
+ to: toISODate(draft.to),
56
+ })
57
+ setOpen(false)
58
+ }
59
+
60
+ const reset = () => {
61
+ setDraft(undefined)
62
+ onChange(null)
63
+ setOpen(false)
64
+ }
65
+
66
+ return (
67
+ <Popover onOpenChange={handleOpenChange} open={open}>
68
+ <div className="group relative inline-flex shrink-0">
69
+ <PopoverTrigger asChild>
70
+ <Button
71
+ className={cn('cursor-pointer', triggerClassName)}
72
+ size="sm"
73
+ variant={hasValue ? 'secondary' : triggerVariant}
74
+ >
75
+ <CalendarRange
76
+ className={cn('transition-opacity', hasValue && 'group-hover:opacity-0')}
77
+ />
78
+ {hasValue ? formatLabel(value) : placeholder}
79
+ </Button>
80
+ </PopoverTrigger>
81
+
82
+ {hasValue && (
83
+ <button
84
+ aria-label="Clear range"
85
+ className="absolute top-1/2 left-3 z-10 flex size-4 -translate-y-1/2 cursor-pointer items-center justify-center text-muted-foreground opacity-0 transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100"
86
+ onClick={(event) => {
87
+ event.stopPropagation()
88
+ reset()
89
+ }}
90
+ type="button"
91
+ >
92
+ <X className="size-4" />
93
+ </button>
94
+ )}
95
+ </div>
96
+ <PopoverContent align={align} className="w-auto p-0">
97
+ <Calendar
98
+ autoFocus
99
+ mode="range"
100
+ numberOfMonths={numberOfMonths}
101
+ onSelect={setDraft}
102
+ selected={draft}
103
+ />
104
+ <div className="flex items-center justify-between gap-2 border-t p-2">
105
+ <Button
106
+ className="cursor-pointer"
107
+ disabled={!(hasValue || draft?.from)}
108
+ onClick={reset}
109
+ size="sm"
110
+ variant="ghost"
111
+ >
112
+ Réinitialiser
113
+ </Button>
114
+ <div className="flex gap-2">
115
+ <Button
116
+ className="cursor-pointer"
117
+ onClick={() => setOpen(false)}
118
+ size="sm"
119
+ variant="ghost"
120
+ >
121
+ Annuler
122
+ </Button>
123
+ <Button
124
+ className="cursor-pointer"
125
+ disabled={!(draft?.from && draft.to)}
126
+ onClick={applyDraft}
127
+ size="sm"
128
+ >
129
+ Appliquer
130
+ </Button>
131
+ </div>
132
+ </div>
133
+ </PopoverContent>
134
+ </Popover>
135
+ )
136
+ }
@@ -0,0 +1,246 @@
1
+ 'use client'
2
+
3
+ import { CheckIcon, ChevronRightIcon } from 'lucide-react'
4
+ import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui'
5
+ import type * as React from 'react'
6
+ import { cn } from '~/lib/utils'
7
+
8
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
9
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
10
+ }
11
+
12
+ function DropdownMenuPortal({
13
+ ...props
14
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
15
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
16
+ }
17
+
18
+ function DropdownMenuTrigger({
19
+ ...props
20
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
21
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />
22
+ }
23
+
24
+ function DropdownMenuContent({
25
+ className,
26
+ align = 'start',
27
+ sideOffset = 4,
28
+ ...props
29
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
30
+ return (
31
+ <DropdownMenuPrimitive.Portal>
32
+ <DropdownMenuPrimitive.Content
33
+ align={align}
34
+ className={cn(
35
+ '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 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-48 origin-(--radix-dropdown-menu-content-transform-origin) animate-none! overflow-y-auto overflow-x-hidden rounded-3xl bg-popover/70 p-1.5 text-popover-foreground shadow-lg ring-1 ring-foreground/5 duration-100 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 data-closed:animate-out data-open:animate-in data-[state=closed]:overflow-hidden **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[variant=destructive]:**:text-accent-foreground! **:data-[variant=destructive]:text-accent-foreground! **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[variant=destructive]:focus:bg-foreground/10! dark:ring-foreground/10',
36
+ className,
37
+ )}
38
+ data-slot="dropdown-menu-content"
39
+ sideOffset={sideOffset}
40
+ {...props}
41
+ />
42
+ </DropdownMenuPrimitive.Portal>
43
+ )
44
+ }
45
+
46
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
47
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
48
+ }
49
+
50
+ function DropdownMenuItem({
51
+ className,
52
+ inset,
53
+ variant = 'default',
54
+ ...props
55
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
56
+ inset?: boolean
57
+ variant?: 'default' | 'destructive'
58
+ }) {
59
+ return (
60
+ <DropdownMenuPrimitive.Item
61
+ className={cn(
62
+ "group/dropdown-menu-item relative flex cursor-default select-none items-center gap-2.5 rounded-2xl px-3 py-2 font-medium text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-9.5 data-[variant=destructive]:text-destructive data-disabled:opacity-50 data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 data-[variant=destructive]:*:[svg]:text-destructive",
63
+ className,
64
+ )}
65
+ data-inset={inset}
66
+ data-slot="dropdown-menu-item"
67
+ data-variant={variant}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function DropdownMenuCheckboxItem({
74
+ className,
75
+ children,
76
+ checked,
77
+ inset,
78
+ ...props
79
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {
80
+ inset?: boolean
81
+ }) {
82
+ return (
83
+ <DropdownMenuPrimitive.CheckboxItem
84
+ checked={checked}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center gap-2.5 rounded-2xl py-2 pr-8 pl-3 font-medium text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-9.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
87
+ className,
88
+ )}
89
+ data-inset={inset}
90
+ data-slot="dropdown-menu-checkbox-item"
91
+ {...props}
92
+ >
93
+ <span
94
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
95
+ data-slot="dropdown-menu-checkbox-item-indicator"
96
+ >
97
+ <DropdownMenuPrimitive.ItemIndicator>
98
+ <CheckIcon />
99
+ </DropdownMenuPrimitive.ItemIndicator>
100
+ </span>
101
+ {children}
102
+ </DropdownMenuPrimitive.CheckboxItem>
103
+ )
104
+ }
105
+
106
+ function DropdownMenuRadioGroup({
107
+ ...props
108
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
109
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />
110
+ }
111
+
112
+ function DropdownMenuRadioItem({
113
+ className,
114
+ children,
115
+ inset,
116
+ ...props
117
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {
118
+ inset?: boolean
119
+ }) {
120
+ return (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ className={cn(
123
+ "relative flex cursor-default select-none items-center gap-2.5 rounded-2xl py-2 pr-8 pl-3 font-medium text-sm outline-hidden focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-disabled:pointer-events-none data-inset:pl-9.5 data-disabled:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
124
+ className,
125
+ )}
126
+ data-inset={inset}
127
+ data-slot="dropdown-menu-radio-item"
128
+ {...props}
129
+ >
130
+ <span
131
+ className="pointer-events-none absolute right-2 flex items-center justify-center"
132
+ data-slot="dropdown-menu-radio-item-indicator"
133
+ >
134
+ <DropdownMenuPrimitive.ItemIndicator>
135
+ <CheckIcon />
136
+ </DropdownMenuPrimitive.ItemIndicator>
137
+ </span>
138
+ {children}
139
+ </DropdownMenuPrimitive.RadioItem>
140
+ )
141
+ }
142
+
143
+ function DropdownMenuLabel({
144
+ className,
145
+ inset,
146
+ ...props
147
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
148
+ inset?: boolean
149
+ }) {
150
+ return (
151
+ <DropdownMenuPrimitive.Label
152
+ className={cn('px-3 py-2.5 text-muted-foreground text-xs data-inset:pl-9.5', className)}
153
+ data-inset={inset}
154
+ data-slot="dropdown-menu-label"
155
+ {...props}
156
+ />
157
+ )
158
+ }
159
+
160
+ function DropdownMenuSeparator({
161
+ className,
162
+ ...props
163
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
164
+ return (
165
+ <DropdownMenuPrimitive.Separator
166
+ className={cn('-mx-1.5 my-1.5 h-px bg-border/50', className)}
167
+ data-slot="dropdown-menu-separator"
168
+ {...props}
169
+ />
170
+ )
171
+ }
172
+
173
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
174
+ return (
175
+ <span
176
+ className={cn(
177
+ 'ml-auto text-muted-foreground text-xs tracking-widest group-focus/dropdown-menu-item:text-accent-foreground',
178
+ className,
179
+ )}
180
+ data-slot="dropdown-menu-shortcut"
181
+ {...props}
182
+ />
183
+ )
184
+ }
185
+
186
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
187
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
188
+ }
189
+
190
+ function DropdownMenuSubTrigger({
191
+ className,
192
+ inset,
193
+ children,
194
+ ...props
195
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
196
+ inset?: boolean
197
+ }) {
198
+ return (
199
+ <DropdownMenuPrimitive.SubTrigger
200
+ className={cn(
201
+ "flex cursor-default select-none items-center gap-2 rounded-2xl px-3 py-2 font-medium text-sm outline-hidden focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-open:bg-accent data-inset:pl-9.5 data-open:text-accent-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
202
+ className,
203
+ )}
204
+ data-inset={inset}
205
+ data-slot="dropdown-menu-sub-trigger"
206
+ {...props}
207
+ >
208
+ {children}
209
+ <ChevronRightIcon className="ml-auto" />
210
+ </DropdownMenuPrimitive.SubTrigger>
211
+ )
212
+ }
213
+
214
+ function DropdownMenuSubContent({
215
+ className,
216
+ ...props
217
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
218
+ return (
219
+ <DropdownMenuPrimitive.SubContent
220
+ className={cn(
221
+ '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 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 relative z-50 min-w-36 origin-(--radix-dropdown-menu-content-transform-origin) animate-none! overflow-hidden rounded-3xl bg-popover/70 p-1.5 text-popover-foreground shadow-lg ring-1 ring-foreground/5 duration-100 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 data-closed:animate-out data-open:animate-in **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[variant=destructive]:**:text-accent-foreground! **:data-[variant=destructive]:text-accent-foreground! **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[variant=destructive]:focus:bg-foreground/10! dark:ring-foreground/10',
222
+ className,
223
+ )}
224
+ data-slot="dropdown-menu-sub-content"
225
+ {...props}
226
+ />
227
+ )
228
+ }
229
+
230
+ export {
231
+ DropdownMenu,
232
+ DropdownMenuCheckboxItem,
233
+ DropdownMenuContent,
234
+ DropdownMenuGroup,
235
+ DropdownMenuItem,
236
+ DropdownMenuLabel,
237
+ DropdownMenuPortal,
238
+ DropdownMenuRadioGroup,
239
+ DropdownMenuRadioItem,
240
+ DropdownMenuSeparator,
241
+ DropdownMenuShortcut,
242
+ DropdownMenuSub,
243
+ DropdownMenuSubContent,
244
+ DropdownMenuSubTrigger,
245
+ DropdownMenuTrigger,
246
+ }
@@ -0,0 +1,97 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import type { ComponentProps } from 'react'
3
+ import { Button } from '~/components/ui/button'
4
+ import { Input } from '~/components/ui/input'
5
+ import { cn } from '~/lib/utils'
6
+
7
+ function InputGroup({ className, ...props }: ComponentProps<'div'>) {
8
+ return (
9
+ <div
10
+ className={cn(
11
+ 'group/input-group relative flex h-9 w-full min-w-0 items-center rounded-4xl border border-transparent bg-input/50 outline-none transition-[color,box-shadow,background-color] has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
12
+ className,
13
+ )}
14
+ data-slot="input-group"
15
+ role="group"
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ const inputGroupAddonVariants = cva(
22
+ "flex h-auto cursor-text select-none items-center justify-center gap-2 py-2 font-medium text-muted-foreground text-sm group-data-[disabled=true]/input-group:opacity-50 [&>svg:not([class*='size-'])]:size-4",
23
+ {
24
+ variants: {
25
+ align: {
26
+ 'inline-start': 'order-first pl-3 has-[>button]:-ml-1',
27
+ 'inline-end': 'order-last pr-3 has-[>button]:-mr-1',
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ align: 'inline-start',
32
+ },
33
+ },
34
+ )
35
+
36
+ function InputGroupAddon({
37
+ className,
38
+ align = 'inline-start',
39
+ ...props
40
+ }: ComponentProps<'div'> & VariantProps<typeof inputGroupAddonVariants>) {
41
+ return (
42
+ <div
43
+ className={cn(inputGroupAddonVariants({ align }), className)}
44
+ data-align={align}
45
+ data-slot="input-group-addon"
46
+ onClick={(e) => {
47
+ if ((e.target as HTMLElement).closest('button')) return
48
+ e.currentTarget.parentElement?.querySelector('input')?.focus()
49
+ }}
50
+ role="group"
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ function InputGroupText({ className, ...props }: ComponentProps<'span'>) {
57
+ return (
58
+ <span
59
+ className={cn(
60
+ "flex items-center gap-2 text-muted-foreground text-sm [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ }
67
+
68
+ function InputGroupInput({ className, ...props }: ComponentProps<'input'>) {
69
+ return (
70
+ <Input
71
+ className={cn(
72
+ 'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent',
73
+ className,
74
+ )}
75
+ data-slot="input-group-control"
76
+ {...props}
77
+ />
78
+ )
79
+ }
80
+
81
+ function InputGroupButton({
82
+ className,
83
+ type = 'button',
84
+ variant = 'ghost',
85
+ ...props
86
+ }: ComponentProps<typeof Button>) {
87
+ return (
88
+ <Button
89
+ className={cn('flex items-center gap-2 rounded-4xl text-sm shadow-none', className)}
90
+ type={type}
91
+ variant={variant}
92
+ {...props}
93
+ />
94
+ )
95
+ }
96
+
97
+ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText }
@@ -0,0 +1,18 @@
1
+ import type { ComponentProps } from 'react'
2
+ import { cn } from '~/lib/utils'
3
+
4
+ function Input({ className, type, ...props }: ComponentProps<'input'>) {
5
+ return (
6
+ <input
7
+ className={cn(
8
+ 'h-9 w-full min-w-0 rounded-3xl border border-transparent bg-input/50 px-3 py-1 text-base outline-none transition-[color,box-shadow,background-color] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
9
+ className,
10
+ )}
11
+ data-slot="input"
12
+ type={type}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Input }
@@ -0,0 +1,18 @@
1
+ import { Label as LabelPrimitive } from 'radix-ui'
2
+ import type { ComponentProps } from 'react'
3
+ import { cn } from '~/lib/utils'
4
+
5
+ function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {
6
+ return (
7
+ <LabelPrimitive.Root
8
+ className={cn(
9
+ 'flex select-none items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',
10
+ className,
11
+ )}
12
+ data-slot="label"
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Label }
@@ -0,0 +1,74 @@
1
+ import { Popover as PopoverPrimitive } from 'radix-ui'
2
+ import type * as React from 'react'
3
+
4
+ import { cn } from '~/lib/utils'
5
+
6
+ function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
7
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />
8
+ }
9
+
10
+ function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
11
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
12
+ }
13
+
14
+ function PopoverContent({
15
+ className,
16
+ align = 'center',
17
+ sideOffset = 4,
18
+ ...props
19
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
20
+ return (
21
+ <PopoverPrimitive.Portal>
22
+ <PopoverPrimitive.Content
23
+ align={align}
24
+ className={cn(
25
+ '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 data-open:fade-in-0 data-open:zoom-in-95 data-closed:fade-out-0 data-closed:zoom-out-95 z-50 flex w-72 origin-(--radix-popover-content-transform-origin) flex-col gap-4 rounded-3xl bg-popover p-4 text-popover-foreground text-sm shadow-lg outline-hidden ring-1 ring-foreground/5 duration-100 data-closed:animate-out data-open:animate-in dark:ring-foreground/10',
26
+ className,
27
+ )}
28
+ data-slot="popover-content"
29
+ sideOffset={sideOffset}
30
+ {...props}
31
+ />
32
+ </PopoverPrimitive.Portal>
33
+ )
34
+ }
35
+
36
+ function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
37
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
38
+ }
39
+
40
+ function PopoverHeader({ className, ...props }: React.ComponentProps<'div'>) {
41
+ return (
42
+ <div
43
+ className={cn('flex flex-col gap-1 text-sm', className)}
44
+ data-slot="popover-header"
45
+ {...props}
46
+ />
47
+ )
48
+ }
49
+
50
+ function PopoverTitle({ className, ...props }: React.ComponentProps<'h2'>) {
51
+ return (
52
+ <div className={cn('font-medium text-base', className)} data-slot="popover-title" {...props} />
53
+ )
54
+ }
55
+
56
+ function PopoverDescription({ className, ...props }: React.ComponentProps<'p'>) {
57
+ return (
58
+ <p
59
+ className={cn('text-muted-foreground', className)}
60
+ data-slot="popover-description"
61
+ {...props}
62
+ />
63
+ )
64
+ }
65
+
66
+ export {
67
+ Popover,
68
+ PopoverAnchor,
69
+ PopoverContent,
70
+ PopoverDescription,
71
+ PopoverHeader,
72
+ PopoverTitle,
73
+ PopoverTrigger,
74
+ }
@@ -0,0 +1,13 @@
1
+ import { cn } from '~/lib/utils'
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
4
+ return (
5
+ <div
6
+ className={cn('animate-pulse rounded-2xl bg-muted', className)}
7
+ data-slot="skeleton"
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }