@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,24 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ export type InputProps = React.InputHTMLAttributes<HTMLInputElement>
6
+
7
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
8
+ ({ className, type, ...props }, ref) => {
9
+ return (
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
14
+ className,
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ },
21
+ )
22
+ Input.displayName = 'Input'
23
+
24
+ export { Input }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react'
2
+ import * as LabelPrimitive from '@radix-ui/react-label'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '../lib/utils.js'
6
+
7
+ const labelVariants = cva(
8
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
14
+ >(({ className, ...props }, ref) => (
15
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
16
+ ))
17
+ Label.displayName = LabelPrimitive.Root.displayName
18
+
19
+ export { Label }
@@ -0,0 +1,31 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import * as PopoverPrimitive from '@radix-ui/react-popover'
5
+
6
+ import { cn } from '../lib/utils.js'
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ 'z-50 w-72 rounded-md border bg-card p-4 text-card-foreground shadow-lg outline-none 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-[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',
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ))
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent }
@@ -0,0 +1,158 @@
1
+ import * as React from 'react'
2
+ import * as SelectPrimitive from '@radix-ui/react-select'
3
+
4
+ import { cn } from '../lib/utils.js'
5
+
6
+ const Select = SelectPrimitive.Root
7
+
8
+ const SelectGroup = SelectPrimitive.Group
9
+
10
+ const SelectValue = SelectPrimitive.Value
11
+
12
+ const SelectTrigger = React.forwardRef<
13
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
14
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
15
+ >(({ className, children, ...props }, ref) => (
16
+ <SelectPrimitive.Trigger
17
+ ref={ref}
18
+ className={cn(
19
+ '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 [&>span]:line-clamp-1',
20
+ className,
21
+ )}
22
+ {...props}
23
+ >
24
+ {children}
25
+ <SelectPrimitive.Icon asChild>
26
+ <svg className="h-4 w-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
27
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
28
+ </svg>
29
+ </SelectPrimitive.Icon>
30
+ </SelectPrimitive.Trigger>
31
+ ))
32
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
33
+
34
+ const SelectScrollUpButton = React.forwardRef<
35
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
36
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
37
+ >(({ className, ...props }, ref) => (
38
+ <SelectPrimitive.ScrollUpButton
39
+ ref={ref}
40
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
41
+ {...props}
42
+ >
43
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
44
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 15l7-7 7 7" />
45
+ </svg>
46
+ </SelectPrimitive.ScrollUpButton>
47
+ ))
48
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
49
+
50
+ const SelectScrollDownButton = React.forwardRef<
51
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
52
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
53
+ >(({ className, ...props }, ref) => (
54
+ <SelectPrimitive.ScrollDownButton
55
+ ref={ref}
56
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
57
+ {...props}
58
+ >
59
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
60
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
61
+ </svg>
62
+ </SelectPrimitive.ScrollDownButton>
63
+ ))
64
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
65
+
66
+ const SelectContent = React.forwardRef<
67
+ React.ElementRef<typeof SelectPrimitive.Content>,
68
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
69
+ >(({ className, children, position = 'popper', ...props }, ref) => (
70
+ <SelectPrimitive.Portal>
71
+ <SelectPrimitive.Content
72
+ ref={ref}
73
+ className={cn(
74
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md 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-[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',
75
+ position === 'popper' &&
76
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
77
+ className,
78
+ )}
79
+ position={position}
80
+ {...props}
81
+ >
82
+ <SelectScrollUpButton />
83
+ <SelectPrimitive.Viewport
84
+ className={cn(
85
+ 'p-1',
86
+ position === 'popper' &&
87
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
88
+ )}
89
+ >
90
+ {children}
91
+ </SelectPrimitive.Viewport>
92
+ <SelectScrollDownButton />
93
+ </SelectPrimitive.Content>
94
+ </SelectPrimitive.Portal>
95
+ ))
96
+ SelectContent.displayName = SelectPrimitive.Content.displayName
97
+
98
+ const SelectLabel = React.forwardRef<
99
+ React.ElementRef<typeof SelectPrimitive.Label>,
100
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
101
+ >(({ className, ...props }, ref) => (
102
+ <SelectPrimitive.Label
103
+ ref={ref}
104
+ className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
105
+ {...props}
106
+ />
107
+ ))
108
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
109
+
110
+ const SelectItem = React.forwardRef<
111
+ React.ElementRef<typeof SelectPrimitive.Item>,
112
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
113
+ >(({ className, children, ...props }, ref) => (
114
+ <SelectPrimitive.Item
115
+ ref={ref}
116
+ className={cn(
117
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
118
+ className,
119
+ )}
120
+ {...props}
121
+ >
122
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
123
+ <SelectPrimitive.ItemIndicator>
124
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
125
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
126
+ </svg>
127
+ </SelectPrimitive.ItemIndicator>
128
+ </span>
129
+
130
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
131
+ </SelectPrimitive.Item>
132
+ ))
133
+ SelectItem.displayName = SelectPrimitive.Item.displayName
134
+
135
+ const SelectSeparator = React.forwardRef<
136
+ React.ElementRef<typeof SelectPrimitive.Separator>,
137
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
138
+ >(({ className, ...props }, ref) => (
139
+ <SelectPrimitive.Separator
140
+ ref={ref}
141
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
142
+ {...props}
143
+ />
144
+ ))
145
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
146
+
147
+ export {
148
+ Select,
149
+ SelectGroup,
150
+ SelectValue,
151
+ SelectTrigger,
152
+ SelectContent,
153
+ SelectLabel,
154
+ SelectItem,
155
+ SelectSeparator,
156
+ SelectScrollUpButton,
157
+ SelectScrollDownButton,
158
+ }
@@ -0,0 +1,91 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '../lib/utils.js'
4
+
5
+ const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
6
+ ({ className, ...props }, ref) => (
7
+ <div className="relative w-full overflow-auto">
8
+ <table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
9
+ </div>
10
+ ),
11
+ )
12
+ Table.displayName = 'Table'
13
+
14
+ const TableHeader = React.forwardRef<
15
+ HTMLTableSectionElement,
16
+ React.HTMLAttributes<HTMLTableSectionElement>
17
+ >(({ className, ...props }, ref) => (
18
+ <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
19
+ ))
20
+ TableHeader.displayName = 'TableHeader'
21
+
22
+ const TableBody = React.forwardRef<
23
+ HTMLTableSectionElement,
24
+ React.HTMLAttributes<HTMLTableSectionElement>
25
+ >(({ className, ...props }, ref) => (
26
+ <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
27
+ ))
28
+ TableBody.displayName = 'TableBody'
29
+
30
+ const TableFooter = React.forwardRef<
31
+ HTMLTableSectionElement,
32
+ React.HTMLAttributes<HTMLTableSectionElement>
33
+ >(({ className, ...props }, ref) => (
34
+ <tfoot
35
+ ref={ref}
36
+ className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ TableFooter.displayName = 'TableFooter'
41
+
42
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
43
+ ({ className, ...props }, ref) => (
44
+ <tr
45
+ ref={ref}
46
+ className={cn(
47
+ 'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
48
+ className,
49
+ )}
50
+ {...props}
51
+ />
52
+ ),
53
+ )
54
+ TableRow.displayName = 'TableRow'
55
+
56
+ const TableHead = React.forwardRef<
57
+ HTMLTableCellElement,
58
+ React.ThHTMLAttributes<HTMLTableCellElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <th
61
+ ref={ref}
62
+ className={cn(
63
+ 'h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0',
64
+ className,
65
+ )}
66
+ {...props}
67
+ />
68
+ ))
69
+ TableHead.displayName = 'TableHead'
70
+
71
+ const TableCell = React.forwardRef<
72
+ HTMLTableCellElement,
73
+ React.TdHTMLAttributes<HTMLTableCellElement>
74
+ >(({ className, ...props }, ref) => (
75
+ <td
76
+ ref={ref}
77
+ className={cn('p-4 align-middle [&:has([role=checkbox])]:pr-0', className)}
78
+ {...props}
79
+ />
80
+ ))
81
+ TableCell.displayName = 'TableCell'
82
+
83
+ const TableCaption = React.forwardRef<
84
+ HTMLTableCaptionElement,
85
+ React.HTMLAttributes<HTMLTableCaptionElement>
86
+ >(({ className, ...props }, ref) => (
87
+ <caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
88
+ ))
89
+ TableCaption.displayName = 'TableCaption'
90
+
91
+ export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
@@ -0,0 +1,65 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../lib/utils.js'
5
+ import { Input } from './input.js'
6
+
7
+ export interface TimePickerProps {
8
+ value?: Date
9
+ onChange?: (date: Date) => void
10
+ disabled?: boolean
11
+ className?: string
12
+ }
13
+
14
+ export function TimePicker({ value, onChange, disabled, className }: TimePickerProps) {
15
+ const hours = value ? value.getHours() : 12
16
+ const minutes = value ? value.getMinutes() : 30
17
+
18
+ const handleHourChange = (e: React.ChangeEvent<HTMLInputElement>) => {
19
+ const newHours = parseInt(e.target.value) || 0
20
+ const clampedHours = Math.max(0, Math.min(23, newHours))
21
+
22
+ if (onChange) {
23
+ const newDate = value ? new Date(value) : new Date()
24
+ newDate.setHours(clampedHours)
25
+ onChange(newDate)
26
+ }
27
+ }
28
+
29
+ const handleMinuteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
30
+ const newMinutes = parseInt(e.target.value) || 0
31
+ const clampedMinutes = Math.max(0, Math.min(59, newMinutes))
32
+
33
+ if (onChange) {
34
+ const newDate = value ? new Date(value) : new Date()
35
+ newDate.setMinutes(clampedMinutes)
36
+ onChange(newDate)
37
+ }
38
+ }
39
+
40
+ return (
41
+ <div className={cn('flex items-center gap-2', className)}>
42
+ <div className="flex items-center">
43
+ <Input
44
+ type="number"
45
+ min={0}
46
+ max={23}
47
+ value={hours.toString().padStart(2, '0')}
48
+ onChange={handleHourChange}
49
+ disabled={disabled}
50
+ className="w-14 text-center"
51
+ />
52
+ <span className="mx-1 text-lg">:</span>
53
+ <Input
54
+ type="number"
55
+ min={0}
56
+ max={59}
57
+ value={minutes.toString().padStart(2, '0')}
58
+ onChange={handleMinuteChange}
59
+ disabled={disabled}
60
+ className="w-14 text-center"
61
+ />
62
+ </div>
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,3 @@
1
+ // Server utilities for OpenSaas UI
2
+
3
+ export type { ServerActionInput, ActionResult } from './types.js'
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Input for the generic server action
3
+ * Re-exported from @opensaas/stack-core for convenience
4
+ */
5
+ export type { ServerActionProps as ServerActionInput } from '@opensaas/stack-core'
6
+
7
+ /**
8
+ * Result of a server action
9
+ */
10
+ export interface ActionResult<T = Record<string, unknown>> {
11
+ success: boolean
12
+ data?: T
13
+ error?: string
14
+ fieldErrors?: Record<string, string>
15
+ }
@@ -0,0 +1,123 @@
1
+ @import 'tailwindcss';
2
+
3
+ @theme inline {
4
+ /* Light mode colors */
5
+ --color-background-light: oklch(0.97 0.01 264);
6
+ --color-foreground-light: oklch(0.2 0.02 264);
7
+ --color-card-light: oklch(1 0 0);
8
+ --color-card-foreground-light: oklch(0.2 0.02 264);
9
+ --color-popover-light: oklch(1 0 0);
10
+ --color-popover-foreground-light: oklch(0.2 0.02 264);
11
+
12
+ --color-primary-light: oklch(0.68 0.19 210);
13
+ --color-primary-foreground-light: oklch(0.2 0.02 264);
14
+
15
+ --color-secondary-light: oklch(0.95 0.01 264);
16
+ --color-secondary-foreground-light: oklch(0.2 0.02 264);
17
+ --color-muted-light: oklch(0.95 0.01 264);
18
+ --color-muted-foreground-light: oklch(0.5 0.01 264);
19
+ --color-accent-light: oklch(0.7 0.21 320);
20
+ --color-accent-foreground-light: oklch(1 0 0);
21
+
22
+ --color-destructive-light: oklch(0.65 0.23 25);
23
+ --color-destructive-foreground-light: oklch(1 0 0);
24
+ --color-border-light: oklch(0.9 0.01 264);
25
+ --color-input-light: oklch(0.9 0.01 264);
26
+ --color-ring-light: oklch(0.68 0.19 210);
27
+
28
+ /* Dark mode colors */
29
+ --color-background-dark: oklch(0.15 0.02 264);
30
+ --color-foreground-dark: oklch(0.95 0.01 264);
31
+ --color-card-dark: oklch(0.18 0.02 264);
32
+ --color-card-foreground-dark: oklch(0.95 0.01 264);
33
+ --color-popover-dark: oklch(0.18 0.02 264);
34
+ --color-popover-foreground-dark: oklch(0.95 0.01 264);
35
+
36
+ --color-primary-dark: oklch(0.72 0.2 210);
37
+ --color-primary-foreground-dark: oklch(0.2 0.02 264);
38
+
39
+ --color-secondary-dark: oklch(0.25 0.02 264);
40
+ --color-secondary-foreground-dark: oklch(0.95 0.01 264);
41
+ --color-muted-dark: oklch(0.25 0.02 264);
42
+ --color-muted-foreground-dark: oklch(0.6 0.01 264);
43
+ --color-accent-dark: oklch(0.75 0.22 320);
44
+ --color-accent-foreground-dark: oklch(0.2 0.02 264);
45
+
46
+ --color-destructive-dark: oklch(0.68 0.24 25);
47
+ --color-destructive-foreground-dark: oklch(1 0 0);
48
+ --color-border-dark: oklch(0.3 0.02 264);
49
+ --color-input-dark: oklch(0.3 0.02 264);
50
+ --color-ring-dark: oklch(0.72 0.2 210);
51
+
52
+ /* Semantic tokens using light-dark() */
53
+ --color-background: light-dark(var(--color-background-light), var(--color-background-dark));
54
+ --color-foreground: light-dark(var(--color-foreground-light), var(--color-foreground-dark));
55
+ --color-card: light-dark(var(--color-card-light), var(--color-card-dark));
56
+ --color-card-foreground: light-dark(
57
+ var(--color-card-foreground-light),
58
+ var(--color-card-foreground-dark)
59
+ );
60
+ --color-popover: light-dark(var(--color-popover-light), var(--color-popover-dark));
61
+ --color-popover-foreground: light-dark(
62
+ var(--color-popover-foreground-light),
63
+ var(--color-popover-foreground-dark)
64
+ );
65
+
66
+ --color-primary: light-dark(var(--color-primary-light), var(--color-primary-dark));
67
+ --color-primary-foreground: light-dark(
68
+ var(--color-primary-foreground-light),
69
+ var(--color-primary-foreground-dark)
70
+ );
71
+
72
+ --color-secondary: light-dark(var(--color-secondary-light), var(--color-secondary-dark));
73
+ --color-secondary-foreground: light-dark(
74
+ var(--color-secondary-foreground-light),
75
+ var(--color-secondary-foreground-dark)
76
+ );
77
+ --color-muted: light-dark(var(--color-muted-light), var(--color-muted-dark));
78
+ --color-muted-foreground: light-dark(
79
+ var(--color-muted-foreground-light),
80
+ var(--color-muted-foreground-dark)
81
+ );
82
+ --color-accent: light-dark(var(--color-accent-light), var(--color-accent-dark));
83
+ --color-accent-foreground: light-dark(
84
+ var(--color-accent-foreground-light),
85
+ var(--color-accent-foreground-dark)
86
+ );
87
+
88
+ --color-destructive: light-dark(var(--color-destructive-light), var(--color-destructive-dark));
89
+ --color-destructive-foreground: light-dark(
90
+ var(--color-destructive-foreground-light),
91
+ var(--color-destructive-foreground-dark)
92
+ );
93
+ --color-border: light-dark(var(--color-border-light), var(--color-border-dark));
94
+ --color-input: light-dark(var(--color-input-light), var(--color-input-dark));
95
+ --color-ring: light-dark(var(--color-ring-light), var(--color-ring-dark));
96
+
97
+ --radius: 0.75rem;
98
+
99
+ /* Gradient colors for modern effects */
100
+ --gradient-from-light: oklch(0.68 0.19 210);
101
+ --gradient-to-light: oklch(0.7 0.21 320);
102
+ --gradient-from-dark: oklch(0.72 0.2 210);
103
+ --gradient-to-dark: oklch(0.75 0.22 320);
104
+ --gradient-from: light-dark(var(--gradient-from-light), var(--gradient-from-dark));
105
+ --gradient-to: light-dark(var(--gradient-to-light), var(--gradient-to-dark));
106
+ }
107
+
108
+ :root {
109
+ color-scheme: light dark;
110
+ }
111
+
112
+ * {
113
+ border-color: var(--color-border);
114
+ }
115
+
116
+ body {
117
+ background-color: var(--color-background);
118
+ color: var(--color-foreground);
119
+ font-family:
120
+ system-ui,
121
+ -apple-system,
122
+ sans-serif;
123
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
3
+ }
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, screen } from '@testing-library/react'
3
+ import userEvent from '@testing-library/user-event'
4
+ import { TextField } from '../../src/components/fields/TextField.js'
5
+
6
+ describe('TextField', () => {
7
+ describe('edit mode', () => {
8
+ it('should render text input with label', () => {
9
+ render(<TextField name="username" value="" onChange={vi.fn()} label="Username" />)
10
+
11
+ expect(screen.getByLabelText('Username')).toBeInTheDocument()
12
+ expect(screen.getByRole('textbox')).toBeInTheDocument()
13
+ })
14
+
15
+ it('should display current value', () => {
16
+ render(<TextField name="username" value="john" onChange={vi.fn()} label="Username" />)
17
+
18
+ const input = screen.getByRole('textbox')
19
+ expect(input).toHaveValue('john')
20
+ })
21
+
22
+ it('should call onChange when user types', async () => {
23
+ const onChange = vi.fn()
24
+ const user = userEvent.setup()
25
+
26
+ render(<TextField name="username" value="" onChange={onChange} label="Username" />)
27
+
28
+ const input = screen.getByRole('textbox')
29
+ await user.type(input, 't')
30
+
31
+ expect(onChange).toHaveBeenCalled()
32
+ expect(onChange).toHaveBeenCalledWith('t')
33
+ })
34
+
35
+ it('should show required indicator when required', () => {
36
+ render(<TextField name="username" value="" onChange={vi.fn()} label="Username" required />)
37
+
38
+ expect(screen.getByText('*')).toBeInTheDocument()
39
+ })
40
+
41
+ it('should display error message', () => {
42
+ render(
43
+ <TextField
44
+ name="username"
45
+ value=""
46
+ onChange={vi.fn()}
47
+ label="Username"
48
+ error="Username is required"
49
+ />,
50
+ )
51
+
52
+ expect(screen.getByText('Username is required')).toBeInTheDocument()
53
+ })
54
+
55
+ it('should be disabled when disabled prop is true', () => {
56
+ render(<TextField name="username" value="" onChange={vi.fn()} label="Username" disabled />)
57
+
58
+ const input = screen.getByRole('textbox')
59
+ expect(input).toBeDisabled()
60
+ })
61
+
62
+ it('should show placeholder text', () => {
63
+ render(
64
+ <TextField
65
+ name="username"
66
+ value=""
67
+ onChange={vi.fn()}
68
+ label="Username"
69
+ placeholder="Enter your username"
70
+ />,
71
+ )
72
+
73
+ expect(screen.getByPlaceholderText('Enter your username')).toBeInTheDocument()
74
+ })
75
+ })
76
+
77
+ describe('read mode', () => {
78
+ it('should render value as text', () => {
79
+ render(
80
+ <TextField name="username" value="john" onChange={vi.fn()} label="Username" mode="read" />,
81
+ )
82
+
83
+ expect(screen.getByText('Username')).toBeInTheDocument()
84
+ expect(screen.getByText('john')).toBeInTheDocument()
85
+ expect(screen.queryByRole('textbox')).not.toBeInTheDocument()
86
+ })
87
+
88
+ it('should show dash when value is empty', () => {
89
+ render(<TextField name="username" value="" onChange={vi.fn()} label="Username" mode="read" />)
90
+
91
+ expect(screen.getByText('-')).toBeInTheDocument()
92
+ })
93
+ })
94
+ })
package/tests/setup.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { expect, afterEach } from 'vitest'
2
+ import { cleanup } from '@testing-library/react'
3
+ import * as matchers from '@testing-library/jest-dom/matchers'
4
+
5
+ // Extend Vitest matchers with jest-dom
6
+ expect.extend(matchers)
7
+
8
+ // Cleanup after each test
9
+ afterEach(() => {
10
+ cleanup()
11
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "noEmit": false,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "outDir": "./dist",
14
+ "esModuleInterop": true,
15
+ "skipLibCheck": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "jsx": "react-jsx",
18
+ "allowImportingTsExtensions": false,
19
+ "isolatedModules": true,
20
+ "paths": {
21
+ "@/*": ["./src/*"]
22
+ }
23
+ },
24
+ "include": ["src/**/*"],
25
+ "exclude": ["node_modules", "dist", "tests"]
26
+ }