@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,194 @@
1
+ "use client";
2
+
3
+ import { InputProps } from "@repo/ayasofyazilim-ui/components/input";
4
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
5
+ import { EyeIcon, EyeOffIcon, RotateCcwKey } from "lucide-react";
6
+ import {
7
+ forwardRef,
8
+ useCallback,
9
+ useImperativeHandle,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from "react";
14
+ import {
15
+ InputGroup,
16
+ InputGroupAddon,
17
+ InputGroupButton,
18
+ InputGroupInput,
19
+ } from "../components/input-group";
20
+
21
+ interface PasswordInputProps extends InputProps {
22
+ passwordLength?: number;
23
+ showGenerator?: boolean;
24
+ }
25
+
26
+ const PasswordInput = forwardRef<HTMLInputElement, PasswordInputProps>(
27
+ (
28
+ { className, showGenerator = false, passwordLength = 10, ...props },
29
+ ref
30
+ ) => {
31
+ const [showPassword, setShowPassword] = useState(false);
32
+ const internalRef = useRef<HTMLInputElement>(null);
33
+ const { disabled } = props;
34
+
35
+ useImperativeHandle(ref, () => internalRef.current!, []);
36
+
37
+ // Memoize character sets to avoid recreation on every render
38
+ const characterSets = useMemo(() => {
39
+ const sets = {
40
+ lowercase: "abcdefghijklmnopqrstuvwxyz",
41
+ uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
42
+ numbers: "0123456789",
43
+ symbols: "!@#$%^&*()_+-=[]{}|;:,.<>?",
44
+ };
45
+
46
+ return {
47
+ ...sets,
48
+ all: sets.lowercase + sets.uppercase + sets.numbers + sets.symbols,
49
+ };
50
+ }, []);
51
+ // Memoize class names to prevent unnecessary re-renders
52
+ const inputClassName = useMemo(
53
+ () =>
54
+ cn(
55
+ "hide-password-toggle",
56
+ showGenerator ? "pr-20" : "pr-10",
57
+ className
58
+ ),
59
+ [showGenerator, className]
60
+ );
61
+
62
+ // Use crypto.getRandomValues for better randomness when available
63
+ const getRandomInt = useCallback((max: number): number => {
64
+ if (typeof window !== "undefined" && window.crypto?.getRandomValues) {
65
+ const array = new Uint32Array(1);
66
+ window.crypto.getRandomValues(array);
67
+ return (array[0] ?? 0) % max;
68
+ }
69
+ return Math.floor(Math.random() * max);
70
+ }, []);
71
+
72
+ // Fisher-Yates shuffle algorithm for better randomization
73
+ const shuffleArray = useCallback(
74
+ (array: string[]): string[] => {
75
+ const shuffled = [...array];
76
+ for (let i = shuffled.length - 1; i > 0; i--) {
77
+ const j = getRandomInt(i + 1);
78
+ const elemI = shuffled[i];
79
+ const elemJ = shuffled[j];
80
+ if (elemI !== undefined && elemJ !== undefined) {
81
+ [shuffled[i], shuffled[j]] = [elemJ, elemI];
82
+ }
83
+ }
84
+ return shuffled;
85
+ },
86
+ [getRandomInt]
87
+ );
88
+
89
+ const generatePassword = useCallback(
90
+ (length: number = passwordLength): string => {
91
+ const { lowercase, uppercase, numbers, symbols, all } = characterSets;
92
+
93
+ // Ensure minimum requirements
94
+ const requiredChars = [
95
+ lowercase[getRandomInt(lowercase.length)],
96
+ uppercase[getRandomInt(uppercase.length)],
97
+ numbers[getRandomInt(numbers.length)],
98
+ symbols[getRandomInt(symbols.length)],
99
+ ];
100
+
101
+ // Fill remaining positions
102
+ const remainingLength = Math.max(0, length - requiredChars.length);
103
+ const additionalChars = Array.from(
104
+ { length: remainingLength },
105
+ () => all[getRandomInt(all.length)]
106
+ );
107
+
108
+ // Combine and shuffle
109
+ const allChars = [...requiredChars, ...additionalChars] as string[];
110
+ return shuffleArray(allChars).join("");
111
+ },
112
+ [passwordLength, characterSets, getRandomInt, shuffleArray]
113
+ );
114
+
115
+ // Optimized event dispatching
116
+ const dispatchInputEvents = useCallback(
117
+ (input: HTMLInputElement, value: string) => {
118
+ const _input = input;
119
+ // Use React's internal event system when possible
120
+ const descriptor = Object.getOwnPropertyDescriptor(
121
+ HTMLInputElement.prototype,
122
+ "value"
123
+ );
124
+ if (descriptor?.set) {
125
+ descriptor.set.call(_input, value);
126
+ } else {
127
+ _input.value = value;
128
+ }
129
+
130
+ // Dispatch events in the correct order
131
+ const inputEvent = new Event("input", { bubbles: true });
132
+ const changeEvent = new Event("change", { bubbles: true });
133
+
134
+ _input.dispatchEvent(inputEvent);
135
+ _input.dispatchEvent(changeEvent);
136
+ },
137
+ []
138
+ );
139
+
140
+ const handleGeneratePassword = useCallback(() => {
141
+ if (disabled || !internalRef.current) return;
142
+
143
+ const newPassword = generatePassword(passwordLength);
144
+ dispatchInputEvents(internalRef.current, newPassword);
145
+ }, [disabled, generatePassword, passwordLength, dispatchInputEvents]);
146
+
147
+ const togglePasswordVisibility = useCallback(() => {
148
+ setShowPassword((prev) => !prev);
149
+ }, []);
150
+
151
+ return (
152
+ <InputGroup>
153
+ <InputGroupInput
154
+ placeholder={props.placeholder}
155
+ className={inputClassName}
156
+ ref={internalRef}
157
+ {...props}
158
+ type={showPassword ? "text" : "password"}
159
+ />
160
+ <InputGroupAddon align="inline-end">
161
+ <InputGroupButton
162
+ id="toggle-password-visibility-button"
163
+ variant="ghost"
164
+ aria-label={showPassword ? "Hide password" : "Show password"}
165
+ size="icon-xs"
166
+ onClick={togglePasswordVisibility}
167
+ disabled={disabled}
168
+ >
169
+ {showPassword ? <EyeOffIcon /> : <EyeIcon />}
170
+ </InputGroupButton>
171
+ </InputGroupAddon>
172
+ {showGenerator && (
173
+ <InputGroupAddon align="inline-start">
174
+ <InputGroupButton
175
+ id="generate-password-button"
176
+ variant="ghost"
177
+ size="icon-xs"
178
+ onClick={handleGeneratePassword}
179
+ disabled={disabled}
180
+ >
181
+ <RotateCcwKey />
182
+ <span className="sr-only">Generate password</span>
183
+ </InputGroupButton>
184
+ </InputGroupAddon>
185
+ )}
186
+ </InputGroup>
187
+ );
188
+ }
189
+ );
190
+
191
+ PasswordInput.displayName = "PasswordInput";
192
+
193
+ export { PasswordInput };
194
+ export type { PasswordInputProps };
@@ -0,0 +1,172 @@
1
+ "use client";
2
+
3
+ import { ChevronDownIcon, PhoneIcon } from "lucide-react";
4
+ import React, { useState } from "react";
5
+ import PhoneInputWithCountrySelect, {
6
+ Country,
7
+ FlagProps,
8
+ getCountryCallingCode,
9
+ parsePhoneNumber,
10
+ } from "react-phone-number-input";
11
+ import flags from "react-phone-number-input/flags";
12
+
13
+ import { Input } from "@repo/ayasofyazilim-ui/components/input";
14
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
15
+ import {
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ } from "../components/select";
22
+ // import { FieldErrorTemplate } from "./schema-form/fields";
23
+ export type PhoneInputValues = {
24
+ value: string | undefined;
25
+ parsed: ReturnType<typeof parsePhoneNumber>;
26
+ };
27
+ export type PhoneInputProps = {
28
+ id: string;
29
+ name?: string;
30
+ placeholder?: string;
31
+ defaultValue?: string | undefined;
32
+ value?: string;
33
+ onChange?: (values: PhoneInputValues) => void;
34
+ disabled?: boolean;
35
+ className?: string;
36
+ required?: boolean;
37
+ defaultCountry?: string;
38
+ };
39
+ export function PhoneInput({
40
+ id,
41
+ name,
42
+ placeholder,
43
+ defaultValue,
44
+ value: initialValue,
45
+ onChange,
46
+ disabled,
47
+ required,
48
+ className,
49
+ defaultCountry = "US",
50
+ }: PhoneInputProps) {
51
+ const [value, setValue] = useState(initialValue || defaultValue || "");
52
+ return (
53
+ <>
54
+ <PhoneInputWithCountrySelect
55
+ className={cn("flex rounded-r-md shadow-xs", className)}
56
+ international
57
+ flagComponent={FlagComponent}
58
+ defaultCountry={defaultCountry as Country}
59
+ countrySelectComponent={(props) => CountrySelect({ ...props, id })}
60
+ inputComponent={_PhoneInput}
61
+ id={id}
62
+ required={required}
63
+ data-testid={id}
64
+ disabled={disabled}
65
+ name={name}
66
+ placeholder={placeholder}
67
+ value={value}
68
+ onChange={(newValue) => {
69
+ setValue(newValue ?? "");
70
+ if (onChange) {
71
+ onChange({
72
+ value: newValue ?? undefined,
73
+ parsed: parsePhoneNumber(newValue || ""),
74
+ });
75
+ }
76
+ }}
77
+ />
78
+ </>
79
+ );
80
+ }
81
+
82
+ // Use forwardRef to support refs from react-phone-number-input
83
+ const _PhoneInput = React.forwardRef<
84
+ HTMLInputElement,
85
+ React.ComponentProps<"input">
86
+ >(({ className, ...props }, ref) => (
87
+ <Input
88
+ data-testid={`${props.id}_input`}
89
+ data-slot="phone-input"
90
+ className={cn(
91
+ "-ms-px rounded-s-none rounded-l-none! shadow-none focus-visible:z-10",
92
+ className
93
+ )}
94
+ ref={ref}
95
+ {...props}
96
+ />
97
+ ));
98
+
99
+ _PhoneInput.displayName = "_PhoneInput";
100
+
101
+ type CountrySelectProps = {
102
+ id: string;
103
+ disabled?: boolean;
104
+ value: Country;
105
+ onChange?: (value: Country) => void;
106
+ options: { label: string; value: Country | undefined }[];
107
+ };
108
+
109
+ const CountrySelect = ({
110
+ id,
111
+ disabled,
112
+ value,
113
+ onChange,
114
+ options,
115
+ }: CountrySelectProps) => {
116
+ const handleSelect = (value: Country) => {
117
+ if (!onChange) return;
118
+ onChange(value);
119
+ };
120
+ return (
121
+ <div className="border-input bg-background text-muted-foreground focus-within:border-ring focus-within:ring-ring/50 hover:bg-accent hover:text-foreground has-aria-invalid:border-destructive/60 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 relative inline-flex items-center self-stretch rounded-s-md border py-2 ps-3 pe-2 transition-[color,box-shadow] outline-none focus-within:z-10 focus-within:ring-[3px] has-disabled:pointer-events-none has-disabled:opacity-50">
122
+ <div className="inline-flex items-center gap-1" aria-hidden="true">
123
+ <FlagComponent country={value} countryName={value} aria-hidden="true" />
124
+ <span className="text-muted-foreground/80">
125
+ <ChevronDownIcon size={16} aria-hidden="true" />
126
+ </span>
127
+ </div>
128
+ <Select
129
+ disabled={disabled}
130
+ value={value}
131
+ data-testid={`${id}_select`}
132
+ onValueChange={handleSelect}
133
+ aria-label="Select country"
134
+ >
135
+ <SelectTrigger className="absolute inset-0 text-sm opacity-0">
136
+ <SelectValue placeholder="Select a country" />
137
+ </SelectTrigger>
138
+ <SelectContent>
139
+ {options
140
+ .filter((x) => x.value)
141
+ .map((option, i) => {
142
+ if (!option.value) return null;
143
+ return (
144
+ <SelectItem
145
+ key={option.value}
146
+ value={option.value}
147
+ data-testid={`${id}_${option.value}`}
148
+ >
149
+ {option.label}
150
+ {option.value && `+${getCountryCallingCode(option.value)}`}
151
+ </SelectItem>
152
+ );
153
+ })}
154
+ </SelectContent>
155
+ </Select>
156
+ </div>
157
+ );
158
+ };
159
+
160
+ const FlagComponent = ({ country, countryName }: FlagProps) => {
161
+ const Flag = flags[country];
162
+
163
+ return (
164
+ <span className="w-5 overflow-hidden rounded-sm">
165
+ {Flag ? (
166
+ <Flag title={countryName} />
167
+ ) : (
168
+ <PhoneIcon size={16} aria-hidden="true" />
169
+ )}
170
+ </span>
171
+ );
172
+ };
@@ -0,0 +1 @@
1
+ export * from "./label";
@@ -0,0 +1,53 @@
1
+ import { Label } from "@repo/ayasofyazilim-ui/components/label";
2
+ import {
3
+ Tooltip,
4
+ TooltipContent,
5
+ TooltipProvider,
6
+ TooltipTrigger,
7
+ } from "@repo/ayasofyazilim-ui/components/tooltip";
8
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
9
+ import { InfoIcon } from "lucide-react";
10
+ import { JSXElementConstructor, ReactElement } from "react";
11
+
12
+ export function FieldLabel({
13
+ id,
14
+ className,
15
+ required,
16
+ label,
17
+ description,
18
+ }: {
19
+ label: string | undefined;
20
+ id: string | undefined;
21
+ className?: string;
22
+ required?: boolean;
23
+ description?:
24
+ | ReactElement<unknown, string | JSXElementConstructor<any>>
25
+ | string
26
+ | undefined;
27
+ }) {
28
+ if (!label) return null;
29
+ return (
30
+ <Label
31
+ data-testid={`${id}_label`}
32
+ htmlFor={id}
33
+ className={cn("flex items-center gap-0 text-nowrap", className)}
34
+ >
35
+ {label}
36
+ {required ? <span className="text-destructive">*</span> : null}
37
+ {description &&
38
+ typeof description === "string" &&
39
+ description.length > 0 ? (
40
+ <TooltipProvider>
41
+ <Tooltip>
42
+ <TooltipTrigger asChild>
43
+ <InfoIcon className="size-3.5 ml-1 text-muted-foreground cursor-help" />
44
+ </TooltipTrigger>
45
+ <TooltipContent>{description}</TooltipContent>
46
+ </Tooltip>
47
+ </TooltipProvider>
48
+ ) : (
49
+ description
50
+ )}
51
+ </Label>
52
+ );
53
+ }
@@ -0,0 +1,82 @@
1
+ import { Input } from "@repo/ayasofyazilim-ui/components/input";
2
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
3
+ import {
4
+ ariaDescribedByIds,
5
+ BaseInputTemplateProps,
6
+ examplesId,
7
+ getInputProps,
8
+ } from "@rjsf/utils";
9
+ import { ChangeEvent, FocusEvent } from "react";
10
+
11
+ export function BaseInputTemplate({
12
+ id,
13
+ htmlName,
14
+ placeholder,
15
+ required,
16
+ readonly,
17
+ disabled,
18
+ type,
19
+ value,
20
+ onChange,
21
+ onChangeOverride,
22
+ onBlur,
23
+ onFocus,
24
+ autofocus,
25
+ options,
26
+ schema,
27
+ rawErrors = [],
28
+ children,
29
+ extraProps,
30
+ className,
31
+ }: BaseInputTemplateProps) {
32
+ const inputProps = {
33
+ ...extraProps,
34
+ ...getInputProps(schema, type, options),
35
+ };
36
+ const _onChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) =>
37
+ onChange(value === "" ? options.emptyValue : value);
38
+ const _onBlur = ({ target }: FocusEvent<HTMLInputElement>) =>
39
+ onBlur(id, target && target.value);
40
+ const _onFocus = ({ target }: FocusEvent<HTMLInputElement>) =>
41
+ onFocus(id, target && target.value);
42
+
43
+ return (
44
+ <>
45
+ <Input
46
+ id={id}
47
+ name={htmlName || id}
48
+ type={type}
49
+ placeholder={placeholder}
50
+ autoFocus={autofocus}
51
+ required={required}
52
+ disabled={disabled}
53
+ readOnly={readonly}
54
+ className={cn(
55
+ { "border-destructive focus-visible:ring-0": rawErrors.length > 0 },
56
+ className
57
+ )}
58
+ list={schema.examples ? examplesId(id) : undefined}
59
+ {...inputProps}
60
+ value={value || value === 0 ? value : ""}
61
+ onChange={onChangeOverride || _onChange}
62
+ onBlur={_onBlur}
63
+ onFocus={_onFocus}
64
+ aria-describedby={ariaDescribedByIds(id, !!schema.examples)}
65
+ />
66
+ {children}
67
+ {Array.isArray(schema.examples) ? (
68
+ <datalist id={examplesId(id)}>
69
+ {(schema.examples as string[])
70
+ .concat(
71
+ schema.default && !schema.examples.includes(schema.default)
72
+ ? ([schema.default] as string[])
73
+ : []
74
+ )
75
+ .map((example: any) => {
76
+ return <option key={example} value={example} />;
77
+ })}
78
+ </datalist>
79
+ ) : null}
80
+ </>
81
+ );
82
+ }
@@ -0,0 +1,67 @@
1
+ import { TableCell } from "@repo/ayasofyazilim-ui/components/table";
2
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
3
+ import { FieldTemplateProps } from "@rjsf/utils";
4
+ import { FieldLabel } from "../custom";
5
+
6
+ export const FieldTemplate = (props: FieldTemplateProps) => {
7
+ const {
8
+ id,
9
+ classNames,
10
+ style,
11
+ label,
12
+ help,
13
+ required,
14
+ description,
15
+ errors,
16
+ children,
17
+ displayLabel,
18
+ hidden,
19
+ schema,
20
+ registry,
21
+ } = props;
22
+
23
+ const shouldRenderLabel = label && displayLabel && schema.type !== "boolean";
24
+ if (hidden) return null;
25
+ const isInArray = /root_\w+_\d+/.test(id);
26
+ const { useTableForArrayFields } = registry.formContext;
27
+ if (schema.type === "object" && isInArray && useTableForArrayFields)
28
+ return children;
29
+ if (isInArray && useTableForArrayFields)
30
+ return (
31
+ <TableCell
32
+ className={cn(
33
+ "p-0",
34
+ "*:data-wrapper:border-0 *:data-wrapper:shadow-none *:data-wrapper:rounded-none *:data-wrapper:justify-center",
35
+ "**:[[role=combobox]]:border-0 **:[[role=combobox]]:shadow-none **:[[role=combobox]]:rounded-none",
36
+ "**:data-[slot=input]:border-0 **:data-[slot=input]:shadow-none **:data-[slot=input]:rounded-none",
37
+ "**:data-rac:border-0 **:data-rac:shadow-none **:data-rac:rounded-none",
38
+ "*:data-[slot=drawer-trigger]:border-0"
39
+ )}
40
+ >
41
+ {children}
42
+ </TableCell>
43
+ );
44
+ return (
45
+ <div
46
+ className={cn(
47
+ "flex flex-col gap-1.5 h-max",
48
+ props.schema.type === "object" && "gap-3",
49
+ classNames,
50
+ label
51
+ )}
52
+ style={style}
53
+ >
54
+ {shouldRenderLabel && (
55
+ <FieldLabel
56
+ id={id}
57
+ label={label}
58
+ description={description}
59
+ required={required}
60
+ />
61
+ )}
62
+ {children}
63
+ {errors}
64
+ {help}
65
+ </div>
66
+ );
67
+ };
@@ -0,0 +1,5 @@
1
+ export * from "./field";
2
+ export * from "./object";
3
+ export * from "./table-array/array-field-item";
4
+ export * from "./table-array/array-field-template";
5
+ export * from "./base-input-field";
@@ -0,0 +1,12 @@
1
+ import { ObjectFieldTemplateProps } from "@rjsf/utils";
2
+ import { Fragment } from "react";
3
+
4
+ export const ObjectFieldTemplate = (props: ObjectFieldTemplateProps) => {
5
+ return (
6
+ <>
7
+ {props.properties.map((element, idx) => (
8
+ <Fragment key={element.name + idx}>{element.content}</Fragment>
9
+ ))}
10
+ </>
11
+ );
12
+ };
@@ -0,0 +1,90 @@
1
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
2
+ import { ButtonGroup } from "@repo/ayasofyazilim-ui/components/button-group";
3
+ import {
4
+ Popover,
5
+ PopoverContent,
6
+ PopoverTrigger,
7
+ } from "@repo/ayasofyazilim-ui/components/popover";
8
+ import { TableCell, TableRow } from "@repo/ayasofyazilim-ui/components/table";
9
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
10
+ import {
11
+ ArrayFieldItemTemplateProps,
12
+ FormContextType,
13
+ RJSFSchema,
14
+ StrictRJSFSchema,
15
+ } from "@rjsf/utils";
16
+ import {
17
+ ArrowDownIcon,
18
+ ArrowUpIcon,
19
+ ClipboardCheck,
20
+ MoreHorizontal,
21
+ Trash2,
22
+ } from "lucide-react";
23
+
24
+ export function ArrayFieldItemTemplate<
25
+ T = any,
26
+ S extends StrictRJSFSchema = RJSFSchema,
27
+ F extends FormContextType = any,
28
+ >(props: ArrayFieldItemTemplateProps<T, S, F>) {
29
+ const { children, buttonsProps, hasToolbar } = props;
30
+ const {
31
+ hasCopy,
32
+ hasMoveDown,
33
+ hasMoveUp,
34
+ hasRemove,
35
+ onCopyItem,
36
+ onMoveDownItem,
37
+ onMoveUpItem,
38
+ onRemoveItem,
39
+ } = buttonsProps;
40
+ const isMultipleToolbar = hasRemove && (hasCopy || hasMoveDown || hasMoveUp);
41
+ return (
42
+ <TableRow className={cn("divide-x", props.className)} key={props.itemKey}>
43
+ {children}
44
+ <TableCell className="p-0">
45
+ {hasToolbar && hasRemove && !isMultipleToolbar && (
46
+ <Button
47
+ variant="ghost"
48
+ type="button"
49
+ onClick={buttonsProps.onRemoveItem}
50
+ >
51
+ <Trash2 className="size-4" />
52
+ </Button>
53
+ )}
54
+ {hasToolbar && isMultipleToolbar && (
55
+ <Popover>
56
+ <PopoverTrigger asChild>
57
+ <Button variant="ghost" type="button" className="">
58
+ <MoreHorizontal className="size-4" />
59
+ </Button>
60
+ </PopoverTrigger>
61
+ <PopoverContent className="p-0 border-0 min-w-max">
62
+ <ButtonGroup>
63
+ {hasCopy && (
64
+ <Button variant="outline" onClick={onCopyItem}>
65
+ <ClipboardCheck className="size-4" />
66
+ </Button>
67
+ )}
68
+ {hasMoveUp && (
69
+ <Button variant="outline" onClick={onMoveUpItem}>
70
+ <ArrowUpIcon className="size-4" />
71
+ </Button>
72
+ )}
73
+ {hasRemove && (
74
+ <Button variant="outline" onClick={onRemoveItem}>
75
+ <Trash2 className="size-4" />
76
+ </Button>
77
+ )}
78
+ {hasMoveDown && (
79
+ <Button variant="outline" onClick={onMoveDownItem}>
80
+ <ArrowDownIcon className="size-4" />
81
+ </Button>
82
+ )}
83
+ </ButtonGroup>
84
+ </PopoverContent>
85
+ </Popover>
86
+ )}
87
+ </TableCell>
88
+ </TableRow>
89
+ );
90
+ }