@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.
- package/README.md +56 -0
- package/_stack/apps/next-base/.dockerignore +10 -0
- package/_stack/apps/next-base/Dockerfile +34 -0
- package/_stack/apps/next-base/README.md +32 -0
- package/_stack/apps/next-base/components.json +25 -0
- package/_stack/apps/next-base/drizzle.config.ts +16 -0
- package/_stack/apps/next-base/next.config.ts +8 -0
- package/_stack/apps/next-base/package.json +70 -0
- package/_stack/apps/next-base/postcss.config.mjs +7 -0
- package/_stack/apps/next-base/src/app/api/auth/[...all]/route.ts +4 -0
- package/_stack/apps/next-base/src/app/api/trpc/[trpc]/route.ts +23 -0
- package/_stack/apps/next-base/src/app/auth/_components/forgot-password-form.tsx +92 -0
- package/_stack/apps/next-base/src/app/auth/_components/reset-password-form.tsx +105 -0
- package/_stack/apps/next-base/src/app/auth/_components/sign-in-form.tsx +126 -0
- package/_stack/apps/next-base/src/app/auth/_components/sign-up-form.tsx +139 -0
- package/_stack/apps/next-base/src/app/auth/_components/verify-email-actions.tsx +45 -0
- package/_stack/apps/next-base/src/app/auth/forgot-password/page.tsx +19 -0
- package/_stack/apps/next-base/src/app/auth/layout.tsx +9 -0
- package/_stack/apps/next-base/src/app/auth/reset-password/page.tsx +26 -0
- package/_stack/apps/next-base/src/app/auth/sign-in/page.tsx +27 -0
- package/_stack/apps/next-base/src/app/auth/sign-up/page.tsx +27 -0
- package/_stack/apps/next-base/src/app/auth/verify-email/page.tsx +30 -0
- package/_stack/apps/next-base/src/app/dashboard/page.tsx +12 -0
- package/_stack/apps/next-base/src/app/globals.css +171 -0
- package/_stack/apps/next-base/src/app/layout.tsx +23 -0
- package/_stack/apps/next-base/src/app/page.tsx +15 -0
- package/_stack/apps/next-base/src/components/data-table.tsx +77 -0
- package/_stack/apps/next-base/src/components/infinite-data-table.tsx +102 -0
- package/_stack/apps/next-base/src/components/sortable-header.tsx +37 -0
- package/_stack/apps/next-base/src/components/theme-provider.tsx +8 -0
- package/_stack/apps/next-base/src/components/theme-toggle.tsx +37 -0
- package/_stack/apps/next-base/src/components/ui/button.tsx +64 -0
- package/_stack/apps/next-base/src/components/ui/calendar.tsx +185 -0
- package/_stack/apps/next-base/src/components/ui/card.tsx +84 -0
- package/_stack/apps/next-base/src/components/ui/date-picker.tsx +85 -0
- package/_stack/apps/next-base/src/components/ui/date-range-picker.tsx +138 -0
- package/_stack/apps/next-base/src/components/ui/dropdown-menu.tsx +246 -0
- package/_stack/apps/next-base/src/components/ui/form.tsx +149 -0
- package/_stack/apps/next-base/src/components/ui/input-group.tsx +97 -0
- package/_stack/apps/next-base/src/components/ui/input.tsx +18 -0
- package/_stack/apps/next-base/src/components/ui/label.tsx +18 -0
- package/_stack/apps/next-base/src/components/ui/popover.tsx +76 -0
- package/_stack/apps/next-base/src/components/ui/skeleton.tsx +13 -0
- package/_stack/apps/next-base/src/components/ui/spinner.tsx +8 -0
- package/_stack/apps/next-base/src/components/ui/table.tsx +87 -0
- package/_stack/apps/next-base/src/emails/components/components.tsx +199 -0
- package/_stack/apps/next-base/src/emails/components/context.tsx +18 -0
- package/_stack/apps/next-base/src/emails/components/index.ts +23 -0
- package/_stack/apps/next-base/src/emails/components/theme.ts +65 -0
- package/_stack/apps/next-base/src/emails/reset-password.tsx +16 -0
- package/_stack/apps/next-base/src/emails/verify-email.tsx +15 -0
- package/_stack/apps/next-base/src/env.ts +41 -0
- package/_stack/apps/next-base/src/features/auth/auth-card.tsx +30 -0
- package/_stack/apps/next-base/src/features/auth/form-alert.tsx +27 -0
- package/_stack/apps/next-base/src/features/auth/google-button.tsx +66 -0
- package/_stack/apps/next-base/src/features/auth/schemas.ts +35 -0
- package/_stack/apps/next-base/src/lib/date.ts +4 -0
- package/_stack/apps/next-base/src/lib/utils.ts +6 -0
- package/_stack/apps/next-base/src/server/api/root.ts +10 -0
- package/_stack/apps/next-base/src/server/api/routers/health.router.ts +8 -0
- package/_stack/apps/next-base/src/server/api/trpc.ts +56 -0
- package/_stack/apps/next-base/src/server/auth/guards.ts +10 -0
- package/_stack/apps/next-base/src/server/better-auth/client.ts +9 -0
- package/_stack/apps/next-base/src/server/better-auth/config.ts +60 -0
- package/_stack/apps/next-base/src/server/better-auth/emails.tsx +25 -0
- package/_stack/apps/next-base/src/server/better-auth/index.ts +1 -0
- package/_stack/apps/next-base/src/server/better-auth/server.ts +14 -0
- package/_stack/apps/next-base/src/server/db/index.ts +6 -0
- package/_stack/apps/next-base/src/server/db/keyset.ts +63 -0
- package/_stack/apps/next-base/src/server/db/schemas/auth.schema.ts +71 -0
- package/_stack/apps/next-base/src/server/db/schemas/index.ts +2 -0
- package/_stack/apps/next-base/src/server/db/seed.ts +27 -0
- package/_stack/apps/next-base/src/server/email/adapters/resend/config.ts +7 -0
- package/_stack/apps/next-base/src/server/email/adapters/resend/index.ts +75 -0
- package/_stack/apps/next-base/src/server/email/core/address.ts +21 -0
- package/_stack/apps/next-base/src/server/email/core/port.ts +89 -0
- package/_stack/apps/next-base/src/server/email/core/render.ts +16 -0
- package/_stack/apps/next-base/src/server/email/factory.ts +47 -0
- package/_stack/apps/next-base/src/server/email/index.ts +36 -0
- package/_stack/apps/next-base/src/trpc/query-client.ts +19 -0
- package/_stack/apps/next-base/src/trpc/react.tsx +62 -0
- package/_stack/apps/next-base/src/trpc/server.ts +23 -0
- package/_stack/apps/next-base/tsconfig.json +37 -0
- package/_stack/apps/tanstack-base/.dockerignore +13 -0
- package/_stack/apps/tanstack-base/Dockerfile +28 -0
- package/_stack/apps/tanstack-base/README.md +31 -0
- package/_stack/apps/tanstack-base/components.json +25 -0
- package/_stack/apps/tanstack-base/drizzle.config.ts +16 -0
- package/_stack/apps/tanstack-base/package.json +85 -0
- package/_stack/apps/tanstack-base/public/favicon.ico +0 -0
- package/_stack/apps/tanstack-base/public/logo192.png +0 -0
- package/_stack/apps/tanstack-base/public/logo512.png +0 -0
- package/_stack/apps/tanstack-base/public/manifest.json +25 -0
- package/_stack/apps/tanstack-base/public/robots.txt +3 -0
- package/_stack/apps/tanstack-base/src/components/data-table.tsx +77 -0
- package/_stack/apps/tanstack-base/src/components/form/field-error.tsx +18 -0
- package/_stack/apps/tanstack-base/src/components/form/text-field.tsx +47 -0
- package/_stack/apps/tanstack-base/src/components/infinite-data-table.tsx +102 -0
- package/_stack/apps/tanstack-base/src/components/sortable-header.tsx +37 -0
- package/_stack/apps/tanstack-base/src/components/theme-provider.tsx +69 -0
- package/_stack/apps/tanstack-base/src/components/theme-toggle.tsx +35 -0
- package/_stack/apps/tanstack-base/src/components/ui/button.tsx +64 -0
- package/_stack/apps/tanstack-base/src/components/ui/calendar.tsx +185 -0
- package/_stack/apps/tanstack-base/src/components/ui/card.tsx +84 -0
- package/_stack/apps/tanstack-base/src/components/ui/date-picker.tsx +83 -0
- package/_stack/apps/tanstack-base/src/components/ui/date-range-picker.tsx +136 -0
- package/_stack/apps/tanstack-base/src/components/ui/dropdown-menu.tsx +246 -0
- package/_stack/apps/tanstack-base/src/components/ui/input-group.tsx +97 -0
- package/_stack/apps/tanstack-base/src/components/ui/input.tsx +18 -0
- package/_stack/apps/tanstack-base/src/components/ui/label.tsx +18 -0
- package/_stack/apps/tanstack-base/src/components/ui/popover.tsx +74 -0
- package/_stack/apps/tanstack-base/src/components/ui/skeleton.tsx +13 -0
- package/_stack/apps/tanstack-base/src/components/ui/spinner.tsx +8 -0
- package/_stack/apps/tanstack-base/src/components/ui/table.tsx +87 -0
- package/_stack/apps/tanstack-base/src/emails/components/components.tsx +199 -0
- package/_stack/apps/tanstack-base/src/emails/components/context.tsx +18 -0
- package/_stack/apps/tanstack-base/src/emails/components/index.ts +23 -0
- package/_stack/apps/tanstack-base/src/emails/components/theme.ts +65 -0
- package/_stack/apps/tanstack-base/src/emails/reset-password.tsx +16 -0
- package/_stack/apps/tanstack-base/src/emails/verify-email.tsx +15 -0
- package/_stack/apps/tanstack-base/src/env.ts +41 -0
- package/_stack/apps/tanstack-base/src/features/auth/auth-card.tsx +30 -0
- package/_stack/apps/tanstack-base/src/features/auth/form-alert.tsx +27 -0
- package/_stack/apps/tanstack-base/src/features/auth/google-button.tsx +64 -0
- package/_stack/apps/tanstack-base/src/features/auth/schemas.ts +35 -0
- package/_stack/apps/tanstack-base/src/lib/date.ts +4 -0
- package/_stack/apps/tanstack-base/src/lib/utils.ts +6 -0
- package/_stack/apps/tanstack-base/src/router.tsx +40 -0
- package/_stack/apps/tanstack-base/src/routes/__root.tsx +73 -0
- package/_stack/apps/tanstack-base/src/routes/_authed/dashboard.tsx +12 -0
- package/_stack/apps/tanstack-base/src/routes/_authed.tsx +21 -0
- package/_stack/apps/tanstack-base/src/routes/api/auth/$.ts +14 -0
- package/_stack/apps/tanstack-base/src/routes/api.trpc.$.tsx +31 -0
- package/_stack/apps/tanstack-base/src/routes/auth/forgot-password.tsx +89 -0
- package/_stack/apps/tanstack-base/src/routes/auth/reset-password.tsx +111 -0
- package/_stack/apps/tanstack-base/src/routes/auth/sign-in.tsx +117 -0
- package/_stack/apps/tanstack-base/src/routes/auth/sign-up.tsx +119 -0
- package/_stack/apps/tanstack-base/src/routes/auth/verify-email.tsx +72 -0
- package/_stack/apps/tanstack-base/src/routes/auth.tsx +22 -0
- package/_stack/apps/tanstack-base/src/routes/index.tsx +18 -0
- package/_stack/apps/tanstack-base/src/server/api/root.ts +10 -0
- package/_stack/apps/tanstack-base/src/server/api/routers/health.router.ts +8 -0
- package/_stack/apps/tanstack-base/src/server/api/trpc.ts +61 -0
- package/_stack/apps/tanstack-base/src/server/better-auth/client.ts +9 -0
- package/_stack/apps/tanstack-base/src/server/better-auth/config.ts +68 -0
- package/_stack/apps/tanstack-base/src/server/better-auth/emails.tsx +25 -0
- package/_stack/apps/tanstack-base/src/server/better-auth/index.ts +1 -0
- package/_stack/apps/tanstack-base/src/server/better-auth/session.ts +9 -0
- package/_stack/apps/tanstack-base/src/server/db/index.ts +6 -0
- package/_stack/apps/tanstack-base/src/server/db/keyset.ts +63 -0
- package/_stack/apps/tanstack-base/src/server/db/schemas/auth.schema.ts +71 -0
- package/_stack/apps/tanstack-base/src/server/db/schemas/index.ts +2 -0
- package/_stack/apps/tanstack-base/src/server/db/seed.ts +27 -0
- package/_stack/apps/tanstack-base/src/server/email/adapters/resend/config.ts +7 -0
- package/_stack/apps/tanstack-base/src/server/email/adapters/resend/index.ts +75 -0
- package/_stack/apps/tanstack-base/src/server/email/core/address.ts +21 -0
- package/_stack/apps/tanstack-base/src/server/email/core/port.ts +89 -0
- package/_stack/apps/tanstack-base/src/server/email/core/render.ts +16 -0
- package/_stack/apps/tanstack-base/src/server/email/factory.ts +47 -0
- package/_stack/apps/tanstack-base/src/server/email/index.ts +36 -0
- package/_stack/apps/tanstack-base/src/styles.css +171 -0
- package/_stack/apps/tanstack-base/src/trpc/devtools.tsx +6 -0
- package/_stack/apps/tanstack-base/src/trpc/query-client.ts +19 -0
- package/_stack/apps/tanstack-base/src/trpc/react.tsx +49 -0
- package/_stack/apps/tanstack-base/src/trpc/server.ts +11 -0
- package/_stack/apps/tanstack-base/tsconfig.json +27 -0
- package/_stack/apps/tanstack-base/tsr.config.json +3 -0
- package/_stack/apps/tanstack-base/vite.config.ts +15 -0
- package/_stack/packages/analytics/capability.json +26 -0
- package/_stack/packages/cache/capability.json +21 -0
- package/_stack/packages/error-tracking/capability.json +21 -0
- package/_stack/packages/jobs/capability.json +26 -0
- package/_stack/packages/logger/capability.json +21 -0
- package/_stack/packages/mailer/capability.json +28 -0
- package/_stack/packages/mailer/package.json +37 -0
- package/_stack/packages/mailer/src/adapters/brevo/config.ts +7 -0
- package/_stack/packages/mailer/src/adapters/brevo/index.ts +90 -0
- package/_stack/packages/mailer/src/adapters/resend/config.ts +7 -0
- package/_stack/packages/mailer/src/adapters/resend/index.ts +75 -0
- package/_stack/packages/mailer/src/adapters/ses/config.ts +13 -0
- package/_stack/packages/mailer/src/adapters/ses/index.ts +103 -0
- package/_stack/packages/storage/capability.json +32 -0
- package/_stack/patterns/README.md +58 -0
- package/_stack/patterns/_baseline/README-author.md +10 -0
- package/_stack/patterns/_baseline/biome.jsonc +119 -0
- package/_stack/patterns/_baseline/env.ts +31 -0
- package/_stack/patterns/_baseline/tsconfig.json +27 -0
- package/_stack/patterns/better-auth/pattern.json +73 -0
- package/_stack/patterns/better-auth-next/pattern.json +76 -0
- package/_stack/patterns/data-table/pattern.json +43 -0
- package/_stack/patterns/drizzle/pattern.json +61 -0
- package/_stack/patterns/trpc/pattern.json +61 -0
- package/_stack/patterns/trpc-next/pattern.json +64 -0
- package/index.mjs +216 -0
- package/lib/build.mjs +64 -0
- package/lib/env.mjs +56 -0
- package/lib/identity.mjs +33 -0
- package/lib/mailer.mjs +95 -0
- package/lib/manifests.mjs +61 -0
- package/lib/scaffold.mjs +49 -0
- package/lib/strip.mjs +132 -0
- package/lib/util.mjs +82 -0
- package/package.json +51 -0
- package/templates/next/layout.no-trpc.tsx +22 -0
- package/templates/tanstack/__root.no-trpc.tsx +63 -0
- package/templates/tanstack/router.no-trpc.tsx +24 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { format, parseISO } from 'date-fns'
|
|
4
|
+
import { CalendarRange, X } from 'lucide-react'
|
|
5
|
+
import { type ComponentProps, useState } from 'react'
|
|
6
|
+
import type { DateRange } from 'react-day-picker'
|
|
7
|
+
import { Button } from '~/components/ui/button'
|
|
8
|
+
import { Calendar } from '~/components/ui/calendar'
|
|
9
|
+
import { Popover, PopoverContent, PopoverTrigger } from '~/components/ui/popover'
|
|
10
|
+
import { toISODate } from '~/lib/date'
|
|
11
|
+
import { cn } from '~/lib/utils'
|
|
12
|
+
|
|
13
|
+
export interface DateRangeValue {
|
|
14
|
+
from: string
|
|
15
|
+
to: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface DateRangePickerProps {
|
|
19
|
+
value: DateRangeValue | null
|
|
20
|
+
onChange: (value: DateRangeValue | null) => void
|
|
21
|
+
placeholder?: string
|
|
22
|
+
numberOfMonths?: number
|
|
23
|
+
align?: ComponentProps<typeof PopoverContent>['align']
|
|
24
|
+
triggerVariant?: ComponentProps<typeof Button>['variant']
|
|
25
|
+
triggerClassName?: string
|
|
26
|
+
formatLabel?: (value: DateRangeValue) => string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultFormatLabel = (value: DateRangeValue) =>
|
|
30
|
+
`${format(parseISO(value.from), 'd MMM')} – ${format(parseISO(value.to), 'd MMM')}`
|
|
31
|
+
|
|
32
|
+
export function DateRangePicker({
|
|
33
|
+
value,
|
|
34
|
+
onChange,
|
|
35
|
+
placeholder = 'Plage de dates',
|
|
36
|
+
numberOfMonths = 2,
|
|
37
|
+
align = 'end',
|
|
38
|
+
triggerVariant = 'outline',
|
|
39
|
+
triggerClassName,
|
|
40
|
+
formatLabel = defaultFormatLabel,
|
|
41
|
+
}: DateRangePickerProps) {
|
|
42
|
+
const [open, setOpen] = useState(false)
|
|
43
|
+
const [draft, setDraft] = useState<DateRange | undefined>()
|
|
44
|
+
const hasValue = value !== null
|
|
45
|
+
|
|
46
|
+
const handleOpenChange = (next: boolean) => {
|
|
47
|
+
if (next) {
|
|
48
|
+
setDraft(value ? { from: parseISO(value.from), to: parseISO(value.to) } : undefined)
|
|
49
|
+
}
|
|
50
|
+
setOpen(next)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const applyDraft = () => {
|
|
54
|
+
if (!(draft?.from && draft.to)) return
|
|
55
|
+
onChange({
|
|
56
|
+
from: toISODate(draft.from),
|
|
57
|
+
to: toISODate(draft.to),
|
|
58
|
+
})
|
|
59
|
+
setOpen(false)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const reset = () => {
|
|
63
|
+
setDraft(undefined)
|
|
64
|
+
onChange(null)
|
|
65
|
+
setOpen(false)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Popover onOpenChange={handleOpenChange} open={open}>
|
|
70
|
+
<div className="group relative inline-flex shrink-0">
|
|
71
|
+
<PopoverTrigger asChild>
|
|
72
|
+
<Button
|
|
73
|
+
className={cn('cursor-pointer', triggerClassName)}
|
|
74
|
+
size="sm"
|
|
75
|
+
variant={hasValue ? 'secondary' : triggerVariant}
|
|
76
|
+
>
|
|
77
|
+
<CalendarRange
|
|
78
|
+
className={cn('transition-opacity', hasValue && 'group-hover:opacity-0')}
|
|
79
|
+
/>
|
|
80
|
+
{hasValue ? formatLabel(value) : placeholder}
|
|
81
|
+
</Button>
|
|
82
|
+
</PopoverTrigger>
|
|
83
|
+
|
|
84
|
+
{hasValue && (
|
|
85
|
+
<button
|
|
86
|
+
aria-label="Clear range"
|
|
87
|
+
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"
|
|
88
|
+
onClick={(event) => {
|
|
89
|
+
event.stopPropagation()
|
|
90
|
+
reset()
|
|
91
|
+
}}
|
|
92
|
+
type="button"
|
|
93
|
+
>
|
|
94
|
+
<X className="size-4" />
|
|
95
|
+
</button>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
<PopoverContent align={align} className="w-auto p-0">
|
|
99
|
+
<Calendar
|
|
100
|
+
autoFocus
|
|
101
|
+
mode="range"
|
|
102
|
+
numberOfMonths={numberOfMonths}
|
|
103
|
+
onSelect={setDraft}
|
|
104
|
+
selected={draft}
|
|
105
|
+
/>
|
|
106
|
+
<div className="flex items-center justify-between gap-2 border-t p-2">
|
|
107
|
+
<Button
|
|
108
|
+
className="cursor-pointer"
|
|
109
|
+
disabled={!(hasValue || draft?.from)}
|
|
110
|
+
onClick={reset}
|
|
111
|
+
size="sm"
|
|
112
|
+
variant="ghost"
|
|
113
|
+
>
|
|
114
|
+
Réinitialiser
|
|
115
|
+
</Button>
|
|
116
|
+
<div className="flex gap-2">
|
|
117
|
+
<Button
|
|
118
|
+
className="cursor-pointer"
|
|
119
|
+
onClick={() => setOpen(false)}
|
|
120
|
+
size="sm"
|
|
121
|
+
variant="ghost"
|
|
122
|
+
>
|
|
123
|
+
Annuler
|
|
124
|
+
</Button>
|
|
125
|
+
<Button
|
|
126
|
+
className="cursor-pointer"
|
|
127
|
+
disabled={!(draft?.from && draft.to)}
|
|
128
|
+
onClick={applyDraft}
|
|
129
|
+
size="sm"
|
|
130
|
+
>
|
|
131
|
+
Appliquer
|
|
132
|
+
</Button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
</PopoverContent>
|
|
136
|
+
</Popover>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
@@ -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,149 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { Label as LabelPrimitive } from 'radix-ui'
|
|
4
|
+
import { Slot } from 'radix-ui'
|
|
5
|
+
import { type ComponentProps, createContext, useContext, useId } from 'react'
|
|
6
|
+
import {
|
|
7
|
+
Controller,
|
|
8
|
+
type ControllerProps,
|
|
9
|
+
type FieldPath,
|
|
10
|
+
type FieldValues,
|
|
11
|
+
FormProvider,
|
|
12
|
+
useFormContext,
|
|
13
|
+
useFormState,
|
|
14
|
+
} from 'react-hook-form'
|
|
15
|
+
import { Label } from '~/components/ui/label'
|
|
16
|
+
import { cn } from '~/lib/utils'
|
|
17
|
+
|
|
18
|
+
const Form = FormProvider
|
|
19
|
+
|
|
20
|
+
type FormFieldContextValue<
|
|
21
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
22
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
23
|
+
> = {
|
|
24
|
+
name: TName
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FormFieldContext = createContext<FormFieldContextValue>({} as FormFieldContextValue)
|
|
28
|
+
|
|
29
|
+
const FormField = <
|
|
30
|
+
TFieldValues extends FieldValues = FieldValues,
|
|
31
|
+
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
32
|
+
>({
|
|
33
|
+
...props
|
|
34
|
+
}: ControllerProps<TFieldValues, TName>) => {
|
|
35
|
+
return (
|
|
36
|
+
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
37
|
+
<Controller {...props} />
|
|
38
|
+
</FormFieldContext.Provider>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const useFormField = () => {
|
|
43
|
+
const fieldContext = useContext(FormFieldContext)
|
|
44
|
+
const itemContext = useContext(FormItemContext)
|
|
45
|
+
const { getFieldState } = useFormContext()
|
|
46
|
+
const formState = useFormState({ name: fieldContext.name })
|
|
47
|
+
const fieldState = getFieldState(fieldContext.name, formState)
|
|
48
|
+
|
|
49
|
+
if (!fieldContext) {
|
|
50
|
+
throw new Error('useFormField should be used within <FormField>')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { id } = itemContext
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
name: fieldContext.name,
|
|
58
|
+
formItemId: `${id}-form-item`,
|
|
59
|
+
formDescriptionId: `${id}-form-item-description`,
|
|
60
|
+
formMessageId: `${id}-form-item-message`,
|
|
61
|
+
...fieldState,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type FormItemContextValue = {
|
|
66
|
+
id: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const FormItemContext = createContext<FormItemContextValue>({} as FormItemContextValue)
|
|
70
|
+
|
|
71
|
+
function FormItem({ className, ...props }: ComponentProps<'div'>) {
|
|
72
|
+
const id = useId()
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<FormItemContext.Provider value={{ id }}>
|
|
76
|
+
<div className={cn('grid gap-2', className)} data-slot="form-item" {...props} />
|
|
77
|
+
</FormItemContext.Provider>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function FormLabel({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {
|
|
82
|
+
const { error, formItemId } = useFormField()
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Label
|
|
86
|
+
className={cn('data-[error=true]:text-destructive', className)}
|
|
87
|
+
data-error={!!error}
|
|
88
|
+
data-slot="form-label"
|
|
89
|
+
htmlFor={formItemId}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function FormControl({ ...props }: ComponentProps<typeof Slot.Root>) {
|
|
96
|
+
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Slot.Root
|
|
100
|
+
aria-describedby={error ? `${formDescriptionId} ${formMessageId}` : `${formDescriptionId}`}
|
|
101
|
+
aria-invalid={!!error}
|
|
102
|
+
data-slot="form-control"
|
|
103
|
+
id={formItemId}
|
|
104
|
+
{...props}
|
|
105
|
+
/>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function FormDescription({ className, ...props }: ComponentProps<'p'>) {
|
|
110
|
+
const { formDescriptionId } = useFormField()
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<p
|
|
114
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
115
|
+
data-slot="form-description"
|
|
116
|
+
id={formDescriptionId}
|
|
117
|
+
{...props}
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function FormMessage({ className, ...props }: ComponentProps<'p'>) {
|
|
123
|
+
const { error, formMessageId } = useFormField()
|
|
124
|
+
const body = error ? String(error?.message ?? '') : props.children
|
|
125
|
+
|
|
126
|
+
if (!body) return null
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<p
|
|
130
|
+
className={cn('text-destructive text-xs', className)}
|
|
131
|
+
data-slot="form-message"
|
|
132
|
+
id={formMessageId}
|
|
133
|
+
{...props}
|
|
134
|
+
>
|
|
135
|
+
{body}
|
|
136
|
+
</p>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
Form,
|
|
142
|
+
FormControl,
|
|
143
|
+
FormDescription,
|
|
144
|
+
FormField,
|
|
145
|
+
FormItem,
|
|
146
|
+
FormLabel,
|
|
147
|
+
FormMessage,
|
|
148
|
+
useFormField,
|
|
149
|
+
}
|
|
@@ -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 }
|