@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,541 @@
1
+ "use client";
2
+
3
+ import { FileText, Folder, FolderOpen, Upload, X } from "lucide-react";
4
+ import * as React from "react";
5
+ import Dropzone, {
6
+ FileWithPath as DefaultFileWithPath,
7
+ DropzoneState,
8
+ type DropzoneProps,
9
+ type FileRejection,
10
+ } from "react-dropzone";
11
+ import {
12
+ Avatar,
13
+ AvatarFallback,
14
+ AvatarImage,
15
+ } from "@repo/ayasofyazilim-ui/components/avatar";
16
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
17
+ import {
18
+ Collapsible,
19
+ CollapsibleContent,
20
+ CollapsibleTrigger,
21
+ } from "@repo/ayasofyazilim-ui/components/collapsible";
22
+ import { Progress } from "@repo/ayasofyazilim-ui/components/progress";
23
+ import { ScrollArea } from "@repo/ayasofyazilim-ui/components/scroll-area";
24
+ import { toast } from "@repo/ayasofyazilim-ui/components/sonner";
25
+ import { cn, formatBytes } from "@repo/ayasofyazilim-ui/lib/utils";
26
+ import { useControllableState } from "../hooks/use-controllable-state";
27
+
28
+ export type FileWithPath = DefaultFileWithPath;
29
+ export type BaseFileUploaderProps = React.HTMLAttributes<HTMLDivElement> & {
30
+ /**
31
+ * Value of the uploader.
32
+ * @type File[]
33
+ * @default undefined
34
+ * @example value={files}
35
+ */
36
+ value?: DefaultFileWithPath[];
37
+
38
+ /**
39
+ * Function to be called when the value changes.
40
+ * @type (files: File[]) => void
41
+ * @default undefined
42
+ * @example onValueChange={(files) => setFiles(files)}
43
+ */
44
+ onValueChange?: (files: DefaultFileWithPath[]) => void;
45
+
46
+ /**
47
+ * Function to be called when files are uploaded.
48
+ * @type (files: File[]) => Promise<void>
49
+ * @default undefined
50
+ * @example onUpload={(files) => uploadFiles(files)}
51
+ */
52
+ onUpload?: (files: File[]) => Promise<void>;
53
+
54
+ /**
55
+ * Progress of the uploaded files.
56
+ * @type Record<string, number> | undefined
57
+ * @default undefined
58
+ * @example progresses={{ "file1.png": 50 }}
59
+ */
60
+ progresses?: Record<string, number>;
61
+
62
+ /**
63
+ * Accepted file types for the uploader.
64
+ * @type { [key: string]: string[]}
65
+ * @default
66
+ * ```ts
67
+ * { "image/*": [] }
68
+ * ```
69
+ * @example accept={["image/png", "image/jpeg"]}
70
+ */
71
+ accept?: DropzoneProps["accept"];
72
+
73
+ /**
74
+ * Maximum file size for the uploader.
75
+ * @type number | undefined
76
+ * @default 1024 * 1024 * 2 // 2MB
77
+ * @example maxSize={1024 * 1024 * 2} // 2MB
78
+ */
79
+ maxSize?: DropzoneProps["maxSize"];
80
+
81
+ /**
82
+ * Maximum number of files for the uploader.
83
+ * @type number | undefined
84
+ * @default 1
85
+ * @example maxFileCount={4}
86
+ */
87
+ maxFileCount?: DropzoneProps["maxFiles"];
88
+
89
+ /**
90
+ * Whether the uploader should accept multiple files.
91
+ * @type boolean
92
+ * @default false
93
+ * @example multiple
94
+ */
95
+ multiple?: boolean;
96
+
97
+ /**
98
+ * Whether the uploader is disabled.
99
+ * @type boolean
100
+ * @default false
101
+ * @example disabled
102
+ */
103
+ disabled?: boolean;
104
+
105
+ /**
106
+ * Whether the uploader has drag and drop functionality.
107
+ * @type boolean
108
+ * @default false
109
+ * @example noDrag
110
+ */
111
+ label?: string | React.ReactNode;
112
+ description?: string | React.ReactNode;
113
+ classNames?: {
114
+ container?: string;
115
+ collapsible?: string;
116
+ dropzoneContainer?: string;
117
+ dropzone?: string;
118
+ fileList?: string;
119
+ header?: string;
120
+ collapsibleContent?: string;
121
+ };
122
+ showFileList?: boolean;
123
+ fileCardRenderer?: (props: FileCardProps) => React.ReactNode;
124
+ } & (ButtonFileUploaderProps | DropzoneFileUploaderProps);
125
+
126
+ type ButtonFileUploaderProps = {
127
+ variant: "button";
128
+ children?: React.ReactNode;
129
+ headerChildren?: React.ReactNode;
130
+ };
131
+ type DropzoneFileUploaderProps = {
132
+ variant: "dropzone";
133
+ };
134
+ export function FileUploader(props: BaseFileUploaderProps) {
135
+ const {
136
+ value: valueProp,
137
+ onValueChange,
138
+ onUpload,
139
+ progresses,
140
+ accept = {
141
+ "image/*": [],
142
+ },
143
+ maxSize = 1024 * 1024 * 2,
144
+ maxFileCount = 1,
145
+ multiple = false,
146
+ disabled = false,
147
+ classNames,
148
+ showFileList = true,
149
+ label,
150
+ description,
151
+ fileCardRenderer,
152
+ } = props;
153
+
154
+ const [files, setFiles] = useControllableState({
155
+ prop: valueProp,
156
+ onChange: onValueChange,
157
+ });
158
+
159
+ const onDrop = React.useCallback(
160
+ (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
161
+ if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
162
+ toast.error("Cannot upload more than 1 file at a time");
163
+ return;
164
+ }
165
+
166
+ if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
167
+ toast.error(`Cannot upload more than ${maxFileCount} files`);
168
+ return;
169
+ }
170
+
171
+ const newFiles = acceptedFiles.map((file) =>
172
+ Object.assign(file, {
173
+ preview: URL.createObjectURL(file),
174
+ })
175
+ );
176
+ if (newFiles.length > 0 && !isOpen) setIsOpen(true);
177
+ const updatedFiles = files ? [...files, ...newFiles] : newFiles;
178
+
179
+ setFiles(updatedFiles);
180
+
181
+ if (rejectedFiles.length > 0) {
182
+ rejectedFiles.forEach(({ file, errors }) => {
183
+ toast.error(
184
+ `File ${file.name} was rejected because of ${errors.map((error) => error.message).join(", ")}`
185
+ );
186
+ });
187
+ }
188
+
189
+ if (
190
+ onUpload &&
191
+ updatedFiles.length > 0 &&
192
+ updatedFiles.length <= maxFileCount
193
+ ) {
194
+ const target =
195
+ updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;
196
+
197
+ toast.promise(onUpload(updatedFiles), {
198
+ loading: `Uploading ${target}...`,
199
+ success: () => {
200
+ setFiles([]);
201
+ return `${target} uploaded`;
202
+ },
203
+ error: `Failed to upload ${target}`,
204
+ });
205
+ }
206
+ },
207
+
208
+ [files, maxFileCount, multiple, onUpload, setFiles]
209
+ );
210
+
211
+ function onRemove(index: number) {
212
+ if (!files) return;
213
+ const newFiles = files.filter((_, i) => i !== index);
214
+ setFiles(newFiles);
215
+ if (newFiles.length === 0) setIsOpen(false);
216
+ onValueChange?.(newFiles);
217
+ }
218
+
219
+ // Revoke preview url when component unmounts
220
+ React.useEffect(
221
+ () => () => {
222
+ if (!files) return;
223
+ files.forEach((file) => {
224
+ if (isFileWithPreview(file)) {
225
+ URL.revokeObjectURL(file.preview);
226
+ }
227
+ });
228
+ },
229
+ []
230
+ );
231
+
232
+ const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;
233
+ const [isOpen, setIsOpen] = React.useState(false);
234
+ return (
235
+ <Collapsible
236
+ onOpenChange={setIsOpen}
237
+ open={isOpen}
238
+ className={cn(
239
+ "transition-all flex flex-col w-full border rounded-lg [&>h3]:w-full",
240
+ classNames?.collapsible
241
+ )}
242
+ >
243
+ <div
244
+ className={cn(
245
+ " gap-4 p-4",
246
+ props.variant === "button"
247
+ ? "flex flex-col sm:flex-row"
248
+ : "grid grid-cols-12",
249
+ classNames?.container
250
+ )}
251
+ >
252
+ <CollapsibleTrigger
253
+ className={cn(
254
+ "gap-4 group/trigger hover:no-underline",
255
+ !files?.length && "opacity-50"
256
+ )}
257
+ asChild
258
+ >
259
+ <Button
260
+ variant="outline"
261
+ className="gap-2"
262
+ disabled={isDisabled || !files?.length}
263
+ >
264
+ <FolderOpen className="w-4 group-data-[state=open]/trigger:hidden" />
265
+ <Folder className="w-4 group-data-[state=closed]/trigger:hidden" />
266
+ Files
267
+ </Button>
268
+ </CollapsibleTrigger>
269
+ <Dropzone
270
+ onDrop={onDrop}
271
+ accept={accept}
272
+ maxSize={maxSize}
273
+ maxFiles={maxFileCount}
274
+ multiple={maxFileCount > 1 || multiple}
275
+ disabled={isDisabled}
276
+ noDrag={props.variant === "button"}
277
+ >
278
+ {(dropzone) => (
279
+ <DropzoneTrigger {...dropzone} {...props} isDisabled={isDisabled} />
280
+ )}
281
+ </Dropzone>
282
+ <div className={cn("flex w-full", classNames?.header)}>
283
+ <div className="flex flex-col w-full text-nowrap justify-center">
284
+ {label && (
285
+ <span className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
286
+ {label}
287
+ </span>
288
+ )}
289
+ {description && (
290
+ <span className="text-muted-foreground text-sm">
291
+ {description}
292
+ </span>
293
+ )}
294
+ </div>
295
+ {props.variant === "button" &&
296
+ props.headerChildren &&
297
+ props.headerChildren}
298
+ </div>
299
+ </div>
300
+ {props.children && props.children}
301
+ <CollapsibleContent
302
+ className={cn("w-full p-0 h-max", classNames?.collapsibleContent)}
303
+ >
304
+ <div
305
+ className={cn(
306
+ "group relative flex flex-col gap-4 overflow-hidden",
307
+ files?.length && props.variant === "button" && "",
308
+ files?.length && files?.length > 0 && "p-4 border-t",
309
+ classNames?.container
310
+ )}
311
+ >
312
+ {files?.length && showFileList !== false ? (
313
+ <ScrollArea className="h-fit w-full">
314
+ <div
315
+ className={cn(
316
+ "grid max-h-48 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 flex-col gap-4",
317
+ files.length === 1 && "grid-cols-1!",
318
+ files.length === 2 && "sm:grid-cols-2!",
319
+ files.length === 3 && "lg:grid-cols-3!",
320
+ files.length === 4 && "2xl:grid-cols-4!",
321
+ classNames?.fileList
322
+ )}
323
+ >
324
+ {files?.map((file, index) => (
325
+ <React.Fragment
326
+ key={file.name + file.lastModified + file.path}
327
+ >
328
+ {fileCardRenderer ? (
329
+ fileCardRenderer({
330
+ file,
331
+ index,
332
+ onRemove: () => onRemove(index),
333
+ })
334
+ ) : (
335
+ <FileCard
336
+ file={file}
337
+ onRemove={() => onRemove(index)}
338
+ progress={progresses?.[file.name]}
339
+ />
340
+ )}
341
+ </React.Fragment>
342
+ ))}
343
+ </div>
344
+ </ScrollArea>
345
+ ) : null}
346
+ </div>
347
+ </CollapsibleContent>
348
+ </Collapsible>
349
+ );
350
+ }
351
+
352
+ export interface FileCardProps {
353
+ index: number;
354
+ file: DefaultFileWithPath;
355
+ onRemove?: () => void;
356
+ disabled?: boolean;
357
+ progress?: number;
358
+ classNames?: {
359
+ container?: string;
360
+ removeButton?: string;
361
+ };
362
+ }
363
+
364
+ export function FileCard({
365
+ file,
366
+ progress,
367
+ onRemove,
368
+ disabled,
369
+ classNames,
370
+ }: Omit<FileCardProps, "index">) {
371
+ return (
372
+ <div
373
+ className={cn(
374
+ "bg-muted relative flex items-center gap-2.5 overflow-hidden rounded-md p-2",
375
+ classNames?.container
376
+ )}
377
+ >
378
+ <div className="flex flex-1 gap-2.5">
379
+ {isFileWithPreview(file) ? <FilePreview file={file} /> : null}
380
+ <div className="flex w-full flex-col gap-2">
381
+ <div className="flex flex-col gap-px overflow-hidden">
382
+ <p className="text-foreground/80 line-clamp-1 text-sm font-medium max-w-60 text-ellipsis">
383
+ {file.name}
384
+ </p>
385
+ <p className="text-muted-foreground text-xs">
386
+ {formatBytes(file.size)}
387
+ </p>
388
+ </div>
389
+ {progress ? (
390
+ <Progress value={progress} className="duration-1000" />
391
+ ) : null}
392
+ </div>
393
+ </div>
394
+ {onRemove && (
395
+ <div className="flex items-center gap-2">
396
+ <Button
397
+ type="button"
398
+ variant="outline"
399
+ size="icon"
400
+ className={cn("size-7", classNames?.removeButton)}
401
+ disabled={!!progress || disabled}
402
+ onClick={onRemove}
403
+ >
404
+ <X className="size-4" aria-hidden="true" />
405
+ <span className="sr-only">Remove file</span>
406
+ </Button>
407
+ </div>
408
+ )}
409
+ </div>
410
+ );
411
+ }
412
+
413
+ export function isFileWithPreview(
414
+ file: FileWithPath
415
+ ): file is FileWithPath & { preview: string } {
416
+ return "preview" in file && typeof file.preview === "string";
417
+ }
418
+
419
+ export interface FilePreviewProps {
420
+ file: FileWithPath & { preview: string };
421
+ }
422
+
423
+ export function FilePreview({ file }: FilePreviewProps) {
424
+ if (file.type.startsWith("image/")) {
425
+ return (
426
+ <Avatar className="rounded-md border">
427
+ <AvatarImage
428
+ className="rounded-md"
429
+ src={file.preview}
430
+ alt={file.name}
431
+ loading="lazy"
432
+ />
433
+ <AvatarFallback className="bg-white rounded-md text-xs">
434
+ {`.${file.name.split(".")[1]}`}
435
+ </AvatarFallback>
436
+ </Avatar>
437
+ );
438
+ }
439
+
440
+ return (
441
+ <FileText className="text-muted-foreground size-10" aria-hidden="true" />
442
+ );
443
+ }
444
+
445
+ type DropzoneTriggerProps = DropzoneState &
446
+ BaseFileUploaderProps & {
447
+ isDisabled?: boolean;
448
+ };
449
+ function DropzoneTrigger(props: DropzoneTriggerProps) {
450
+ const {
451
+ classNames,
452
+ variant,
453
+ getRootProps,
454
+ getInputProps,
455
+ isDragActive = true,
456
+ maxFileCount = 1,
457
+ maxSize = 1024 * 1024 * 2,
458
+ isDisabled,
459
+ // ...dropzoneProps
460
+ } = props;
461
+ if (variant === "button") {
462
+ return (
463
+ <div
464
+ className={cn(
465
+ "flex flex-col sm:flex-row gap-4",
466
+ classNames?.dropzoneContainer
467
+ )}
468
+ >
469
+ <div
470
+ {...getRootProps()}
471
+ // {...dropzoneProps}
472
+ className={cn(
473
+ "relative flex items-center gap-4 rounded-lg",
474
+ classNames?.dropzone
475
+ )}
476
+ >
477
+ <input {...getInputProps()} />
478
+ <Button
479
+ type="button"
480
+ variant="outline"
481
+ className="min-h-9 gap-2 w-full sm:max-w-max"
482
+ disabled={isDisabled}
483
+ >
484
+ <Upload className="size-4" aria-hidden="true" />
485
+ Select
486
+ </Button>
487
+ </div>
488
+ </div>
489
+ );
490
+ }
491
+ return (
492
+ <div
493
+ {...getRootProps()}
494
+ // {...dropzoneProps}
495
+ className={cn(
496
+ "border-muted-foreground/25 hover:bg-muted/25 group relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed px-5 py-2.5 text-center transition",
497
+ "ring-offset-background focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
498
+ isDragActive && "border-muted-foreground/50",
499
+ isDisabled && "pointer-events-none opacity-60",
500
+ variant === "dropzone" && "col-span-full row-start-2",
501
+ classNames?.dropzone
502
+ )}
503
+ >
504
+ <input {...getInputProps()} />
505
+ {isDragActive ? (
506
+ <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
507
+ <div className="rounded-full border border-dashed p-3">
508
+ <Upload
509
+ className="text-muted-foreground size-7"
510
+ aria-hidden="true"
511
+ />
512
+ </div>
513
+ <p className="text-muted-foreground font-medium">
514
+ Drop the files here
515
+ </p>
516
+ </div>
517
+ ) : (
518
+ <div className="flex flex-col items-center justify-center gap-4 sm:px-5">
519
+ <div className="rounded-full border border-dashed p-3">
520
+ <Upload
521
+ className="text-muted-foreground size-7"
522
+ aria-hidden="true"
523
+ />
524
+ </div>
525
+ <div className="flex flex-col gap-px">
526
+ <p className="text-muted-foreground font-medium">
527
+ Drag{`'n'`} drop files here, or click to select files
528
+ </p>
529
+ <p className="text-muted-foreground/70 text-sm">
530
+ You can upload
531
+ {maxFileCount > 1
532
+ ? ` ${maxFileCount === Infinity ? "multiple" : maxFileCount}
533
+ files (up to ${formatBytes(maxSize)} each)`
534
+ : ` a file with ${formatBytes(maxSize)}`}
535
+ </p>
536
+ </div>
537
+ </div>
538
+ )}
539
+ </div>
540
+ );
541
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import { Label } from "@repo/ayasofyazilim-ui/components/label";
4
+ import type { AsyncSelectType } from "..";
5
+ import AsyncSelect from "../../async-select";
6
+
7
+ function AsyncSelectField({
8
+ filter,
9
+ isPending,
10
+ searchText,
11
+ }: {
12
+ filter: AsyncSelectType;
13
+ isPending: boolean;
14
+ searchText: string;
15
+ }) {
16
+ return (
17
+ <div className="grid items-center gap-1.5" key={filter.title}>
18
+ <Label htmlFor="refund-point">{filter.title}</Label>
19
+ <AsyncSelect
20
+ id={filter.id}
21
+ fetchAction={filter.fetchAction}
22
+ onChange={filter.onChange}
23
+ value={filter.value}
24
+ disabled={isPending}
25
+ multiple={filter.multiple}
26
+ closeOnSelect={!filter.multiple}
27
+ searchText={searchText}
28
+ />
29
+ </div>
30
+ );
31
+ }
32
+
33
+ export default AsyncSelectField;
@@ -0,0 +1,60 @@
1
+ "use client";
2
+
3
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
4
+ import { Label } from "@repo/ayasofyazilim-ui/components/label";
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectGroup,
9
+ SelectItem,
10
+ SelectLabel,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from "@repo/ayasofyazilim-ui/components/select";
14
+ import type { DateSelectType } from "..";
15
+
16
+ function DateField({
17
+ filter,
18
+ isPending,
19
+ }: {
20
+ filter: DateSelectType;
21
+ isPending: boolean;
22
+ }) {
23
+ return (
24
+ <div className="grid items-center gap-1.5" key={filter.title}>
25
+ <Label htmlFor="refund-point">{filter.title}</Label>
26
+ <Select
27
+ onValueChange={filter.onChange}
28
+ value={filter.value}
29
+ disabled={isPending}
30
+ >
31
+ <SelectTrigger className="w-full">
32
+ <SelectValue placeholder={filter.placeholder || "Select a date"} />
33
+ </SelectTrigger>
34
+ <SelectContent>
35
+ <SelectGroup>
36
+ <SelectLabel>{filter.title}</SelectLabel>
37
+ {filter.options.map((option) => (
38
+ <SelectItem key={option.value} value={option.value}>
39
+ {option.label}
40
+ </SelectItem>
41
+ ))}
42
+ </SelectGroup>
43
+ <Button
44
+ className="w-full px-2"
45
+ variant="secondary"
46
+ size="sm"
47
+ onClick={(e) => {
48
+ e.stopPropagation();
49
+ filter.onChange("");
50
+ }}
51
+ >
52
+ Clear
53
+ </Button>
54
+ </SelectContent>
55
+ </Select>
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export default DateField;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+
3
+ import { Label } from "@repo/ayasofyazilim-ui/components/label";
4
+
5
+ import { MultiSelect } from "../../multi-select";
6
+ import type { MultiSelectType } from "..";
7
+
8
+ function MultiSelectField({
9
+ filter,
10
+ isPending,
11
+ }: {
12
+ filter: MultiSelectType;
13
+ isPending: boolean;
14
+ }) {
15
+ return (
16
+ <div className="grid items-center gap-1.5" key={filter.title}>
17
+ <Label htmlFor="refund-point">{filter.title}</Label>
18
+ <MultiSelect
19
+ onValueChange={filter.onChange}
20
+ options={filter.options}
21
+ defaultValue={filter.value}
22
+ disabled={isPending}
23
+ placeholder={filter.placeholder}
24
+ selectAllLabel={filter.selectAllLabel}
25
+ />
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export default MultiSelectField;