@opensaas/stack-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.turbo/turbo-build.log +8 -0
  2. package/README.md +286 -0
  3. package/dist/components/AdminUI.d.ts +24 -0
  4. package/dist/components/AdminUI.d.ts.map +1 -0
  5. package/dist/components/AdminUI.js +48 -0
  6. package/dist/components/ConfirmDialog.d.ts +16 -0
  7. package/dist/components/ConfirmDialog.d.ts.map +1 -0
  8. package/dist/components/ConfirmDialog.js +11 -0
  9. package/dist/components/Dashboard.d.ts +12 -0
  10. package/dist/components/Dashboard.d.ts.map +1 -0
  11. package/dist/components/Dashboard.js +30 -0
  12. package/dist/components/ItemForm.d.ts +17 -0
  13. package/dist/components/ItemForm.d.ts.map +1 -0
  14. package/dist/components/ItemForm.js +97 -0
  15. package/dist/components/ItemFormClient.d.ts +22 -0
  16. package/dist/components/ItemFormClient.d.ts.map +1 -0
  17. package/dist/components/ItemFormClient.js +127 -0
  18. package/dist/components/ListView.d.ts +17 -0
  19. package/dist/components/ListView.d.ts.map +1 -0
  20. package/dist/components/ListView.js +76 -0
  21. package/dist/components/ListViewClient.d.ts +19 -0
  22. package/dist/components/ListViewClient.d.ts.map +1 -0
  23. package/dist/components/ListViewClient.js +108 -0
  24. package/dist/components/LoadingSpinner.d.ts +10 -0
  25. package/dist/components/LoadingSpinner.d.ts.map +1 -0
  26. package/dist/components/LoadingSpinner.js +14 -0
  27. package/dist/components/Navigation.d.ts +13 -0
  28. package/dist/components/Navigation.d.ts.map +1 -0
  29. package/dist/components/Navigation.js +20 -0
  30. package/dist/components/SkeletonLoader.d.ts +22 -0
  31. package/dist/components/SkeletonLoader.d.ts.map +1 -0
  32. package/dist/components/SkeletonLoader.js +25 -0
  33. package/dist/components/fields/CheckboxField.d.ts +11 -0
  34. package/dist/components/fields/CheckboxField.d.ts.map +1 -0
  35. package/dist/components/fields/CheckboxField.js +10 -0
  36. package/dist/components/fields/ComboboxField.d.ts +18 -0
  37. package/dist/components/fields/ComboboxField.d.ts.map +1 -0
  38. package/dist/components/fields/ComboboxField.js +32 -0
  39. package/dist/components/fields/FieldRenderer.d.ts +22 -0
  40. package/dist/components/fields/FieldRenderer.d.ts.map +1 -0
  41. package/dist/components/fields/FieldRenderer.js +81 -0
  42. package/dist/components/fields/IntegerField.d.ts +15 -0
  43. package/dist/components/fields/IntegerField.d.ts.map +1 -0
  44. package/dist/components/fields/IntegerField.js +14 -0
  45. package/dist/components/fields/PasswordField.d.ts +18 -0
  46. package/dist/components/fields/PasswordField.d.ts.map +1 -0
  47. package/dist/components/fields/PasswordField.js +42 -0
  48. package/dist/components/fields/RelationshipField.d.ts +20 -0
  49. package/dist/components/fields/RelationshipField.d.ts.map +1 -0
  50. package/dist/components/fields/RelationshipField.js +11 -0
  51. package/dist/components/fields/RelationshipManager.d.ts +19 -0
  52. package/dist/components/fields/RelationshipManager.d.ts.map +1 -0
  53. package/dist/components/fields/RelationshipManager.js +37 -0
  54. package/dist/components/fields/SelectField.d.ts +16 -0
  55. package/dist/components/fields/SelectField.d.ts.map +1 -0
  56. package/dist/components/fields/SelectField.js +11 -0
  57. package/dist/components/fields/TextField.d.ts +13 -0
  58. package/dist/components/fields/TextField.d.ts.map +1 -0
  59. package/dist/components/fields/TextField.js +11 -0
  60. package/dist/components/fields/TimestampField.d.ts +12 -0
  61. package/dist/components/fields/TimestampField.d.ts.map +1 -0
  62. package/dist/components/fields/TimestampField.js +12 -0
  63. package/dist/components/fields/index.d.ts +23 -0
  64. package/dist/components/fields/index.d.ts.map +1 -0
  65. package/dist/components/fields/index.js +13 -0
  66. package/dist/components/fields/registry.d.ts +43 -0
  67. package/dist/components/fields/registry.d.ts.map +1 -0
  68. package/dist/components/fields/registry.js +42 -0
  69. package/dist/components/standalone/DeleteButton.d.ts +35 -0
  70. package/dist/components/standalone/DeleteButton.d.ts.map +1 -0
  71. package/dist/components/standalone/DeleteButton.js +46 -0
  72. package/dist/components/standalone/ItemCreateForm.d.ts +34 -0
  73. package/dist/components/standalone/ItemCreateForm.d.ts.map +1 -0
  74. package/dist/components/standalone/ItemCreateForm.js +91 -0
  75. package/dist/components/standalone/ItemEditForm.d.ts +37 -0
  76. package/dist/components/standalone/ItemEditForm.d.ts.map +1 -0
  77. package/dist/components/standalone/ItemEditForm.js +112 -0
  78. package/dist/components/standalone/ListTable.d.ts +33 -0
  79. package/dist/components/standalone/ListTable.d.ts.map +1 -0
  80. package/dist/components/standalone/ListTable.js +94 -0
  81. package/dist/components/standalone/SearchBar.d.ts +29 -0
  82. package/dist/components/standalone/SearchBar.d.ts.map +1 -0
  83. package/dist/components/standalone/SearchBar.js +43 -0
  84. package/dist/components/standalone/index.d.ts +11 -0
  85. package/dist/components/standalone/index.d.ts.map +1 -0
  86. package/dist/components/standalone/index.js +6 -0
  87. package/dist/index.d.ts +27 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +19 -0
  90. package/dist/lib/serializeFieldConfig.d.ts +43 -0
  91. package/dist/lib/serializeFieldConfig.d.ts.map +1 -0
  92. package/dist/lib/serializeFieldConfig.js +48 -0
  93. package/dist/lib/theme.d.ts +17 -0
  94. package/dist/lib/theme.d.ts.map +1 -0
  95. package/dist/lib/theme.js +192 -0
  96. package/dist/lib/utils.d.ts +18 -0
  97. package/dist/lib/utils.d.ts.map +1 -0
  98. package/dist/lib/utils.js +76 -0
  99. package/dist/primitives/button.d.ts +12 -0
  100. package/dist/primitives/button.d.ts.map +1 -0
  101. package/dist/primitives/button.js +33 -0
  102. package/dist/primitives/calendar.d.ts +9 -0
  103. package/dist/primitives/calendar.d.ts.map +1 -0
  104. package/dist/primitives/calendar.js +48 -0
  105. package/dist/primitives/card.d.ts +9 -0
  106. package/dist/primitives/card.d.ts.map +1 -0
  107. package/dist/primitives/card.js +16 -0
  108. package/dist/primitives/checkbox.d.ts +5 -0
  109. package/dist/primitives/checkbox.d.ts.map +1 -0
  110. package/dist/primitives/checkbox.js +7 -0
  111. package/dist/primitives/combobox.d.ts +14 -0
  112. package/dist/primitives/combobox.d.ts.map +1 -0
  113. package/dist/primitives/combobox.js +20 -0
  114. package/dist/primitives/datetime-picker.d.ts +9 -0
  115. package/dist/primitives/datetime-picker.d.ts.map +1 -0
  116. package/dist/primitives/datetime-picker.js +42 -0
  117. package/dist/primitives/dialog.d.ts +20 -0
  118. package/dist/primitives/dialog.d.ts.map +1 -0
  119. package/dist/primitives/dialog.js +21 -0
  120. package/dist/primitives/index.d.ts +14 -0
  121. package/dist/primitives/index.d.ts.map +1 -0
  122. package/dist/primitives/index.js +14 -0
  123. package/dist/primitives/input.d.ts +5 -0
  124. package/dist/primitives/input.d.ts.map +1 -0
  125. package/dist/primitives/input.js +8 -0
  126. package/dist/primitives/label.d.ts +6 -0
  127. package/dist/primitives/label.d.ts.map +1 -0
  128. package/dist/primitives/label.js +9 -0
  129. package/dist/primitives/popover.d.ts +7 -0
  130. package/dist/primitives/popover.d.ts.map +1 -0
  131. package/dist/primitives/popover.js +10 -0
  132. package/dist/primitives/select.d.ts +14 -0
  133. package/dist/primitives/select.d.ts.map +1 -0
  134. package/dist/primitives/select.js +24 -0
  135. package/dist/primitives/table.d.ts +11 -0
  136. package/dist/primitives/table.d.ts.map +1 -0
  137. package/dist/primitives/table.js +20 -0
  138. package/dist/primitives/time-picker.d.ts +8 -0
  139. package/dist/primitives/time-picker.d.ts.map +1 -0
  140. package/dist/primitives/time-picker.js +27 -0
  141. package/dist/server/index.d.ts +2 -0
  142. package/dist/server/index.d.ts.map +1 -0
  143. package/dist/server/index.js +2 -0
  144. package/dist/server/types.d.ts +15 -0
  145. package/dist/server/types.d.ts.map +1 -0
  146. package/dist/server/types.js +1 -0
  147. package/dist/styles/globals.css +1896 -0
  148. package/package.json +91 -0
  149. package/postcss.config.cjs +5 -0
  150. package/src/components/AdminUI.tsx +112 -0
  151. package/src/components/ConfirmDialog.tsx +56 -0
  152. package/src/components/Dashboard.tsx +134 -0
  153. package/src/components/ItemForm.tsx +195 -0
  154. package/src/components/ItemFormClient.tsx +237 -0
  155. package/src/components/ListView.tsx +153 -0
  156. package/src/components/ListViewClient.tsx +282 -0
  157. package/src/components/LoadingSpinner.tsx +32 -0
  158. package/src/components/Navigation.tsx +117 -0
  159. package/src/components/SkeletonLoader.tsx +82 -0
  160. package/src/components/fields/CheckboxField.tsx +54 -0
  161. package/src/components/fields/ComboboxField.tsx +127 -0
  162. package/src/components/fields/FieldRenderer.tsx +132 -0
  163. package/src/components/fields/IntegerField.tsx +68 -0
  164. package/src/components/fields/PasswordField.tsx +159 -0
  165. package/src/components/fields/RelationshipField.tsx +71 -0
  166. package/src/components/fields/RelationshipManager.tsx +189 -0
  167. package/src/components/fields/SelectField.tsx +71 -0
  168. package/src/components/fields/TextField.tsx +59 -0
  169. package/src/components/fields/TimestampField.tsx +49 -0
  170. package/src/components/fields/index.ts +27 -0
  171. package/src/components/fields/registry.ts +72 -0
  172. package/src/components/standalone/DeleteButton.tsx +114 -0
  173. package/src/components/standalone/ItemCreateForm.tsx +161 -0
  174. package/src/components/standalone/ItemEditForm.tsx +193 -0
  175. package/src/components/standalone/ListTable.tsx +211 -0
  176. package/src/components/standalone/SearchBar.tsx +86 -0
  177. package/src/components/standalone/index.ts +13 -0
  178. package/src/index.ts +74 -0
  179. package/src/lib/serializeFieldConfig.ts +88 -0
  180. package/src/lib/theme.ts +202 -0
  181. package/src/lib/utils.ts +81 -0
  182. package/src/primitives/button.tsx +49 -0
  183. package/src/primitives/calendar.tsx +160 -0
  184. package/src/primitives/card.tsx +58 -0
  185. package/src/primitives/checkbox.tsx +27 -0
  186. package/src/primitives/combobox.tsx +159 -0
  187. package/src/primitives/datetime-picker.tsx +130 -0
  188. package/src/primitives/dialog.tsx +108 -0
  189. package/src/primitives/index.ts +54 -0
  190. package/src/primitives/input.tsx +24 -0
  191. package/src/primitives/label.tsx +19 -0
  192. package/src/primitives/popover.tsx +31 -0
  193. package/src/primitives/select.tsx +158 -0
  194. package/src/primitives/table.tsx +91 -0
  195. package/src/primitives/time-picker.tsx +65 -0
  196. package/src/server/index.ts +3 -0
  197. package/src/server/types.ts +15 -0
  198. package/src/styles/globals.css +123 -0
  199. package/tailwind.config.ts +3 -0
  200. package/tests/components/TextField.test.tsx +94 -0
  201. package/tests/setup.ts +11 -0
  202. package/tsconfig.json +26 -0
  203. package/vitest.config.ts +22 -0
