@tuturuuu/ui 0.0.4

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 (104) hide show
  1. package/.checksum +1 -0
  2. package/README.md +46 -0
  3. package/components.json +20 -0
  4. package/eslint.config.mjs +20 -0
  5. package/jsr.json +10 -0
  6. package/package.json +120 -0
  7. package/postcss.config.mjs +8 -0
  8. package/rollup.config.js +40 -0
  9. package/src/components/ui/accordion.tsx +70 -0
  10. package/src/components/ui/alert-dialog.tsx +156 -0
  11. package/src/components/ui/alert.tsx +58 -0
  12. package/src/components/ui/aspect-ratio.tsx +11 -0
  13. package/src/components/ui/avatar.tsx +52 -0
  14. package/src/components/ui/badge.tsx +49 -0
  15. package/src/components/ui/breadcrumb.tsx +108 -0
  16. package/src/components/ui/button.tsx +61 -0
  17. package/src/components/ui/calendar.tsx +212 -0
  18. package/src/components/ui/card.tsx +74 -0
  19. package/src/components/ui/carousel.tsx +240 -0
  20. package/src/components/ui/chart.tsx +365 -0
  21. package/src/components/ui/checkbox.tsx +31 -0
  22. package/src/components/ui/codeblock.tsx +161 -0
  23. package/src/components/ui/collapsible.tsx +33 -0
  24. package/src/components/ui/color-picker.tsx +143 -0
  25. package/src/components/ui/command.tsx +176 -0
  26. package/src/components/ui/context-menu.tsx +251 -0
  27. package/src/components/ui/custom/autosize-textarea.tsx +111 -0
  28. package/src/components/ui/custom/calendar/core.tsx +61 -0
  29. package/src/components/ui/custom/calendar/day-cell.tsx +74 -0
  30. package/src/components/ui/custom/calendar/month-header.tsx +59 -0
  31. package/src/components/ui/custom/calendar/month-view.tsx +110 -0
  32. package/src/components/ui/custom/calendar/utils.ts +76 -0
  33. package/src/components/ui/custom/calendar/year-calendar.tsx +64 -0
  34. package/src/components/ui/custom/calendar/year-view.tsx +58 -0
  35. package/src/components/ui/custom/combobox.tsx +197 -0
  36. package/src/components/ui/custom/common-footer.tsx +215 -0
  37. package/src/components/ui/custom/compared-date-range-picker.tsx +561 -0
  38. package/src/components/ui/custom/date-input.tsx +279 -0
  39. package/src/components/ui/custom/empty-card.tsx +39 -0
  40. package/src/components/ui/custom/feature-summary.tsx +135 -0
  41. package/src/components/ui/custom/file-uploader.tsx +349 -0
  42. package/src/components/ui/custom/input-field.tsx +29 -0
  43. package/src/components/ui/custom/loading-indicator.tsx +28 -0
  44. package/src/components/ui/custom/modifiable-dialog-trigger.tsx +83 -0
  45. package/src/components/ui/custom/month-picker.tsx +157 -0
  46. package/src/components/ui/custom/report-preview.tsx +175 -0
  47. package/src/components/ui/custom/search-bar.tsx +56 -0
  48. package/src/components/ui/custom/select-field.tsx +78 -0
  49. package/src/components/ui/custom/tables/data-table-column-header.tsx +72 -0
  50. package/src/components/ui/custom/tables/data-table-create-button.tsx +31 -0
  51. package/src/components/ui/custom/tables/data-table-faceted-filter.tsx +142 -0
  52. package/src/components/ui/custom/tables/data-table-pagination.tsx +243 -0
  53. package/src/components/ui/custom/tables/data-table-refresh-button.tsx +45 -0
  54. package/src/components/ui/custom/tables/data-table-toolbar.tsx +133 -0
  55. package/src/components/ui/custom/tables/data-table-view-options.tsx +112 -0
  56. package/src/components/ui/custom/tables/data-table.tsx +228 -0
  57. package/src/components/ui/custom/uploaded-files-card.tsx +50 -0
  58. package/src/components/ui/dialog.tsx +137 -0
  59. package/src/components/ui/drawer.tsx +131 -0
  60. package/src/components/ui/dropdown-menu.tsx +256 -0
  61. package/src/components/ui/form.tsx +167 -0
  62. package/src/components/ui/hover-card.tsx +41 -0
  63. package/src/components/ui/icons.tsx +506 -0
  64. package/src/components/ui/input-otp.tsx +78 -0
  65. package/src/components/ui/input.tsx +18 -0
  66. package/src/components/ui/label.tsx +23 -0
  67. package/src/components/ui/markdown.tsx +7 -0
  68. package/src/components/ui/menubar.tsx +275 -0
  69. package/src/components/ui/navigation-menu.tsx +169 -0
  70. package/src/components/ui/pagination.tsx +126 -0
  71. package/src/components/ui/popover.tsx +47 -0
  72. package/src/components/ui/progress.tsx +30 -0
  73. package/src/components/ui/radio-group.tsx +44 -0
  74. package/src/components/ui/resizable.tsx +55 -0
  75. package/src/components/ui/scroll-area.tsx +57 -0
  76. package/src/components/ui/select.tsx +180 -0
  77. package/src/components/ui/separator.tsx +27 -0
  78. package/src/components/ui/sheet.tsx +138 -0
  79. package/src/components/ui/sidebar.tsx +734 -0
  80. package/src/components/ui/skeleton.tsx +13 -0
  81. package/src/components/ui/slider.tsx +62 -0
  82. package/src/components/ui/sonner.tsx +29 -0
  83. package/src/components/ui/switch.tsx +30 -0
  84. package/src/components/ui/table.tsx +112 -0
  85. package/src/components/ui/tabs.tsx +68 -0
  86. package/src/components/ui/tag-input.tsx +141 -0
  87. package/src/components/ui/textarea.tsx +17 -0
  88. package/src/components/ui/time-picker-input.tsx +117 -0
  89. package/src/components/ui/time-picker-utils.tsx +146 -0
  90. package/src/components/ui/toast.tsx +128 -0
  91. package/src/components/ui/toaster.tsx +35 -0
  92. package/src/components/ui/toggle-group.tsx +72 -0
  93. package/src/components/ui/toggle.tsx +46 -0
  94. package/src/components/ui/tooltip.tsx +60 -0
  95. package/src/globals.css +252 -0
  96. package/src/hooks/use-callback-ref.ts +28 -0
  97. package/src/hooks/use-controllable-state.ts +68 -0
  98. package/src/hooks/use-copy-to-clipboard.ts +46 -0
  99. package/src/hooks/use-form.ts +23 -0
  100. package/src/hooks/use-forwarded-ref.ts +17 -0
  101. package/src/hooks/use-mobile.tsx +21 -0
  102. package/src/hooks/use-toast.ts +191 -0
  103. package/src/resolvers.ts +3 -0
  104. package/tsconfig.json +17 -0
