create-surf-app 0.1.5 → 0.1.6

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 (77) hide show
  1. package/dist/chunk-AMAMASIV.js +1016 -0
  2. package/dist/cli.js +51 -0
  3. package/dist/index.js +5 -387
  4. package/dist/templates/default/CLAUDE.md +46 -0
  5. package/dist/templates/default/backend/db/index.js +23 -0
  6. package/dist/templates/default/backend/db/schema.js +20 -0
  7. package/dist/templates/{backend → default/backend}/eslint.config.mjs +1 -1
  8. package/dist/templates/default/backend/lib/db.js +67 -0
  9. package/dist/templates/default/backend/package.json +25 -0
  10. package/dist/templates/default/backend/routes/proxy.js +66 -0
  11. package/dist/templates/default/backend/server.js +444 -0
  12. package/dist/templates/default/frontend/components.json +21 -0
  13. package/{templates → dist/templates/default}/frontend/eslint.config.js +1 -1
  14. package/dist/templates/default/frontend/package.json +82 -0
  15. package/dist/templates/default/frontend/src/App.tsx +23 -0
  16. package/dist/templates/default/frontend/src/ErrorBoundary.tsx +106 -0
  17. package/dist/templates/default/frontend/src/components/ui/accordion.tsx +55 -0
  18. package/dist/templates/default/frontend/src/components/ui/alert.tsx +59 -0
  19. package/dist/templates/default/frontend/src/components/ui/aspect-ratio.tsx +5 -0
  20. package/dist/templates/default/frontend/src/components/ui/avatar.tsx +48 -0
  21. package/dist/templates/default/frontend/src/components/ui/badge.tsx +36 -0
  22. package/dist/templates/default/frontend/src/components/ui/breadcrumb.tsx +115 -0
  23. package/dist/templates/default/frontend/src/components/ui/button.tsx +57 -0
  24. package/dist/templates/default/frontend/src/components/ui/calendar.tsx +211 -0
  25. package/dist/templates/default/frontend/src/components/ui/card.tsx +76 -0
  26. package/dist/templates/default/frontend/src/components/ui/carousel.tsx +262 -0
  27. package/dist/templates/default/frontend/src/components/ui/checkbox.tsx +30 -0
  28. package/dist/templates/default/frontend/src/components/ui/collapsible.tsx +9 -0
  29. package/dist/templates/default/frontend/src/components/ui/command.tsx +153 -0
  30. package/dist/templates/default/frontend/src/components/ui/context-menu.tsx +200 -0
  31. package/dist/templates/default/frontend/src/components/ui/dialog.tsx +120 -0
  32. package/dist/templates/default/frontend/src/components/ui/drawer.tsx +118 -0
  33. package/dist/templates/default/frontend/src/components/ui/dropdown-menu.tsx +201 -0
  34. package/dist/templates/default/frontend/src/components/ui/form.tsx +176 -0
  35. package/dist/templates/default/frontend/src/components/ui/hover-card.tsx +29 -0
  36. package/dist/templates/default/frontend/src/components/ui/input.tsx +22 -0
  37. package/dist/templates/default/frontend/src/components/ui/label.tsx +26 -0
  38. package/dist/templates/default/frontend/src/components/ui/menubar.tsx +256 -0
  39. package/dist/templates/default/frontend/src/components/ui/navigation-menu.tsx +128 -0
  40. package/dist/templates/default/frontend/src/components/ui/popover.tsx +33 -0
  41. package/dist/templates/default/frontend/src/components/ui/progress.tsx +26 -0
  42. package/dist/templates/default/frontend/src/components/ui/radio-group.tsx +42 -0
  43. package/dist/templates/default/frontend/src/components/ui/resizable.tsx +43 -0
  44. package/dist/templates/default/frontend/src/components/ui/scroll-area.tsx +46 -0
  45. package/dist/templates/default/frontend/src/components/ui/select.tsx +157 -0
  46. package/dist/templates/default/frontend/src/components/ui/separator.tsx +31 -0
  47. package/dist/templates/default/frontend/src/components/ui/sheet.tsx +140 -0
  48. package/dist/templates/default/frontend/src/components/ui/skeleton.tsx +15 -0
  49. package/dist/templates/default/frontend/src/components/ui/slider.tsx +26 -0
  50. package/dist/templates/default/frontend/src/components/ui/sonner.tsx +29 -0
  51. package/dist/templates/default/frontend/src/components/ui/switch.tsx +29 -0
  52. package/dist/templates/default/frontend/src/components/ui/table.tsx +120 -0
  53. package/dist/templates/default/frontend/src/components/ui/tabs.tsx +53 -0
  54. package/dist/templates/default/frontend/src/components/ui/textarea.tsx +22 -0
  55. package/dist/templates/default/frontend/src/components/ui/toast.tsx +129 -0
  56. package/dist/templates/default/frontend/src/components/ui/toaster.tsx +35 -0
  57. package/dist/templates/default/frontend/src/components/ui/toggle-group.tsx +59 -0
  58. package/dist/templates/default/frontend/src/components/ui/toggle.tsx +43 -0
  59. package/dist/templates/default/frontend/src/components/ui/tooltip.tsx +30 -0
  60. package/dist/templates/default/frontend/src/db/schema.ts +16 -0
  61. package/{templates → dist/templates/default}/frontend/src/entry-client.tsx +11 -8
  62. package/dist/templates/default/frontend/src/hooks/use-toast.ts +95 -0
  63. package/dist/templates/default/frontend/src/index.css +314 -0
  64. package/dist/templates/default/frontend/src/lib/api.ts +31 -0
  65. package/dist/templates/default/frontend/src/lib/fetch.ts +38 -0
  66. package/dist/templates/default/frontend/src/lib/utils.ts +6 -0
  67. package/dist/templates/default/frontend/src/vite-env.d.ts +11 -0
  68. package/dist/templates/default/frontend/tsconfig.json +22 -0
  69. package/dist/templates/default/frontend/vite.config.ts +162 -0
  70. package/package.json +7 -7
  71. package/dist/templates/frontend/eslint.config.js +0 -42
  72. package/dist/templates/frontend/src/entry-client.tsx +0 -109
  73. package/templates/backend/eslint.config.mjs +0 -21
  74. package/templates/frontend/index.html +0 -43
  75. package/templates/frontend/src/entry-server.tsx +0 -13
  76. /package/dist/templates/{frontend → default/frontend}/index.html +0 -0
  77. /package/dist/templates/{frontend → default/frontend}/src/entry-server.tsx +0 -0
