@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
File without changes
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+
3
+ /**
4
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
5
+ */
6
+
7
+ /**
8
+ * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
9
+ * prop or avoid re-executing effects when passed as a dependency
10
+ */
11
+ function useCallbackRef<T extends (...args: never[]) => unknown>(
12
+ callback: T | undefined
13
+ ): T {
14
+ const callbackRef = React.useRef(callback);
15
+
16
+ React.useEffect(() => {
17
+ callbackRef.current = callback;
18
+ });
19
+
20
+ // https://github.com/facebook/react/issues/19240
21
+ return React.useMemo(
22
+ () => ((...args) => callbackRef.current?.(...args)) as T,
23
+ []
24
+ );
25
+ }
26
+
27
+ export { useCallbackRef };
@@ -0,0 +1,67 @@
1
+ import * as React from "react";
2
+
3
+ import { useCallbackRef } from "./use-callback-ref";
4
+
5
+ /**
6
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
7
+ */
8
+
9
+ type UseControllableStateParams<T> = {
10
+ prop?: T | undefined;
11
+ defaultProp?: T | undefined;
12
+ onChange?: (state: T) => void;
13
+ };
14
+
15
+ type SetStateFn<T> = (prevState?: T) => T;
16
+
17
+ function useControllableState<T>({
18
+ prop,
19
+ defaultProp,
20
+ onChange = () => {},
21
+ }: UseControllableStateParams<T>) {
22
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
23
+ defaultProp,
24
+ onChange,
25
+ });
26
+ const isControlled = prop !== undefined;
27
+ const value = isControlled ? prop : uncontrolledProp;
28
+ const handleChange = useCallbackRef(onChange);
29
+
30
+ const setValue: React.Dispatch<React.SetStateAction<T | undefined>> =
31
+ React.useCallback(
32
+ (nextValue) => {
33
+ if (isControlled) {
34
+ const setter = nextValue as SetStateFn<T>;
35
+ const value =
36
+ typeof nextValue === "function" ? setter(prop) : nextValue;
37
+ if (value !== prop) handleChange(value as T);
38
+ } else {
39
+ setUncontrolledProp(nextValue);
40
+ }
41
+ },
42
+ [isControlled, prop, setUncontrolledProp, handleChange]
43
+ );
44
+
45
+ return [value, setValue] as const;
46
+ }
47
+
48
+ function useUncontrolledState<T>({
49
+ defaultProp,
50
+ onChange,
51
+ }: Omit<UseControllableStateParams<T>, "prop">) {
52
+ const uncontrolledState = React.useState<T | undefined>(defaultProp);
53
+ const [value] = uncontrolledState;
54
+ const prevValueRef = React.useRef(value);
55
+ const handleChange = useCallbackRef(onChange);
56
+
57
+ React.useEffect(() => {
58
+ if (prevValueRef.current !== value) {
59
+ handleChange(value as T);
60
+ prevValueRef.current = value;
61
+ }
62
+ }, [value, prevValueRef, handleChange]);
63
+
64
+ return uncontrolledState;
65
+ }
66
+
67
+ export { useControllableState };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+
5
+ export function useDebounce<T>(value: T, delay: number) {
6
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
7
+
8
+ useEffect(() => {
9
+ const handler = setTimeout(() => {
10
+ setDebouncedValue(value);
11
+ }, delay);
12
+
13
+ return () => {
14
+ clearTimeout(handler);
15
+ };
16
+ }, [value, delay]);
17
+
18
+ return debouncedValue;
19
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export function useIsVisible(ref: any, threshold = 0.3) {
6
+ const [isIntersecting, setIntersecting] = useState(false);
7
+
8
+ useEffect(() => {
9
+ const observer = new IntersectionObserver(
10
+ ([entry]) => {
11
+ setIntersecting(entry?.isIntersecting || false);
12
+ },
13
+ { threshold }
14
+ );
15
+
16
+ observer.observe(ref.current);
17
+ return () => {
18
+ observer.disconnect();
19
+ };
20
+ }, [ref, threshold]);
21
+
22
+ return isIntersecting;
23
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+
5
+ export function useMediaQuery(query: string) {
6
+ const [value, setValue] = React.useState(false);
7
+
8
+ React.useEffect(() => {
9
+ function onChange(event: MediaQueryListEvent) {
10
+ setValue(event.matches);
11
+ }
12
+
13
+ const result = matchMedia(query);
14
+ result.addEventListener("change", onChange);
15
+ setValue(result.matches);
16
+
17
+ return () => result.removeEventListener("change", onChange);
18
+ }, [query]);
19
+
20
+ return value;
21
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
7
+ undefined
8
+ );
9
+
10
+ React.useEffect(() => {
11
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12
+ const onChange = () => {
13
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14
+ };
15
+ mql.addEventListener("change", onChange);
16
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, []);
19
+
20
+ return !!isMobile;
21
+ }
@@ -0,0 +1,15 @@
1
+ // Tremor Raw useOnWindowResize [v0.0.0]
2
+
3
+ import * as React from "react";
4
+
5
+ export const useOnWindowResize = (handler: { (): void }) => {
6
+ React.useEffect(() => {
7
+ const handleResize = () => {
8
+ handler();
9
+ };
10
+ handleResize();
11
+ window.addEventListener("resize", handleResize);
12
+
13
+ return () => window.removeEventListener("resize", handleResize);
14
+ }, [handler]);
15
+ };
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import React from "react";
3
+
4
+ export function useScroll(threshold: number) {
5
+ const [scrolled, setScrolled] = React.useState(false);
6
+
7
+ const onScroll = React.useCallback(() => {
8
+ setScrolled(window.scrollY > threshold);
9
+ }, [threshold]);
10
+
11
+ React.useEffect(() => {
12
+ window.addEventListener("scroll", onScroll);
13
+ return () => window.removeEventListener("scroll", onScroll);
14
+ }, [onScroll]);
15
+
16
+ // also check on first load
17
+ React.useEffect(() => {
18
+ onScroll();
19
+ }, [onScroll]);
20
+
21
+ return scrolled;
22
+ }
@@ -0,0 +1,61 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { JSX } from "react";
3
+ import { twMerge } from "tailwind-merge";
4
+ import _ from "lodash";
5
+ export const lodash = _;
6
+ export * from "class-variance-authority";
7
+ export function cn(...inputs: ClassValue[]) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+
11
+ export function formatBytes(
12
+ bytes: number,
13
+ opts: {
14
+ decimals?: number;
15
+ sizeType?: "accurate" | "normal";
16
+ } = {}
17
+ ) {
18
+ const { decimals = 0, sizeType = "normal" } = opts;
19
+
20
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
21
+ const accurateSizes = ["Bytes", "KiB", "MiB", "GiB", "TiB"];
22
+ if (bytes === 0) return "0 Byte";
23
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
24
+ return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
25
+ sizeType === "accurate" ? accurateSizes[i] ?? "Bytest" : sizes[i] ?? "Bytes"
26
+ }`;
27
+ }
28
+
29
+ export function replacePlaceholders(
30
+ string: string,
31
+ replacements: { holder: string; replacement: string | React.ReactNode }[]
32
+ ): (string | React.ReactNode | JSX.Element)[] {
33
+ if (!string || !replacements || replacements.length === 0) {
34
+ return [];
35
+ }
36
+
37
+ let result: (string | React.ReactNode | JSX.Element)[] = [string];
38
+
39
+ replacements.forEach(({ holder, replacement }) => {
40
+ const updatedResult: (string | React.ReactNode)[] = [];
41
+
42
+ result.forEach((element) => {
43
+ if (typeof element === "string") {
44
+ const parts: string[] = (element as string).split(holder);
45
+ parts.forEach((part, i) => {
46
+ if (i !== parts.length - 1) {
47
+ updatedResult.push(part, replacement);
48
+ } else {
49
+ updatedResult.push(part);
50
+ }
51
+ });
52
+ } else {
53
+ updatedResult.push(element);
54
+ }
55
+ });
56
+
57
+ result = updatedResult;
58
+ });
59
+
60
+ return result;
61
+ }
package/src/lib/zod.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { z } from "zod";
2
+ export { zodResolver } from "@hookform/resolvers/zod";
@@ -0,0 +1,57 @@
1
+ * {
2
+ scroll-behavior: smooth;
3
+ }
4
+
5
+ .no-visible-scrollbar {
6
+ scrollbar-width: none;
7
+ -ms-overflow-style: none;
8
+ -webkit-overflow-scrolling: touch;
9
+ }
10
+
11
+ .no-visible-scrollbar::-webkit-scrollbar {
12
+ display: none;
13
+ }
14
+ body {
15
+ height: 100%;
16
+ }
17
+
18
+ *:has(> .form-ready-component) {
19
+ overflow: hidden;
20
+ }
21
+ .hide-password-toggle::-ms-reveal,
22
+ .hide-password-toggle::-ms-clear {
23
+ visibility: hidden;
24
+ pointer-events: none;
25
+ display: none;
26
+ }
27
+
28
+ .loading-stroke path {
29
+ stroke-width: 1;
30
+ animation: loadingStroke 4s infinite;
31
+ }
32
+
33
+ @keyframes loadingStroke {
34
+ 0% {
35
+ stroke-dasharray: 360px, 0px;
36
+ stroke-dashoffset: 460px;
37
+ }
38
+
39
+ 50% {
40
+ stroke-dasharray: 360px, 100px;
41
+ }
42
+
43
+ 100% {
44
+ stroke-dasharray: 360px, 0px;
45
+ }
46
+ }
47
+ .loading-text {
48
+ --ch-size: 1ch;
49
+ clip-path: inset(0 var(--ch-size) 0 0);
50
+ animation: l 2s steps(4, jump-none) infinite;
51
+ }
52
+
53
+ @keyframes l {
54
+ to {
55
+ clip-path: inset(0);
56
+ }
57
+ }
@@ -0,0 +1,130 @@
1
+ @import "tailwindcss";
2
+ @import "./core.css";
3
+ @source "../../../../packages/ui/**/*.{ts,tsx}";
4
+ @source "../**/*.{ts,tsx}";
5
+
6
+ @import "tw-animate-css";
7
+
8
+ @custom-variant dark (&:is(.dark *));
9
+
10
+ :root {
11
+ --radius: 0.65rem;
12
+ --background: oklch(1 0 0);
13
+ --foreground: oklch(0.145 0 0);
14
+ --card: oklch(1 0 0);
15
+ --card-foreground: oklch(0.145 0 0);
16
+ --popover: oklch(1 0 0);
17
+ --popover-foreground: oklch(0.145 0 0);
18
+ --primary: oklch(0.205 0 0);
19
+ --primary-foreground: oklch(0.985 0 0);
20
+ --secondary: oklch(0.97 0 0);
21
+ --secondary-foreground: oklch(0.205 0 0);
22
+ --muted: oklch(0.97 0 0);
23
+ --muted-foreground: oklch(0.556 0 0);
24
+ --accent: oklch(0.97 0 0);
25
+ --accent-foreground: oklch(0.205 0 0);
26
+ --destructive: oklch(0.577 0.245 27.325);
27
+ --border: oklch(0.922 0 0);
28
+ --input: oklch(0.922 0 0);
29
+ --ring: oklch(0.708 0 0);
30
+ --chart-1: oklch(0.646 0.222 41.116);
31
+ --chart-2: oklch(0.6 0.118 184.704);
32
+ --chart-3: oklch(0.398 0.07 227.392);
33
+ --chart-4: oklch(0.828 0.189 84.429);
34
+ --chart-5: oklch(0.769 0.188 70.08);
35
+ --radius: 0.625rem;
36
+ --sidebar: oklch(0.985 0 0);
37
+ --sidebar-foreground: oklch(0.145 0 0);
38
+ --sidebar-primary: oklch(0.205 0 0);
39
+ --sidebar-primary-foreground: oklch(0.985 0 0);
40
+ --sidebar-accent: oklch(0.97 0 0);
41
+ --sidebar-accent-foreground: oklch(0.205 0 0);
42
+ --sidebar-border: oklch(0.922 0 0);
43
+ --sidebar-ring: oklch(0.708 0 0);
44
+ }
45
+
46
+ .dark {
47
+ --background: oklch(0.145 0 0);
48
+ --foreground: oklch(0.985 0 0);
49
+ --card: oklch(0.205 0 0);
50
+ --card-foreground: oklch(0.985 0 0);
51
+ --popover: oklch(0.205 0 0);
52
+ --popover-foreground: oklch(0.985 0 0);
53
+ --primary: oklch(0.922 0 0);
54
+ --primary-foreground: oklch(0.205 0 0);
55
+ --secondary: oklch(0.269 0 0);
56
+ --secondary-foreground: oklch(0.985 0 0);
57
+ --muted: oklch(0.269 0 0);
58
+ --muted-foreground: oklch(0.708 0 0);
59
+ --accent: oklch(0.269 0 0);
60
+ --accent-foreground: oklch(0.985 0 0);
61
+ --destructive: oklch(0.704 0.191 22.216);
62
+ --border: oklch(1 0 0 / 10%);
63
+ --input: oklch(1 0 0 / 15%);
64
+ --ring: oklch(0.556 0 0);
65
+ --chart-1: oklch(0.488 0.243 264.376);
66
+ --chart-2: oklch(0.696 0.17 162.48);
67
+ --chart-3: oklch(0.769 0.188 70.08);
68
+ --chart-4: oklch(0.627 0.265 303.9);
69
+ --chart-5: oklch(0.645 0.246 16.439);
70
+ --sidebar: oklch(0.205 0 0);
71
+ --sidebar-foreground: oklch(0.985 0 0);
72
+ --sidebar-primary: oklch(0.488 0.243 264.376);
73
+ --sidebar-primary-foreground: oklch(0.985 0 0);
74
+ --sidebar-accent: oklch(0.269 0 0);
75
+ --sidebar-accent-foreground: oklch(0.985 0 0);
76
+ --sidebar-border: oklch(1 0 0 / 10%);
77
+ --sidebar-ring: oklch(0.556 0 0);
78
+ }
79
+
80
+ @theme inline {
81
+ --color-background: var(--background);
82
+ --color-foreground: var(--foreground);
83
+ --color-card: var(--card);
84
+ --color-card-foreground: var(--card-foreground);
85
+ --color-popover: var(--popover);
86
+ --color-popover-foreground: var(--popover-foreground);
87
+ --color-primary: var(--primary);
88
+ --color-primary-foreground: var(--primary-foreground);
89
+ --color-secondary: var(--secondary);
90
+ --color-secondary-foreground: var(--secondary-foreground);
91
+ --color-muted: var(--muted);
92
+ --color-muted-foreground: var(--muted-foreground);
93
+ --color-accent: var(--accent);
94
+ --color-accent-foreground: var(--accent-foreground);
95
+ --color-destructive: var(--destructive);
96
+ --color-destructive-foreground: var(--destructive-foreground);
97
+ --color-border: var(--border);
98
+ --color-input: var(--input);
99
+ --color-ring: var(--ring);
100
+ --color-chart-1: var(--chart-1);
101
+ --color-chart-2: var(--chart-2);
102
+ --color-chart-3: var(--chart-3);
103
+ --color-chart-4: var(--chart-4);
104
+ --color-chart-5: var(--chart-5);
105
+ --radius-sm: calc(var(--radius) - 4px);
106
+ --radius-md: calc(var(--radius) - 2px);
107
+ --radius-lg: var(--radius);
108
+ --radius-xl: calc(var(--radius) + 4px);
109
+ --color-sidebar: var(--sidebar);
110
+ --color-sidebar-foreground: var(--sidebar-foreground);
111
+ --color-sidebar-primary: var(--sidebar-primary);
112
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
113
+ --color-sidebar-accent: var(--sidebar-accent);
114
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
115
+ --color-sidebar-border: var(--sidebar-border);
116
+ --color-sidebar-ring: var(--sidebar-ring);
117
+ }
118
+
119
+ @layer base {
120
+ * {
121
+ @apply border-border outline-ring/50;
122
+ }
123
+ body {
124
+ @apply bg-background text-foreground;
125
+ }
126
+
127
+ :where(button, [role="button"]):not([disabled]) {
128
+ cursor: pointer;
129
+ }
130
+ }
@@ -0,0 +1,217 @@
1
+ import React from "react";
2
+ import { render, screen, waitFor } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { EmailInput } from "../custom/email-input";
5
+
6
+ describe("EmailInput", () => {
7
+ it("renders input field", () => {
8
+ render(<EmailInput id="email" />);
9
+ const input = screen.getByTestId("email") as HTMLInputElement;
10
+ expect(input).toBeInTheDocument();
11
+ expect(input.type).toBe("email");
12
+ });
13
+
14
+ it("renders with label", () => {
15
+ render(<EmailInput id="email" label="Email Address" />);
16
+ expect(screen.getByText("Email Address")).toBeInTheDocument();
17
+ });
18
+
19
+ it("shows required asterisk when required", () => {
20
+ render(<EmailInput id="email" label="Email" required />);
21
+ const label = screen.getByText("Email");
22
+ expect(label).toHaveClass("after:content-['*']");
23
+ });
24
+
25
+ it("accepts typed input", async () => {
26
+ const user = userEvent.setup();
27
+ render(<EmailInput id="email" />);
28
+ const input = screen.getByTestId("email") as HTMLInputElement;
29
+
30
+ await user.type(input, "test@example.com");
31
+ expect(input.value).toBe("test@example.com");
32
+ });
33
+
34
+ it("calls onValueChange when typing", async () => {
35
+ const handleChange = jest.fn();
36
+ const user = userEvent.setup();
37
+ render(<EmailInput id="email" onValueChange={handleChange} />);
38
+ const input = screen.getByTestId("email");
39
+
40
+ await user.type(input, "test");
41
+ expect(handleChange).toHaveBeenCalled();
42
+ expect(handleChange).toHaveBeenLastCalledWith("test");
43
+ });
44
+
45
+ it("shows suggestions when typing @ symbol", async () => {
46
+ const user = userEvent.setup();
47
+ render(<EmailInput id="email" />);
48
+ const input = screen.getByTestId("email");
49
+
50
+ await user.type(input, "test@");
51
+
52
+ await waitFor(() => {
53
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
54
+ });
55
+ });
56
+
57
+ it("filters suggestions based on domain input", async () => {
58
+ const user = userEvent.setup();
59
+ render(<EmailInput id="email" />);
60
+ const input = screen.getByTestId("email");
61
+
62
+ await user.type(input, "test@gm");
63
+
64
+ await waitFor(() => {
65
+ const suggestion = screen.getByTestId("email_suggestion_0");
66
+ expect(suggestion.textContent).toContain("gmail");
67
+ });
68
+ });
69
+
70
+ it("applies suggestion when clicked", async () => {
71
+ const user = userEvent.setup();
72
+ render(<EmailInput id="email" />);
73
+ const input = screen.getByTestId("email") as HTMLInputElement;
74
+
75
+ await user.type(input, "test@");
76
+
77
+ await waitFor(() => {
78
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
79
+ });
80
+
81
+ const suggestion = screen.getByTestId("email_suggestion_0");
82
+ await user.click(suggestion);
83
+
84
+ expect(input.value).toContain("@");
85
+ await waitFor(() => {
86
+ expect(
87
+ screen.queryByTestId("email_suggestion_0")
88
+ ).not.toBeInTheDocument();
89
+ });
90
+ });
91
+
92
+ it("navigates suggestions with arrow keys", async () => {
93
+ const user = userEvent.setup();
94
+ render(<EmailInput id="email" />);
95
+ const input = screen.getByTestId("email");
96
+
97
+ await user.type(input, "test@");
98
+
99
+ await waitFor(() => {
100
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
101
+ });
102
+
103
+ await user.keyboard("{ArrowDown}");
104
+ const firstSuggestion = screen.getByTestId("email_suggestion_0");
105
+ expect(firstSuggestion).toHaveClass("bg-accent");
106
+
107
+ await user.keyboard("{ArrowDown}");
108
+ const secondSuggestion = screen.getByTestId("email_suggestion_1");
109
+ expect(secondSuggestion).toHaveClass("bg-accent");
110
+ });
111
+
112
+ it("applies suggestion with Enter key", async () => {
113
+ const user = userEvent.setup();
114
+ render(<EmailInput id="email" />);
115
+ const input = screen.getByTestId("email") as HTMLInputElement;
116
+
117
+ await user.type(input, "test@");
118
+
119
+ await waitFor(() => {
120
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
121
+ });
122
+
123
+ await user.keyboard("{ArrowDown}");
124
+ await user.keyboard("{Enter}");
125
+
126
+ expect(input.value).toContain("@");
127
+ await waitFor(() => {
128
+ expect(
129
+ screen.queryByTestId("email_suggestion_0")
130
+ ).not.toBeInTheDocument();
131
+ });
132
+ });
133
+
134
+ it("closes suggestions with Escape key", async () => {
135
+ const user = userEvent.setup();
136
+ render(<EmailInput id="email" />);
137
+ const input = screen.getByTestId("email");
138
+
139
+ await user.type(input, "test@");
140
+
141
+ await waitFor(() => {
142
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
143
+ });
144
+
145
+ await user.keyboard("{Escape}");
146
+
147
+ await waitFor(() => {
148
+ expect(
149
+ screen.queryByTestId("email_suggestion_0")
150
+ ).not.toBeInTheDocument();
151
+ });
152
+ });
153
+
154
+ it("accepts custom suggestions", async () => {
155
+ const user = userEvent.setup();
156
+ const customDomains = ["custom.com", "example.org"];
157
+ render(<EmailInput id="email" suggestions={customDomains} />);
158
+ const input = screen.getByTestId("email");
159
+
160
+ await user.type(input, "test@custom");
161
+
162
+ await waitFor(() => {
163
+ const suggestion = screen.getByTestId("email_suggestion_0");
164
+ expect(suggestion.textContent).toContain("custom.com");
165
+ });
166
+ });
167
+
168
+ it("forwards ref correctly", () => {
169
+ const ref = React.createRef<HTMLInputElement>();
170
+ render(<EmailInput id="email" ref={ref} />);
171
+ expect(ref.current).toBeInstanceOf(HTMLInputElement);
172
+ });
173
+
174
+ it("disables input when disabled prop is true", () => {
175
+ render(<EmailInput id="email" disabled />);
176
+ const input = screen.getByTestId("email");
177
+ expect(input).toBeDisabled();
178
+ });
179
+
180
+ it("updates value when controlled", () => {
181
+ const { rerender } = render(
182
+ <EmailInput id="email" value="test@example.com" onChange={() => {}} />
183
+ );
184
+ const input = screen.getByTestId("email") as HTMLInputElement;
185
+ expect(input.value).toBe("test@example.com");
186
+
187
+ rerender(
188
+ <EmailInput id="email" value="new@example.com" onChange={() => {}} />
189
+ );
190
+ expect(input.value).toBe("new@example.com");
191
+ });
192
+
193
+ it("hides suggestions when clicking outside", async () => {
194
+ const user = userEvent.setup();
195
+ render(
196
+ <div>
197
+ <EmailInput id="email" />
198
+ <button>Outside</button>
199
+ </div>
200
+ );
201
+ const input = screen.getByTestId("email");
202
+
203
+ await user.type(input, "test@");
204
+
205
+ await waitFor(() => {
206
+ expect(screen.getByTestId("email_suggestion_0")).toBeInTheDocument();
207
+ });
208
+
209
+ await user.click(screen.getByText("Outside"));
210
+
211
+ await waitFor(() => {
212
+ expect(
213
+ screen.queryByTestId("email_suggestion_0")
214
+ ).not.toBeInTheDocument();
215
+ });
216
+ });
217
+ });