@@ -0,0 +1,160 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import {
5
+ format,
6
+ addMonths,
7
+ subMonths,
8
+ startOfMonth,
9
+ endOfMonth,
10
+ startOfWeek,
11
+ endOfWeek,
12
+ addDays,
13
+ isSameMonth,
14
+ isSameDay,
15
+ isToday,
16
+ } from 'date-fns'
17
+ import { cn } from '../lib/utils.js'
18
+ import { Button } from './button.js'
19
+
20
+ export interface CalendarProps {
21
+ selected?: Date
22
+ onSelect?: (date: Date) => void
23
+ disabled?: boolean
24
+ mode?: 'single'
25
+ className?: string
26
+ }
27
+
28
+ export function Calendar({ selected, onSelect, disabled, className }: CalendarProps) {
29
+ const [currentMonth, setCurrentMonth] = React.useState(selected || new Date())
30
+
31
+ const monthStart = startOfMonth(currentMonth)
32
+ const monthEnd = endOfMonth(monthStart)
33
+ const startDate = startOfWeek(monthStart)
34
+ const endDate = endOfWeek(monthEnd)
35
+
36
+ const handlePrevMonth = () => {
37
+ setCurrentMonth(subMonths(currentMonth, 1))
38
+ }
39
+
40
+ const handleNextMonth = () => {
41
+ setCurrentMonth(addMonths(currentMonth, 1))
42
+ }
43
+
44
+ const handleToday = () => {
45
+ const today = new Date()
46
+ setCurrentMonth(today)
47
+ if (onSelect) {
48
+ onSelect(today)
49
+ }
50
+ }
51
+
52
+ const rows: Date[][] = []
53
+ let days: Date[] = []
54
+ let day = startDate
55
+
56
+ while (day <= endDate) {
57
+ for (let i = 0; i < 7; i++) {
58
+ days.push(day)
59
+ day = addDays(day, 1)
60
+ }
61
+ rows.push(days)
62
+ days = []
63
+ }
64
+
65
+ return (
66
+ <div className={cn('p-3', className)}>
67
+ <div className="flex items-center justify-between mb-4">
68
+ <Button
69
+ variant="outline"
70
+ size="icon"
71
+ onClick={handlePrevMonth}
72
+ disabled={disabled}
73
+ type="button"
74
+ className="h-8 w-8"
75
+ >
76
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
+ <path
78
+ strokeLinecap="round"
79
+ strokeLinejoin="round"
80
+ strokeWidth={2}
81
+ d="M15 19l-7-7 7-7"
82
+ />
83
+ </svg>
84
+ </Button>
85
+ <div className="flex items-center gap-2">
86
+ <h2 className="text-sm font-semibold">{format(currentMonth, 'MMMM yyyy')}</h2>
87
+ <Button
88
+ variant="ghost"
89
+ size="sm"
90
+ onClick={handleToday}
91
+ disabled={disabled}
92
+ type="button"
93
+ className="h-7 text-xs"
94
+ >
95
+ Today
96
+ </Button>
97
+ </div>
98
+ <Button
99
+ variant="outline"
100
+ size="icon"
101
+ onClick={handleNextMonth}
102
+ disabled={disabled}
103
+ type="button"
104
+ className="h-8 w-8"
105
+ >
106
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
107
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
108
+ </svg>
109
+ </Button>
110
+ </div>
111
+
112
+ <table className="w-full border-collapse">
113
+ <thead>
114
+ <tr>
115
+ {['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'].map((day) => (
116
+ <th key={day} className="text-muted-foreground text-xs font-normal h-9 w-9 p-0">
117
+ {day}
118
+ </th>
119
+ ))}
120
+ </tr>
121
+ </thead>
122
+ <tbody>
123
+ {rows.map((week, weekIdx) => (
124
+ <tr key={weekIdx}>
125
+ {week.map((day, dayIdx) => {
126
+ const isSelected = selected && isSameDay(day, selected)
127
+ const isCurrentMonth = isSameMonth(day, currentMonth)
128
+ const isDayToday = isToday(day)
129
+
130
+ return (
131
+ <td key={dayIdx} className="p-0 text-center">
132
+ <Button
133
+ variant="ghost"
134
+ type="button"
135
+ onClick={() => {
136
+ if (onSelect && !disabled) {
137
+ onSelect(day)
138
+ }
139
+ }}
140
+ disabled={disabled}
141
+ className={cn(
142
+ 'h-9 w-9 p-0 font-normal hover:bg-accent',
143
+ !isCurrentMonth && 'text-muted-foreground opacity-50',
144
+ isSelected &&
145
+ 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground',
146
+ isDayToday && !isSelected && 'bg-accent',
147
+ )}
148
+ >
149
+ {format(day, 'd')}
150
+ </Button>
151
+ </td>
152
+ )
153
+ })}
154
+ </tr>
155
+ ))}
156
+ </tbody>
157
+ </table>
158
+ </div>
159
+ )
160
+ }
@@ -0,0 +1,58 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div
8
+ ref={ref}
9
+ className={cn(
10
+ 'rounded-lg border border-border bg-card text-card-foreground shadow-sm',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ ),
16
+ )
17
+ Card.displayName = 'Card'
18
+
19
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
20
+ ({ className, ...props }, ref) => (
21
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
22
+ ),
23
+ )
24
+ CardHeader.displayName = 'CardHeader'
25
+
26
+ const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
27
+ ({ className, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ className={cn('text-2xl font-semibold leading-none tracking-tight', className)}
31
+ {...props}
32
+ />
33
+ ),
34
+ )
35
+ CardTitle.displayName = 'CardTitle'
36
+
37
+ const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
38
+ ({ className, ...props }, ref) => (
39
+ <div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
40
+ ),
41
+ )
42
+ CardDescription.displayName = 'CardDescription'
43
+
44
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
45
+ ({ className, ...props }, ref) => (
46
+ <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
47
+ ),
48
+ )
49
+ CardContent.displayName = 'CardContent'
50
+
51
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
52
+ ({ className, ...props }, ref) => (
53
+ <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
54
+ ),
55
+ )
56
+ CardFooter.displayName = 'CardFooter'
57
+
58
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,27 @@
1
+ import * as React from 'react'
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
3
+
4
+ import { cn } from '../lib/utils.js'
5
+
6
+ const Checkbox = React.forwardRef<
7
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <CheckboxPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
14
+ className,
15
+ )}
16
+ {...props}
17
+ >
18
+ <CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
19
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
20
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
21
+ </svg>
22
+ </CheckboxPrimitive.Indicator>
23
+ </CheckboxPrimitive.Root>
24
+ ))
25
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
26
+
27
+ export { Checkbox }
@@ -0,0 +1,159 @@
1
+ import * as React from 'react'
2
+ import * as PopoverPrimitive from '@radix-ui/react-popover'
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ const Combobox = PopoverPrimitive.Root
6
+
7
+ const ComboboxTrigger = React.forwardRef<
8
+ React.ElementRef<typeof PopoverPrimitive.Trigger>,
9
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>
10
+ >(({ className, children, ...props }, ref) => (
11
+ <PopoverPrimitive.Trigger
12
+ ref={ref}
13
+ className={cn(
14
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
15
+ className,
16
+ )}
17
+ {...props}
18
+ >
19
+ {children}
20
+ <svg
21
+ className="ml-2 h-4 w-4 shrink-0 opacity-50"
22
+ fill="none"
23
+ stroke="currentColor"
24
+ viewBox="0 0 24 24"
25
+ >
26
+ <path
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ strokeWidth={2}
30
+ d="M8 9l4-4 4 4m0 6l-4 4-4-4"
31
+ />
32
+ </svg>
33
+ </PopoverPrimitive.Trigger>
34
+ ))
35
+ ComboboxTrigger.displayName = PopoverPrimitive.Trigger.displayName
36
+
37
+ const ComboboxContent = React.forwardRef<
38
+ React.ElementRef<typeof PopoverPrimitive.Content>,
39
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
40
+ >(({ className, children, align = 'start', sideOffset = 4, ...props }, ref) => (
41
+ <PopoverPrimitive.Portal>
42
+ <PopoverPrimitive.Content
43
+ ref={ref}
44
+ align={align}
45
+ sideOffset={sideOffset}
46
+ className={cn(
47
+ 'z-50 w-full min-w-[var(--radix-popover-trigger-width)] rounded-md border border-border bg-popover p-0 text-popover-foreground shadow-md outline-none',
48
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
49
+ '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',
50
+ className,
51
+ )}
52
+ {...props}
53
+ >
54
+ {children}
55
+ </PopoverPrimitive.Content>
56
+ </PopoverPrimitive.Portal>
57
+ ))
58
+ ComboboxContent.displayName = PopoverPrimitive.Content.displayName
59
+
60
+ const ComboboxSearch = React.forwardRef<
61
+ HTMLInputElement,
62
+ React.InputHTMLAttributes<HTMLInputElement>
63
+ >(({ className, ...props }, ref) => (
64
+ <div className="flex items-center border-b border-border px-3">
65
+ <svg
66
+ className="mr-2 h-4 w-4 shrink-0 opacity-50"
67
+ fill="none"
68
+ stroke="currentColor"
69
+ viewBox="0 0 24 24"
70
+ >
71
+ <path
72
+ strokeLinecap="round"
73
+ strokeLinejoin="round"
74
+ strokeWidth={2}
75
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
76
+ />
77
+ </svg>
78
+ <input
79
+ ref={ref}
80
+ className={cn(
81
+ 'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
82
+ className,
83
+ )}
84
+ {...props}
85
+ />
86
+ </div>
87
+ ))
88
+ ComboboxSearch.displayName = 'ComboboxSearch'
89
+
90
+ const ComboboxList = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
91
+ ({ className, children, ...props }, ref) => (
92
+ <div
93
+ ref={ref}
94
+ className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
95
+ {...props}
96
+ >
97
+ {children}
98
+ </div>
99
+ ),
100
+ )
101
+ ComboboxList.displayName = 'ComboboxList'
102
+
103
+ const ComboboxEmpty = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
104
+ ({ className, children = 'No results found.', ...props }, ref) => (
105
+ <div
106
+ ref={ref}
107
+ className={cn('py-6 text-center text-sm text-muted-foreground', className)}
108
+ {...props}
109
+ >
110
+ {children}
111
+ </div>
112
+ ),
113
+ )
114
+ ComboboxEmpty.displayName = 'ComboboxEmpty'
115
+
116
+ const ComboboxItem = React.forwardRef<
117
+ HTMLDivElement,
118
+ React.HTMLAttributes<HTMLDivElement> & { selected?: boolean }
119
+ >(({ className, children, selected, ...props }, ref) => (
120
+ <div
121
+ ref={ref}
122
+ className={cn(
123
+ 'relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none',
124
+ 'hover:bg-accent hover:text-accent-foreground',
125
+ 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
126
+ selected && 'bg-accent text-accent-foreground',
127
+ className,
128
+ )}
129
+ {...props}
130
+ >
131
+ {selected && (
132
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
133
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
134
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
135
+ </svg>
136
+ </span>
137
+ )}
138
+ {children}
139
+ </div>
140
+ ))
141
+ ComboboxItem.displayName = 'ComboboxItem'
142
+
143
+ const ComboboxSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
144
+ ({ className, ...props }, ref) => (
145
+ <div ref={ref} className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />
146
+ ),
147
+ )
148
+ ComboboxSeparator.displayName = 'ComboboxSeparator'
149
+
150
+ export {
151
+ Combobox,
152
+ ComboboxTrigger,
153
+ ComboboxContent,
154
+ ComboboxSearch,
155
+ ComboboxList,
156
+ ComboboxEmpty,
157
+ ComboboxItem,
158
+ ComboboxSeparator,
159
+ }
@@ -0,0 +1,130 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { format } from 'date-fns'
5
+ import { cn } from '../lib/utils.js'
6
+ import { Button } from './button.js'
7
+ import { Popover, PopoverContent, PopoverTrigger } from './popover.js'
8
+ import { Calendar } from './calendar.js'
9
+ import { TimePicker } from './time-picker.js'
10
+
11
+ export interface DateTimePickerProps {
12
+ value?: Date | null
13
+ onChange?: (date: Date | null) => void
14
+ disabled?: boolean
15
+ className?: string
16
+ placeholder?: string
17
+ }
18
+
19
+ export function DateTimePicker({
20
+ value,
21
+ onChange,
22
+ disabled,
23
+ className,
24
+ placeholder = 'Pick a date and time',
25
+ }: DateTimePickerProps) {
26
+ const [open, setOpen] = React.useState(false)
27
+ const [selectedDate, setSelectedDate] = React.useState<Date | undefined>(value || undefined)
28
+
29
+ const handleDateSelect = (date: Date) => {
30
+ // Preserve time if we already have a selected date
31
+ if (selectedDate) {
32
+ date.setHours(selectedDate.getHours())
33
+ date.setMinutes(selectedDate.getMinutes())
34
+ }
35
+ setSelectedDate(date)
36
+ }
37
+
38
+ const handleTimeChange = (date: Date) => {
39
+ setSelectedDate(date)
40
+ }
41
+
42
+ const handleConfirm = () => {
43
+ if (onChange) {
44
+ onChange(selectedDate || null)
45
+ }
46
+ setOpen(false)
47
+ }
48
+
49
+ const handleCancel = () => {
50
+ setSelectedDate(value || undefined)
51
+ setOpen(false)
52
+ }
53
+
54
+ const handleClear = () => {
55
+ setSelectedDate(undefined)
56
+ if (onChange) {
57
+ onChange(null)
58
+ }
59
+ setOpen(false)
60
+ }
61
+
62
+ return (
63
+ <Popover open={open} onOpenChange={setOpen}>
64
+ <PopoverTrigger asChild>
65
+ <Button
66
+ variant="outline"
67
+ type="button"
68
+ disabled={disabled}
69
+ className={cn(
70
+ 'w-full justify-start text-left font-normal',
71
+ !value && 'text-muted-foreground',
72
+ className,
73
+ )}
74
+ >
75
+ <svg className="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
76
+ <path
77
+ strokeLinecap="round"
78
+ strokeLinejoin="round"
79
+ strokeWidth={2}
80
+ d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
81
+ />
82
+ </svg>
83
+ {value ? format(value, 'dd/MM/yyyy, h:mm a') : <span>{placeholder}</span>}
84
+ </Button>
85
+ </PopoverTrigger>
86
+ <PopoverContent className="w-auto p-0" align="start">
87
+ <div className="p-4 space-y-4">
88
+ <Calendar selected={selectedDate} onSelect={handleDateSelect} disabled={disabled} />
89
+
90
+ <div className="border-t pt-4">
91
+ <div className="text-sm font-medium mb-2">Time</div>
92
+ <TimePicker value={selectedDate} onChange={handleTimeChange} disabled={disabled} />
93
+ </div>
94
+
95
+ <div className="flex gap-2 border-t pt-4">
96
+ <Button
97
+ variant="outline"
98
+ size="sm"
99
+ onClick={handleClear}
100
+ disabled={disabled}
101
+ type="button"
102
+ className="flex-1"
103
+ >
104
+ Clear
105
+ </Button>
106
+ <Button
107
+ variant="outline"
108
+ size="sm"
109
+ onClick={handleCancel}
110
+ disabled={disabled}
111
+ type="button"
112
+ className="flex-1"
113
+ >
114
+ Cancel
115
+ </Button>
116
+ <Button
117
+ size="sm"
118
+ onClick={handleConfirm}
119
+ disabled={disabled}
120
+ type="button"
121
+ className="flex-1"
122
+ >
123
+ Confirm
124
+ </Button>
125
+ </div>
126
+ </div>
127
+ </PopoverContent>
128
+ </Popover>
129
+ )
130
+ }
@@ -0,0 +1,108 @@
1
+ import * as React from 'react'
2
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
3
+
4
+ import { cn } from '../lib/utils.js'
5
+
6
+ const Dialog = DialogPrimitive.Root
7
+
8
+ const DialogTrigger = DialogPrimitive.Trigger
9
+
10
+ const DialogPortal = DialogPrimitive.Portal
11
+
12
+ const DialogClose = DialogPrimitive.Close
13
+
14
+ const DialogOverlay = React.forwardRef<
15
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
16
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
17
+ >(({ className, ...props }, ref) => (
18
+ <DialogPrimitive.Overlay
19
+ ref={ref}
20
+ className={cn(
21
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
22
+ className,
23
+ )}
24
+ {...props}
25
+ />
26
+ ))
27
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
28
+
29
+ const DialogContent = React.forwardRef<
30
+ React.ElementRef<typeof DialogPrimitive.Content>,
31
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
32
+ >(({ className, children, ...props }, ref) => (
33
+ <DialogPortal>
34
+ <DialogOverlay />
35
+ <DialogPrimitive.Content
36
+ ref={ref}
37
+ className={cn(
38
+ 'fixed left-[50%] top-[50%] z-50 grid w-2/3 max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border-2 border-gray-200 bg-white p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
39
+ className,
40
+ )}
41
+ {...props}
42
+ >
43
+ {children}
44
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
45
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
46
+ <path
47
+ strokeLinecap="round"
48
+ strokeLinejoin="round"
49
+ strokeWidth={2}
50
+ d="M6 18L18 6M6 6l12 12"
51
+ />
52
+ </svg>
53
+ <span className="sr-only">Close</span>
54
+ </DialogPrimitive.Close>
55
+ </DialogPrimitive.Content>
56
+ </DialogPortal>
57
+ ))
58
+ DialogContent.displayName = DialogPrimitive.Content.displayName
59
+
60
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
61
+ <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
62
+ )
63
+ DialogHeader.displayName = 'DialogHeader'
64
+
65
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
66
+ <div
67
+ className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
68
+ {...props}
69
+ />
70
+ )
71
+ DialogFooter.displayName = 'DialogFooter'
72
+
73
+ const DialogTitle = React.forwardRef<
74
+ React.ElementRef<typeof DialogPrimitive.Title>,
75
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
76
+ >(({ className, ...props }, ref) => (
77
+ <DialogPrimitive.Title
78
+ ref={ref}
79
+ className={cn('text-lg font-semibold leading-none tracking-tight', className)}
80
+ {...props}
81
+ />
82
+ ))
83
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
84
+
85
+ const DialogDescription = React.forwardRef<
86
+ React.ElementRef<typeof DialogPrimitive.Description>,
87
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
88
+ >(({ className, ...props }, ref) => (
89
+ <DialogPrimitive.Description
90
+ ref={ref}
91
+ className={cn('text-sm text-muted-foreground', className)}
92
+ {...props}
93
+ />
94
+ ))
95
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
96
+
97
+ export {
98
+ Dialog,
99
+ DialogPortal,
100
+ DialogOverlay,
101
+ DialogClose,
102
+ DialogTrigger,
103
+ DialogContent,
104
+ DialogHeader,
105
+ DialogFooter,
106
+ DialogTitle,
107
+ DialogDescription,
108
+ }
@@ -0,0 +1,54 @@
1
+ // Primitive components from shadcn/ui
2
+ export { Button, buttonVariants, type ButtonProps } from './button.js'
3
+ export { Input, type InputProps } from './input.js'
4
+ export { Label } from './label.js'
5
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from './card.js'
6
+ export {
7
+ Table,
8
+ TableHeader,
9
+ TableBody,
10
+ TableFooter,
11
+ TableHead,
12
+ TableRow,
13
+ TableCell,
14
+ TableCaption,
15
+ } from './table.js'
16
+ export {
17
+ Dialog,
18
+ DialogPortal,
19
+ DialogOverlay,
20
+ DialogClose,
21
+ DialogTrigger,
22
+ DialogContent,
23
+ DialogHeader,
24
+ DialogFooter,
25
+ DialogTitle,
26
+ DialogDescription,
27
+ } from './dialog.js'
28
+ export {
29
+ Select,
30
+ SelectGroup,
31
+ SelectValue,
32
+ SelectTrigger,
33
+ SelectContent,
34
+ SelectLabel,
35
+ SelectItem,
36
+ SelectSeparator,
37
+ SelectScrollUpButton,
38
+ SelectScrollDownButton,
39
+ } from './select.js'
40
+ export { Checkbox } from './checkbox.js'
41
+ export { Popover, PopoverTrigger, PopoverContent } from './popover.js'
42
+ export { Calendar, type CalendarProps } from './calendar.js'
43
+ export { TimePicker, type TimePickerProps } from './time-picker.js'
44
+ export { DateTimePicker, type DateTimePickerProps } from './datetime-picker.js'
45
+ export {
46
+ Combobox,
47
+ ComboboxTrigger,
48
+ ComboboxContent,
49
+ ComboboxSearch,
50
+ ComboboxList,
51
+ ComboboxEmpty,
52
+ ComboboxItem,
53
+ ComboboxSeparator,
54
+ } from './combobox.js'