@@ -0,0 +1,106 @@
1
+ import { Component, type ReactNode } from 'react'
2
+
3
+ interface Props { children: ReactNode; fallback?: ReactNode }
4
+ interface State { error: Error | null }
5
+
6
+ // Auto-reload config for transient dep-optimization errors.
7
+ // Shares the same key as entry-client's cold-start guard so reload
8
+ // attempts are counted together, preventing double reload loops.
9
+ const RELOAD_KEY = '__dep_reload'
10
+ const MAX_RELOADS = 6
11
+ // If the last reload was more than this many ms ago, reset the counter.
12
+ // This prevents stale counters from blocking legitimate retries on a
13
+ // later visit, while still capping rapid reload loops.
14
+ const RELOAD_WINDOW_MS = 60_000
15
+
16
+ // Patterns that indicate React modules loaded as stubs (dep optimization in progress)
17
+ const DEP_OPT_PATTERNS = [
18
+ "reading 'useState'",
19
+ "reading 'useEffect'",
20
+ "reading 'useRef'",
21
+ "reading 'useCallback'",
22
+ "reading 'useMemo'",
23
+ "reading 'useContext'",
24
+ "reading 'useReducer'",
25
+ ]
26
+
27
+ function isDepOptError(msg: string): boolean {
28
+ return DEP_OPT_PATTERNS.some((p) => msg.includes(p))
29
+ }
30
+
31
+ // Shared format with entry-client: { c: count, t: timestamp }
32
+ function getReloadState(): { c: number; t: number } {
33
+ try {
34
+ const raw = sessionStorage.getItem(RELOAD_KEY)
35
+ if (raw) return JSON.parse(raw)
36
+ } catch { /* ignore parse errors */ }
37
+ return { c: 0, t: 0 }
38
+ }
39
+
40
+ export default class ErrorBoundary extends Component<Props, State> {
41
+ state: State = { error: null }
42
+
43
+ static getDerivedStateFromError(error: Error) {
44
+ return { error }
45
+ }
46
+
47
+ componentDidCatch(error: Error) {
48
+ if (!isDepOptError(error.message)) return
49
+ // Vite dep optimization may serve React stubs during cold start.
50
+ // Auto-reload so the browser fetches the real modules once ready.
51
+ const prev = getReloadState()
52
+ // Reset counter if outside the rapid-reload window (stale from earlier visit)
53
+ const count = (Date.now() - prev.t > RELOAD_WINDOW_MS) ? 0 : prev.c
54
+ if (count < MAX_RELOADS) {
55
+ sessionStorage.setItem(RELOAD_KEY, JSON.stringify({ c: count + 1, t: Date.now() }))
56
+ setTimeout(() => location.reload(), 3000)
57
+ }
58
+ }
59
+
60
+ render() {
61
+ if (this.state.error) {
62
+ if (this.props.fallback) return this.props.fallback
63
+
64
+ // Show a friendlier message for dep optimization errors that will auto-reload
65
+ if (isDepOptError(this.state.error.message)) {
66
+ const { c, t } = getReloadState()
67
+ const fresh = (Date.now() - t <= RELOAD_WINDOW_MS)
68
+ if (!fresh || c < MAX_RELOADS) {
69
+ return (
70
+ <div style={{
71
+ padding: '24px',
72
+ margin: '16px',
73
+ borderRadius: '12px',
74
+ background: 'rgba(59,130,246,0.06)',
75
+ border: '1px solid rgba(59,130,246,0.15)',
76
+ color: '#3b82f6',
77
+ fontSize: '13px',
78
+ fontFamily: 'system-ui, sans-serif',
79
+ textAlign: 'center',
80
+ }}>
81
+ <p style={{ fontWeight: 600, marginBottom: '4px' }}>Loading dependencies...</p>
82
+ <p style={{ opacity: 0.7, fontSize: '12px' }}>Reloading automatically</p>
83
+ </div>
84
+ )
85
+ }
86
+ }
87
+
88
+ return (
89
+ <div style={{
90
+ padding: '24px',
91
+ margin: '16px',
92
+ borderRadius: '12px',
93
+ background: 'rgba(245,34,45,0.06)',
94
+ border: '1px solid rgba(245,34,45,0.15)',
95
+ color: '#c0392b',
96
+ fontSize: '13px',
97
+ fontFamily: 'monospace',
98
+ }}>
99
+ <p style={{ fontWeight: 600, marginBottom: '8px' }}>Component Error</p>
100
+ <p style={{ opacity: 0.8 }}>{this.state.error.message}</p>
101
+ </div>
102
+ )
103
+ }
104
+ return this.props.children
105
+ }
106
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item
14
+ ref={ref}
15
+ className={cn("border-b", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ AccordionItem.displayName = "AccordionItem"
20
+
21
+ const AccordionTrigger = React.forwardRef<
22
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
+ >(({ className, children, ...props }, ref) => (
25
+ <AccordionPrimitive.Header className="flex">
26
+ <AccordionPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
36
+ </AccordionPrimitive.Trigger>
37
+ </AccordionPrimitive.Header>
38
+ ))
39
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40
+
41
+ const AccordionContent = React.forwardRef<
42
+ React.ElementRef<typeof AccordionPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <AccordionPrimitive.Content
46
+ ref={ref}
47
+ className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
48
+ {...props}
49
+ >
50
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
51
+ </AccordionPrimitive.Content>
52
+ ))
53
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
54
+
55
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,59 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
@@ -0,0 +1,5 @@
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
@@ -0,0 +1,48 @@
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Avatar = React.forwardRef<
7
+ React.ElementRef<typeof AvatarPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <AvatarPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Avatar.displayName = AvatarPrimitive.Root.displayName
20
+
21
+ const AvatarImage = React.forwardRef<
22
+ React.ElementRef<typeof AvatarPrimitive.Image>,
23
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24
+ >(({ className, ...props }, ref) => (
25
+ <AvatarPrimitive.Image
26
+ ref={ref}
27
+ className={cn("aspect-square h-full w-full", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
32
+
33
+ const AvatarFallback = React.forwardRef<
34
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
35
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36
+ >(({ className, ...props }, ref) => (
37
+ <AvatarPrimitive.Fallback
38
+ ref={ref}
39
+ className={cn(
40
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47
+
48
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
@@ -0,0 +1,115 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
@@ -0,0 +1,57 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
+ ghost: "hover:bg-accent hover:text-accent-foreground",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2",
25
+ sm: "h-8 rounded-md px-3 text-xs",
26
+ lg: "h-10 rounded-md px-8",
27
+ icon: "h-9 w-9",
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: "default",
32
+ size: "default",
33
+ },
34
+ }
35
+ )
36
+
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
41
+ }
42
+
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
56
+
57
+ export { Button, buttonVariants }
@@ -0,0 +1,211 @@
1
+ import * as React from "react"
2
+ import {
3
+ ChevronDownIcon,
4
+ ChevronLeftIcon,
5
+ ChevronRightIcon,
6
+ } from "lucide-react"
7
+ import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
8
+
9
+ import { cn } from "@/lib/utils"
10
+ import { Button, buttonVariants } from "@/components/ui/button"
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ captionLayout = "label",
17
+ buttonVariant = "ghost",
18
+ formatters,
19
+ components,
20
+ ...props
21
+ }: React.ComponentProps<typeof DayPicker> & {
22
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"]
23
+ }) {
24
+ const defaultClassNames = getDefaultClassNames()
25
+
26
+ return (
27
+ <DayPicker
28
+ showOutsideDays={showOutsideDays}
29
+ className={cn(
30
+ "bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
31
+ String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
32
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
33
+ className
34
+ )}
35
+ captionLayout={captionLayout}
36
+ formatters={{
37
+ formatMonthDropdown: (date) =>
38
+ date.toLocaleString("default", { month: "short" }),
39
+ ...formatters,
40
+ }}
41
+ classNames={{
42
+ root: cn("w-fit", defaultClassNames.root),
43
+ months: cn(
44
+ "relative flex flex-col gap-4 md:flex-row",
45
+ defaultClassNames.months
46
+ ),
47
+ month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
48
+ nav: cn(
49
+ "absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
50
+ defaultClassNames.nav
51
+ ),
52
+ button_previous: cn(
53
+ buttonVariants({ variant: buttonVariant }),
54
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
55
+ defaultClassNames.button_previous
56
+ ),
57
+ button_next: cn(
58
+ buttonVariants({ variant: buttonVariant }),
59
+ "h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50",
60
+ defaultClassNames.button_next
61
+ ),
62
+ month_caption: cn(
63
+ "flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]",
64
+ defaultClassNames.month_caption
65
+ ),
66
+ dropdowns: cn(
67
+ "flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium",
68
+ defaultClassNames.dropdowns
69
+ ),
70
+ dropdown_root: cn(
71
+ "has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border",
72
+ defaultClassNames.dropdown_root
73
+ ),
74
+ dropdown: cn(
75
+ "bg-popover absolute inset-0 opacity-0",
76
+ defaultClassNames.dropdown
77
+ ),
78
+ caption_label: cn(
79
+ "select-none font-medium",
80
+ captionLayout === "label"
81
+ ? "text-sm"
82
+ : "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
83
+ defaultClassNames.caption_label
84
+ ),
85
+ table: "w-full border-collapse",
86
+ weekdays: cn("flex", defaultClassNames.weekdays),
87
+ weekday: cn(
88
+ "text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
89
+ defaultClassNames.weekday
90
+ ),
91
+ week: cn("mt-2 flex w-full", defaultClassNames.week),
92
+ week_number_header: cn(
93
+ "w-[--cell-size] select-none",
94
+ defaultClassNames.week_number_header
95
+ ),
96
+ week_number: cn(
97
+ "text-muted-foreground select-none text-[0.8rem]",
98
+ defaultClassNames.week_number
99
+ ),
100
+ day: cn(
101
+ "group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md",
102
+ defaultClassNames.day
103
+ ),
104
+ range_start: cn(
105
+ "bg-accent rounded-l-md",
106
+ defaultClassNames.range_start
107
+ ),
108
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
109
+ range_end: cn("bg-accent rounded-r-md", defaultClassNames.range_end),
110
+ today: cn(
111
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
112
+ defaultClassNames.today
113
+ ),
114
+ outside: cn(
115
+ "text-muted-foreground aria-selected:text-muted-foreground",
116
+ defaultClassNames.outside
117
+ ),
118
+ disabled: cn(
119
+ "text-muted-foreground opacity-50",
120
+ defaultClassNames.disabled
121
+ ),
122
+ hidden: cn("invisible", defaultClassNames.hidden),
123
+ ...classNames,
124
+ }}
125
+ components={{
126
+ Root: ({ className, rootRef, ...props }) => {
127
+ return (
128
+ <div
129
+ data-slot="calendar"
130
+ ref={rootRef}
131
+ className={cn(className)}
132
+ {...props}
133
+ />
134
+ )
135
+ },
136
+ Chevron: ({ className, orientation, ...props }) => {
137
+ if (orientation === "left") {
138
+ return (
139
+ <ChevronLeftIcon className={cn("size-4", className)} {...props} />
140
+ )
141
+ }
142
+
143
+ if (orientation === "right") {
144
+ return (
145
+ <ChevronRightIcon
146
+ className={cn("size-4", className)}
147
+ {...props}
148
+ />
149
+ )
150
+ }
151
+
152
+ return (
153
+ <ChevronDownIcon className={cn("size-4", className)} {...props} />
154
+ )
155
+ },
156
+ DayButton: CalendarDayButton,
157
+ WeekNumber: ({ children, ...props }) => {
158
+ return (
159
+ <td {...props}>
160
+ <div className="flex size-[--cell-size] items-center justify-center text-center">
161
+ {children}
162
+ </div>
163
+ </td>
164
+ )
165
+ },
166
+ ...components,
167
+ }}
168
+ {...props}
169
+ />
170
+ )
171
+ }
172
+
173
+ function CalendarDayButton({
174
+ className,
175
+ day,
176
+ modifiers,
177
+ ...props
178
+ }: React.ComponentProps<typeof DayButton>) {
179
+ const defaultClassNames = getDefaultClassNames()
180
+
181
+ const ref = React.useRef<HTMLButtonElement>(null)
182
+ React.useEffect(() => {
183
+ if (modifiers.focused) ref.current?.focus()
184
+ }, [modifiers.focused])
185
+
186
+ return (
187
+ <Button
188
+ ref={ref}
189
+ variant="ghost"
190
+ size="icon"
191
+ data-day={day.date.toLocaleDateString()}
192
+ data-selected-single={
193
+ modifiers.selected &&
194
+ !modifiers.range_start &&
195
+ !modifiers.range_end &&
196
+ !modifiers.range_middle
197
+ }
198
+ data-range-start={modifiers.range_start}
199
+ data-range-end={modifiers.range_end}
200
+ data-range-middle={modifiers.range_middle}
201
+ className={cn(
202
+ "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70",
203
+ defaultClassNames.day,
204
+ className
205
+ )}
206
+ {...props}
207
+ />
208
+ )
209
+ }
210
+
211
+ export { Calendar, CalendarDayButton }