@object-ui/components 0.3.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 (295) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +170 -0
  4. package/dist/index.css +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +46186 -0
  7. package/dist/index.umd.cjs +92 -0
  8. package/dist/src/hooks/use-mobile.d.ts +1 -0
  9. package/dist/src/index.d.ts +2 -0
  10. package/dist/src/index.test.d.ts +1 -0
  11. package/dist/src/lib/utils.d.ts +4 -0
  12. package/dist/src/new-components.test.d.ts +1 -0
  13. package/dist/src/renderers/basic/div.d.ts +1 -0
  14. package/dist/src/renderers/basic/html.d.ts +1 -0
  15. package/dist/src/renderers/basic/icon.d.ts +1 -0
  16. package/dist/src/renderers/basic/image.d.ts +1 -0
  17. package/dist/src/renderers/basic/index.d.ts +0 -0
  18. package/dist/src/renderers/basic/separator.d.ts +1 -0
  19. package/dist/src/renderers/basic/span.d.ts +1 -0
  20. package/dist/src/renderers/basic/text.d.ts +1 -0
  21. package/dist/src/renderers/complex/__tests__/data-table.test.d.ts +0 -0
  22. package/dist/src/renderers/complex/calendar-view.d.ts +1 -0
  23. package/dist/src/renderers/complex/carousel.d.ts +1 -0
  24. package/dist/src/renderers/complex/chatbot.d.ts +1 -0
  25. package/dist/src/renderers/complex/chatbot.test.d.ts +1 -0
  26. package/dist/src/renderers/complex/data-table.d.ts +1 -0
  27. package/dist/src/renderers/complex/filter-builder.d.ts +1 -0
  28. package/dist/src/renderers/complex/index.d.ts +0 -0
  29. package/dist/src/renderers/complex/resizable.d.ts +1 -0
  30. package/dist/src/renderers/complex/scroll-area.d.ts +1 -0
  31. package/dist/src/renderers/complex/table.d.ts +1 -0
  32. package/dist/src/renderers/complex/timeline.d.ts +1 -0
  33. package/dist/src/renderers/data-display/alert.d.ts +1 -0
  34. package/dist/src/renderers/data-display/avatar.d.ts +1 -0
  35. package/dist/src/renderers/data-display/badge.d.ts +1 -0
  36. package/dist/src/renderers/data-display/index.d.ts +0 -0
  37. package/dist/src/renderers/data-display/list.d.ts +1 -0
  38. package/dist/src/renderers/data-display/statistic.d.ts +1 -0
  39. package/dist/src/renderers/data-display/tree-view.d.ts +1 -0
  40. package/dist/src/renderers/disclosure/accordion.d.ts +1 -0
  41. package/dist/src/renderers/disclosure/collapsible.d.ts +1 -0
  42. package/dist/src/renderers/disclosure/index.d.ts +0 -0
  43. package/dist/src/renderers/feedback/index.d.ts +0 -0
  44. package/dist/src/renderers/feedback/loading.d.ts +1 -0
  45. package/dist/src/renderers/feedback/progress.d.ts +1 -0
  46. package/dist/src/renderers/feedback/skeleton.d.ts +1 -0
  47. package/dist/src/renderers/feedback/toaster.d.ts +1 -0
  48. package/dist/src/renderers/form/button.d.ts +1 -0
  49. package/dist/src/renderers/form/calendar.d.ts +1 -0
  50. package/dist/src/renderers/form/checkbox.d.ts +1 -0
  51. package/dist/src/renderers/form/date-picker.d.ts +1 -0
  52. package/dist/src/renderers/form/file-upload.d.ts +1 -0
  53. package/dist/src/renderers/form/form.d.ts +1 -0
  54. package/dist/src/renderers/form/index.d.ts +0 -0
  55. package/dist/src/renderers/form/input-otp.d.ts +1 -0
  56. package/dist/src/renderers/form/input.d.ts +1 -0
  57. package/dist/src/renderers/form/label.d.ts +1 -0
  58. package/dist/src/renderers/form/radio-group.d.ts +1 -0
  59. package/dist/src/renderers/form/select.d.ts +1 -0
  60. package/dist/src/renderers/form/slider.d.ts +1 -0
  61. package/dist/src/renderers/form/switch.d.ts +1 -0
  62. package/dist/src/renderers/form/textarea.d.ts +1 -0
  63. package/dist/src/renderers/form/toggle.d.ts +1 -0
  64. package/dist/src/renderers/index.d.ts +0 -0
  65. package/dist/src/renderers/layout/card.d.ts +1 -0
  66. package/dist/src/renderers/layout/container.d.ts +1 -0
  67. package/dist/src/renderers/layout/flex.d.ts +1 -0
  68. package/dist/src/renderers/layout/grid.d.ts +1 -0
  69. package/dist/src/renderers/layout/index.d.ts +0 -0
  70. package/dist/src/renderers/layout/page.d.ts +7 -0
  71. package/dist/src/renderers/layout/semantic.d.ts +1 -0
  72. package/dist/src/renderers/layout/stack.d.ts +1 -0
  73. package/dist/src/renderers/layout/tabs.d.ts +1 -0
  74. package/dist/src/renderers/navigation/header-bar.d.ts +1 -0
  75. package/dist/src/renderers/navigation/index.d.ts +0 -0
  76. package/dist/src/renderers/navigation/sidebar.d.ts +1 -0
  77. package/dist/src/renderers/overlay/alert-dialog.d.ts +1 -0
  78. package/dist/src/renderers/overlay/context-menu.d.ts +1 -0
  79. package/dist/src/renderers/overlay/dialog.d.ts +1 -0
  80. package/dist/src/renderers/overlay/drawer.d.ts +1 -0
  81. package/dist/src/renderers/overlay/dropdown-menu.d.ts +1 -0
  82. package/dist/src/renderers/overlay/hover-card.d.ts +1 -0
  83. package/dist/src/renderers/overlay/index.d.ts +0 -0
  84. package/dist/src/renderers/overlay/popover.d.ts +1 -0
  85. package/dist/src/renderers/overlay/sheet.d.ts +1 -0
  86. package/dist/src/renderers/overlay/tooltip.d.ts +1 -0
  87. package/dist/src/ui/accordion.d.ts +7 -0
  88. package/dist/src/ui/alert-dialog.d.ts +14 -0
  89. package/dist/src/ui/alert.d.ts +9 -0
  90. package/dist/src/ui/aspect-ratio.d.ts +3 -0
  91. package/dist/src/ui/avatar.d.ts +6 -0
  92. package/dist/src/ui/badge.d.ts +9 -0
  93. package/dist/src/ui/breadcrumb.d.ts +11 -0
  94. package/dist/src/ui/button-group.d.ts +11 -0
  95. package/dist/src/ui/button.d.ts +13 -0
  96. package/dist/src/ui/calendar-view.d.ts +21 -0
  97. package/dist/src/ui/calendar.d.ts +8 -0
  98. package/dist/src/ui/card.d.ts +9 -0
  99. package/dist/src/ui/carousel.d.ts +19 -0
  100. package/dist/src/ui/chatbot.d.ts +36 -0
  101. package/dist/src/ui/checkbox.d.ts +4 -0
  102. package/dist/src/ui/collapsible.d.ts +5 -0
  103. package/dist/src/ui/command.d.ts +18 -0
  104. package/dist/src/ui/context-menu.d.ts +25 -0
  105. package/dist/src/ui/dialog.d.ts +15 -0
  106. package/dist/src/ui/drawer.d.ts +13 -0
  107. package/dist/src/ui/dropdown-menu.d.ts +25 -0
  108. package/dist/src/ui/empty.d.ts +11 -0
  109. package/dist/src/ui/field.d.ts +24 -0
  110. package/dist/src/ui/filter-builder.d.ts +31 -0
  111. package/dist/src/ui/form.d.ts +24 -0
  112. package/dist/src/ui/hover-card.d.ts +6 -0
  113. package/dist/src/ui/index.d.ts +56 -0
  114. package/dist/src/ui/input-group.d.ts +16 -0
  115. package/dist/src/ui/input-otp.d.ts +11 -0
  116. package/dist/src/ui/input.d.ts +3 -0
  117. package/dist/src/ui/item.d.ts +23 -0
  118. package/dist/src/ui/kbd.d.ts +3 -0
  119. package/dist/src/ui/label.d.ts +4 -0
  120. package/dist/src/ui/menubar.d.ts +26 -0
  121. package/dist/src/ui/navigation-menu.d.ts +14 -0
  122. package/dist/src/ui/pagination.d.ts +13 -0
  123. package/dist/src/ui/popover.d.ts +7 -0
  124. package/dist/src/ui/progress.d.ts +4 -0
  125. package/dist/src/ui/radio-group.d.ts +5 -0
  126. package/dist/src/ui/resizable.d.ts +10 -0
  127. package/dist/src/ui/scroll-area.d.ts +5 -0
  128. package/dist/src/ui/select.d.ts +15 -0
  129. package/dist/src/ui/separator.d.ts +4 -0
  130. package/dist/src/ui/sheet.d.ts +13 -0
  131. package/dist/src/ui/sidebar.d.ts +69 -0
  132. package/dist/src/ui/skeleton.d.ts +2 -0
  133. package/dist/src/ui/slider.d.ts +4 -0
  134. package/dist/src/ui/sonner.d.ts +3 -0
  135. package/dist/src/ui/spinner.d.ts +3 -0
  136. package/dist/src/ui/switch.d.ts +4 -0
  137. package/dist/src/ui/table.d.ts +10 -0
  138. package/dist/src/ui/tabs.d.ts +7 -0
  139. package/dist/src/ui/textarea.d.ts +3 -0
  140. package/dist/src/ui/timeline.d.ts +25 -0
  141. package/dist/src/ui/toggle-group.d.ts +9 -0
  142. package/dist/src/ui/toggle.d.ts +9 -0
  143. package/dist/src/ui/tooltip.d.ts +7 -0
  144. package/docs/FilterBuilder.md +268 -0
  145. package/metadata/Chart.component.yml +30 -0
  146. package/metadata/FilterBuilder.component.yml +39 -0
  147. package/metadata/GridLayout.component.yml +27 -0
  148. package/metadata/Menu.component.yml +31 -0
  149. package/metadata/ObjectForm.component.yml +34 -0
  150. package/metadata/ObjectTable.component.yml +41 -0
  151. package/metadata/Page.component.yml +24 -0
  152. package/package.json +87 -0
  153. package/postcss.config.js +6 -0
  154. package/src/hooks/use-mobile.tsx +19 -0
  155. package/src/index.css +76 -0
  156. package/src/index.test.ts +7 -0
  157. package/src/index.ts +10 -0
  158. package/src/lib/utils.tsx +27 -0
  159. package/src/new-components.test.ts +74 -0
  160. package/src/renderers/basic/div.tsx +41 -0
  161. package/src/renderers/basic/html.tsx +34 -0
  162. package/src/renderers/basic/icon.tsx +25 -0
  163. package/src/renderers/basic/image.tsx +37 -0
  164. package/src/renderers/basic/index.ts +7 -0
  165. package/src/renderers/basic/separator.tsx +48 -0
  166. package/src/renderers/basic/span.tsx +44 -0
  167. package/src/renderers/basic/text.tsx +42 -0
  168. package/src/renderers/complex/README-KANBAN.md +208 -0
  169. package/src/renderers/complex/TIMELINE.md +353 -0
  170. package/src/renderers/complex/__tests__/data-table.test.ts +52 -0
  171. package/src/renderers/complex/calendar-view.tsx +219 -0
  172. package/src/renderers/complex/carousel.tsx +60 -0
  173. package/src/renderers/complex/chatbot.test.ts +44 -0
  174. package/src/renderers/complex/chatbot.tsx +185 -0
  175. package/src/renderers/complex/data-table.tsx +650 -0
  176. package/src/renderers/complex/filter-builder.tsx +68 -0
  177. package/src/renderers/complex/index.ts +10 -0
  178. package/src/renderers/complex/resizable.tsx +54 -0
  179. package/src/renderers/complex/scroll-area.tsx +32 -0
  180. package/src/renderers/complex/table.tsx +86 -0
  181. package/src/renderers/complex/timeline.tsx +466 -0
  182. package/src/renderers/data-display/alert.tsx +37 -0
  183. package/src/renderers/data-display/avatar.tsx +29 -0
  184. package/src/renderers/data-display/badge.tsx +46 -0
  185. package/src/renderers/data-display/index.ts +6 -0
  186. package/src/renderers/data-display/list.tsx +95 -0
  187. package/src/renderers/data-display/statistic.tsx +98 -0
  188. package/src/renderers/data-display/tree-view.tsx +180 -0
  189. package/src/renderers/disclosure/accordion.tsx +60 -0
  190. package/src/renderers/disclosure/collapsible.tsx +44 -0
  191. package/src/renderers/disclosure/index.ts +2 -0
  192. package/src/renderers/feedback/index.ts +4 -0
  193. package/src/renderers/feedback/loading.tsx +69 -0
  194. package/src/renderers/feedback/progress.tsx +20 -0
  195. package/src/renderers/feedback/skeleton.tsx +22 -0
  196. package/src/renderers/feedback/toaster.tsx +26 -0
  197. package/src/renderers/form/button.tsx +61 -0
  198. package/src/renderers/form/calendar.tsx +25 -0
  199. package/src/renderers/form/checkbox.tsx +41 -0
  200. package/src/renderers/form/date-picker.tsx +75 -0
  201. package/src/renderers/form/file-upload.tsx +175 -0
  202. package/src/renderers/form/form.tsx +417 -0
  203. package/src/renderers/form/index.ts +16 -0
  204. package/src/renderers/form/input-otp.tsx +31 -0
  205. package/src/renderers/form/input.tsx +79 -0
  206. package/src/renderers/form/label.tsx +36 -0
  207. package/src/renderers/form/radio-group.tsx +54 -0
  208. package/src/renderers/form/select.tsx +66 -0
  209. package/src/renderers/form/slider.tsx +45 -0
  210. package/src/renderers/form/switch.tsx +39 -0
  211. package/src/renderers/form/textarea.tsx +45 -0
  212. package/src/renderers/form/toggle.tsx +76 -0
  213. package/src/renderers/index.ts +9 -0
  214. package/src/renderers/layout/card.tsx +69 -0
  215. package/src/renderers/layout/container.tsx +113 -0
  216. package/src/renderers/layout/flex.tsx +123 -0
  217. package/src/renderers/layout/grid.tsx +155 -0
  218. package/src/renderers/layout/index.ts +10 -0
  219. package/src/renderers/layout/page.tsx +82 -0
  220. package/src/renderers/layout/semantic.tsx +39 -0
  221. package/src/renderers/layout/stack.tsx +123 -0
  222. package/src/renderers/layout/tabs.tsx +63 -0
  223. package/src/renderers/navigation/header-bar.tsx +50 -0
  224. package/src/renderers/navigation/index.ts +2 -0
  225. package/src/renderers/navigation/sidebar.tsx +189 -0
  226. package/src/renderers/overlay/alert-dialog.tsx +63 -0
  227. package/src/renderers/overlay/context-menu.tsx +91 -0
  228. package/src/renderers/overlay/dialog.tsx +68 -0
  229. package/src/renderers/overlay/drawer.tsx +68 -0
  230. package/src/renderers/overlay/dropdown-menu.tsx +90 -0
  231. package/src/renderers/overlay/hover-card.tsx +46 -0
  232. package/src/renderers/overlay/index.ts +9 -0
  233. package/src/renderers/overlay/popover.tsx +47 -0
  234. package/src/renderers/overlay/sheet.tsx +68 -0
  235. package/src/renderers/overlay/tooltip.tsx +58 -0
  236. package/src/ui/accordion.tsx +64 -0
  237. package/src/ui/alert-dialog.tsx +155 -0
  238. package/src/ui/alert.tsx +78 -0
  239. package/src/ui/aspect-ratio.tsx +11 -0
  240. package/src/ui/avatar.tsx +51 -0
  241. package/src/ui/badge.tsx +46 -0
  242. package/src/ui/breadcrumb.tsx +109 -0
  243. package/src/ui/button-group.tsx +83 -0
  244. package/src/ui/button.tsx +65 -0
  245. package/src/ui/calendar-view.tsx +503 -0
  246. package/src/ui/calendar.tsx +237 -0
  247. package/src/ui/card.tsx +138 -0
  248. package/src/ui/carousel.tsx +239 -0
  249. package/src/ui/chatbot.tsx +240 -0
  250. package/src/ui/checkbox.tsx +32 -0
  251. package/src/ui/collapsible.tsx +31 -0
  252. package/src/ui/command.tsx +182 -0
  253. package/src/ui/context-menu.tsx +247 -0
  254. package/src/ui/dialog.tsx +141 -0
  255. package/src/ui/drawer.tsx +135 -0
  256. package/src/ui/dropdown-menu.tsx +254 -0
  257. package/src/ui/empty.tsx +104 -0
  258. package/src/ui/field.tsx +246 -0
  259. package/src/ui/filter-builder.tsx +359 -0
  260. package/src/ui/form.tsx +167 -0
  261. package/src/ui/hover-card.tsx +44 -0
  262. package/src/ui/index.ts +56 -0
  263. package/src/ui/input-group.tsx +170 -0
  264. package/src/ui/input-otp.tsx +81 -0
  265. package/src/ui/input.tsx +24 -0
  266. package/src/ui/item.tsx +193 -0
  267. package/src/ui/kbd.tsx +28 -0
  268. package/src/ui/label.tsx +24 -0
  269. package/src/ui/menubar.tsx +274 -0
  270. package/src/ui/navigation-menu.tsx +168 -0
  271. package/src/ui/pagination.tsx +127 -0
  272. package/src/ui/popover.tsx +48 -0
  273. package/src/ui/progress.tsx +41 -0
  274. package/src/ui/radio-group.tsx +45 -0
  275. package/src/ui/resizable.tsx +55 -0
  276. package/src/ui/scroll-area.tsx +58 -0
  277. package/src/ui/select.tsx +188 -0
  278. package/src/ui/separator.tsx +31 -0
  279. package/src/ui/sheet.tsx +137 -0
  280. package/src/ui/sidebar.tsx +726 -0
  281. package/src/ui/skeleton.tsx +20 -0
  282. package/src/ui/slider.tsx +63 -0
  283. package/src/ui/sonner.tsx +43 -0
  284. package/src/ui/spinner.tsx +38 -0
  285. package/src/ui/switch.tsx +31 -0
  286. package/src/ui/table.tsx +120 -0
  287. package/src/ui/tabs.tsx +86 -0
  288. package/src/ui/textarea.tsx +18 -0
  289. package/src/ui/timeline.tsx +266 -0
  290. package/src/ui/toggle-group.tsx +87 -0
  291. package/src/ui/toggle.tsx +50 -0
  292. package/src/ui/tooltip.tsx +61 -0
  293. package/tailwind.config.js +75 -0
  294. package/tsconfig.json +18 -0
  295. package/vite.config.ts +44 -0
