@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,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 }
|