@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,217 @@
1
+ "use client";
2
+
3
+ import { FilterIcon } from "lucide-react";
4
+ import {
5
+ Dispatch,
6
+ JSX,
7
+ ReactNode,
8
+ SetStateAction,
9
+ useState,
10
+ useTransition,
11
+ } from "react";
12
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
13
+ import {
14
+ Card,
15
+ CardContent,
16
+ CardHeader,
17
+ } from "@repo/ayasofyazilim-ui/components/card";
18
+ import {
19
+ Collapsible,
20
+ CollapsibleContent,
21
+ CollapsibleTrigger,
22
+ } from "@repo/ayasofyazilim-ui/components/collapsible";
23
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
24
+ import AsyncSelectField from "./fields/async-select";
25
+ import DateField from "./fields/date";
26
+ import MultiSelectField from "./fields/multi-select";
27
+
28
+ export type FilterComponentSearchItem = { id: string; name: string };
29
+ export type DateSelectType = {
30
+ title: string;
31
+ id: string;
32
+ placeholder?: string;
33
+ onChange: Dispatch<SetStateAction<string>>;
34
+ value: string;
35
+ options: {
36
+ label: string;
37
+ value: string;
38
+ }[];
39
+ order?: number;
40
+ };
41
+
42
+ export type MultiSelectType = {
43
+ title: string;
44
+ value: string[];
45
+ options: { label: string; value: string }[];
46
+ onChange: Dispatch<SetStateAction<string[]>>;
47
+ order?: number;
48
+ placeholder?: string;
49
+ selectAllLabel?: string;
50
+ id: string;
51
+ };
52
+ export type AsyncSelectType = {
53
+ id: string;
54
+ title: string;
55
+ fetchAction: (search: string) => Promise<FilterComponentSearchItem[]>;
56
+ onChange: Dispatch<SetStateAction<FilterComponentSearchItem[]>>;
57
+ value: FilterComponentSearchItem[];
58
+ multiple?: boolean;
59
+ order?: number;
60
+ };
61
+ export type CustomFieldType = { order?: number; component: JSX.Element };
62
+
63
+ function isAsyncSelectType(
64
+ filter: DateSelectType | MultiSelectType | AsyncSelectType | CustomFieldType
65
+ ): filter is AsyncSelectType {
66
+ return (filter as AsyncSelectType).fetchAction !== undefined;
67
+ }
68
+
69
+ function isMultiSelectType(
70
+ filter: DateSelectType | MultiSelectType | AsyncSelectType | CustomFieldType
71
+ ): filter is MultiSelectType {
72
+ return (
73
+ (filter as MultiSelectType).options !== undefined &&
74
+ Array.isArray((filter as MultiSelectType).value)
75
+ );
76
+ }
77
+
78
+ function isDateSelectType(
79
+ filter: DateSelectType | MultiSelectType | AsyncSelectType | CustomFieldType
80
+ ): filter is DateSelectType {
81
+ return (
82
+ (filter as DateSelectType).options !== undefined &&
83
+ typeof (filter as DateSelectType).value === "string"
84
+ );
85
+ }
86
+
87
+ export default function FilterComponent({
88
+ dateSelect,
89
+ multiSelect,
90
+ asyncSelect,
91
+ onSubmit,
92
+ filtersText = "Filters",
93
+ searchText = "Search",
94
+ applyFilterText = "Apply",
95
+ className,
96
+ cardClassName,
97
+ defaultOpen = true,
98
+ customField,
99
+ disabled = false,
100
+ isCollapsible = true,
101
+ filterGuidanceContent,
102
+ }: {
103
+ dateSelect: DateSelectType[];
104
+ multiSelect: MultiSelectType[];
105
+ asyncSelect: AsyncSelectType[];
106
+ onSubmit: () => void;
107
+ filtersText?: string;
108
+ applyFilterText?: string;
109
+ searchText?: string;
110
+ className?: string;
111
+ cardClassName?: string;
112
+ defaultOpen?: boolean;
113
+ disabled?: boolean;
114
+ customField?: CustomFieldType[];
115
+ isCollapsible?: boolean;
116
+ filterGuidanceContent?: ReactNode;
117
+ }) {
118
+ const fields = [
119
+ ...dateSelect,
120
+ ...multiSelect,
121
+ ...asyncSelect,
122
+ ...(customField || []),
123
+ ].sort((a, b) => (a.order || 0) - (b.order || 0));
124
+
125
+ const [isPending, startTransition] = useTransition();
126
+ const [isOpen, setIsOpen] = useState(defaultOpen);
127
+ function handleSubmit() {
128
+ startTransition(() => {
129
+ onSubmit();
130
+ });
131
+ }
132
+ return (
133
+ <Collapsible
134
+ open={isOpen}
135
+ onOpenChange={setIsOpen}
136
+ className={cn("w-full space-y-2", className)}
137
+ >
138
+ {isCollapsible && (
139
+ <CollapsibleTrigger asChild>
140
+ <Button variant="outline" size="icon" disabled={!isCollapsible}>
141
+ <FilterIcon className="h-4 w-4" />
142
+ <span className="sr-only">Filters</span>
143
+ </Button>
144
+ </CollapsibleTrigger>
145
+ )}
146
+ <CollapsibleContent className="space-y-2">
147
+ <Card
148
+ className={cn(
149
+ "shadow-none",
150
+ !filterGuidanceContent && "mx-auto",
151
+ cardClassName
152
+ )}
153
+ >
154
+ <CardHeader className="flex flex-row font-bold text-xl items-center justify-between">
155
+ {filtersText}
156
+ </CardHeader>
157
+ <CardContent className="flex flex-row gap-3 items-start">
158
+ {filterGuidanceContent && (
159
+ <div className="w-1/2 text-sm text-muted-foreground">
160
+ {filterGuidanceContent}
161
+ </div>
162
+ )}
163
+
164
+ <div
165
+ className={cn(
166
+ "flex flex-col gap-4",
167
+ filterGuidanceContent ? "w-1/2" : "w-full"
168
+ )}
169
+ >
170
+ {fields.map((filter, index) => {
171
+ if (isAsyncSelectType(filter)) {
172
+ return (
173
+ <AsyncSelectField
174
+ key={filter.id}
175
+ filter={filter}
176
+ isPending={isPending || disabled}
177
+ searchText={searchText}
178
+ />
179
+ );
180
+ }
181
+ if (isMultiSelectType(filter)) {
182
+ return (
183
+ <MultiSelectField
184
+ key={filter.id}
185
+ filter={filter}
186
+ isPending={isPending || disabled}
187
+ />
188
+ );
189
+ }
190
+ if (isDateSelectType(filter)) {
191
+ return (
192
+ <DateField
193
+ key={filter.id}
194
+ filter={filter}
195
+ isPending={isPending || disabled}
196
+ />
197
+ );
198
+ }
199
+ return (
200
+ <div key={`c${index.toString()}`}>{filter.component}</div>
201
+ );
202
+ })}
203
+
204
+ <Button
205
+ disabled={isPending || disabled}
206
+ onClick={() => handleSubmit()}
207
+ variant="default"
208
+ >
209
+ {applyFilterText}
210
+ </Button>
211
+ </div>
212
+ </CardContent>
213
+ </Card>
214
+ </CollapsibleContent>
215
+ </Collapsible>
216
+ );
217
+ }
@@ -0,0 +1,260 @@
1
+ "use client";
2
+
3
+ import React, { useCallback, useEffect, useRef, useState } from "react";
4
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
5
+
6
+ type Props = {
7
+ imageUrl: string;
8
+ zoom?: number;
9
+ classNames?: {
10
+ container?: string;
11
+ canvas?: string;
12
+ };
13
+ minZoom?: number;
14
+ maxZoom?: number;
15
+ onZoomChange?: (zoom: number) => void;
16
+ };
17
+
18
+ export default function ImageCanvas({
19
+ imageUrl,
20
+ zoom = 1,
21
+ classNames,
22
+ onZoomChange,
23
+ minZoom = 0.1,
24
+ maxZoom = 5,
25
+ }: Props) {
26
+ const canvasRef = useRef<HTMLCanvasElement>(null);
27
+ const [image, setImage] = useState<HTMLImageElement | null>(null);
28
+ const [currentZoom, setCurrentZoom] = useState(zoom);
29
+ const [position, setPosition] = useState({ x: 0, y: 0 });
30
+ const [isDragging, setIsDragging] = useState(false);
31
+ const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
32
+ const [error, setError] = useState<string | null>(null);
33
+
34
+ const renderError = useCallback((errorMessage: string) => {
35
+ if (!canvasRef.current) return;
36
+
37
+ const canvas = canvasRef.current;
38
+ const ctx = canvas.getContext("2d");
39
+ if (!ctx) return;
40
+
41
+ // Clear canvas
42
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
43
+
44
+ // Set styling for the error message
45
+ ctx.fillStyle = "rgba(220, 53, 69, 0.1)"; // Light red background
46
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
47
+
48
+ ctx.font = "16px Arial";
49
+ ctx.fillStyle = "#dc3545"; // Bootstrap danger color
50
+ ctx.textAlign = "center";
51
+ ctx.textBaseline = "middle";
52
+
53
+ // Split message by newlines and render each line
54
+ const lines = errorMessage.split("\n");
55
+ const lineHeight = 20;
56
+ const startY = canvas.height / 2 - (lines.length * lineHeight) / 2;
57
+
58
+ lines.forEach((line, index) => {
59
+ ctx.fillText(line, canvas.width / 2, startY + index * lineHeight);
60
+ });
61
+ }, []);
62
+
63
+ // Memoized rendering function
64
+ const renderCanvas = useCallback(() => {
65
+ if (!canvasRef.current) return;
66
+ if (!image) {
67
+ if (error) {
68
+ renderError(error);
69
+ }
70
+ return;
71
+ }
72
+ const canvas = canvasRef.current;
73
+ const ctx = canvas.getContext("2d");
74
+ if (!ctx) return;
75
+
76
+ // Set canvas dimensions to match container
77
+ canvas.width = canvas.clientWidth;
78
+ canvas.height = canvas.clientHeight;
79
+
80
+ // Clear the canvas
81
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
82
+
83
+ // Calculate the center of the canvas
84
+ const centerX = canvas.width / 2;
85
+ const centerY = canvas.height / 2;
86
+
87
+ // Calculate the scaled dimensions
88
+ const scaledWidth = image.width * currentZoom;
89
+ const scaledHeight = image.height * currentZoom;
90
+
91
+ // Calculate position to center the image
92
+ const x = centerX - scaledWidth / 2 + position.x;
93
+ const y = centerY - scaledHeight / 2 + position.y;
94
+
95
+ // Draw the image with the current zoom and position
96
+ ctx.drawImage(image, x, y, scaledWidth, scaledHeight);
97
+ }, [image, currentZoom, position]); // Load the image - optimized to properly clean up
98
+ useEffect(() => {
99
+ const img = new Image();
100
+
101
+ const onLoad = () => {
102
+ setImage(img);
103
+ // We need to wait for the next render cycle after setImage before rendering the canvas
104
+ // This ensures the image state is updated before trying to render
105
+ setTimeout(() => renderCanvas(), 0);
106
+ };
107
+
108
+ const onError = (e: ErrorEvent) => {
109
+ const errorMsg = e.message || "Failed to load image";
110
+ setError(`Failed to load image: ${errorMsg}`);
111
+ setImage(null);
112
+ setTimeout(() => renderCanvas(), 0);
113
+ };
114
+
115
+ img.addEventListener("load", onLoad);
116
+ img.addEventListener("error", onError);
117
+
118
+ try {
119
+ img.src = imageUrl;
120
+ img.crossOrigin = "anonymous"; // Handle CORS if needed
121
+ img.className = "border rounded-md";
122
+ } catch (err) {
123
+ const errorMsg = err instanceof Error ? err.message : "Invalid image URL";
124
+ setError(`Image error: ${errorMsg}`);
125
+ }
126
+ return () => {
127
+ img.removeEventListener("load", onLoad);
128
+ img.removeEventListener("error", onError);
129
+ img.src = ""; // Clean up to avoid memory leaks
130
+ };
131
+ }, [imageUrl]);
132
+
133
+ // Ensure the canvas is drawn whenever image, zoom, or position changes
134
+ useEffect(() => {
135
+ if (image) {
136
+ renderCanvas();
137
+ }
138
+ }, [image, currentZoom, position, renderCanvas]);
139
+
140
+ // Handle window resize with requestAnimationFrame for better performance
141
+ useEffect(() => {
142
+ let frameId: number;
143
+ let isResizing = false;
144
+
145
+ const handleResize = () => {
146
+ if (!isResizing) {
147
+ isResizing = true;
148
+
149
+ frameId = requestAnimationFrame(() => {
150
+ renderCanvas();
151
+ isResizing = false;
152
+ });
153
+ }
154
+ };
155
+
156
+ window.addEventListener("resize", handleResize);
157
+
158
+ return () => {
159
+ window.removeEventListener("resize", handleResize);
160
+ cancelAnimationFrame(frameId);
161
+ };
162
+ }, [renderCanvas]);
163
+
164
+ // Handle zoom changes from props
165
+ useEffect(() => {
166
+ if (zoom !== undefined) {
167
+ // Set the zoom level
168
+ if (zoom >= minZoom && zoom <= maxZoom && zoom !== currentZoom) {
169
+ setCurrentZoom(zoom);
170
+ if (onZoomChange) {
171
+ onZoomChange(zoom);
172
+ }
173
+ }
174
+ }
175
+ }, [zoom, minZoom, maxZoom, currentZoom, onZoomChange]);
176
+
177
+ // Mouse event handlers for dragging - memoized with useCallback
178
+ const handleMouseDown = useCallback(
179
+ (e: React.MouseEvent<HTMLCanvasElement>) => {
180
+ e.preventDefault();
181
+ setIsDragging(true);
182
+ setDragStart({ x: e.clientX - position.x, y: e.clientY - position.y });
183
+ },
184
+ [position]
185
+ );
186
+
187
+ const handleMouseMove = useCallback(
188
+ (e: React.MouseEvent<HTMLCanvasElement>) => {
189
+ if (!isDragging) return;
190
+
191
+ e.preventDefault();
192
+ setPosition({
193
+ x: e.clientX - dragStart.x,
194
+ y: e.clientY - dragStart.y,
195
+ });
196
+ },
197
+ [isDragging, dragStart]
198
+ );
199
+
200
+ const handleMouseUp = useCallback(() => {
201
+ setIsDragging(false);
202
+ }, []);
203
+
204
+ const handleMouseLeave = useCallback(() => {
205
+ setIsDragging(false);
206
+ }, []);
207
+ const handleWheel = useCallback(
208
+ (e: WheelEvent) => {
209
+ e.preventDefault();
210
+
211
+ // Calculate new zoom level
212
+ const zoomFactor = 0.1;
213
+ let newZoom = currentZoom;
214
+
215
+ if (e.deltaY < 0) {
216
+ // Zoom in
217
+ newZoom = Math.min(currentZoom + zoomFactor, maxZoom);
218
+ } else {
219
+ // Zoom out
220
+ newZoom = Math.max(currentZoom - zoomFactor, minZoom);
221
+ }
222
+
223
+ if (newZoom !== currentZoom) {
224
+ setCurrentZoom(newZoom);
225
+ if (onZoomChange) {
226
+ onZoomChange(newZoom);
227
+ }
228
+ }
229
+ },
230
+ [currentZoom, minZoom, maxZoom, onZoomChange]
231
+ ); // Add non-passive wheel event listener
232
+ useEffect(() => {
233
+ const canvas = canvasRef.current;
234
+ if (!canvas) return undefined;
235
+
236
+ canvas.addEventListener("wheel", handleWheel, { passive: false });
237
+
238
+ return () => {
239
+ canvas.removeEventListener("wheel", handleWheel);
240
+ };
241
+ }, [handleWheel]);
242
+
243
+ return (
244
+ <div className={cn(`relative w-full h-full`, classNames?.container)}>
245
+ <canvas
246
+ ref={canvasRef}
247
+ className={cn(
248
+ "w-full h-full cursor-grab",
249
+ isDragging ? "cursor-grabbing" : "",
250
+ classNames?.canvas
251
+ )}
252
+ onMouseDown={handleMouseDown}
253
+ onMouseMove={handleMouseMove}
254
+ onMouseUp={handleMouseUp}
255
+ onMouseLeave={handleMouseLeave}
256
+ // onWheel handler removed and replaced with addEventListener above
257
+ />
258
+ </div>
259
+ );
260
+ }
@@ -0,0 +1,22 @@
1
+ import { githubDarkTheme, JsonEditor, JsonEditorProps } from "json-edit-react";
2
+ export { githubDarkTheme, githubLightTheme } from "json-edit-react";
3
+ export function JSONEditor({ config }: { config: JsonEditorProps }) {
4
+ return (
5
+ <JsonEditor
6
+ {...{
7
+ showCollectionCount: false,
8
+ indent: 4,
9
+ showArrayIndices: false,
10
+ restrictNewKey: true,
11
+ restrictEdit: true,
12
+ restrictDelete: true,
13
+ restrictAdd: true,
14
+ restrictTypeSelection: true,
15
+ restrictDrag: true,
16
+ keySort: true,
17
+ theme: githubDarkTheme,
18
+ ...config,
19
+ }}
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,100 @@
1
+ "use no memo";
2
+
3
+ import type { Column, Table as TanStackTable } from "@tanstack/react-table";
4
+ import { Button } from "../../../../components/button";
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogHeader,
10
+ DialogTitle,
11
+ } from "../../../../components/dialog";
12
+ import { ScrollArea } from "../../../../components/scroll-area";
13
+ import { Separator } from "../../../../components/separator";
14
+ import { Switch } from "../../../../components/switch";
15
+ import { Label } from "../../../../components/label";
16
+ import { getColumnName, getTranslations } from "../../utils/translation-utils";
17
+ import { MasterDataGridResources } from "../../types";
18
+
19
+ interface ColumnSettingsDialogProps<TData> {
20
+ open: boolean;
21
+ onOpenChange: (open: boolean) => void;
22
+ table: TanStackTable<TData>;
23
+ t?: MasterDataGridResources;
24
+ }
25
+
26
+ export function ColumnSettingsDialog<TData>({
27
+ open,
28
+ onOpenChange,
29
+ table,
30
+ t,
31
+ }: ColumnSettingsDialogProps<TData>) {
32
+ const hideableColumns = table
33
+ .getAllLeafColumns()
34
+ .filter((column) => column.getCanHide());
35
+
36
+ const handleToggleVisibility = (column: Column<TData>, value: boolean) => {
37
+ column.toggleVisibility(value);
38
+ };
39
+
40
+ return (
41
+ <Dialog open={open} onOpenChange={onOpenChange}>
42
+ <DialogContent className="max-w-md">
43
+ <DialogHeader>
44
+ <DialogTitle>
45
+ {getTranslations("columnSettings.title", t)}
46
+ </DialogTitle>
47
+ <DialogDescription>
48
+ {getTranslations("columnSettings.description", t)}
49
+ </DialogDescription>
50
+ </DialogHeader>
51
+
52
+ <div className="space-y-4">
53
+ <div className="flex items-center justify-between">
54
+ <Button
55
+ variant="outline"
56
+ size="sm"
57
+ onClick={() => table.toggleAllColumnsVisible(true)}
58
+ >
59
+ {getTranslations("columnSettings.showAll", t)}
60
+ </Button>
61
+ <Button
62
+ variant="outline"
63
+ size="sm"
64
+ onClick={() => table.toggleAllColumnsVisible(false)}
65
+ >
66
+ {getTranslations("columnSettings.hideAll", t)}
67
+ </Button>
68
+ </div>
69
+
70
+ <Separator />
71
+
72
+ <ScrollArea className="h-[400px] pr-4">
73
+ <div className="space-y-3">
74
+ {hideableColumns.map((column) => (
75
+ <div
76
+ key={column.id}
77
+ className="flex items-center justify-between gap-3"
78
+ >
79
+ <Label
80
+ htmlFor={`column-${column.id}`}
81
+ className="text-sm font-medium cursor-pointer flex-1"
82
+ >
83
+ {getColumnName(column, t)}
84
+ </Label>
85
+ <Switch
86
+ id={`column-${column.id}`}
87
+ checked={column.getIsVisible()}
88
+ onCheckedChange={(value) =>
89
+ handleToggleVisibility(column, value)
90
+ }
91
+ />
92
+ </div>
93
+ ))}
94
+ </div>
95
+ </ScrollArea>
96
+ </div>
97
+ </DialogContent>
98
+ </Dialog>
99
+ );
100
+ }
@@ -0,0 +1 @@
1
+ export { ColumnSettingsDialog } from "./column-settings-dialog";