@@ -0,0 +1,252 @@
1
+ @import 'tailwindcss';
2
+ @source "components";
3
+
4
+ @plugin '@tailwindcss/typography';
5
+ @plugin 'tailwindcss-animate';
6
+
7
+ @custom-variant dark (&:is(.dark *));
8
+
9
+ @theme {
10
+ --color-background: hsl(var(--background));
11
+ --color-foreground: hsl(var(--foreground));
12
+
13
+ --color-card: hsl(var(--card));
14
+ --color-card-foreground: hsl(var(--card-foreground));
15
+
16
+ --color-popover: hsl(var(--popover));
17
+ --color-popover-foreground: hsl(var(--popover-foreground));
18
+
19
+ --color-primary: hsl(var(--primary));
20
+ --color-primary-foreground: hsl(var(--primary-foreground));
21
+
22
+ --color-secondary: hsl(var(--secondary));
23
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
24
+
25
+ --color-muted: hsl(var(--muted));
26
+ --color-muted-foreground: hsl(var(--muted-foreground));
27
+
28
+ --color-accent: hsl(var(--accent));
29
+ --color-accent-foreground: hsl(var(--accent-foreground));
30
+
31
+ --color-destructive: hsl(var(--destructive));
32
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
33
+
34
+ --color-border: hsl(var(--border));
35
+ --color-input: hsl(var(--input));
36
+ --color-ring: hsl(var(--ring));
37
+
38
+ --color-chart-1: hsl(var(--chart-1));
39
+ --color-chart-2: hsl(var(--chart-2));
40
+ --color-chart-3: hsl(var(--chart-3));
41
+ --color-chart-4: hsl(var(--chart-4));
42
+ --color-chart-5: hsl(var(--chart-5));
43
+
44
+ --color-sidebar: hsl(var(--sidebar-background));
45
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
46
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
47
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
48
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
49
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
50
+ --color-sidebar-border: hsl(var(--sidebar-border));
51
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
52
+
53
+ --radius-lg: var(--radius);
54
+ --radius-md: calc(var(--radius) - 2px);
55
+ --radius-sm: calc(var(--radius) - 4px);
56
+
57
+ --animate-accordion-down: accordion-down 0.2s ease-out;
58
+ --animate-accordion-up: accordion-up 0.2s ease-out;
59
+
60
+ --color-dynamic-lime: hsl(var(--lime));
61
+ --color-dynamic-purple: hsl(var(--purple));
62
+ --color-dynamic-blue: hsl(var(--blue));
63
+ --color-dynamic-sky: hsl(var(--sky));
64
+ --color-dynamic-green: hsl(var(--green));
65
+ --color-dynamic-yellow: hsl(var(--yellow));
66
+ --color-dynamic-orange: hsl(var(--orange));
67
+ --color-dynamic-red: hsl(var(--red));
68
+ --color-deep-blue: #0b1023;
69
+ --color-midnight-blue: #1e2240;
70
+ --color-dark-purple: #2c124b;
71
+ --color-dynamic-light-lime: hsl(var(--light-lime));
72
+ --color-dynamic-light-purple: hsl(var(--light-purple));
73
+ --color-dynamic-light-pink: hsl(var(--light-pink));
74
+ --color-dynamic-light-blue: hsl(var(--light-blue));
75
+ --color-dynamic-light-sky: hsl(var(--light-sky));
76
+ --dynamic-light-green: hsl(var(--light-green));
77
+ --color-dynamic-light-yellow: hsl(var(--light-yellow));
78
+ --color-dynamic-light-orange: hsl(var(--light-orange));
79
+ --color-dynamic-light-red: hsl(var(--light-red));
80
+
81
+ @keyframes accordion-down {
82
+ from {
83
+ height: 0;
84
+ }
85
+ to {
86
+ height: var(--radix-accordion-content-height);
87
+ }
88
+ }
89
+
90
+ @keyframes accordion-up {
91
+ from {
92
+ height: var(--radix-accordion-content-height);
93
+ }
94
+ to {
95
+ height: 0;
96
+ }
97
+ }
98
+ }
99
+
100
+ /*
101
+ The default border color has changed to `currentColor` in Tailwind CSS v4,
102
+ so we've added these compatibility styles to make sure everything still
103
+ looks the same as it did with Tailwind CSS v3.
104
+
105
+ If we ever want to remove these styles, we need to add an explicit border
106
+ color utility to any element that depends on these defaults.
107
+ */
108
+ @layer base {
109
+ *,
110
+ ::after,
111
+ ::before,
112
+ ::backdrop,
113
+ ::file-selector-button {
114
+ border-color: var(--color-gray-200, currentColor);
115
+ }
116
+ }
117
+
118
+ @layer utilities {
119
+ body {
120
+ font-family: Arial, Helvetica, sans-serif;
121
+ }
122
+ button {
123
+ @apply cursor-pointer;
124
+ }
125
+ }
126
+
127
+ @layer base {
128
+ :root {
129
+ --background: 0 0% 100%;
130
+ --foreground: 0 0% 3.9%;
131
+ --card: 0 0% 100%;
132
+ --card-foreground: 0 0% 3.9%;
133
+ --popover: 0 0% 100%;
134
+ --popover-foreground: 0 0% 3.9%;
135
+ --primary: 0 0% 9%;
136
+ --primary-foreground: 0 0% 98%;
137
+ --secondary: 0 0% 96.1%;
138
+ --secondary-foreground: 0 0% 9%;
139
+ --muted: 0 0% 96.1%;
140
+ --muted-foreground: 0 0% 45.1%;
141
+ --accent: 0 0% 96.1%;
142
+ --accent-foreground: 0 0% 9%;
143
+ --destructive: 0 84.2% 60.2%;
144
+ --destructive-foreground: 0 0% 98%;
145
+ --border: 0 0% 89.8%;
146
+ --input: 0 0% 89.8%;
147
+ --ring: 0 0% 3.9%;
148
+ --chart-1: 12 76% 61%;
149
+ --chart-2: 173 58% 39%;
150
+ --chart-3: 197 37% 24%;
151
+ --chart-4: 43 74% 66%;
152
+ --chart-5: 27 87% 67%;
153
+ --radius: 0.5rem;
154
+ --sidebar-background: 0 0% 98%;
155
+ --sidebar-foreground: 240 5.3% 26.1%;
156
+ --sidebar-primary: 240 5.9% 10%;
157
+ --sidebar-primary-foreground: 0 0% 98%;
158
+ --sidebar-accent: 240 4.8% 95.9%;
159
+ --sidebar-accent-foreground: 240 5.9% 10%;
160
+ --sidebar-border: 220 13% 91%;
161
+ --sidebar-ring: 217.2 91.2% 59.8%;
162
+
163
+ /* Custom colors */
164
+ --lime: 90 40% 30%;
165
+ --purple: 270 40% 30%;
166
+ --blue: 220 40% 30%;
167
+ --sky: 200 40% 30%;
168
+ --green: 140 40% 30%;
169
+ --yellow: 40 40% 30%;
170
+ --orange: 25 40% 30%;
171
+ --red: 0 40% 30%;
172
+
173
+ --light-lime: 90 80% 60%;
174
+ --light-purple: 240 70% 60%;
175
+ --light-pink: 330 70% 60%;
176
+ --light-blue: 220 80% 60%;
177
+ --light-sky: 200 80% 60%;
178
+ --light-green: 140 80% 60%;
179
+ --light-yellow: 40 80% 60%;
180
+ --light-orange: 25 80% 60%;
181
+ --light-red: 0 70% 80%;
182
+ }
183
+
184
+ .dark {
185
+ --background: 0 0% 3.9%;
186
+ --foreground: 0 0% 98%;
187
+ --card: 0 0% 3.9%;
188
+ --card-foreground: 0 0% 98%;
189
+ --popover: 0 0% 3.9%;
190
+ --popover-foreground: 0 0% 98%;
191
+ --primary: 0 0% 98%;
192
+ --primary-foreground: 0 0% 9%;
193
+ --secondary: 0 0% 14.9%;
194
+ --secondary-foreground: 0 0% 98%;
195
+ --muted: 0 0% 14.9%;
196
+ --muted-foreground: 0 0% 63.9%;
197
+ --accent: 0 0% 14.9%;
198
+ --accent-foreground: 0 0% 98%;
199
+ --destructive: 0 62.8% 30.6%;
200
+ --destructive-foreground: 0 0% 98%;
201
+ --border: 0 0% 14.9%;
202
+ --input: 0 0% 14.9%;
203
+ --ring: 0 0% 83.1%;
204
+ --chart-1: 220 70% 50%;
205
+ --chart-2: 160 60% 45%;
206
+ --chart-3: 30 80% 55%;
207
+ --chart-4: 280 65% 60%;
208
+ --chart-5: 340 75% 55%;
209
+ --sidebar-background: 240 5.9% 10%;
210
+ --sidebar-foreground: 240 4.8% 95.9%;
211
+ --sidebar-primary: 224.3 76.3% 48%;
212
+ --sidebar-primary-foreground: 0 0% 100%;
213
+ --sidebar-accent: 240 3.7% 15.9%;
214
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
215
+ --sidebar-border: 240 3.7% 15.9%;
216
+ --sidebar-ring: 217.2 91.2% 59.8%;
217
+
218
+ /* Custom colors */
219
+ --lime: 90 50% 70%;
220
+ --purple: 270 50% 70%;
221
+ --blue: 220 50% 70%;
222
+ --sky: 200 70% 80%;
223
+ --green: 160 50% 70%;
224
+ --yellow: 40 50% 70%;
225
+ --orange: 25 50% 70%;
226
+ --red: 0 50% 70%;
227
+
228
+ --light-lime: 90 60% 75%;
229
+ --light-purple: 270 60% 75%;
230
+ --light-pink: 330 60% 75%;
231
+ --light-blue: 220 60% 75%;
232
+ --light-sky: 200 70% 80%;
233
+ --light-green: 160 60% 75%;
234
+ --light-yellow: 40 60% 75%;
235
+ --light-orange: 25 60% 75%;
236
+ --light-red: 0 60% 75%;
237
+ }
238
+ }
239
+
240
+ @utility container {
241
+ margin-inline: auto;
242
+ padding-inline: 2rem;
243
+ }
244
+
245
+ @layer base {
246
+ * {
247
+ @apply border-border outline-ring/50;
248
+ }
249
+ body {
250
+ @apply bg-background text-foreground;
251
+ }
252
+ }
@@ -0,0 +1,28 @@
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
+ // eslint-disable-next-line no-unused-vars
12
+ function useCallbackRef<T extends (...args: never[]) => unknown>(
13
+ callback: T | undefined
14
+ ): T {
15
+ const callbackRef = React.useRef(callback);
16
+
17
+ React.useEffect(() => {
18
+ callbackRef.current = callback;
19
+ });
20
+
21
+ // https://github.com/facebook/react/issues/19240
22
+ return React.useMemo(
23
+ () => ((...args) => callbackRef.current?.(...args)) as T,
24
+ []
25
+ );
26
+ }
27
+
28
+ export { useCallbackRef };
@@ -0,0 +1,68 @@
1
+ import { useCallbackRef } from './use-callback-ref';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
6
+ */
7
+
8
+ type UseControllableStateParams<T> = {
9
+ prop?: T | undefined;
10
+ defaultProp?: T | undefined;
11
+ // eslint-disable-next-line no-unused-vars
12
+ onChange?: (state: T) => void;
13
+ };
14
+
15
+ // eslint-disable-next-line no-unused-vars
16
+ type SetStateFn<T> = (prevState?: T) => T;
17
+
18
+ function useControllableState<T>({
19
+ prop,
20
+ defaultProp,
21
+ onChange = () => {},
22
+ }: UseControllableStateParams<T>) {
23
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
24
+ defaultProp,
25
+ onChange,
26
+ });
27
+ const isControlled = prop !== undefined;
28
+ const value = isControlled ? prop : uncontrolledProp;
29
+ const handleChange = useCallbackRef(onChange);
30
+
31
+ const setValue: React.Dispatch<React.SetStateAction<T | undefined>> =
32
+ React.useCallback(
33
+ (nextValue) => {
34
+ if (isControlled) {
35
+ const setter = nextValue as SetStateFn<T>;
36
+ const value =
37
+ typeof nextValue === 'function' ? setter(prop) : nextValue;
38
+ if (value !== prop) handleChange(value as T);
39
+ } else {
40
+ setUncontrolledProp(nextValue);
41
+ }
42
+ },
43
+ [isControlled, prop, setUncontrolledProp, handleChange]
44
+ );
45
+
46
+ return [value, setValue] as const;
47
+ }
48
+
49
+ function useUncontrolledState<T>({
50
+ defaultProp,
51
+ onChange,
52
+ }: Omit<UseControllableStateParams<T>, 'prop'>) {
53
+ const uncontrolledState = React.useState<T | undefined>(defaultProp);
54
+ const [value] = uncontrolledState;
55
+ const prevValueRef = React.useRef(value);
56
+ const handleChange = useCallbackRef(onChange);
57
+
58
+ React.useEffect(() => {
59
+ if (prevValueRef.current !== value) {
60
+ handleChange(value as T);
61
+ prevValueRef.current = value;
62
+ }
63
+ }, [value, prevValueRef, handleChange]);
64
+
65
+ return uncontrolledState;
66
+ }
67
+
68
+ export { useControllableState };
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ export interface useCopyToClipboardProps {
6
+ timeout?: number;
7
+ }
8
+
9
+ export function useCopyToClipboard({
10
+ timeout = 2000,
11
+ }: useCopyToClipboardProps) {
12
+ const [isCopied, setIsCopied] = React.useState<Boolean>(false);
13
+
14
+ const copyToClipboard = async (value: string) => {
15
+ if (typeof window === 'undefined' || !navigator.clipboard) {
16
+ return;
17
+ }
18
+
19
+ if (!value) {
20
+ return;
21
+ }
22
+
23
+ try {
24
+ const textBlob = new Blob([value], { type: 'text/plain' });
25
+ const clipboardItem = new ClipboardItem({
26
+ 'text/plain': textBlob,
27
+ });
28
+
29
+ await navigator.clipboard.write([clipboardItem]);
30
+ setIsCopied(true);
31
+ setTimeout(() => setIsCopied(false), timeout);
32
+ } catch (err) {
33
+ console.error('Failed to copy:', err);
34
+ // Fallback to basic text copying
35
+ try {
36
+ await navigator.clipboard.writeText(value);
37
+ setIsCopied(true);
38
+ setTimeout(() => setIsCopied(false), timeout);
39
+ } catch (err) {
40
+ console.error('Failed to copy text:', err);
41
+ }
42
+ }
43
+ };
44
+
45
+ return { isCopied, copyToClipboard };
46
+ }
@@ -0,0 +1,23 @@
1
+ import {
2
+ Controller,
3
+ ControllerProps,
4
+ FieldPath,
5
+ FieldValues,
6
+ FormProvider,
7
+ UseFormReturn,
8
+ useFieldArray,
9
+ useForm,
10
+ useFormContext,
11
+ useFormState,
12
+ } from 'react-hook-form';
13
+
14
+ export {
15
+ Controller,
16
+ FormProvider,
17
+ useFieldArray,
18
+ useForm,
19
+ useFormContext,
20
+ useFormState,
21
+ };
22
+
23
+ export type { ControllerProps, FieldPath, FieldValues, UseFormReturn };
@@ -0,0 +1,17 @@
1
+ import type React from 'react';
2
+ import { useEffect, useRef } from 'react';
3
+
4
+ export function useForwardedRef<T>(ref: React.ForwardedRef<T>) {
5
+ const innerRef = useRef<T>(null);
6
+
7
+ useEffect(() => {
8
+ if (!ref) return;
9
+ if (typeof ref === 'function') {
10
+ ref(innerRef.current);
11
+ } else {
12
+ ref.current = innerRef.current;
13
+ }
14
+ });
15
+
16
+ return innerRef;
17
+ }
@@ -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,191 @@
1
+ 'use client';
2
+
3
+ // Inspired by react-hot-toast library
4
+ import type { ToastActionElement, ToastProps } from '../components/ui/toast';
5
+ import * as React from 'react';
6
+
7
+ const TOAST_LIMIT = 1;
8
+ const TOAST_REMOVE_DELAY = 1000000;
9
+
10
+ type ToasterToast = ToastProps & {
11
+ id: string;
12
+ title?: React.ReactNode;
13
+ description?: React.ReactNode;
14
+ action?: ToastActionElement;
15
+ };
16
+
17
+ const actionTypes = {
18
+ ADD_TOAST: 'ADD_TOAST',
19
+ UPDATE_TOAST: 'UPDATE_TOAST',
20
+ DISMISS_TOAST: 'DISMISS_TOAST',
21
+ REMOVE_TOAST: 'REMOVE_TOAST',
22
+ } as const;
23
+
24
+ let count = 0;
25
+
26
+ function genId() {
27
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
28
+ return count.toString();
29
+ }
30
+
31
+ type ActionType = typeof actionTypes;
32
+
33
+ type Action =
34
+ | {
35
+ type: ActionType['ADD_TOAST'];
36
+ toast: ToasterToast;
37
+ }
38
+ | {
39
+ type: ActionType['UPDATE_TOAST'];
40
+ toast: Partial<ToasterToast>;
41
+ }
42
+ | {
43
+ type: ActionType['DISMISS_TOAST'];
44
+ toastId?: ToasterToast['id'];
45
+ }
46
+ | {
47
+ type: ActionType['REMOVE_TOAST'];
48
+ toastId?: ToasterToast['id'];
49
+ };
50
+
51
+ interface State {
52
+ toasts: ToasterToast[];
53
+ }
54
+
55
+ const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
56
+
57
+ const addToRemoveQueue = (toastId: string) => {
58
+ if (toastTimeouts.has(toastId)) {
59
+ return;
60
+ }
61
+
62
+ const timeout = setTimeout(() => {
63
+ toastTimeouts.delete(toastId);
64
+ dispatch({
65
+ type: 'REMOVE_TOAST',
66
+ toastId: toastId,
67
+ });
68
+ }, TOAST_REMOVE_DELAY);
69
+
70
+ toastTimeouts.set(toastId, timeout);
71
+ };
72
+
73
+ export const reducer = (state: State, action: Action): State => {
74
+ switch (action.type) {
75
+ case 'ADD_TOAST':
76
+ return {
77
+ ...state,
78
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
79
+ };
80
+
81
+ case 'UPDATE_TOAST':
82
+ return {
83
+ ...state,
84
+ toasts: state.toasts.map((t) =>
85
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
86
+ ),
87
+ };
88
+
89
+ case 'DISMISS_TOAST': {
90
+ const { toastId } = action;
91
+
92
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
93
+ // but I'll keep it here for simplicity
94
+ if (toastId) {
95
+ addToRemoveQueue(toastId);
96
+ } else {
97
+ state.toasts.forEach((toast) => {
98
+ addToRemoveQueue(toast.id);
99
+ });
100
+ }
101
+
102
+ return {
103
+ ...state,
104
+ toasts: state.toasts.map((t) =>
105
+ t.id === toastId || toastId === undefined
106
+ ? {
107
+ ...t,
108
+ open: false,
109
+ }
110
+ : t
111
+ ),
112
+ };
113
+ }
114
+ case 'REMOVE_TOAST':
115
+ if (action.toastId === undefined) {
116
+ return {
117
+ ...state,
118
+ toasts: [],
119
+ };
120
+ }
121
+ return {
122
+ ...state,
123
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
124
+ };
125
+ }
126
+ };
127
+
128
+ // eslint-disable-next-line no-unused-vars
129
+ const listeners: Array<(state: State) => void> = [];
130
+
131
+ let memoryState: State = { toasts: [] };
132
+
133
+ function dispatch(action: Action) {
134
+ memoryState = reducer(memoryState, action);
135
+ listeners.forEach((listener) => {
136
+ listener(memoryState);
137
+ });
138
+ }
139
+
140
+ type Toast = Omit<ToasterToast, 'id'>;
141
+
142
+ function toast({ ...props }: Toast) {
143
+ const id = genId();
144
+
145
+ const update = (props: ToasterToast) =>
146
+ dispatch({
147
+ type: 'UPDATE_TOAST',
148
+ toast: { ...props, id },
149
+ });
150
+ const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
151
+
152
+ dispatch({
153
+ type: 'ADD_TOAST',
154
+ toast: {
155
+ ...props,
156
+ id,
157
+ open: true,
158
+ onOpenChange: (open) => {
159
+ if (!open) dismiss();
160
+ },
161
+ },
162
+ });
163
+
164
+ return {
165
+ id: id,
166
+ dismiss,
167
+ update,
168
+ };
169
+ }
170
+
171
+ function useToast() {
172
+ const [state, setState] = React.useState<State>(memoryState);
173
+
174
+ React.useEffect(() => {
175
+ listeners.push(setState);
176
+ return () => {
177
+ const index = listeners.indexOf(setState);
178
+ if (index > -1) {
179
+ listeners.splice(index, 1);
180
+ }
181
+ };
182
+ }, [state]);
183
+
184
+ return {
185
+ ...state,
186
+ toast,
187
+ dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
188
+ };
189
+ }
190
+
191
+ export { toast, useToast };
@@ -0,0 +1,3 @@
1
+ import { zodResolver } from '@hookform/resolvers/zod';
2
+
3
+ export { zodResolver };
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": "@tuturuuu/typescript-config/react-library.json",
3
+ "compilerOptions": {
4
+ "target": "ES6",
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "baseUrl": ".",
7
+ "esModuleInterop": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "module": "ESNext",
10
+ "moduleResolution": "node",
11
+ "paths": {
12
+ "@tuturuuu/ui/*": ["./src/*"]
13
+ }
14
+ },
15
+ "include": ["src"],
16
+ "exclude": ["node_modules"]
17
+ }