@ayasofyazilim/ui 0.0.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 (236) hide show
  1. package/__mocks__/canvas.ts +8 -0
  2. package/components.json +21 -0
  3. package/eslint.config.js +4 -0
  4. package/jest-environment.js +37 -0
  5. package/jest.config.ts +47 -0
  6. package/jest.setup.ts +69 -0
  7. package/package.json +124 -0
  8. package/postcss.config.mjs +6 -0
  9. package/src/aria/index.tsx +1 -0
  10. package/src/aria/number-field.tsx +41 -0
  11. package/src/components/.gitkeep +0 -0
  12. package/src/components/accordion.tsx +66 -0
  13. package/src/components/alert-dialog.tsx +157 -0
  14. package/src/components/alert.tsx +70 -0
  15. package/src/components/aspect-ratio.tsx +11 -0
  16. package/src/components/avatar.tsx +53 -0
  17. package/src/components/badge.tsx +67 -0
  18. package/src/components/breadcrumb.tsx +109 -0
  19. package/src/components/button-group.tsx +83 -0
  20. package/src/components/button.tsx +68 -0
  21. package/src/components/calendar.tsx +219 -0
  22. package/src/components/card.tsx +92 -0
  23. package/src/components/carousel.tsx +241 -0
  24. package/src/components/chart.tsx +363 -0
  25. package/src/components/checkbox.tsx +32 -0
  26. package/src/components/collapsible.tsx +33 -0
  27. package/src/components/command.tsx +184 -0
  28. package/src/components/context-menu.tsx +252 -0
  29. package/src/components/dialog.tsx +144 -0
  30. package/src/components/drawer.tsx +135 -0
  31. package/src/components/dropdown-menu.tsx +258 -0
  32. package/src/components/empty.tsx +100 -0
  33. package/src/components/field.tsx +248 -0
  34. package/src/components/form.tsx +169 -0
  35. package/src/components/hover-card.tsx +44 -0
  36. package/src/components/input-group.tsx +170 -0
  37. package/src/components/input-otp.tsx +77 -0
  38. package/src/components/input.tsx +21 -0
  39. package/src/components/item.tsx +193 -0
  40. package/src/components/kbd.tsx +28 -0
  41. package/src/components/label.tsx +24 -0
  42. package/src/components/menubar.tsx +276 -0
  43. package/src/components/navigation-menu.tsx +168 -0
  44. package/src/components/pagination.tsx +130 -0
  45. package/src/components/popover.tsx +88 -0
  46. package/src/components/progress.tsx +31 -0
  47. package/src/components/radio-group.tsx +45 -0
  48. package/src/components/resizable.tsx +56 -0
  49. package/src/components/scroll-area.tsx +58 -0
  50. package/src/components/select.tsx +189 -0
  51. package/src/components/separator.tsx +28 -0
  52. package/src/components/sheet.tsx +140 -0
  53. package/src/components/sidebar.tsx +862 -0
  54. package/src/components/skeleton.tsx +13 -0
  55. package/src/components/slider.tsx +63 -0
  56. package/src/components/sonner.tsx +40 -0
  57. package/src/components/spinner.tsx +16 -0
  58. package/src/components/stepper.tsx +291 -0
  59. package/src/components/switch.tsx +31 -0
  60. package/src/components/table.tsx +133 -0
  61. package/src/components/tabs.tsx +66 -0
  62. package/src/components/textarea.tsx +18 -0
  63. package/src/components/toggle-group.tsx +83 -0
  64. package/src/components/toggle.tsx +47 -0
  65. package/src/components/tooltip.tsx +66 -0
  66. package/src/custom/action-button.tsx +48 -0
  67. package/src/custom/async-select.tsx +287 -0
  68. package/src/custom/awesome-not-found.tsx +116 -0
  69. package/src/custom/charts/area-chart.tsx +147 -0
  70. package/src/custom/charts/bar-chart.tsx +233 -0
  71. package/src/custom/charts/chart-card.tsx +103 -0
  72. package/src/custom/charts/index.tsx +16 -0
  73. package/src/custom/charts/pie-chart.tsx +168 -0
  74. package/src/custom/charts/radar-chart.tsx +126 -0
  75. package/src/custom/checkbox-tree.tsx +100 -0
  76. package/src/custom/combobox.tsx +296 -0
  77. package/src/custom/confirm-dialog.tsx +102 -0
  78. package/src/custom/country-selector.tsx +204 -0
  79. package/src/custom/date-picker/calendar-rac.tsx +109 -0
  80. package/src/custom/date-picker/datefield-rac.tsx +84 -0
  81. package/src/custom/date-picker/index.tsx +273 -0
  82. package/src/custom/date-picker/types/index.ts +4 -0
  83. package/src/custom/date-picker/utils/index.ts +42 -0
  84. package/src/custom/date-picker-old.tsx +50 -0
  85. package/src/custom/date-tooltip.tsx +98 -0
  86. package/src/custom/document-scanner/consts.ts +5 -0
  87. package/src/custom/document-scanner/corner-adjustment/action-buttons.tsx +33 -0
  88. package/src/custom/document-scanner/corner-adjustment/corner-handle.tsx +43 -0
  89. package/src/custom/document-scanner/corner-adjustment/hooks/use-corner-drag.ts +85 -0
  90. package/src/custom/document-scanner/corner-adjustment/index.tsx +125 -0
  91. package/src/custom/document-scanner/corner-adjustment/types.ts +53 -0
  92. package/src/custom/document-scanner/corner-adjustment/utils/clip-path.ts +22 -0
  93. package/src/custom/document-scanner/corner-adjustment/zoom-magnifier.tsx +115 -0
  94. package/src/custom/document-scanner/hooks/use-document-capture.ts +81 -0
  95. package/src/custom/document-scanner/hooks/use-document-scanner.ts +80 -0
  96. package/src/custom/document-scanner/hooks/use-perspective-crop.ts +38 -0
  97. package/src/custom/document-scanner/index.tsx +255 -0
  98. package/src/custom/document-scanner/lib.ts +407 -0
  99. package/src/custom/document-scanner/types.ts +205 -0
  100. package/src/custom/document-scanner/utils/perspective-correction.ts +139 -0
  101. package/src/custom/document-viewer/controllers.tsx +98 -0
  102. package/src/custom/document-viewer/index.tsx +43 -0
  103. package/src/custom/document-viewer/renderers/image.tsx +37 -0
  104. package/src/custom/document-viewer/renderers/index.tsx +2 -0
  105. package/src/custom/document-viewer/renderers/pdf.tsx +105 -0
  106. package/src/custom/email-input/domains.json +159 -0
  107. package/src/custom/email-input/email.tsx +229 -0
  108. package/src/custom/email-input/index.tsx +4 -0
  109. package/src/custom/email-input/types.ts +104 -0
  110. package/src/custom/file-uploader.tsx +541 -0
  111. package/src/custom/filter-component/fields/async-select.tsx +33 -0
  112. package/src/custom/filter-component/fields/date.tsx +60 -0
  113. package/src/custom/filter-component/fields/multi-select.tsx +30 -0
  114. package/src/custom/filter-component/index.tsx +217 -0
  115. package/src/custom/image-canvas.tsx +260 -0
  116. package/src/custom/json-editor.tsx +22 -0
  117. package/src/custom/master-data-grid/components/dialogs/column-settings-dialog.tsx +100 -0
  118. package/src/custom/master-data-grid/components/dialogs/index.ts +1 -0
  119. package/src/custom/master-data-grid/components/filters/client-filter.tsx +368 -0
  120. package/src/custom/master-data-grid/components/filters/filter-input.tsx +256 -0
  121. package/src/custom/master-data-grid/components/filters/index.ts +3 -0
  122. package/src/custom/master-data-grid/components/filters/inline-column-filter.tsx +233 -0
  123. package/src/custom/master-data-grid/components/filters/multi-filter-dialog.tsx +90 -0
  124. package/src/custom/master-data-grid/components/filters/server-filter.tsx +255 -0
  125. package/src/custom/master-data-grid/components/master-data-grid.tsx +472 -0
  126. package/src/custom/master-data-grid/components/pagination/index.ts +1 -0
  127. package/src/custom/master-data-grid/components/pagination/pagination.tsx +178 -0
  128. package/src/custom/master-data-grid/components/table/cell-renderer.tsx +634 -0
  129. package/src/custom/master-data-grid/components/table/header-cell.tsx +162 -0
  130. package/src/custom/master-data-grid/components/table/index.ts +4 -0
  131. package/src/custom/master-data-grid/components/table/table-body-renderer.tsx +113 -0
  132. package/src/custom/master-data-grid/components/table/virtual-body.tsx +138 -0
  133. package/src/custom/master-data-grid/components/toolbar/index.ts +1 -0
  134. package/src/custom/master-data-grid/components/toolbar/toolbar.tsx +314 -0
  135. package/src/custom/master-data-grid/hooks/index.ts +3 -0
  136. package/src/custom/master-data-grid/hooks/use-columns.tsx +332 -0
  137. package/src/custom/master-data-grid/hooks/use-editing.ts +106 -0
  138. package/src/custom/master-data-grid/hooks/use-table-state-reducer.ts +157 -0
  139. package/src/custom/master-data-grid/hooks/use-table-state.ts +31 -0
  140. package/src/custom/master-data-grid/index.ts +16 -0
  141. package/src/custom/master-data-grid/types.ts +466 -0
  142. package/src/custom/master-data-grid/utils/column-generator.tsx +306 -0
  143. package/src/custom/master-data-grid/utils/export-utils.ts +67 -0
  144. package/src/custom/master-data-grid/utils/filter-fns.ts +290 -0
  145. package/src/custom/master-data-grid/utils/index.ts +8 -0
  146. package/src/custom/master-data-grid/utils/pinning-utils.ts +88 -0
  147. package/src/custom/master-data-grid/utils/translation-utils.ts +42 -0
  148. package/src/custom/multi-select.tsx +432 -0
  149. package/src/custom/password-input.tsx +194 -0
  150. package/src/custom/phone-input.tsx +172 -0
  151. package/src/custom/schema-form/custom/index.tsx +1 -0
  152. package/src/custom/schema-form/custom/label.tsx +53 -0
  153. package/src/custom/schema-form/fields/base-input-field.tsx +82 -0
  154. package/src/custom/schema-form/fields/field.tsx +67 -0
  155. package/src/custom/schema-form/fields/index.tsx +5 -0
  156. package/src/custom/schema-form/fields/object.tsx +12 -0
  157. package/src/custom/schema-form/fields/table-array/array-field-item.tsx +90 -0
  158. package/src/custom/schema-form/fields/table-array/array-field-template.tsx +115 -0
  159. package/src/custom/schema-form/index.tsx +259 -0
  160. package/src/custom/schema-form/templates/description.tsx +20 -0
  161. package/src/custom/schema-form/templates/index.tsx +2 -0
  162. package/src/custom/schema-form/templates/submit.tsx +32 -0
  163. package/src/custom/schema-form/types.ts +64 -0
  164. package/src/custom/schema-form/utils/index.ts +4 -0
  165. package/src/custom/schema-form/utils/schema-dependency.ts +655 -0
  166. package/src/custom/schema-form/utils/schemas.ts +289 -0
  167. package/src/custom/schema-form/utils/validation.ts +23 -0
  168. package/src/custom/schema-form/widgets/boolean.tsx +77 -0
  169. package/src/custom/schema-form/widgets/combobox.tsx +274 -0
  170. package/src/custom/schema-form/widgets/date.tsx +59 -0
  171. package/src/custom/schema-form/widgets/email.tsx +34 -0
  172. package/src/custom/schema-form/widgets/index.tsx +10 -0
  173. package/src/custom/schema-form/widgets/password.tsx +40 -0
  174. package/src/custom/schema-form/widgets/phone.tsx +40 -0
  175. package/src/custom/schema-form/widgets/select.tsx +105 -0
  176. package/src/custom/schema-form/widgets/selectable.tsx +25 -0
  177. package/src/custom/schema-form/widgets/string-array.tsx +296 -0
  178. package/src/custom/schema-form/widgets/url.tsx +56 -0
  179. package/src/custom/section-layout-v2.tsx +212 -0
  180. package/src/custom/select-tabs.tsx +109 -0
  181. package/src/custom/selectable.tsx +316 -0
  182. package/src/custom/stepper.tsx +236 -0
  183. package/src/custom/tab-layout.tsx +213 -0
  184. package/src/custom/tanstack-table/fields/index.tsx +12 -0
  185. package/src/custom/tanstack-table/fields/tanstack-table-action-dialogs.tsx +89 -0
  186. package/src/custom/tanstack-table/fields/tanstack-table-column-header.tsx +66 -0
  187. package/src/custom/tanstack-table/fields/tanstack-table-filter-date.tsx +180 -0
  188. package/src/custom/tanstack-table/fields/tanstack-table-filter-faceted.tsx +158 -0
  189. package/src/custom/tanstack-table/fields/tanstack-table-filter-text.tsx +76 -0
  190. package/src/custom/tanstack-table/fields/tanstack-table-pagination.tsx +136 -0
  191. package/src/custom/tanstack-table/fields/tanstack-table-plain-table.tsx +142 -0
  192. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-confirmation.tsx +77 -0
  193. package/src/custom/tanstack-table/fields/tanstack-table-row-actions-custom-dialog.tsx +87 -0
  194. package/src/custom/tanstack-table/fields/tanstack-table-row-actions.tsx +151 -0
  195. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-custom-dialog.tsx +88 -0
  196. package/src/custom/tanstack-table/fields/tanstack-table-table-actions-schemaform-dialog.tsx +47 -0
  197. package/src/custom/tanstack-table/fields/tanstack-table-toolbar.tsx +143 -0
  198. package/src/custom/tanstack-table/fields/tanstack-table-view-options.tsx +171 -0
  199. package/src/custom/tanstack-table/index.tsx +244 -0
  200. package/src/custom/tanstack-table/types/index.ts +328 -0
  201. package/src/custom/tanstack-table/utils/cell-with-actions.tsx +21 -0
  202. package/src/custom/tanstack-table/utils/column-names.ts +26 -0
  203. package/src/custom/tanstack-table/utils/columns-by-row-data.tsx +312 -0
  204. package/src/custom/tanstack-table/utils/editable-columns-by-row-data.tsx +219 -0
  205. package/src/custom/tanstack-table/utils/faceted-boolean-options.tsx +22 -0
  206. package/src/custom/tanstack-table/utils/index.tsx +10 -0
  207. package/src/custom/tanstack-table/utils/pinning-styles.ts +57 -0
  208. package/src/custom/tanstack-table/utils/table.tsx +83 -0
  209. package/src/custom/tanstack-table/utils/test-conditions.ts +17 -0
  210. package/src/custom/timeline.tsx +208 -0
  211. package/src/custom/tree.tsx +200 -0
  212. package/src/custom/tscanify/browser.ts +66 -0
  213. package/src/custom/tscanify/index.ts +51 -0
  214. package/src/custom/tscanify/tscanify-browser.ts +522 -0
  215. package/src/custom/tscanify/tscanify.ts +262 -0
  216. package/src/custom/tscanify/types.ts +22 -0
  217. package/src/custom/webcam.tsx +737 -0
  218. package/src/hooks/.gitkeep +0 -0
  219. package/src/hooks/use-callback-ref.ts +27 -0
  220. package/src/hooks/use-controllable-state.ts +67 -0
  221. package/src/hooks/use-debounce.ts +19 -0
  222. package/src/hooks/use-is-visible.ts +23 -0
  223. package/src/hooks/use-media-query.ts +21 -0
  224. package/src/hooks/use-mobile.ts +21 -0
  225. package/src/hooks/use-on-window-resize.ts +15 -0
  226. package/src/hooks/use-scroll.tsx +22 -0
  227. package/src/lib/utils.ts +61 -0
  228. package/src/lib/zod.ts +2 -0
  229. package/src/styles/core.css +57 -0
  230. package/src/styles/globals.css +130 -0
  231. package/src/test/email-input.test.tsx +217 -0
  232. package/src/test/password-input.test.tsx +92 -0
  233. package/src/test/select-tabs.test.tsx +302 -0
  234. package/src/test/selectable.test.tsx +1093 -0
  235. package/tsconfig.json +13 -0
  236. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,634 @@
