@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,368 @@
1
+ import { Dispatch, SetStateAction, useState } from "react";
2
+ import {
3
+ Select,
4
+ SelectContent,
5
+ SelectItem,
6
+ SelectTrigger,
7
+ SelectValue,
8
+ } from "../../../../components/select";
9
+ import type { ColumnFilter, ColumnMeta, FilterOperator } from "../../types";
10
+ import { getFilterOperators } from "../../utils/filter-fns";
11
+ import { getColumnName, getTranslations } from "../../utils/translation-utils";
12
+ import { FilterInput } from "./filter-input";
13
+ import { BaseMultiFilterDialogProps } from "./multi-filter-dialog";
14
+ import { ButtonGroup } from "@repo/ayasofyazilim-ui/components/button-group";
15
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
16
+ import {
17
+ Trash2,
18
+ ArrowUpDown,
19
+ CalendarCheck,
20
+ CalendarX,
21
+ ChevronLeft,
22
+ ChevronRight,
23
+ Circle,
24
+ CircleOff,
25
+ Equal,
26
+ List,
27
+ ListX,
28
+ X as NotEqual,
29
+ SearchCode,
30
+ SearchX,
31
+ TextCursor,
32
+ TextCursorInput,
33
+ } from "lucide-react";
34
+
35
+ interface FilterRow {
36
+ id: string;
37
+ columnId: string;
38
+ operator: FilterOperator;
39
+ value: string;
40
+ value2?: string;
41
+ }
42
+ interface ClientFilterContentProps<TData>
43
+ extends BaseMultiFilterDialogProps<TData> {
44
+ setOpen: Dispatch<SetStateAction<boolean>>;
45
+ }
46
+
47
+ function getOperatorIcon(operator: FilterOperator) {
48
+ const icons: Record<FilterOperator, React.ReactNode> = {
49
+ equals: <Equal className="h-3.5 w-3.5 min-w-3.5" />,
50
+ notEquals: <NotEqual className="h-3.5 w-3.5 min-w-3.5" />,
51
+ contains: <SearchCode className="h-3.5 w-3.5 min-w-3.5" />,
52
+ notContains: <SearchX className="h-3.5 w-3.5 min-w-3.5" />,
53
+ startsWith: <TextCursorInput className="h-3.5 w-3.5 min-w-3.5" />,
54
+ endsWith: <TextCursor className="h-3.5 w-3.5 min-w-3.5" />,
55
+ isEmpty: <Circle className="h-3.5 w-3.5 min-w-3.5" />,
56
+ isNotEmpty: <CircleOff className="h-3.5 w-3.5 min-w-3.5" />,
57
+ greaterThan: <ChevronRight className="h-3.5 w-3.5 min-w-3.5" />,
58
+ greaterThanOrEqual: <ChevronRight className="h-3.5 w-3.5 min-w-3.5" />,
59
+ lessThan: <ChevronLeft className="h-3.5 w-3.5 min-w-3.5" />,
60
+ lessThanOrEqual: <ChevronLeft className="h-3.5 w-3.5 min-w-3.5" />,
61
+ between: <ArrowUpDown className="h-3.5 w-3.5 min-w-3.5" />,
62
+ inRange: <ArrowUpDown className="h-3.5 w-3.5 min-w-3.5" />,
63
+ before: <CalendarX className="h-3.5 w-3.5 min-w-3.5" />,
64
+ after: <CalendarCheck className="h-3.5 w-3.5 min-w-3.5" />,
65
+ inList: <List className="h-3.5 w-3.5 min-w-3.5" />,
66
+ notInList: <ListX className="h-3.5 w-3.5 min-w-3.5" />,
67
+ };
68
+ return icons[operator] || <SearchCode className="h-3.5 w-3.5 min-w-3.5" />;
69
+ }
70
+
71
+ export function ClientFilterContent<TData>({
72
+ table,
73
+ config,
74
+ setOpen,
75
+ }: ClientFilterContentProps<TData>) {
76
+ const { t } = config;
77
+ const currentFilters = table.getState().columnFilters;
78
+ const [filterRows, setFilterRows] = useState<FilterRow[]>(() => {
79
+ if (currentFilters.length > 0) {
80
+ return currentFilters.map((tableFilter, index) => {
81
+ // TanStack Table stores the entire ColumnFilter object in the value property
82
+ const filterValue = tableFilter.value as ColumnFilter;
83
+
84
+ // Handle different value types
85
+ let parsedValue = "";
86
+ let parsedValue2 = "";
87
+
88
+ if (filterValue?.value !== null && filterValue?.value !== undefined) {
89
+ parsedValue =
90
+ typeof filterValue.value === "object"
91
+ ? JSON.stringify(filterValue.value)
92
+ : String(filterValue.value);
93
+ }
94
+
95
+ if (filterValue?.value2 !== null && filterValue?.value2 !== undefined) {
96
+ parsedValue2 =
97
+ typeof filterValue.value2 === "object"
98
+ ? JSON.stringify(filterValue.value2)
99
+ : String(filterValue.value2);
100
+ }
101
+
102
+ return {
103
+ id: `filter-${index}`,
104
+ columnId: tableFilter.id,
105
+ operator: filterValue?.operator || "contains",
106
+ value: parsedValue,
107
+ value2: parsedValue2,
108
+ };
109
+ });
110
+ }
111
+ return [
112
+ {
113
+ id: "filter-0",
114
+ columnId: "",
115
+ operator: "contains",
116
+ value: "",
117
+ value2: "",
118
+ },
119
+ ];
120
+ });
121
+ const filterableColumns = table
122
+ .getAllColumns()
123
+ .filter((col) => col.getCanFilter());
124
+
125
+ const addFilter = () => {
126
+ const newId = `filter-${Date.now()}`;
127
+ setFilterRows([
128
+ ...filterRows,
129
+ { id: newId, columnId: "", operator: "contains", value: "", value2: "" },
130
+ ]);
131
+ };
132
+
133
+ const removeFilter = (id: string) => {
134
+ if (filterRows.length === 1) {
135
+ setFilterRows([
136
+ {
137
+ id: "filter-0",
138
+ columnId: "",
139
+ operator: "contains",
140
+ value: "",
141
+ value2: "",
142
+ },
143
+ ]);
144
+ } else {
145
+ setFilterRows(filterRows.filter((row) => row.id !== id));
146
+ }
147
+ };
148
+
149
+ const updateFilter = (id: string, updates: Partial<FilterRow>) => {
150
+ setFilterRows(
151
+ filterRows.map((row) => (row.id === id ? { ...row, ...updates } : row))
152
+ );
153
+ };
154
+
155
+ const getAvailableOperators = (columnId: string): FilterOperator[] => {
156
+ if (!columnId) return ["contains"];
157
+ const column = table.getColumn(columnId);
158
+ if (!column) return ["contains"];
159
+
160
+ const meta = column.columnDef.meta as ColumnMeta | undefined;
161
+ const schemaProperty = meta?.schemaProperty;
162
+ const filterOperators = meta?.filterOperators;
163
+
164
+ return (
165
+ filterOperators ||
166
+ (schemaProperty
167
+ ? getFilterOperators(schemaProperty.type, schemaProperty.format)
168
+ : ["contains"])
169
+ );
170
+ };
171
+
172
+ const getColumnMeta = (columnId: string): ColumnMeta | undefined => {
173
+ if (!columnId) return undefined;
174
+ const column = table.getColumn(columnId);
175
+ if (!column) return undefined;
176
+ return column.columnDef.meta as ColumnMeta | undefined;
177
+ };
178
+
179
+ const handleColumnChange = (filterId: string, columnId: string) => {
180
+ const operators = getAvailableOperators(columnId);
181
+ updateFilter(filterId, { columnId, operator: operators[0] || "contains" });
182
+ };
183
+
184
+ const applyFilters = (closePopover?: () => void) => {
185
+ table.resetColumnFilters();
186
+ filterRows.forEach((row) => {
187
+ const isRange = row.operator === "between" || row.operator === "inRange";
188
+ const needsNoInput =
189
+ row.operator === "isEmpty" || row.operator === "isNotEmpty";
190
+
191
+ if (row.columnId) {
192
+ const column = table.getColumn(row.columnId);
193
+ if (column) {
194
+ if (needsNoInput) {
195
+ const filter: ColumnFilter = {
196
+ id: row.columnId,
197
+ operator: row.operator,
198
+ value: "",
199
+ };
200
+ column.setFilterValue(filter);
201
+ } else if (isRange && row.value && row.value2) {
202
+ const filter: ColumnFilter = {
203
+ id: row.columnId,
204
+ operator: row.operator,
205
+ value: row.value,
206
+ value2: row.value2,
207
+ };
208
+ column.setFilterValue(filter);
209
+ } else if (!isRange && row.value) {
210
+ const filter: ColumnFilter = {
211
+ id: row.columnId,
212
+ operator: row.operator,
213
+ value: row.value,
214
+ };
215
+ column.setFilterValue(filter);
216
+ }
217
+ }
218
+ }
219
+ });
220
+
221
+ closePopover?.();
222
+ setOpen(false);
223
+ };
224
+
225
+ const resetFilters = () => {
226
+ table.resetColumnFilters();
227
+ setFilterRows([
228
+ {
229
+ id: "filter-0",
230
+ columnId: "",
231
+ operator: "contains",
232
+ value: "",
233
+ value2: "",
234
+ },
235
+ ]);
236
+ setOpen(false);
237
+ };
238
+ return (
239
+ <div className="space-y-4">
240
+ <div className="font-semibold text-sm sm:hidden">
241
+ {getTranslations("filter.title", t)}
242
+ </div>
243
+ <div className="space-y-2 max-h-[60vh] overflow-y-auto">
244
+ {filterRows.map((row, index) => {
245
+ const availableOps = getAvailableOperators(row.columnId);
246
+ const columnMeta = getColumnMeta(row.columnId);
247
+
248
+ return (
249
+ <ButtonGroup
250
+ className="w-full min-w-0 flex-wrap sm:flex-nowrap"
251
+ key={row.id}
252
+ >
253
+ {index === 0 ? (
254
+ <div className="w-12 sm:w-16 rounded-l-md border flex items-center justify-center px-1 sm:px-2 text-xs font-medium text-muted-foreground shrink-0">
255
+ {getTranslations("filter.where", t)}
256
+ </div>
257
+ ) : (
258
+ <div className="w-12 sm:w-16 rounded-l-md border flex items-center justify-center px-1 sm:px-2 text-xs font-medium text-muted-foreground shrink-0">
259
+ {getTranslations("filter.and", t)}
260
+ </div>
261
+ )}
262
+ <Select
263
+ value={row.columnId}
264
+ onValueChange={(value) => handleColumnChange(row.id, value)}
265
+ >
266
+ <SelectTrigger className="w-32 sm:w-40 min-w-0">
267
+ <SelectValue
268
+ placeholder={getTranslations("filter.selectColumn", t)}
269
+ />
270
+ </SelectTrigger>
271
+ <SelectContent>
272
+ {filterableColumns.map((col) => (
273
+ <SelectItem key={col.id} value={col.id}>
274
+ {getColumnName(col, t)}
275
+ </SelectItem>
276
+ ))}
277
+ </SelectContent>
278
+ </Select>
279
+ <Select
280
+ value={row.operator}
281
+ onValueChange={(value) =>
282
+ updateFilter(row.id, { operator: value as FilterOperator })
283
+ }
284
+ disabled={!row.columnId}
285
+ >
286
+ <SelectTrigger className="w-16 sm:w-40 min-w-0">
287
+ <span className="flex items-center gap-2">
288
+ <span className="sm:hidden">
289
+ {getOperatorIcon(row.operator)}
290
+ </span>
291
+ <span className="hidden sm:inline">
292
+ {getTranslations(`filter.operator.${row.operator}`, t)}
293
+ </span>
294
+ </span>
295
+ </SelectTrigger>
296
+ <SelectContent>
297
+ {availableOps.map((op) => (
298
+ <SelectItem key={op} value={op}>
299
+ <span className="flex items-center gap-2">
300
+ {getOperatorIcon(op)}
301
+ <span>
302
+ {getTranslations(`filter.operator.${op}`, t)}
303
+ </span>
304
+ </span>
305
+ </SelectItem>
306
+ ))}
307
+ </SelectContent>
308
+ </Select>
309
+ <FilterInput
310
+ operator={row.operator}
311
+ value={row.value}
312
+ value2={row.value2}
313
+ columnMeta={columnMeta}
314
+ onValueChange={(value) => updateFilter(row.id, { value })}
315
+ onValue2Change={(value2) => updateFilter(row.id, { value2 })}
316
+ onSliderChange={(values) => {
317
+ const [min, max] = values;
318
+ updateFilter(row.id, {
319
+ value: String(min),
320
+ value2: String(max),
321
+ });
322
+ }}
323
+ onClear={() => updateFilter(row.id, { value: "", value2: "" })}
324
+ t={t}
325
+ variant="popover"
326
+ />
327
+ <Button
328
+ variant="outline"
329
+ size="icon"
330
+ onClick={() => removeFilter(row.id)}
331
+ className="shrink-0"
332
+ >
333
+ <Trash2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
334
+ </Button>
335
+ </ButtonGroup>
336
+ );
337
+ })}
338
+ </div>
339
+ <div className="flex flex-col sm:flex-row items-stretch sm:items-center justify-between gap-2 pt-4 border-t">
340
+ <div className="flex flex-col sm:flex-row gap-2">
341
+ <Button
342
+ onClick={addFilter}
343
+ variant="outline"
344
+ size="sm"
345
+ className="text-xs sm:text-sm"
346
+ >
347
+ {getTranslations("filter.addFilter", t)}
348
+ </Button>
349
+ <Button
350
+ onClick={resetFilters}
351
+ variant="outline"
352
+ size="sm"
353
+ className="text-xs sm:text-sm"
354
+ >
355
+ {getTranslations("filter.resetFilters", t)}
356
+ </Button>
357
+ </div>
358
+ <Button
359
+ onClick={() => applyFilters()}
360
+ size="sm"
361
+ className="text-xs sm:text-sm"
362
+ >
363
+ {getTranslations("filter.apply", t)}
364
+ </Button>
365
+ </div>
366
+ </div>
367
+ );
368
+ }
@@ -0,0 +1,256 @@
1
+ import { useMemo } from "react";
2
+ import { ArrowRight, X } from "lucide-react";
3
+ import {
4
+ InputGroup,
5
+ InputGroupAddon,
6
+ InputGroupButton,
7
+ InputGroupInput,
8
+ } from "../../../../components/input-group";
9
+ import { Input } from "../../../../components/input";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../../../../components/select";
17
+ import { Slider } from "../../../../components/slider";
18
+ import type {
19
+ FilterOperator,
20
+ ColumnMeta,
21
+ MasterDataGridResources,
22
+ } from "../../types";
23
+ import { getTranslations } from "../../utils/translation-utils";
24
+
25
+ interface FilterInputProps {
26
+ operator: FilterOperator;
27
+ value: string;
28
+ value2?: string;
29
+ columnMeta?: ColumnMeta;
30
+ onValueChange: (value: string) => void;
31
+ onValue2Change?: (value: string) => void;
32
+ onSliderChange?: (values: number[]) => void;
33
+ onClear: () => void;
34
+ t?: MasterDataGridResources;
35
+ variant?: "inline" | "popover";
36
+ }
37
+ export function FilterInput({
38
+ operator,
39
+ value,
40
+ value2 = "",
41
+ columnMeta,
42
+ onValueChange,
43
+ onValue2Change,
44
+ onSliderChange,
45
+ onClear,
46
+ t,
47
+ variant = "popover",
48
+ }: FilterInputProps) {
49
+ const schemaProperty = columnMeta?.schemaProperty;
50
+
51
+ const isRangeOperator = operator === "between" || operator === "inRange";
52
+ const needsNoInput = operator === "isEmpty" || operator === "isNotEmpty";
53
+ const isNumberType =
54
+ schemaProperty?.type === "number" || schemaProperty?.type === "integer";
55
+ const isBooleanType = schemaProperty?.type === "boolean";
56
+ const showSlider = isRangeOperator && isNumberType;
57
+
58
+ const sliderMin = useMemo(
59
+ () => schemaProperty?.minimum ?? 0,
60
+ [schemaProperty?.minimum]
61
+ );
62
+ const sliderMax = useMemo(
63
+ () => schemaProperty?.maximum ?? 100,
64
+ [schemaProperty?.maximum]
65
+ );
66
+
67
+ const inputType = isNumberType ? "number" : "text";
68
+ if (needsNoInput) {
69
+ return null;
70
+ }
71
+ if (isBooleanType && !isRangeOperator) {
72
+ if (variant === "inline") {
73
+ return (
74
+ <InputGroup>
75
+ <Select value={value} onValueChange={onValueChange}>
76
+ <SelectTrigger className="h-9">
77
+ <SelectValue placeholder={getTranslations("filter.value", t)} />
78
+ </SelectTrigger>
79
+ <SelectContent>
80
+ <SelectItem value="true">
81
+ {getTranslations("filter.true", t)}
82
+ </SelectItem>
83
+ <SelectItem value="false">
84
+ {getTranslations("filter.false", t)}
85
+ </SelectItem>
86
+ </SelectContent>
87
+ </Select>
88
+ {value && (
89
+ <InputGroupButton
90
+ size="icon-xs"
91
+ onClick={onClear}
92
+ aria-label={getTranslations("filter.clearFilter", t)}
93
+ >
94
+ <X />
95
+ </InputGroupButton>
96
+ )}
97
+ </InputGroup>
98
+ );
99
+ }
100
+
101
+ return (
102
+ <Select value={value} onValueChange={onValueChange}>
103
+ <SelectTrigger className="flex-1 min-w-0">
104
+ <SelectValue placeholder={getTranslations("filter.value", t)} />
105
+ </SelectTrigger>
106
+ <SelectContent>
107
+ <SelectItem value="true">
108
+ {getTranslations("filter.true", t)}
109
+ </SelectItem>
110
+ <SelectItem value="false">
111
+ {getTranslations("filter.false", t)}
112
+ </SelectItem>
113
+ </SelectContent>
114
+ </Select>
115
+ );
116
+ }
117
+ if (isRangeOperator && onValue2Change) {
118
+ if (variant === "inline") {
119
+ return (
120
+ <div className="space-y-3">
121
+ <InputGroup className="rounded-none border-0 shadow-none">
122
+ <InputGroupInput
123
+ value={value}
124
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
125
+ onValueChange(e.target.value)
126
+ }
127
+ placeholder={getTranslations("filter.min", t)}
128
+ type={inputType}
129
+ className="text-sm rounded-none"
130
+ />
131
+ <span className="text-xs text-muted-foreground px-1">&</span>
132
+ <InputGroupInput
133
+ value={value2}
134
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
135
+ onValue2Change(e.target.value)
136
+ }
137
+ placeholder={getTranslations("filter.max", t)}
138
+ type={inputType}
139
+ className="text-sm rounded-none"
140
+ />
141
+ {(value || value2) && (
142
+ <InputGroupAddon align="inline-end">
143
+ <InputGroupButton
144
+ size="icon-xs"
145
+ onClick={onClear}
146
+ aria-label={getTranslations("filter.clearFilter", t)}
147
+ >
148
+ <X />
149
+ </InputGroupButton>
150
+ </InputGroupAddon>
151
+ )}
152
+ </InputGroup>
153
+
154
+ {showSlider && onSliderChange && (
155
+ <div className="space-y-2">
156
+ <Slider
157
+ min={sliderMin}
158
+ max={sliderMax}
159
+ step={1}
160
+ value={[
161
+ Number(value) || sliderMin,
162
+ Number(value2) || sliderMax,
163
+ ]}
164
+ onValueChange={onSliderChange}
165
+ className="w-full"
166
+ />
167
+ <div className="flex justify-between text-xs font-medium text-foreground">
168
+ <span className="px-2 py-0.5 rounded bg-muted">
169
+ {value || sliderMin}
170
+ </span>
171
+ <span className="px-2 py-0.5 rounded bg-muted">
172
+ {value2 || sliderMax}
173
+ </span>
174
+ </div>
175
+ </div>
176
+ )}
177
+ </div>
178
+ );
179
+ }
180
+ return (
181
+ <InputGroup className="flex-1 min-w-0">
182
+ <InputGroupInput
183
+ placeholder={getTranslations("filter.min", t)}
184
+ value={value}
185
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
186
+ onValueChange(e.target.value)
187
+ }
188
+ type={inputType}
189
+ className="flex-1 min-w-0 text-sm rounded-none"
190
+ />
191
+ <span className="text-muted-foreground px-0.5 sm:px-1">
192
+ <ArrowRight className="h-3 w-3 sm:hidden" />
193
+ <span className="hidden sm:inline text-xs">
194
+ {getTranslations("filter.to", t)}
195
+ </span>
196
+ </span>
197
+ <InputGroupInput
198
+ placeholder={getTranslations("filter.max", t)}
199
+ value={value2}
200
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
201
+ onValue2Change(e.target.value)
202
+ }
203
+ type={inputType}
204
+ className="flex-1 min-w-0 text-sm rounded-none"
205
+ />
206
+ {(value || value2) && (
207
+ <InputGroupAddon align="inline-end">
208
+ <InputGroupButton
209
+ size="icon-xs"
210
+ onClick={onClear}
211
+ aria-label={getTranslations("filter.clearFilter", t)}
212
+ >
213
+ <X />
214
+ </InputGroupButton>
215
+ </InputGroupAddon>
216
+ )}
217
+ </InputGroup>
218
+ );
219
+ }
220
+ if (variant === "inline") {
221
+ return (
222
+ <InputGroup className="rounded-none">
223
+ <InputGroupInput
224
+ value={value}
225
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
226
+ onValueChange(e.target.value)
227
+ }
228
+ placeholder={getTranslations("filter.value", t)}
229
+ type={inputType}
230
+ className="rounded-none"
231
+ />
232
+ {value && (
233
+ <InputGroupAddon align="inline-end">
234
+ <InputGroupButton
235
+ size="icon-xs"
236
+ onClick={onClear}
237
+ aria-label={getTranslations("filter.clearFilter", t)}
238
+ >
239
+ <X />
240
+ </InputGroupButton>
241
+ </InputGroupAddon>
242
+ )}
243
+ </InputGroup>
244
+ );
245
+ }
246
+
247
+ return (
248
+ <Input
249
+ placeholder={getTranslations("filter.value", t)}
250
+ value={value}
251
+ onChange={(e) => onValueChange(e.target.value)}
252
+ type={inputType}
253
+ className="flex-1 min-w-0 text-sm rounded-none"
254
+ />
255
+ );
256
+ }
@@ -0,0 +1,3 @@
1
+ export { FilterInput } from "./filter-input";
2
+ export { InlineColumnFilter } from "./inline-column-filter";
3
+ export { MultiFilterDialog } from "./multi-filter-dialog";