@@ -0,0 +1,359 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { X, Plus, Trash2 } from "lucide-react"
5
+
6
+ import { cn } from "../lib/utils"
7
+ import { Button } from "./button"
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./select"
9
+ import { Input } from "./input"
10
+
11
+ export interface FilterCondition {
12
+ id: string
13
+ field: string
14
+ operator: string
15
+ value: string | number | boolean
16
+ }
17
+
18
+ export interface FilterGroup {
19
+ id: string
20
+ logic: "and" | "or"
21
+ conditions: FilterCondition[]
22
+ }
23
+
24
+ export interface FilterBuilderProps {
25
+ fields?: Array<{
26
+ value: string
27
+ label: string
28
+ type?: string
29
+ options?: Array<{ value: string; label: string }> // For select fields
30
+ }>
31
+ value?: FilterGroup
32
+ onChange?: (value: FilterGroup) => void
33
+ className?: string
34
+ showClearAll?: boolean
35
+ }
36
+
37
+ const defaultOperators = [
38
+ { value: "equals", label: "Equals" },
39
+ { value: "notEquals", label: "Does not equal" },
40
+ { value: "contains", label: "Contains" },
41
+ { value: "notContains", label: "Does not contain" },
42
+ { value: "isEmpty", label: "Is empty" },
43
+ { value: "isNotEmpty", label: "Is not empty" },
44
+ { value: "greaterThan", label: "Greater than" },
45
+ { value: "lessThan", label: "Less than" },
46
+ { value: "greaterOrEqual", label: "Greater than or equal" },
47
+ { value: "lessOrEqual", label: "Less than or equal" },
48
+ { value: "before", label: "Before" },
49
+ { value: "after", label: "After" },
50
+ { value: "between", label: "Between" },
51
+ { value: "in", label: "In" },
52
+ { value: "notIn", label: "Not in" },
53
+ ]
54
+
55
+ const textOperators = ["equals", "notEquals", "contains", "notContains", "isEmpty", "isNotEmpty"]
56
+ const numberOperators = ["equals", "notEquals", "greaterThan", "lessThan", "greaterOrEqual", "lessOrEqual", "isEmpty", "isNotEmpty"]
57
+ const booleanOperators = ["equals", "notEquals"]
58
+ const dateOperators = ["equals", "notEquals", "before", "after", "between", "isEmpty", "isNotEmpty"]
59
+ const selectOperators = ["equals", "notEquals", "in", "notIn", "isEmpty", "isNotEmpty"]
60
+
61
+ function FilterBuilder({
62
+ fields = [],
63
+ value,
64
+ onChange,
65
+ className,
66
+ showClearAll = true,
67
+ }: FilterBuilderProps) {
68
+ const [filterGroup, setFilterGroup] = React.useState<FilterGroup>(
69
+ value || {
70
+ id: "root",
71
+ logic: "and",
72
+ conditions: [],
73
+ }
74
+ )
75
+
76
+ React.useEffect(() => {
77
+ if (value && JSON.stringify(value) !== JSON.stringify(filterGroup)) {
78
+ setFilterGroup(value)
79
+ }
80
+ }, [value])
81
+
82
+ const handleChange = (newGroup: FilterGroup) => {
83
+ setFilterGroup(newGroup)
84
+ onChange?.(newGroup)
85
+ }
86
+
87
+ const addCondition = () => {
88
+ const newCondition: FilterCondition = {
89
+ id: crypto.randomUUID(),
90
+ field: fields[0]?.value || "",
91
+ operator: "equals",
92
+ value: "",
93
+ }
94
+ handleChange({
95
+ ...filterGroup,
96
+ conditions: [...filterGroup.conditions, newCondition],
97
+ })
98
+ }
99
+
100
+ const removeCondition = (conditionId: string) => {
101
+ handleChange({
102
+ ...filterGroup,
103
+ conditions: filterGroup.conditions.filter((c) => c.id !== conditionId),
104
+ })
105
+ }
106
+
107
+ const clearAllConditions = () => {
108
+ handleChange({
109
+ ...filterGroup,
110
+ conditions: [],
111
+ })
112
+ }
113
+
114
+ const updateCondition = (conditionId: string, updates: Partial<FilterCondition>) => {
115
+ handleChange({
116
+ ...filterGroup,
117
+ conditions: filterGroup.conditions.map((c) =>
118
+ c.id === conditionId ? { ...c, ...updates } : c
119
+ ),
120
+ })
121
+ }
122
+
123
+ const toggleLogic = () => {
124
+ handleChange({
125
+ ...filterGroup,
126
+ logic: filterGroup.logic === "and" ? "or" : "and",
127
+ })
128
+ }
129
+
130
+ const getOperatorsForField = (fieldValue: string) => {
131
+ const field = fields.find((f) => f.value === fieldValue)
132
+ const fieldType = field?.type || "text"
133
+
134
+ switch (fieldType) {
135
+ case "number":
136
+ return defaultOperators.filter((op) => numberOperators.includes(op.value))
137
+ case "boolean":
138
+ return defaultOperators.filter((op) => booleanOperators.includes(op.value))
139
+ case "date":
140
+ return defaultOperators.filter((op) => dateOperators.includes(op.value))
141
+ case "select":
142
+ return defaultOperators.filter((op) => selectOperators.includes(op.value))
143
+ case "text":
144
+ default:
145
+ return defaultOperators.filter((op) => textOperators.includes(op.value))
146
+ }
147
+ }
148
+
149
+ const needsValueInput = (operator: string) => {
150
+ return !["isEmpty", "isNotEmpty"].includes(operator)
151
+ }
152
+
153
+ const getInputType = (fieldValue: string) => {
154
+ const field = fields.find((f) => f.value === fieldValue)
155
+ const fieldType = field?.type || "text"
156
+
157
+ switch (fieldType) {
158
+ case "number":
159
+ return "number"
160
+ case "date":
161
+ return "date"
162
+ default:
163
+ return "text"
164
+ }
165
+ }
166
+
167
+ const renderValueInput = (condition: FilterCondition) => {
168
+ const field = fields.find((f) => f.value === condition.field)
169
+
170
+ // For select fields with options
171
+ if (field?.type === "select" && field.options) {
172
+ return (
173
+ <Select
174
+ value={String(condition.value || "")}
175
+ onValueChange={(value) =>
176
+ updateCondition(condition.id, { value })
177
+ }
178
+ >
179
+ <SelectTrigger className="h-9 text-sm">
180
+ <SelectValue placeholder="Select value" />
181
+ </SelectTrigger>
182
+ <SelectContent>
183
+ {field.options.map((opt) => (
184
+ <SelectItem key={opt.value} value={opt.value}>
185
+ {opt.label}
186
+ </SelectItem>
187
+ ))}
188
+ </SelectContent>
189
+ </Select>
190
+ )
191
+ }
192
+
193
+ // For boolean fields
194
+ if (field?.type === "boolean") {
195
+ return (
196
+ <Select
197
+ value={String(condition.value || "")}
198
+ onValueChange={(value) =>
199
+ updateCondition(condition.id, { value: value === "true" })
200
+ }
201
+ >
202
+ <SelectTrigger className="h-9 text-sm">
203
+ <SelectValue placeholder="Select value" />
204
+ </SelectTrigger>
205
+ <SelectContent>
206
+ <SelectItem value="true">True</SelectItem>
207
+ <SelectItem value="false">False</SelectItem>
208
+ </SelectContent>
209
+ </Select>
210
+ )
211
+ }
212
+
213
+ // Default input for text, number, date
214
+ const inputType = getInputType(condition.field)
215
+
216
+ // Format value based on field type
217
+ const formatValue = () => {
218
+ if (!condition.value) return ""
219
+ if (inputType === "date" && typeof condition.value === "string") {
220
+ // Ensure date is in YYYY-MM-DD format
221
+ return condition.value.split('T')[0]
222
+ }
223
+ return String(condition.value)
224
+ }
225
+
226
+ // Handle value change with proper type conversion
227
+ const handleValueChange = (newValue: string) => {
228
+ let convertedValue: string | number | boolean = newValue
229
+
230
+ if (field?.type === "number" && newValue !== "") {
231
+ convertedValue = parseFloat(newValue) || 0
232
+ } else if (field?.type === "date") {
233
+ convertedValue = newValue // Keep as ISO string
234
+ }
235
+
236
+ updateCondition(condition.id, { value: convertedValue })
237
+ }
238
+
239
+ return (
240
+ <Input
241
+ type={inputType}
242
+ className="h-9 text-sm"
243
+ placeholder="Value"
244
+ value={formatValue()}
245
+ onChange={(e) => handleValueChange(e.target.value)}
246
+ />
247
+ )
248
+ }
249
+
250
+ return (
251
+ <div className={cn("space-y-3", className)}>
252
+ <div className="flex items-center justify-between">
253
+ <div className="flex items-center gap-2">
254
+ <span className="text-sm font-medium">Where</span>
255
+ {filterGroup.conditions.length > 1 && (
256
+ <Button
257
+ variant="outline"
258
+ size="sm"
259
+ onClick={toggleLogic}
260
+ className="h-7 text-xs"
261
+ >
262
+ {filterGroup.logic.toUpperCase()}
263
+ </Button>
264
+ )}
265
+ </div>
266
+ {showClearAll && filterGroup.conditions.length > 0 && (
267
+ <Button
268
+ variant="ghost"
269
+ size="sm"
270
+ onClick={clearAllConditions}
271
+ className="h-7 text-xs text-muted-foreground hover:text-destructive"
272
+ >
273
+ <Trash2 className="h-3 w-3 mr-1" />
274
+ Clear all
275
+ </Button>
276
+ )}
277
+ </div>
278
+
279
+ <div className="space-y-2">
280
+ {filterGroup.conditions.map((condition) => (
281
+ <div key={condition.id} className="flex items-start gap-2">
282
+ <div className="flex-1 grid grid-cols-12 gap-2">
283
+ <div className="col-span-4">
284
+ <Select
285
+ value={condition.field}
286
+ onValueChange={(value) =>
287
+ updateCondition(condition.id, { field: value })
288
+ }
289
+ >
290
+ <SelectTrigger className="h-9 text-sm">
291
+ <SelectValue placeholder="Select field" />
292
+ </SelectTrigger>
293
+ <SelectContent>
294
+ {fields.map((field) => (
295
+ <SelectItem key={field.value} value={field.value}>
296
+ {field.label}
297
+ </SelectItem>
298
+ ))}
299
+ </SelectContent>
300
+ </Select>
301
+ </div>
302
+
303
+ <div className="col-span-4">
304
+ <Select
305
+ value={condition.operator}
306
+ onValueChange={(value) =>
307
+ updateCondition(condition.id, { operator: value })
308
+ }
309
+ >
310
+ <SelectTrigger className="h-9 text-sm">
311
+ <SelectValue placeholder="Operator" />
312
+ </SelectTrigger>
313
+ <SelectContent>
314
+ {getOperatorsForField(condition.field).map((op) => (
315
+ <SelectItem key={op.value} value={op.value}>
316
+ {op.label}
317
+ </SelectItem>
318
+ ))}
319
+ </SelectContent>
320
+ </Select>
321
+ </div>
322
+
323
+ {needsValueInput(condition.operator) && (
324
+ <div className="col-span-4">
325
+ {renderValueInput(condition)}
326
+ </div>
327
+ )}
328
+ </div>
329
+
330
+ <Button
331
+ variant="ghost"
332
+ size="icon-sm"
333
+ className="h-9 w-9 shrink-0"
334
+ onClick={() => removeCondition(condition.id)}
335
+ >
336
+ <X className="h-4 w-4" />
337
+ <span className="sr-only">Remove condition</span>
338
+ </Button>
339
+ </div>
340
+ ))}
341
+ </div>
342
+
343
+ <Button
344
+ variant="outline"
345
+ size="sm"
346
+ onClick={addCondition}
347
+ className="h-8"
348
+ disabled={fields.length === 0}
349
+ >
350
+ <Plus className="h-3 w-3" />
351
+ Add filter
352
+ </Button>
353
+ </div>
354
+ )
355
+ }
356
+
357
+ FilterBuilder.displayName = "FilterBuilder"
358
+
359
+ export { FilterBuilder }
@@ -0,0 +1,167 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import type * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { Slot } from "@radix-ui/react-slot"
6
+ import {
7
+ Controller,
8
+ FormProvider,
9
+ useFormContext,
10
+ useFormState,
11
+ type ControllerProps,
12
+ type FieldPath,
13
+ type FieldValues,
14
+ } from "react-hook-form"
15
+
16
+ import { cn } from "../lib/utils"
17
+ import { Label } from "./label"
18
+
19
+ const Form = FormProvider
20
+
21
+ type FormFieldContextValue<
22
+ TFieldValues extends FieldValues = FieldValues,
23
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
24
+ > = {
25
+ name: TName
26
+ }
27
+
28
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
29
+ {} as FormFieldContextValue
30
+ )
31
+
32
+ const FormField = <
33
+ TFieldValues extends FieldValues = FieldValues,
34
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
35
+ >({
36
+ ...props
37
+ }: ControllerProps<TFieldValues, TName>) => {
38
+ return (
39
+ <FormFieldContext.Provider value={{ name: props.name }}>
40
+ <Controller {...props} />
41
+ </FormFieldContext.Provider>
42
+ )
43
+ }
44
+
45
+ const useFormField = () => {
46
+ const fieldContext = React.useContext(FormFieldContext)
47
+ const itemContext = React.useContext(FormItemContext)
48
+ const { getFieldState } = useFormContext()
49
+ const formState = useFormState({ name: fieldContext.name })
50
+ const fieldState = getFieldState(fieldContext.name, formState)
51
+
52
+ if (!fieldContext) {
53
+ throw new Error("useFormField should be used within <FormField>")
54
+ }
55
+
56
+ const { id } = itemContext
57
+
58
+ return {
59
+ id,
60
+ name: fieldContext.name,
61
+ formItemId: `${id}-form-item`,
62
+ formDescriptionId: `${id}-form-item-description`,
63
+ formMessageId: `${id}-form-item-message`,
64
+ ...fieldState,
65
+ }
66
+ }
67
+
68
+ type FormItemContextValue = {
69
+ id: string
70
+ }
71
+
72
+ const FormItemContext = React.createContext<FormItemContextValue>(
73
+ {} as FormItemContextValue
74
+ )
75
+
76
+ function FormItem({ className, ...props }: React.ComponentProps<"div">) {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div
82
+ data-slot="form-item"
83
+ className={cn("grid gap-2", className)}
84
+ {...props}
85
+ />
86
+ </FormItemContext.Provider>
87
+ )
88
+ }
89
+
90
+ function FormLabel({
91
+ className,
92
+ ...props
93
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
94
+ const { error, formItemId } = useFormField()
95
+
96
+ return (
97
+ <Label
98
+ data-slot="form-label"
99
+ data-error={!!error}
100
+ className={cn("data-[error=true]:text-destructive", className)}
101
+ htmlFor={formItemId}
102
+ {...props}
103
+ />
104
+ )
105
+ }
106
+
107
+ function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ data-slot="form-control"
113
+ id={formItemId}
114
+ aria-describedby={
115
+ !error
116
+ ? `${formDescriptionId}`
117
+ : `${formDescriptionId} ${formMessageId}`
118
+ }
119
+ aria-invalid={!!error}
120
+ {...props}
121
+ />
122
+ )
123
+ }
124
+
125
+ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
126
+ const { formDescriptionId } = useFormField()
127
+
128
+ return (
129
+ <p
130
+ data-slot="form-description"
131
+ id={formDescriptionId}
132
+ className={cn("text-muted-foreground text-sm", className)}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
139
+ const { error, formMessageId } = useFormField()
140
+ const body = error ? String(error?.message ?? "") : props.children
141
+
142
+ if (!body) {
143
+ return null
144
+ }
145
+
146
+ return (
147
+ <p
148
+ data-slot="form-message"
149
+ id={formMessageId}
150
+ className={cn("text-destructive text-sm", className)}
151
+ {...props}
152
+ >
153
+ {body}
154
+ </p>
155
+ )
156
+ }
157
+
158
+ export {
159
+ useFormField,
160
+ Form,
161
+ FormItem,
162
+ FormLabel,
163
+ FormControl,
164
+ FormDescription,
165
+ FormMessage,
166
+ FormField,
167
+ }
@@ -0,0 +1,44 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5
+
6
+ import { cn } from "../lib/utils"
7
+
8
+ function HoverCard({
9
+ ...props
10
+ }: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
11
+ return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
12
+ }
13
+
14
+ function HoverCardTrigger({
15
+ ...props
16
+ }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
17
+ return (
18
+ <HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
19
+ )
20
+ }
21
+
22
+ function HoverCardContent({
23
+ className,
24
+ align = "center",
25
+ sideOffset = 4,
26
+ ...props
27
+ }: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
28
+ return (
29
+ <HoverCardPrimitive.Portal data-slot="hover-card-portal">
30
+ <HoverCardPrimitive.Content
31
+ data-slot="hover-card-content"
32
+ align={align}
33
+ sideOffset={sideOffset}
34
+ className={cn(
35
+ "bg-popover text-popover-foreground 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 z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ </HoverCardPrimitive.Portal>
41
+ )
42
+ }
43
+
44
+ export { HoverCard, HoverCardTrigger, HoverCardContent }
@@ -0,0 +1,56 @@
1
+ export * from './accordion';
2
+ export * from './alert-dialog';
3
+ export * from './alert';
4
+ export * from './aspect-ratio';
5
+ export * from './avatar';
6
+ export * from './badge';
7
+ export * from './breadcrumb';
8
+ export * from './button-group';
9
+ export * from './button';
10
+ export * from './calendar';
11
+ export * from './calendar-view';
12
+ export * from './card';
13
+ export * from './carousel';
14
+ export * from './chatbot';
15
+ export * from './checkbox';
16
+ export * from './collapsible';
17
+ export * from './command';
18
+ export * from './context-menu';
19
+ export * from './dialog';
20
+ export * from './drawer';
21
+ export * from './dropdown-menu';
22
+ export * from './empty';
23
+ export * from './field';
24
+ export * from './filter-builder';
25
+ export * from './form';
26
+ export * from './hover-card';
27
+ export * from './input-group';
28
+ export * from './input-otp';
29
+ export * from './input';
30
+ export * from './item';
31
+ export * from './kbd';
32
+ export * from './label';
33
+ export * from './menubar';
34
+ export * from './navigation-menu';
35
+ export * from './pagination';
36
+ export * from './popover';
37
+ export * from './progress';
38
+ export * from './radio-group';
39
+ export * from './resizable';
40
+ export * from './scroll-area';
41
+ export * from './select';
42
+ export * from './separator';
43
+ export * from './sheet';
44
+ export * from './sidebar';
45
+ export * from './skeleton';
46
+ export * from './slider';
47
+ export * from './sonner';
48
+ export * from './spinner';
49
+ export * from './switch';
50
+ export * from './table';
51
+ export * from './tabs';
52
+ export * from './textarea';
53
+ export * from './timeline';
54
+ export * from './toggle-group';
55
+ export * from './toggle';
56
+ export * from './tooltip';