@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,126 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import {
5
+ PolarAngleAxis,
6
+ PolarGrid,
7
+ Radar,
8
+ RadarChart as RechartsRadarChart,
9
+ } from "recharts";
10
+
11
+ import {
12
+ ChartConfig,
13
+ ChartContainer,
14
+ ChartLegend,
15
+ ChartLegendContent,
16
+ ChartTooltip,
17
+ ChartTooltipContent,
18
+ } from "@repo/ayasofyazilim-ui/components/chart";
19
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
20
+ import { ChartCard, CardClassNames } from "./chart-card";
21
+ import { ChartData, EmptyConfig } from ".";
22
+
23
+ export type RadarChartProps = {
24
+ data: ChartData;
25
+ config: ChartConfig;
26
+ polarKey: string;
27
+ title?: React.ReactNode;
28
+ description?: React.ReactNode;
29
+ period?: React.ReactNode;
30
+ footer?: React.ReactNode;
31
+ trendText?: React.ReactNode;
32
+ trendIcon?: React.ReactNode;
33
+ showLegend?: boolean;
34
+ linesOnly?: boolean;
35
+ valuePrefix?: string;
36
+ valueSuffix?: string;
37
+ classNames?: {
38
+ chart?: {
39
+ container?: string;
40
+ tooltip?: string;
41
+ legend?: string;
42
+ radar?: string;
43
+ };
44
+ card?: CardClassNames;
45
+ };
46
+ } & EmptyConfig;
47
+
48
+ export function RadarChart({
49
+ data,
50
+ config,
51
+ polarKey,
52
+ title,
53
+ description,
54
+ period,
55
+ footer,
56
+ trendText,
57
+ trendIcon,
58
+ showLegend = true,
59
+ linesOnly = false,
60
+ classNames,
61
+ valuePrefix,
62
+ valueSuffix,
63
+ emptyState,
64
+ }: RadarChartProps) {
65
+ return (
66
+ <ChartCard
67
+ title={title}
68
+ description={description}
69
+ period={period}
70
+ footer={footer}
71
+ trendText={trendText}
72
+ trendIcon={trendIcon}
73
+ classNames={classNames?.card}
74
+ emptyState={data.length === 0 ? emptyState : undefined}
75
+ >
76
+ <ChartContainer
77
+ config={config}
78
+ className={cn("mx-auto max-h-full", classNames?.chart?.container)}
79
+ >
80
+ <RechartsRadarChart
81
+ data={data}
82
+ className={cn("flex flex-col pb-2", classNames?.chart?.radar)}
83
+ margin={{ top: 0, bottom: 0, left: 0, right: 0 }}
84
+ >
85
+ <ChartTooltip
86
+ cursor={false}
87
+ content={
88
+ <ChartTooltipContent
89
+ indicator="line"
90
+ valuePrefix={valuePrefix}
91
+ valueSuffix={valueSuffix}
92
+ />
93
+ }
94
+ />
95
+ <PolarAngleAxis dataKey={polarKey} />
96
+ <PolarGrid radialLines={!linesOnly} />
97
+ {Object.keys(config).map((key, idx) => (
98
+ <Radar
99
+ key={key}
100
+ dataKey={key}
101
+ fill={config[key]?.color || `var(--color-${idx})`}
102
+ fillOpacity={linesOnly ? 0 : idx === 0 ? 0.6 : 0.4}
103
+ stroke={config[key]?.color || `var(--color-${idx})`}
104
+ strokeWidth={linesOnly ? 2 : 1}
105
+ />
106
+ ))}
107
+ {showLegend && (
108
+ <ChartLegend
109
+ wrapperStyle={{
110
+ position: "relative",
111
+ top: "unset",
112
+ left: "unset",
113
+ bottom: "unset",
114
+ right: "unset",
115
+ width: "100%",
116
+ textAlign: "center",
117
+ }}
118
+ className={cn("p-0", classNames?.chart?.legend)}
119
+ content={<ChartLegendContent />}
120
+ />
121
+ )}
122
+ </RechartsRadarChart>
123
+ </ChartContainer>
124
+ </ChartCard>
125
+ );
126
+ }
@@ -0,0 +1,100 @@
1
+ "use client";
2
+
3
+ import React, { useCallback, useMemo, useState } from "react";
4
+
5
+ interface TreeNode {
6
+ id: string;
7
+ label: string;
8
+ defaultChecked?: boolean;
9
+ children?: TreeNode[];
10
+ }
11
+
12
+ function useCheckboxTree(initialTree: TreeNode) {
13
+ const initialCheckedNodes = useMemo(() => {
14
+ const checkedSet = new Set<string>();
15
+ const initializeCheckedNodes = (node: TreeNode) => {
16
+ if (node.defaultChecked) {
17
+ checkedSet.add(node.id);
18
+ }
19
+ node.children?.forEach(initializeCheckedNodes);
20
+ };
21
+ initializeCheckedNodes(initialTree);
22
+ return checkedSet;
23
+ }, [initialTree]);
24
+
25
+ const [checkedNodes, setCheckedNodes] =
26
+ useState<Set<string>>(initialCheckedNodes);
27
+
28
+ const isChecked = useCallback(
29
+ (node: TreeNode): boolean | "indeterminate" => {
30
+ if (!node.children) {
31
+ return checkedNodes.has(node.id);
32
+ }
33
+
34
+ const childrenChecked = node.children.map((child) => isChecked(child));
35
+ if (childrenChecked.every((status) => status === true)) {
36
+ return true;
37
+ }
38
+ if (
39
+ childrenChecked.some(
40
+ (status) => status === true || status === "indeterminate"
41
+ )
42
+ ) {
43
+ return "indeterminate";
44
+ }
45
+ return false;
46
+ },
47
+ [checkedNodes]
48
+ );
49
+
50
+ const handleCheck = useCallback(
51
+ (node: TreeNode) => {
52
+ const newCheckedNodes = new Set(checkedNodes);
53
+
54
+ const toggleNode = (n: TreeNode, check: boolean) => {
55
+ if (check) {
56
+ newCheckedNodes.add(n.id);
57
+ } else {
58
+ newCheckedNodes.delete(n.id);
59
+ }
60
+ n.children?.forEach((child) => toggleNode(child, check));
61
+ };
62
+
63
+ const currentStatus = isChecked(node);
64
+ const newCheck = currentStatus !== true;
65
+
66
+ toggleNode(node, newCheck);
67
+ setCheckedNodes(newCheckedNodes);
68
+ },
69
+ [checkedNodes, isChecked]
70
+ );
71
+
72
+ return { isChecked, handleCheck };
73
+ }
74
+
75
+ interface CheckboxTreeProps {
76
+ tree: TreeNode;
77
+ renderNode: (props: {
78
+ node: TreeNode;
79
+ isChecked: boolean | "indeterminate";
80
+ onCheckedChange: () => void;
81
+ children: React.ReactNode;
82
+ }) => React.ReactNode;
83
+ }
84
+
85
+ export function CheckboxTree({ tree, renderNode }: CheckboxTreeProps) {
86
+ const { isChecked, handleCheck } = useCheckboxTree(tree);
87
+
88
+ const renderTreeNode = (node: TreeNode): React.ReactNode => {
89
+ const children = node.children?.map(renderTreeNode);
90
+
91
+ return renderNode({
92
+ node,
93
+ isChecked: isChecked(node),
94
+ onCheckedChange: () => handleCheck(node),
95
+ children,
96
+ });
97
+ };
98
+
99
+ return renderTreeNode(tree);
100
+ }
@@ -0,0 +1,296 @@
1
+ "use client";
2
+
3
+ import { CheckIcon, ChevronsUpDown } from "lucide-react";
4
+ import React, { Dispatch, SetStateAction, useState } from "react";
5
+ import { Button } from "@repo/ayasofyazilim-ui/components/button";
6
+ import {
7
+ Command,
8
+ CommandEmpty,
9
+ CommandGroup,
10
+ CommandInput,
11
+ CommandItem,
12
+ CommandList,
13
+ } from "@repo/ayasofyazilim-ui/components/command";
14
+ import {
15
+ Drawer,
16
+ DrawerContent,
17
+ DrawerTrigger,
18
+ } from "@repo/ayasofyazilim-ui/components/drawer";
19
+ import {
20
+ Popover,
21
+ PopoverContent,
22
+ PopoverTrigger,
23
+ } from "@repo/ayasofyazilim-ui/components/popover";
24
+ import { useMediaQuery } from "@repo/ayasofyazilim-ui/hooks/use-media-query";
25
+ import { cn } from "@repo/ayasofyazilim-ui/lib/utils";
26
+ import { Label } from "@repo/ayasofyazilim-ui/components/label";
27
+ import { Badge } from "@repo/ayasofyazilim-ui/components/badge";
28
+
29
+ export type ComboboxBadgeOptions<T> = {
30
+ className?: string;
31
+ label: (item: T) => React.ReactNode;
32
+ };
33
+
34
+ export type ComboboxProps<T> = {
35
+ id?: string;
36
+ disabled?: boolean;
37
+ emptyValue?: string;
38
+ errorMessage?: string;
39
+ classNames?: {
40
+ container?: string;
41
+ label?: string;
42
+ trigger?: {
43
+ button?: string;
44
+ label?: string;
45
+ icon?: string;
46
+ };
47
+ list?: {
48
+ label?: string;
49
+ };
50
+ error?: string;
51
+ required?: string;
52
+ };
53
+ label?: string;
54
+ list: Array<T> | null | undefined;
55
+ onValueChange?: (
56
+ value: T | null | undefined
57
+ ) => void | Dispatch<SetStateAction<T | null | undefined>>;
58
+ required?: boolean;
59
+ searchPlaceholder?: string;
60
+ searchResultLabel?: string;
61
+ selectIdentifier: keyof T;
62
+ selectLabel: keyof T;
63
+ value?: T | null | undefined;
64
+ defaultValue?: T | null | undefined;
65
+ badges?: Partial<Record<keyof T, ComboboxBadgeOptions<T>>>;
66
+ };
67
+
68
+ export function Combobox<T>(props: ComboboxProps<T>) {
69
+ const {
70
+ label,
71
+ list,
72
+ value: controlledValue,
73
+ defaultValue,
74
+ disabled,
75
+ selectIdentifier,
76
+ required,
77
+ errorMessage,
78
+ emptyValue,
79
+ classNames,
80
+ onValueChange,
81
+ } = props;
82
+ const isDesktop = useMediaQuery("(min-width: 768px)");
83
+ const [open, setOpen] = useState(false);
84
+
85
+ const [internalValue, setInternalValue] = useState<T | null | undefined>(
86
+ defaultValue ?? null
87
+ );
88
+
89
+ const isControlled = controlledValue !== undefined;
90
+ const currentValue = isControlled ? controlledValue : internalValue;
91
+
92
+ const handleValueChange = (newValue: T | null | undefined) => {
93
+ if (!isControlled) {
94
+ setInternalValue(newValue);
95
+ }
96
+ onValueChange?.(newValue);
97
+ };
98
+
99
+ const fieldValue =
100
+ (list?.find(
101
+ (x: T) => x[props.selectIdentifier] === currentValue?.[selectIdentifier]
102
+ )?.[props.selectLabel] as string) ||
103
+ emptyValue ||
104
+ (label && `Please select an ${label.toLocaleLowerCase()}`) ||
105
+ "Please select";
106
+ const DesktopContent = (
107
+ <Popover open={open} onOpenChange={setOpen} modal>
108
+ <PopoverTrigger asChild>
109
+ <Button
110
+ disabled={disabled}
111
+ type="button"
112
+ variant="outline"
113
+ role="combobox"
114
+ className={cn(
115
+ "text-muted-foreground w-full justify-between font-normal",
116
+ currentValue && "text-foreground",
117
+ classNames?.trigger?.button
118
+ )}
119
+ >
120
+ <span
121
+ className={cn(
122
+ "overflow-hidden truncate has-[role=dialog]:max-w-xs",
123
+ classNames?.trigger?.label
124
+ )}
125
+ >
126
+ {fieldValue}
127
+ </span>
128
+ <ChevronsUpDown
129
+ className={cn(
130
+ "ml-2 h-4 w-4 shrink-0 opacity-50",
131
+ classNames?.trigger?.icon
132
+ )}
133
+ />
134
+ </Button>
135
+ </PopoverTrigger>
136
+ <PopoverContent className="p-0">
137
+ <List
138
+ {...props}
139
+ setOpen={setOpen}
140
+ currentValue={currentValue}
141
+ handleValueChange={handleValueChange}
142
+ />
143
+ </PopoverContent>
144
+ </Popover>
145
+ );
146
+
147
+ const MobileContent = (
148
+ <Drawer open={open} onOpenChange={setOpen}>
149
+ <DrawerTrigger asChild>
150
+ <Button
151
+ disabled={disabled}
152
+ type="button"
153
+ variant="outline"
154
+ className={cn(
155
+ "text-muted-foreground w-full justify-between font-normal",
156
+ currentValue && "text-foreground",
157
+ classNames?.trigger?.button
158
+ )}
159
+ >
160
+ <span className={cn("truncate", classNames?.trigger?.label)}>
161
+ {fieldValue}
162
+ </span>
163
+ <ChevronsUpDown
164
+ className={cn(
165
+ "ml-2 h-4 w-4 shrink-0 opacity-50",
166
+ classNames?.trigger?.icon
167
+ )}
168
+ />
169
+ </Button>
170
+ </DrawerTrigger>
171
+ <DrawerContent>
172
+ <div className="mt-4 border-t">
173
+ <List
174
+ {...props}
175
+ setOpen={setOpen}
176
+ currentValue={currentValue}
177
+ handleValueChange={handleValueChange}
178
+ />
179
+ </div>
180
+ </DrawerContent>
181
+ </Drawer>
182
+ );
183
+
184
+ const Content = isDesktop ? DesktopContent : MobileContent;
185
+
186
+ return (
187
+ <div className={cn("w-full", classNames?.container)}>
188
+ {label && (
189
+ <Label className={classNames?.label}>
190
+ {label}
191
+ {required && (
192
+ <span className={cn("text-destructive", classNames?.required)}>
193
+ *
194
+ </span>
195
+ )}
196
+ </Label>
197
+ )}
198
+ {Content}
199
+ {errorMessage && (
200
+ <span
201
+ className={cn(
202
+ "text-xs font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-destructive",
203
+ classNames?.error
204
+ )}
205
+ >
206
+ {errorMessage}
207
+ </span>
208
+ )}
209
+ </div>
210
+ );
211
+ }
212
+
213
+ function List<T>({
214
+ setOpen,
215
+ currentValue,
216
+ handleValueChange,
217
+ ...props
218
+ }: ComboboxProps<T> & {
219
+ setOpen: (open: boolean) => void;
220
+ currentValue: T | null | undefined;
221
+ handleValueChange: (value: T | null | undefined) => void;
222
+ }) {
223
+ const {
224
+ list,
225
+ selectIdentifier,
226
+ selectLabel,
227
+ searchPlaceholder,
228
+ searchResultLabel,
229
+ id,
230
+ classNames,
231
+ badges,
232
+ } = props;
233
+
234
+ return (
235
+ <Command
236
+ id={id}
237
+ filter={(value, search) => {
238
+ const filterResult = list?.find(
239
+ (i) =>
240
+ (i[selectIdentifier] as string)?.toLocaleLowerCase() ===
241
+ value.toLocaleLowerCase()
242
+ )?.[selectLabel] as string;
243
+ if (
244
+ value.includes(search) ||
245
+ filterResult?.toLocaleLowerCase().includes(search.toLocaleLowerCase())
246
+ )
247
+ return 1;
248
+ return 0;
249
+ }}
250
+ >
251
+ <CommandInput
252
+ placeholder={searchPlaceholder || "Search..."}
253
+ className="h-9"
254
+ />
255
+ <CommandList className="w-full min-w-full max-w-full">
256
+ <CommandEmpty>{searchResultLabel || "0 search result."}</CommandEmpty>
257
+ <CommandGroup>
258
+ {list?.map((item: T) => (
259
+ <CommandItem
260
+ onSelect={() => {
261
+ handleValueChange(item);
262
+ setOpen(false);
263
+ }}
264
+ key={JSON.stringify(item[selectIdentifier])}
265
+ value={item[selectIdentifier] as string}
266
+ >
267
+ {item[selectIdentifier] === currentValue?.[selectIdentifier] && (
268
+ <CheckIcon className={cn("h-4 w-4")} />
269
+ )}
270
+ <span className={cn(classNames?.list?.label)}>
271
+ {item[selectLabel] as string}
272
+ </span>
273
+ {badges && (
274
+ <div className="ml-auto">
275
+ {Object.keys(badges).map((badgeKey) => {
276
+ const badgeOptions = badges[badgeKey as keyof T];
277
+ if (!badgeOptions) return null;
278
+ return (
279
+ <Badge
280
+ key={badgeKey}
281
+ variant="outline"
282
+ className={cn("ml-2", badgeOptions.className)}
283
+ >
284
+ {badgeOptions.label(item)}
285
+ </Badge>
286
+ );
287
+ })}
288
+ </div>
289
+ )}
290
+ </CommandItem>
291
+ ))}
292
+ </CommandGroup>
293
+ </CommandList>
294
+ </Command>
295
+ );
296
+ }
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ import { JSX, useState } from "react";
4
+ import {
5
+ Dialog,
6
+ DialogContent,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ DialogFooter,
10
+ DialogTrigger,
11
+ DialogDescription,
12
+ DialogClose,
13
+ } from "@repo/ayasofyazilim-ui/components/dialog";
14
+ import { Button, ButtonProps } from "@repo/ayasofyazilim-ui/components/button";
15
+ import { Skeleton } from "@repo/ayasofyazilim-ui/components/skeleton";
16
+
17
+ export type ConfirmDialogProps = {
18
+ closeProps?: ButtonProps;
19
+ confirmProps?: ButtonProps & {
20
+ closeAfterConfirm?: boolean;
21
+ onConfirm?: () => void | Promise<void>;
22
+ };
23
+ description: string | JSX.Element;
24
+ loading?: boolean;
25
+ title: string | JSX.Element;
26
+ } & (WithTriggerConfirmDialogProps | WithoutTriggerConfirmDialogProps);
27
+ type WithTriggerConfirmDialogProps = {
28
+ triggerProps: ButtonProps & { "data-testid"?: string };
29
+ type: "with-trigger";
30
+ };
31
+ type WithoutTriggerConfirmDialogProps = {
32
+ type: "without-trigger";
33
+ children: JSX.Element;
34
+ };
35
+ export default function ConfirmDialog(props: ConfirmDialogProps) {
36
+ const [open, setOpen] = useState(false);
37
+ const isWithTrigger = props.type === "with-trigger";
38
+ const { title, description, loading, confirmProps } = props;
39
+ const { closeAfterConfirm, onConfirm, ...confirmButtonProps } =
40
+ confirmProps || {};
41
+ return (
42
+ <Dialog open={open && loading ? true : open} onOpenChange={setOpen}>
43
+ <DialogTrigger asChild>
44
+ {isWithTrigger ? (
45
+ <Button
46
+ type="button"
47
+ {...props.triggerProps}
48
+ onClick={(e) => {
49
+ setOpen(true);
50
+ if (props.triggerProps?.onClick) props.triggerProps.onClick(e);
51
+ }}
52
+ >
53
+ {props.triggerProps.children}
54
+ </Button>
55
+ ) : (
56
+ props.children
57
+ )}
58
+ </DialogTrigger>
59
+ <DialogContent>
60
+ <DialogHeader>
61
+ <DialogTitle>{title}</DialogTitle>
62
+ <DialogDescription>{description}</DialogDescription>
63
+ </DialogHeader>
64
+ <DialogFooter>
65
+ {loading ? (
66
+ <Skeleton className="w-20 h-9" />
67
+ ) : (
68
+ <DialogClose asChild disabled={loading}>
69
+ <Button
70
+ variant="outline"
71
+ type="button"
72
+ {...props.closeProps}
73
+ disabled={loading}
74
+ >
75
+ {props.closeProps?.children || "Cancel"}
76
+ </Button>
77
+ </DialogClose>
78
+ )}
79
+ {loading ? (
80
+ <Skeleton className="w-20 h-9" />
81
+ ) : (
82
+ <Button
83
+ type="button"
84
+ {...confirmButtonProps}
85
+ onClick={async (e) => {
86
+ if (props.confirmProps?.onClick) {
87
+ props.confirmProps.onClick(e);
88
+ }
89
+ if (onConfirm) {
90
+ await onConfirm();
91
+ if (closeAfterConfirm) setOpen(false);
92
+ }
93
+ }}
94
+ >
95
+ {props.confirmProps?.children || "Confirm"}
96
+ </Button>
97
+ )}
98
+ </DialogFooter>
99
+ </DialogContent>
100
+ </Dialog>
101
+ );
102
+ }