1
+ import { Switch } from "@repo/ayasofyazilim-ui/components/switch";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { z } from "zod";
4
+ import { Badge } from "../../../../components/badge";
5
+ import { Input } from "../../../../components/input";
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from "../../../../components/select";
13
+ import {
14
+ Tooltip,
15
+ TooltipContent,
16
+ TooltipTrigger,
17
+ } from "../../../../components/tooltip";
18
+ import { DatePicker } from "../../../date-picker";
19
+ import DateTooltip from "../../../date-tooltip";
20
+ import { cn } from "../../../../lib/utils";
21
+ import type {
22
+ CellRendererProps,
23
+ JSONSchemaProperty,
24
+ MasterDataGridResources,
25
+ } from "../../types";
26
+ import { getTranslations } from "../../utils/translation-utils";
27
+
28
+ const DEBOUNCE_DELAY = 150;
29
+ const MAX_STRING_LENGTH = 100;
30
+ const MAX_ARRAY_DISPLAY = 3;
31
+
32
+ const BADGE_VARIANT_MAP: Record<
33
+ string,
34
+ "default" | "secondary" | "outline" | "destructive"
35
+ > = {
36
+ active: "default",
37
+ inactive: "secondary",
38
+ pending: "outline",
39
+ success: "default",
40
+ warning: "destructive",
41
+ error: "destructive",
42
+ };
43
+
44
+ function createZodSchema(
45
+ schemaProperty?: JSONSchemaProperty,
46
+ t?: MasterDataGridResources
47
+ ): z.ZodType {
48
+ if (!schemaProperty) return z.unknown();
49
+
50
+ let schema: z.ZodType;
51
+
52
+ switch (schemaProperty.type) {
53
+ case "string":
54
+ schema = z.string({
55
+ message: getTranslations("validation.invalidString", t),
56
+ });
57
+
58
+ if (schemaProperty.minLength !== undefined) {
59
+ schema = (schema as z.ZodString).min(
60
+ schemaProperty.minLength,
61
+ t?.["validation.min_length"]
62
+ ? (t["validation.min_length"] ?? "").replace(
63
+ "{min}",
64
+ String(schemaProperty.minLength)
65
+ )
66
+ : `Must be at least ${schemaProperty.minLength} characters`
67
+ );
68
+ }
69
+ if (schemaProperty.maxLength !== undefined) {
70
+ schema = (schema as z.ZodString).max(
71
+ schemaProperty.maxLength,
72
+ t?.["validation.max_length"]
73
+ ? (t["validation.max_length"] ?? "").replace(
74
+ "{max}",
75
+ String(schemaProperty.maxLength)
76
+ )
77
+ : `Must be at most ${schemaProperty.maxLength} characters`
78
+ );
79
+ }
80
+
81
+ if (schemaProperty.format === "email") {
82
+ schema = (schema as z.ZodString).email(
83
+ t?.["validation.invalid_email"] || "Must be a valid email address"
84
+ );
85
+ }
86
+ if (schemaProperty.format === "uri" || schemaProperty.format === "url") {
87
+ schema = z.url(t?.["validation.invalid_url"] || "Must be a valid URL");
88
+ }
89
+ if (schemaProperty.format === "uuid") {
90
+ schema = z.uuid(
91
+ t?.["validation.invalid_uuid"] || "Must be a valid UUID"
92
+ );
93
+ }
94
+ break;
95
+
96
+ case "number":
97
+ schema = z.number({
98
+ message: t?.["validation.invalid_number"] || "Must be a valid number",
99
+ });
100
+
101
+ if (schemaProperty.minimum !== undefined) {
102
+ schema = (schema as z.ZodNumber).min(
103
+ schemaProperty.minimum,
104
+ t?.["validation.min_value"]
105
+ ? (t["validation.min_value"] ?? "").replace(
106
+ "{min}",
107
+ String(schemaProperty.minimum)
108
+ )
109
+ : `Must be at least ${schemaProperty.minimum}`
110
+ );
111
+ }
112
+ if (schemaProperty.maximum !== undefined) {
113
+ schema = (schema as z.ZodNumber).max(
114
+ schemaProperty.maximum,
115
+ t?.["validation.max_value"]
116
+ ? (t["validation.max_value"] ?? "").replace(
117
+ "{max}",
118
+ String(schemaProperty.maximum)
119
+ )
120
+ : `Must be at most ${schemaProperty.maximum}`
121
+ );
122
+ }
123
+ break;
124
+
125
+ case "integer":
126
+ schema = z
127
+ .number({
128
+ message:
129
+ t?.["validation.invalid_integer"] || "Must be a valid integer",
130
+ })
131
+ .int(t?.["validation.must_be_integer"] || "Must be an integer");
132
+
133
+ if (schemaProperty.minimum !== undefined) {
134
+ schema = (schema as z.ZodNumber).min(
135
+ schemaProperty.minimum,
136
+ t?.["validation.min_value"]
137
+ ? (t["validation.min_value"] ?? "").replace(
138
+ "{min}",
139
+ String(schemaProperty.minimum)
140
+ )
141
+ : `Must be at least ${schemaProperty.minimum}`
142
+ );
143
+ }
144
+ if (schemaProperty.maximum !== undefined) {
145
+ schema = (schema as z.ZodNumber).max(
146
+ schemaProperty.maximum,
147
+ t?.["validation.max_value"]
148
+ ? (t["validation.max_value"] ?? "").replace(
149
+ "{max}",
150
+ String(schemaProperty.maximum)
151
+ )
152
+ : `Must be at most ${schemaProperty.maximum}`
153
+ );
154
+ }
155
+ break;
156
+
157
+ case "boolean":
158
+ schema = z.boolean({
159
+ message: t?.["validation.invalid_boolean"] || "Must be true or false",
160
+ });
161
+ break;
162
+
163
+ default:
164
+ schema = z.unknown();
165
+ }
166
+
167
+ if (schemaProperty.enum && Array.isArray(schemaProperty.enum)) {
168
+ schema = z.enum(
169
+ schemaProperty.enum as [string, ...string[]],
170
+ t?.["validation.invalid_enum"] || "Invalid value"
171
+ );
172
+ }
173
+
174
+ if (!schemaProperty.required) {
175
+ schema = schema.optional().nullable();
176
+ }
177
+
178
+ return schema;
179
+ }
180
+
181
+ interface ErrorWrapperProps {
182
+ error: string | null;
183
+ mode: "tooltip" | "inline" | "both";
184
+ children: React.ReactNode;
185
+ }
186
+
187
+ function ErrorWrapper({ error, mode, children }: ErrorWrapperProps) {
188
+ if (!error) return <>{children}</>;
189
+
190
+ const showTooltip = mode === "tooltip" || mode === "both";
191
+ const showInline = mode === "inline" || mode === "both";
192
+
193
+ const inlineError = showInline && (
194
+ <span className="text-[10px] text-destructive px-2 py-0.5 leading-tight animate-in fade-in slide-in-from-top-1">
195
+ {error}
196
+ </span>
197
+ );
198
+
199
+ if (showTooltip && !showInline) {
200
+ return (
201
+ <Tooltip>
202
+ <TooltipTrigger asChild>{children}</TooltipTrigger>
203
+ <TooltipContent side="bottom" className="max-w-xs">
204
+ {error}
205
+ </TooltipContent>
206
+ </Tooltip>
207
+ );
208
+ }
209
+
210
+ if (showInline && !showTooltip) {
211
+ return (
212
+ <div className="flex flex-col w-full h-full">
213
+ {children}
214
+ {inlineError}
215
+ </div>
216
+ );
217
+ }
218
+
219
+ return (
220
+ <Tooltip>
221
+ <TooltipTrigger asChild>
222
+ <div className="flex flex-col w-full h-full">
223
+ {children}
224
+ {inlineError}
225
+ </div>
226
+ </TooltipTrigger>
227
+ <TooltipContent side="bottom" className="max-w-xs">
228
+ {error}
229
+ </TooltipContent>
230
+ </Tooltip>
231
+ );
232
+ }
233
+
234
+ export function CellRenderer<TData = unknown>({
235
+ value,
236
+ row,
237
+ column,
238
+ schemaProperty,
239
+ editable = false,
240
+ onUpdate,
241
+ t,
242
+ error,
243
+ className,
244
+ dateOptions,
245
+ localization,
246
+ fieldName,
247
+ customRenderers,
248
+ errorDisplayMode = "tooltip",
249
+ }: CellRendererProps<TData>) {
250
+ const [localValue, setLocalValue] = useState(value);
251
+ const [validationError, setValidationError] = useState<string | null>(
252
+ error || null
253
+ );
254
+ const inputRef = useRef<HTMLInputElement>(null);
255
+ const updateTimeoutRef = useRef<NodeJS.Timeout>(null);
256
+ const datePickerIdRef = useRef<string>(
257
+ `date-${schemaProperty?.title || "field"}-${Date.now()}`
258
+ );
259
+ const datePickerMountedRef = useRef<boolean>(false);
260
+ const handleDateChangeRef = useRef<((date: Date) => void) | null>(null);
261
+
262
+ const validationSchema = useMemo(
263
+ () => (schemaProperty ? createZodSchema(schemaProperty, t) : null),
264
+ [schemaProperty, t]
265
+ );
266
+
267
+ useEffect(() => {
268
+ if (!editable) {
269
+ setLocalValue(value);
270
+ datePickerMountedRef.current = false;
271
+ }
272
+ }, [value, editable]);
273
+
274
+ useEffect(() => {
275
+ return () => {
276
+ if (updateTimeoutRef.current) {
277
+ clearTimeout(updateTimeoutRef.current);
278
+ }
279
+ };
280
+ }, []);
281
+
282
+ const debouncedUpdate = useCallback(
283
+ (newValue: unknown) => {
284
+ if (updateTimeoutRef.current) {
285
+ clearTimeout(updateTimeoutRef.current);
286
+ }
287
+ updateTimeoutRef.current = setTimeout(() => {
288
+ if (onUpdate) {
289
+ onUpdate(newValue);
290
+ }
291
+ }, DEBOUNCE_DELAY);
292
+ },
293
+ [onUpdate]
294
+ );
295
+
296
+ const handleChange = useCallback(
297
+ (newValue: unknown) => {
298
+ setLocalValue(newValue);
299
+
300
+ const err = validationSchema
301
+ ? (() => {
302
+ const result = validationSchema.safeParse(newValue);
303
+ return result.success
304
+ ? null
305
+ : result.error.issues[0]?.message || "Invalid value";
306
+ })()
307
+ : null;
308
+
309
+ setValidationError(err);
310
+
311
+ if (!err) {
312
+ debouncedUpdate(newValue);
313
+ }
314
+ },
315
+ [validationSchema, debouncedUpdate]
316
+ );
317
+
318
+ const inputClassName = useMemo(
319
+ () =>
320
+ cn(
321
+ "h-full px-2 shadow-none border-0 rounded-none focus-visible:ring-0 focus-visible:ring-offset-0",
322
+ validationError && "border border-destructive"
323
+ ),
324
+ [validationError]
325
+ );
326
+
327
+ if (editable && !schemaProperty?.readOnly) {
328
+ if (fieldName && customRenderers?.[fieldName]) {
329
+ const customRenderer = customRenderers?.[fieldName]!;
330
+ return (
331
+ <>
332
+ {customRenderer({
333
+ value: localValue,
334
+ row,
335
+ column,
336
+ onUpdate: handleChange,
337
+ error: validationError || undefined,
338
+ schemaProperty,
339
+ t,
340
+ })}
341
+ </>
342
+ );
343
+ }
344
+
345
+ if (schemaProperty?.type === "boolean" || typeof value === "boolean") {
346
+ return (
347
+ <ErrorWrapper error={validationError} mode={errorDisplayMode}>
348
+ <div className="flex items-center px-2">
349
+ <Switch
350
+ checked={!!localValue}
351
+ onCheckedChange={handleChange}
352
+ className={validationError ? "border-destructive" : ""}
353
+ />
354
+ </div>
355
+ </ErrorWrapper>
356
+ );
357
+ }
358
+
359
+ if (schemaProperty?.enum && Array.isArray(schemaProperty.enum)) {
360
+ return (
361
+ <ErrorWrapper error={validationError} mode={errorDisplayMode}>
362
+ <div className="w-full h-full flex items-center">
363
+ <Select value={String(localValue)} onValueChange={handleChange}>
364
+ <SelectTrigger
365
+ size="sm"
366
+ className={cn(
367
+ "h-[35px]! px-2 w-full shadow-none border-0 rounded-none focus:ring-0 focus:ring-offset-0",
368
+ validationError && "border border-destructive"
369
+ )}
370
+ >
371
+ <SelectValue />
372
+ </SelectTrigger>
373
+ <SelectContent>
374
+ {schemaProperty.enum.map((option) => (
375
+ <SelectItem key={String(option)} value={String(option)}>
376
+ {String(option)}
377
+ </SelectItem>
378
+ ))}
379
+ </SelectContent>
380
+ </Select>
381
+ </div>
382
+ </ErrorWrapper>
383
+ );
384
+ }
385
+
386
+ if (
387
+ schemaProperty?.type === "number" ||
388
+ schemaProperty?.type === "integer"
389
+ ) {
390
+ const handleNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
391
+ const val =
392
+ schemaProperty.type === "integer"
393
+ ? parseInt(e.target.value)
394
+ : parseFloat(e.target.value);
395
+ handleChange(isNaN(val) ? e.target.value : val);
396
+ };
397
+
398
+ return (
399
+ <ErrorWrapper error={validationError} mode={errorDisplayMode}>
400
+ <div className="w-full h-full flex items-center">
401
+ <Input
402
+ ref={inputRef}
403
+ type="number"
404
+ value={String(localValue ?? "")}
405
+ onChange={handleNumberChange}
406
+ className={inputClassName}
407
+ />
408
+ </div>
409
+ </ErrorWrapper>
410
+ );
411
+ }
412
+
413
+ if (
414
+ schemaProperty?.format === "date" ||
415
+ schemaProperty?.format === "date-time"
416
+ ) {
417
+ const dateValue =
418
+ localValue instanceof Date
419
+ ? localValue
420
+ : localValue
421
+ ? new Date(String(localValue))
422
+ : undefined;
423
+
424
+ const isDateTime = schemaProperty?.format === "date-time";
425
+
426
+ if (!handleDateChangeRef.current) {
427
+ handleDateChangeRef.current = (date: Date) => {
428
+ if (!datePickerMountedRef.current) {
429
+ datePickerMountedRef.current = true;
430
+ return;
431
+ }
432
+
433
+ if (schemaProperty?.format === "date-time") {
434
+ handleChange(date?.toISOString());
435
+ } else {
436
+ const isoDate = date?.toISOString().split("T")[0];
437
+ handleChange(isoDate);
438
+ }
439
+ };
440
+ }
441
+
442
+ return (
443
+ <ErrorWrapper error={validationError} mode={errorDisplayMode}>
444
+ <DatePicker
445
+ id={datePickerIdRef.current}
446
+ showIcon={false}
447
+ defaultValue={
448
+ dateValue instanceof Date && !Number.isNaN(dateValue.getTime())
449
+ ? dateValue
450
+ : undefined
451
+ }
452
+ onChange={handleDateChangeRef.current}
453
+ classNames={{
454
+ dateInput: cn(
455
+ "shadow-none border-0 h-8! rounded-none",
456
+ validationError && "border border-destructive"
457
+ ),
458
+ }}
459
+ useTime={isDateTime}
460
+ />
461
+ </ErrorWrapper>
462
+ );
463
+ }
464
+
465
+ const inputType = schemaProperty?.format === "email" ? "email" : "text";
466
+
467
+ return (
468
+ <ErrorWrapper error={validationError} mode={errorDisplayMode}>
469
+ <div className="w-full h-full flex items-center">
470
+ <Input
471
+ ref={inputRef}
472
+ type={inputType}
473
+ value={String(localValue ?? "")}
474
+ onChange={(e) => handleChange(e.target.value)}
475
+ className={inputClassName}
476
+ />
477
+ </div>
478
+ </ErrorWrapper>
479
+ );
480
+ }
481
+
482
+ if (fieldName && customRenderers?.[fieldName]) {
483
+ const customRenderer = customRenderers?.[fieldName]!;
484
+ return (
485
+ <>
486
+ {customRenderer({
487
+ value: localValue,
488
+ row,
489
+ column,
490
+ onUpdate: handleChange,
491
+ error: validationError || undefined,
492
+ schemaProperty,
493
+ t,
494
+ })}
495
+ </>
496
+ );
497
+ }
498
+ if (value === null || value === undefined) {
499
+ return (
500
+ <span className={cn("text-muted-foreground italic", className)}>—</span>
501
+ );
502
+ }
503
+
504
+ if (schemaProperty?.type === "boolean" || typeof value === "boolean") {
505
+ const yesLabel = t?.["cell.boolean.yes"] ?? "Yes";
506
+ const noLabel = t?.["cell.boolean.no"] ?? "No";
507
+ return (
508
+ <Badge variant={value ? "default" : "secondary"} className={className}>
509
+ {value ? yesLabel : noLabel}
510
+ </Badge>
511
+ );
512
+ }
513
+
514
+ if (
515
+ schemaProperty?.format === "date" ||
516
+ schemaProperty?.format === "date-time"
517
+ ) {
518
+ try {
519
+ const date = value instanceof Date ? value : new Date(value as string);
520
+
521
+ const defaultLocalization = localization || {
522
+ locale: "en-US",
523
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
524
+ lang: "en",
525
+ };
526
+
527
+ const defaultDateOptions =
528
+ dateOptions ||
529
+ (schemaProperty.format === "date"
530
+ ? {
531
+ day: "2-digit",
532
+ month: "short",
533
+ year: "numeric",
534
+ }
535
+ : {
536
+ day: "2-digit",
537
+ month: "short",
538
+ year: "numeric",
539
+ hour: "2-digit",
540
+ minute: "2-digit",
541
+ hour12: false,
542
+ });
543
+
544
+ return (
545
+ <DateTooltip
546
+ date={date}
547
+ localization={defaultLocalization}
548
+ dateOptions={defaultDateOptions}
549
+ />
550
+ );
551
+ } catch {
552
+ return <span className={className}>{String(value)}</span>;
553
+ }
554
+ }
555
+
556
+ if (schemaProperty?.enum && Array.isArray(schemaProperty.enum)) {
557
+ const label = String(value);
558
+ return (
559
+ <Badge variant="outline" className={cn("font-normal", className)}>
560
+ {t?.[`column.${fieldName}.${label}`] || label}
561
+ </Badge>
562
+ );
563
+ }
564
+
565
+ if (schemaProperty?.format === "badge") {
566
+ const variant = BADGE_VARIANT_MAP[String(value).toLowerCase()] || "outline";
567
+ const label = String(value);
568
+ return (
569
+ <Badge variant={variant} className={className}>
570
+ {t?.[`column.${fieldName}.${label}`] || label}
571
+ </Badge>
572
+ );
573
+ }
574
+
575
+ if (schemaProperty?.format === "uri") {
576
+ return (
577
+ <a
578
+ href={String(value)}
579
+ target="_blank"
580
+ rel="noopener noreferrer"
581
+ className={cn("text-primary hover:underline", className)}
582
+ onClick={(e) => e.stopPropagation()}
583
+ >
584
+ {String(value)}
585
+ </a>
586
+ );
587
+ }
588
+
589
+ if (schemaProperty?.format === "uuid") {
590
+ const uuid = String(value);
591
+ return (
592
+ <code className={cn("text-xs bg-muted 5 rounded", className)}>
593
+ {uuid.slice(0, 8)}...
594
+ </code>
595
+ );
596
+ }
597
+
598
+ if (Array.isArray(value)) {
599
+ return (
600
+ <div className="flex flex-wrap gap-1">
601
+ {value.slice(0, MAX_ARRAY_DISPLAY).map((item, idx) => (
602
+ <Badge key={idx} variant="secondary" className="text-xs">
603
+ {String(item)}
604
+ </Badge>
605
+ ))}
606
+ {value.length > MAX_ARRAY_DISPLAY && (
607
+ <Badge variant="outline" className="text-xs">
608
+ +{value.length - MAX_ARRAY_DISPLAY}
609
+ </Badge>
610
+ )}
611
+ </div>
612
+ );
613
+ }
614
+
615
+ if (typeof value === "object") {
616
+ return (
617
+ <code className={cn("text-xs bg-muted rounded", className)}>
618
+ {JSON.stringify(value)}
619
+ </code>
620
+ );
621
+ }
622
+
623
+ const strValue = String(value);
624
+
625
+ if (strValue.length > MAX_STRING_LENGTH) {
626
+ return (
627
+ <span title={strValue} className={className}>
628
+ {strValue.slice(0, MAX_STRING_LENGTH)}...
629
+ </span>
630
+ );
631
+ }
632
+
633
+ return <span className={className}>{strValue}</span>;
634
+ }