@luanthnh/cntt-ui 0.1.8 → 0.1.9

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 (238) hide show
  1. package/package.json +5 -1
  2. package/.storybook/globals.d.ts +0 -1
  3. package/.storybook/main.ts +0 -29
  4. package/.storybook/preview.ts +0 -32
  5. package/assets/fonts/Montserrat-Black.eot +0 -0
  6. package/assets/fonts/Montserrat-Black.ttf +0 -0
  7. package/assets/fonts/Montserrat-Black.woff +0 -0
  8. package/assets/fonts/Montserrat-Black.woff2 +0 -0
  9. package/assets/fonts/Montserrat-BlackItalic.eot +0 -0
  10. package/assets/fonts/Montserrat-BlackItalic.ttf +0 -0
  11. package/assets/fonts/Montserrat-BlackItalic.woff +0 -0
  12. package/assets/fonts/Montserrat-BlackItalic.woff2 +0 -0
  13. package/assets/fonts/Montserrat-Bold.eot +0 -0
  14. package/assets/fonts/Montserrat-Bold.ttf +0 -0
  15. package/assets/fonts/Montserrat-Bold.woff +0 -0
  16. package/assets/fonts/Montserrat-Bold.woff2 +0 -0
  17. package/assets/fonts/Montserrat-BoldItalic.eot +0 -0
  18. package/assets/fonts/Montserrat-BoldItalic.ttf +0 -0
  19. package/assets/fonts/Montserrat-BoldItalic.woff +0 -0
  20. package/assets/fonts/Montserrat-BoldItalic.woff2 +0 -0
  21. package/assets/fonts/Montserrat-ExtraBold.eot +0 -0
  22. package/assets/fonts/Montserrat-ExtraBold.ttf +0 -0
  23. package/assets/fonts/Montserrat-ExtraBold.woff +0 -0
  24. package/assets/fonts/Montserrat-ExtraBold.woff2 +0 -0
  25. package/assets/fonts/Montserrat-ExtraBoldItalic.eot +0 -0
  26. package/assets/fonts/Montserrat-ExtraBoldItalic.ttf +0 -0
  27. package/assets/fonts/Montserrat-ExtraBoldItalic.woff +0 -0
  28. package/assets/fonts/Montserrat-ExtraBoldItalic.woff2 +0 -0
  29. package/assets/fonts/Montserrat-ExtraLight.eot +0 -0
  30. package/assets/fonts/Montserrat-ExtraLight.ttf +0 -0
  31. package/assets/fonts/Montserrat-ExtraLight.woff +0 -0
  32. package/assets/fonts/Montserrat-ExtraLight.woff2 +0 -0
  33. package/assets/fonts/Montserrat-ExtraLightItalic.eot +0 -0
  34. package/assets/fonts/Montserrat-ExtraLightItalic.ttf +0 -0
  35. package/assets/fonts/Montserrat-ExtraLightItalic.woff +0 -0
  36. package/assets/fonts/Montserrat-ExtraLightItalic.woff2 +0 -0
  37. package/assets/fonts/Montserrat-Italic.eot +0 -0
  38. package/assets/fonts/Montserrat-Italic.ttf +0 -0
  39. package/assets/fonts/Montserrat-Italic.woff +0 -0
  40. package/assets/fonts/Montserrat-Italic.woff2 +0 -0
  41. package/assets/fonts/Montserrat-Light.eot +0 -0
  42. package/assets/fonts/Montserrat-Light.ttf +0 -0
  43. package/assets/fonts/Montserrat-Light.woff +0 -0
  44. package/assets/fonts/Montserrat-Light.woff2 +0 -0
  45. package/assets/fonts/Montserrat-LightItalic.eot +0 -0
  46. package/assets/fonts/Montserrat-LightItalic.ttf +0 -0
  47. package/assets/fonts/Montserrat-LightItalic.woff +0 -0
  48. package/assets/fonts/Montserrat-LightItalic.woff2 +0 -0
  49. package/assets/fonts/Montserrat-Medium.eot +0 -0
  50. package/assets/fonts/Montserrat-Medium.ttf +0 -0
  51. package/assets/fonts/Montserrat-Medium.woff +0 -0
  52. package/assets/fonts/Montserrat-Medium.woff2 +0 -0
  53. package/assets/fonts/Montserrat-MediumItalic.eot +0 -0
  54. package/assets/fonts/Montserrat-MediumItalic.ttf +0 -0
  55. package/assets/fonts/Montserrat-MediumItalic.woff +0 -0
  56. package/assets/fonts/Montserrat-MediumItalic.woff2 +0 -0
  57. package/assets/fonts/Montserrat-Regular.eot +0 -0
  58. package/assets/fonts/Montserrat-Regular.ttf +0 -0
  59. package/assets/fonts/Montserrat-Regular.woff +0 -0
  60. package/assets/fonts/Montserrat-Regular.woff2 +0 -0
  61. package/assets/fonts/Montserrat-SemiBold.eot +0 -0
  62. package/assets/fonts/Montserrat-SemiBold.ttf +0 -0
  63. package/assets/fonts/Montserrat-SemiBold.woff +0 -0
  64. package/assets/fonts/Montserrat-SemiBold.woff2 +0 -0
  65. package/assets/fonts/Montserrat-SemiBoldItalic.eot +0 -0
  66. package/assets/fonts/Montserrat-SemiBoldItalic.ttf +0 -0
  67. package/assets/fonts/Montserrat-SemiBoldItalic.woff +0 -0
  68. package/assets/fonts/Montserrat-SemiBoldItalic.woff2 +0 -0
  69. package/assets/fonts/Montserrat-Thin.eot +0 -0
  70. package/assets/fonts/Montserrat-Thin.ttf +0 -0
  71. package/assets/fonts/Montserrat-Thin.woff +0 -0
  72. package/assets/fonts/Montserrat-Thin.woff2 +0 -0
  73. package/assets/fonts/Montserrat-ThinItalic.eot +0 -0
  74. package/assets/fonts/Montserrat-ThinItalic.ttf +0 -0
  75. package/assets/fonts/Montserrat-ThinItalic.woff +0 -0
  76. package/assets/fonts/Montserrat-ThinItalic.woff2 +0 -0
  77. package/assets/fonts/Montserrat-Variable.eot +0 -0
  78. package/assets/fonts/Montserrat-Variable.ttf +0 -0
  79. package/assets/fonts/Montserrat-Variable.woff +0 -0
  80. package/assets/fonts/Montserrat-Variable.woff2 +0 -0
  81. package/assets/fonts/Montserrat-VariableItalic.eot +0 -0
  82. package/assets/fonts/Montserrat-VariableItalic.ttf +0 -0
  83. package/assets/fonts/Montserrat-VariableItalic.woff +0 -0
  84. package/assets/fonts/Montserrat-VariableItalic.woff2 +0 -0
  85. package/assets/icons/arrow-left.svg +0 -1
  86. package/assets/icons/file.svg +0 -1
  87. package/assets/icons/globe.svg +0 -1
  88. package/assets/icons/logo-line.svg +0 -1
  89. package/assets/icons/next.svg +0 -1
  90. package/assets/icons/panel-left-expand.svg +0 -1
  91. package/assets/icons/placeholder.svg +0 -57
  92. package/assets/icons/vercel.svg +0 -1
  93. package/assets/icons/window.svg +0 -1
  94. package/assets/lotties/error-404.json +0 -19642
  95. package/assets/lotties/error.json +0 -2414
  96. package/assets/lotties/loader.json +0 -305
  97. package/components/Welcome.mdx +0 -74
  98. package/components/lenis/index.tsx +0 -48
  99. package/components/motion/auto-height.tsx +0 -56
  100. package/components/motion/cursor.tsx +0 -108
  101. package/components/motion/highlight.tsx +0 -605
  102. package/components/motion/number-ticker.tsx +0 -55
  103. package/components/motion/slot.tsx +0 -106
  104. package/components/motion/waves.tsx +0 -417
  105. package/components/primitives/tabs.tsx +0 -174
  106. package/components/ui/Accordion/index.stories.tsx +0 -39
  107. package/components/ui/Accordion/index.tsx +0 -170
  108. package/components/ui/Alert/index.stories.tsx +0 -39
  109. package/components/ui/Alert/index.tsx +0 -60
  110. package/components/ui/AlertDialog/index.stories.tsx +0 -47
  111. package/components/ui/AlertDialog/index.tsx +0 -172
  112. package/components/ui/AspectRatio/index.stories.tsx +0 -40
  113. package/components/ui/AspectRatio/index.tsx +0 -9
  114. package/components/ui/Avatar/index.stories.tsx +0 -39
  115. package/components/ui/Avatar/index.tsx +0 -44
  116. package/components/ui/Badge/index.stories.tsx +0 -64
  117. package/components/ui/Badge/index.tsx +0 -46
  118. package/components/ui/Breadcrumb/index.stories.tsx +0 -64
  119. package/components/ui/Breadcrumb/index.tsx +0 -102
  120. package/components/ui/Button/index.stories.tsx +0 -232
  121. package/components/ui/Button/index.tsx +0 -114
  122. package/components/ui/Calendar/index.stories.tsx +0 -20
  123. package/components/ui/Calendar/index.tsx +0 -149
  124. package/components/ui/Card/index.stories.tsx +0 -39
  125. package/components/ui/Card/index.tsx +0 -65
  126. package/components/ui/Carousel/index.stories.tsx +0 -37
  127. package/components/ui/Carousel/index.tsx +0 -242
  128. package/components/ui/Chart/index.stories.tsx +0 -53
  129. package/components/ui/Chart/index.tsx +0 -322
  130. package/components/ui/Checkbox/index.stories.tsx +0 -56
  131. package/components/ui/Checkbox/index.tsx +0 -167
  132. package/components/ui/CircleProcess/index.stories.tsx +0 -29
  133. package/components/ui/CircleProcess/index.tsx +0 -50
  134. package/components/ui/Collapsible/index.stories.tsx +0 -33
  135. package/components/ui/Collapsible/index.tsx +0 -124
  136. package/components/ui/Command/index.stories.tsx +0 -65
  137. package/components/ui/Command/index.tsx +0 -161
  138. package/components/ui/Container/index.stories.tsx +0 -22
  139. package/components/ui/Container/index.tsx +0 -30
  140. package/components/ui/ContextMenu/index.stories.tsx +0 -51
  141. package/components/ui/ContextMenu/index.tsx +0 -224
  142. package/components/ui/Dialog/index.stories.tsx +0 -44
  143. package/components/ui/Dialog/index.tsx +0 -156
  144. package/components/ui/Drawer/index.stories.tsx +0 -54
  145. package/components/ui/Drawer/index.tsx +0 -124
  146. package/components/ui/DropdownMenu/index.stories.tsx +0 -83
  147. package/components/ui/DropdownMenu/index.tsx +0 -231
  148. package/components/ui/Dropzone/index.stories.tsx +0 -18
  149. package/components/ui/Dropzone/index.tsx +0 -47
  150. package/components/ui/Form/date-field.tsx +0 -77
  151. package/components/ui/Form/index.stories.tsx +0 -67
  152. package/components/ui/Form/index.tsx +0 -193
  153. package/components/ui/Form/select-field.tsx +0 -55
  154. package/components/ui/Form/text-area-field.tsx +0 -37
  155. package/components/ui/Form/text-field.tsx +0 -72
  156. package/components/ui/HStack/index.stories.tsx +0 -48
  157. package/components/ui/HStack/index.tsx +0 -73
  158. package/components/ui/HoverCard/index.stories.tsx +0 -38
  159. package/components/ui/HoverCard/index.tsx +0 -38
  160. package/components/ui/Icons/index.stories.tsx +0 -27
  161. package/components/ui/Icons/index.tsx +0 -33
  162. package/components/ui/ImageWithFallback/index.stories.tsx +0 -32
  163. package/components/ui/ImageWithFallback/index.tsx +0 -34
  164. package/components/ui/Input/index.stories.tsx +0 -47
  165. package/components/ui/Input/index.tsx +0 -21
  166. package/components/ui/InputOtp/index.stories.tsx +0 -35
  167. package/components/ui/InputOtp/index.tsx +0 -70
  168. package/components/ui/Label/index.stories.tsx +0 -18
  169. package/components/ui/Label/index.tsx +0 -21
  170. package/components/ui/Marquee/index.stories.tsx +0 -71
  171. package/components/ui/Marquee/index.tsx +0 -65
  172. package/components/ui/Menubar/index.stories.tsx +0 -116
  173. package/components/ui/Menubar/index.tsx +0 -252
  174. package/components/ui/NavigationMenu/index.stories.tsx +0 -112
  175. package/components/ui/NavigationMenu/index.tsx +0 -185
  176. package/components/ui/NoData/index.stories.tsx +0 -24
  177. package/components/ui/NoData/index.tsx +0 -19
  178. package/components/ui/Pagination/index.stories.tsx +0 -53
  179. package/components/ui/Pagination/index.tsx +0 -114
  180. package/components/ui/Popover/index.stories.tsx +0 -31
  181. package/components/ui/Popover/index.tsx +0 -42
  182. package/components/ui/Progress/index.stories.tsx +0 -35
  183. package/components/ui/Progress/index.tsx +0 -28
  184. package/components/ui/RadioGroup/index.stories.tsx +0 -28
  185. package/components/ui/RadioGroup/index.tsx +0 -45
  186. package/components/ui/Resizable/index.stories.tsx +0 -44
  187. package/components/ui/Resizable/index.tsx +0 -54
  188. package/components/ui/ScrollArea/index.stories.tsx +0 -31
  189. package/components/ui/ScrollArea/index.tsx +0 -56
  190. package/components/ui/Select/index.stories.tsx +0 -64
  191. package/components/ui/Select/index.tsx +0 -170
  192. package/components/ui/Separator/index.stories.tsx +0 -31
  193. package/components/ui/Separator/index.tsx +0 -28
  194. package/components/ui/Sheet/index.stories.tsx +0 -45
  195. package/components/ui/Sheet/index.tsx +0 -130
  196. package/components/ui/Sidebar/index.stories.tsx +0 -82
  197. package/components/ui/Sidebar/index.tsx +0 -676
  198. package/components/ui/Skeleton/index.stories.tsx +0 -36
  199. package/components/ui/Skeleton/index.tsx +0 -13
  200. package/components/ui/Slider/index.stories.tsx +0 -48
  201. package/components/ui/Slider/index.tsx +0 -82
  202. package/components/ui/Slot/index.stories.tsx +0 -29
  203. package/components/ui/Slot/index.tsx +0 -106
  204. package/components/ui/Sonner/index.stories.tsx +0 -36
  205. package/components/ui/Sonner/index.tsx +0 -31
  206. package/components/ui/Switch/index.stories.tsx +0 -33
  207. package/components/ui/Switch/index.tsx +0 -28
  208. package/components/ui/Table/index.stories.tsx +0 -74
  209. package/components/ui/Table/index.tsx +0 -95
  210. package/components/ui/Tabs/index.stories.tsx +0 -38
  211. package/components/ui/Tabs/index.tsx +0 -78
  212. package/components/ui/Text/index.stories.tsx +0 -53
  213. package/components/ui/Text/index.tsx +0 -138
  214. package/components/ui/Textarea/index.stories.tsx +0 -25
  215. package/components/ui/Textarea/index.tsx +0 -18
  216. package/components/ui/Toggle/index.stories.tsx +0 -52
  217. package/components/ui/Toggle/index.tsx +0 -46
  218. package/components/ui/ToggleGroup/index.stories.tsx +0 -52
  219. package/components/ui/ToggleGroup/index.tsx +0 -69
  220. package/components/ui/Tooltip/index.stories.tsx +0 -29
  221. package/components/ui/Tooltip/index.tsx +0 -35
  222. package/components/ui/VStack/index.stories.tsx +0 -45
  223. package/components/ui/VStack/index.tsx +0 -69
  224. package/components/ui/colors.stories.tsx +0 -148
  225. package/eslint.config.js +0 -10
  226. package/hooks/index.ts +0 -3
  227. package/hooks/use-auto-height.tsx +0 -99
  228. package/hooks/use-controlled-state.tsx +0 -32
  229. package/hooks/use-mobile.ts +0 -19
  230. package/index.ts +0 -58
  231. package/lib/get-strict-context.ts +0 -15
  232. package/lib/utils.ts +0 -10
  233. package/scripts/generate-exports.ts +0 -32
  234. package/tsconfig.json +0 -12
  235. package/tsconfig.tsbuildinfo +0 -1
  236. package/tsup.config.ts +0 -11
  237. package/types/svg.d.ts +0 -10
  238. package/vercel.json +0 -5
@@ -1,605 +0,0 @@
1
- 'use client';
2
-
3
- import * as React from 'react';
4
- import { AnimatePresence, motion, Transition } from 'motion/react';
5
-
6
- import { cn } from '@/lib/utils';
7
-
8
- type HighlightMode = 'children' | 'parent';
9
-
10
- type Bounds = {
11
- top: number;
12
- left: number;
13
- width: number;
14
- height: number;
15
- };
16
-
17
- type HighlightContextType<T extends string> = {
18
- as?: keyof HTMLElementTagNameMap;
19
- mode: HighlightMode;
20
- activeValue: T | null;
21
- setActiveValue: (value: T | null) => void;
22
- setBounds: (bounds: DOMRect) => void;
23
- clearBounds: () => void;
24
- id: string;
25
- hover: boolean;
26
- click: boolean;
27
- className?: string;
28
- style?: React.CSSProperties;
29
- activeClassName?: string;
30
- setActiveClassName: (className: string) => void;
31
- transition?: Transition;
32
- disabled?: boolean;
33
- enabled?: boolean;
34
- exitDelay?: number;
35
- forceUpdateBounds?: boolean;
36
- };
37
-
38
- const HighlightContext = React.createContext<HighlightContextType<string> | undefined>(undefined);
39
-
40
- function useHighlight<T extends string>(): HighlightContextType<T> {
41
- const context = React.useContext(HighlightContext);
42
- if (!context) {
43
- throw new Error('useHighlight must be used within a HighlightProvider');
44
- }
45
- return context as unknown as HighlightContextType<T>;
46
- }
47
-
48
- type BaseHighlightProps<T extends React.ElementType = 'div'> = {
49
- as?: T;
50
- ref?: React.Ref<HTMLDivElement>;
51
- mode?: HighlightMode;
52
- value?: string | null;
53
- defaultValue?: string | null;
54
- onValueChange?: (value: string | null) => void;
55
- className?: string;
56
- style?: React.CSSProperties;
57
- transition?: Transition;
58
- hover?: boolean;
59
- click?: boolean;
60
- disabled?: boolean;
61
- enabled?: boolean;
62
- exitDelay?: number;
63
- };
64
-
65
- type ParentModeHighlightProps = {
66
- boundsOffset?: Partial<Bounds>;
67
- containerClassName?: string;
68
- forceUpdateBounds?: boolean;
69
- };
70
-
71
- type ControlledParentModeHighlightProps<T extends React.ElementType = 'div'> =
72
- BaseHighlightProps<T> &
73
- ParentModeHighlightProps & {
74
- mode: 'parent';
75
- controlledItems: true;
76
- children: React.ReactNode;
77
- };
78
-
79
- type ControlledChildrenModeHighlightProps<T extends React.ElementType = 'div'> =
80
- BaseHighlightProps<T> & {
81
- mode?: 'children' | undefined;
82
- controlledItems: true;
83
- children: React.ReactNode;
84
- };
85
-
86
- type UncontrolledParentModeHighlightProps<T extends React.ElementType = 'div'> =
87
- BaseHighlightProps<T> &
88
- ParentModeHighlightProps & {
89
- mode: 'parent';
90
- controlledItems?: false;
91
- itemsClassName?: string;
92
- children: React.ReactElement | React.ReactElement[];
93
- };
94
-
95
- type UncontrolledChildrenModeHighlightProps<T extends React.ElementType = 'div'> =
96
- BaseHighlightProps<T> & {
97
- mode?: 'children';
98
- controlledItems?: false;
99
- itemsClassName?: string;
100
- children: React.ReactElement | React.ReactElement[];
101
- };
102
-
103
- type HighlightProps<T extends React.ElementType = 'div'> =
104
- | ControlledParentModeHighlightProps<T>
105
- | ControlledChildrenModeHighlightProps<T>
106
- | UncontrolledParentModeHighlightProps<T>
107
- | UncontrolledChildrenModeHighlightProps<T>;
108
-
109
- function Highlight<T extends React.ElementType = 'div'>({ ref, ...props }: HighlightProps<T>) {
110
- const {
111
- as: Component = 'div',
112
- children,
113
- value,
114
- defaultValue,
115
- onValueChange,
116
- className,
117
- style,
118
- transition = { type: 'spring', stiffness: 350, damping: 35 },
119
- hover = false,
120
- click = true,
121
- enabled = true,
122
- controlledItems,
123
- disabled = false,
124
- exitDelay = 200,
125
- mode = 'children',
126
- } = props;
127
-
128
- const localRef = React.useRef<HTMLDivElement>(null);
129
- React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
130
-
131
- const [activeValue, setActiveValue] = React.useState<string | null>(
132
- value ?? defaultValue ?? null,
133
- );
134
- const [boundsState, setBoundsState] = React.useState<Bounds | null>(null);
135
- const [activeClassNameState, setActiveClassNameState] = React.useState<string>('');
136
-
137
- const onValueChangeRef = React.useRef(onValueChange);
138
- React.useEffect(() => {
139
- onValueChangeRef.current = onValueChange;
140
- }, [onValueChange]);
141
-
142
- const activeValueRef = React.useRef(activeValue);
143
- React.useEffect(() => {
144
- activeValueRef.current = activeValue;
145
- }, [activeValue]);
146
-
147
- const safeSetActiveValue = (id: string | null) => {
148
- setActiveValue((prev) => {
149
- if (prev === id) return prev;
150
- if (id !== activeValueRef.current) {
151
- onValueChangeRef.current?.(id);
152
- }
153
- return id;
154
- });
155
- };
156
-
157
- const boundsOffsetRef = React.useRef(
158
- (props as ParentModeHighlightProps)?.boundsOffset ?? {
159
- top: 0,
160
- left: 0,
161
- width: 0,
162
- height: 0,
163
- },
164
- );
165
- const boundsOffset = (props as ParentModeHighlightProps)?.boundsOffset;
166
- React.useEffect(() => {
167
- boundsOffsetRef.current = boundsOffset ?? {
168
- top: 0,
169
- left: 0,
170
- width: 0,
171
- height: 0,
172
- };
173
- }, [boundsOffset]);
174
-
175
- const safeSetBoundsRef = React.useRef<(bounds: DOMRect) => void>(() => {});
176
- const safeSetBounds = (bounds: DOMRect) => {
177
- if (!localRef.current) return;
178
-
179
- const boundsOffset = boundsOffsetRef.current;
180
- const containerRect = localRef.current.getBoundingClientRect();
181
- const newBounds: Bounds = {
182
- top: bounds.top - containerRect.top + (boundsOffset.top ?? 0),
183
- left: bounds.left - containerRect.left + (boundsOffset.left ?? 0),
184
- width: bounds.width + (boundsOffset.width ?? 0),
185
- height: bounds.height + (boundsOffset.height ?? 0),
186
- };
187
-
188
- setBoundsState((prev) => {
189
- if (
190
- prev &&
191
- prev.top === newBounds.top &&
192
- prev.left === newBounds.left &&
193
- prev.width === newBounds.width &&
194
- prev.height === newBounds.height
195
- ) {
196
- return prev;
197
- }
198
- return newBounds;
199
- });
200
- };
201
- React.useEffect(() => {
202
- safeSetBoundsRef.current = safeSetBounds;
203
- });
204
-
205
- const clearBounds = () => {
206
- setBoundsState((prev) => (prev === null ? prev : null));
207
- };
208
-
209
- React.useEffect(() => {
210
- if (value !== undefined) setActiveValue(value);
211
- else if (defaultValue !== undefined) setActiveValue(defaultValue);
212
- }, [value, defaultValue]);
213
-
214
- const id = React.useId();
215
-
216
- React.useEffect(() => {
217
- if (mode !== 'parent') return undefined;
218
- const container = localRef.current;
219
- if (!container) return;
220
-
221
- const onScroll = () => {
222
- if (!activeValue) return;
223
- const activeEl = container.querySelector<HTMLElement>(
224
- `[data-value="${activeValue}"][data-highlight="true"]`,
225
- );
226
- if (activeEl) safeSetBoundsRef.current?.(activeEl.getBoundingClientRect());
227
- };
228
-
229
- container.addEventListener('scroll', onScroll, { passive: true });
230
- return () => container.removeEventListener('scroll', onScroll);
231
- }, [mode, activeValue]);
232
-
233
- const containerClassName = (props as ParentModeHighlightProps)?.containerClassName;
234
-
235
- const render = (children: React.ReactNode) => {
236
- if (mode === 'parent') {
237
- return (
238
- <Component
239
- ref={localRef}
240
- data-slot="motion-highlight-container"
241
- style={{ position: 'relative', zIndex: 1 }}
242
- className={containerClassName}
243
- >
244
- <AnimatePresence initial={false} mode="wait">
245
- {boundsState && (
246
- <motion.div
247
- data-slot="motion-highlight"
248
- animate={{
249
- top: boundsState.top,
250
- left: boundsState.left,
251
- width: boundsState.width,
252
- height: boundsState.height,
253
- opacity: 1,
254
- }}
255
- initial={{
256
- top: boundsState.top,
257
- left: boundsState.left,
258
- width: boundsState.width,
259
- height: boundsState.height,
260
- opacity: 0,
261
- }}
262
- exit={{
263
- opacity: 0,
264
- transition: {
265
- ...transition,
266
- delay: (transition?.delay ?? 0) + (exitDelay ?? 0) / 1000,
267
- },
268
- }}
269
- transition={transition}
270
- style={{ position: 'absolute', zIndex: 0, ...style }}
271
- className={cn(className, activeClassNameState)}
272
- />
273
- )}
274
- </AnimatePresence>
275
- {children}
276
- </Component>
277
- );
278
- }
279
-
280
- return children;
281
- };
282
-
283
- return (
284
- <HighlightContext.Provider
285
- value={{
286
- mode,
287
- activeValue,
288
- setActiveValue: safeSetActiveValue,
289
- id,
290
- hover,
291
- click,
292
- className,
293
- style,
294
- transition,
295
- disabled,
296
- enabled,
297
- exitDelay,
298
- setBounds: safeSetBounds,
299
- clearBounds,
300
- activeClassName: activeClassNameState,
301
- setActiveClassName: setActiveClassNameState,
302
- forceUpdateBounds: (props as ParentModeHighlightProps)?.forceUpdateBounds,
303
- }}
304
- >
305
- {enabled
306
- ? controlledItems
307
- ? render(children)
308
- : render(
309
- React.Children.map(children, (child, index) => (
310
- <HighlightItem key={index} className={props?.itemsClassName}>
311
- {child}
312
- </HighlightItem>
313
- )),
314
- )
315
- : children}
316
- </HighlightContext.Provider>
317
- );
318
- }
319
-
320
- function getNonOverridingDataAttributes(
321
- element: React.ReactElement,
322
- dataAttributes: Record<string, unknown>,
323
- ): Record<string, unknown> {
324
- return Object.keys(dataAttributes).reduce<Record<string, unknown>>((acc, key) => {
325
- if ((element.props as Record<string, unknown>)[key] === undefined) {
326
- acc[key] = dataAttributes[key];
327
- }
328
- return acc;
329
- }, {});
330
- }
331
-
332
- type ExtendedChildProps = React.ComponentProps<'div'> & {
333
- id?: string;
334
- ref?: React.Ref<HTMLElement>;
335
- 'data-active'?: string;
336
- 'data-value'?: string;
337
- 'data-disabled'?: boolean;
338
- 'data-highlight'?: boolean;
339
- 'data-slot'?: string;
340
- };
341
-
342
- type HighlightItemProps<T extends React.ElementType = 'div'> = React.ComponentProps<T> & {
343
- as?: T;
344
- children: React.ReactElement;
345
- id?: string;
346
- value?: string;
347
- className?: string;
348
- style?: React.CSSProperties;
349
- transition?: Transition;
350
- activeClassName?: string;
351
- disabled?: boolean;
352
- exitDelay?: number;
353
- asChild?: boolean;
354
- forceUpdateBounds?: boolean;
355
- };
356
-
357
- function HighlightItem<T extends React.ElementType>({
358
- ref,
359
- as,
360
- children,
361
- id,
362
- value,
363
- className,
364
- style,
365
- transition,
366
- disabled = false,
367
- activeClassName,
368
- exitDelay,
369
- asChild = false,
370
- forceUpdateBounds,
371
- ...props
372
- }: HighlightItemProps<T>) {
373
- const itemId = React.useId();
374
- const {
375
- activeValue,
376
- setActiveValue,
377
- mode,
378
- setBounds,
379
- clearBounds,
380
- hover,
381
- click,
382
- enabled,
383
- className: contextClassName,
384
- style: contextStyle,
385
- transition: contextTransition,
386
- id: contextId,
387
- disabled: contextDisabled,
388
- exitDelay: contextExitDelay,
389
- forceUpdateBounds: contextForceUpdateBounds,
390
- setActiveClassName,
391
- } = useHighlight();
392
-
393
- const Component = as ?? 'div';
394
- const element = children as React.ReactElement<ExtendedChildProps>;
395
- const childValue = id ?? value ?? element.props?.['data-value'] ?? element.props?.id ?? itemId;
396
- const isActive = activeValue === childValue;
397
- const isDisabled = disabled === undefined ? contextDisabled : disabled;
398
- const itemTransition = transition ?? contextTransition;
399
-
400
- const localRef = React.useRef<HTMLElement | null>(null);
401
- React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);
402
-
403
- const setRef = React.useCallback((node: HTMLElement | null) => {
404
- localRef.current = node;
405
- }, []);
406
-
407
- React.useEffect(() => {
408
- if (mode !== 'parent') return;
409
- let rafId: number;
410
- let previousBounds: Bounds | null = null;
411
- const shouldUpdateBounds =
412
- forceUpdateBounds === true || (contextForceUpdateBounds && forceUpdateBounds !== false);
413
-
414
- const updateBounds = () => {
415
- if (!localRef.current) return;
416
-
417
- const bounds = localRef.current.getBoundingClientRect();
418
-
419
- if (shouldUpdateBounds) {
420
- if (
421
- previousBounds &&
422
- previousBounds.top === bounds.top &&
423
- previousBounds.left === bounds.left &&
424
- previousBounds.width === bounds.width &&
425
- previousBounds.height === bounds.height
426
- ) {
427
- rafId = requestAnimationFrame(updateBounds);
428
- return;
429
- }
430
- previousBounds = bounds;
431
- rafId = requestAnimationFrame(updateBounds);
432
- }
433
-
434
- setBounds(bounds);
435
- };
436
-
437
- if (isActive) {
438
- updateBounds();
439
- setActiveClassName(activeClassName ?? '');
440
- } else if (!activeValue) clearBounds();
441
-
442
- if (shouldUpdateBounds) return () => cancelAnimationFrame(rafId);
443
- return undefined;
444
- }, [
445
- mode,
446
- isActive,
447
- activeValue,
448
- setBounds,
449
- clearBounds,
450
- activeClassName,
451
- setActiveClassName,
452
- forceUpdateBounds,
453
- contextForceUpdateBounds,
454
- ]);
455
-
456
- if (!React.isValidElement(children)) return children;
457
-
458
- const dataAttributes = {
459
- 'data-active': isActive ? 'true' : 'false',
460
- 'aria-selected': isActive,
461
- 'data-disabled': isDisabled,
462
- 'data-value': childValue,
463
- 'data-highlight': true,
464
- };
465
-
466
- const commonHandlers = hover
467
- ? {
468
- onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {
469
- setActiveValue(childValue);
470
- element.props.onMouseEnter?.(e);
471
- },
472
- onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {
473
- setActiveValue(null);
474
- element.props.onMouseLeave?.(e);
475
- },
476
- }
477
- : click
478
- ? {
479
- onClick: (e: React.MouseEvent<HTMLDivElement>) => {
480
- setActiveValue(childValue);
481
- element.props.onClick?.(e);
482
- },
483
- }
484
- : {};
485
-
486
- if (asChild) {
487
- if (mode === 'children') {
488
- return React.cloneElement(
489
- element,
490
- {
491
- key: childValue,
492
- ref: setRef,
493
- className: cn('relative', element.props.className),
494
- ...getNonOverridingDataAttributes(element, {
495
- ...dataAttributes,
496
- 'data-slot': 'motion-highlight-item-container',
497
- }),
498
- ...commonHandlers,
499
- ...props,
500
- },
501
- <>
502
- <AnimatePresence initial={false} mode="wait">
503
- {isActive && !isDisabled && (
504
- <motion.div
505
- layoutId={`transition-background-${contextId}`}
506
- data-slot="motion-highlight"
507
- style={{
508
- position: 'absolute',
509
- zIndex: 0,
510
- ...contextStyle,
511
- ...style,
512
- }}
513
- className={cn(contextClassName, activeClassName)}
514
- transition={itemTransition}
515
- initial={{ opacity: 0 }}
516
- animate={{ opacity: 1 }}
517
- exit={{
518
- opacity: 0,
519
- transition: {
520
- ...itemTransition,
521
- delay:
522
- (itemTransition?.delay ?? 0) + (exitDelay ?? contextExitDelay ?? 0) / 1000,
523
- },
524
- }}
525
- {...dataAttributes}
526
- />
527
- )}
528
- </AnimatePresence>
529
-
530
- <Component
531
- data-slot="motion-highlight-item"
532
- style={{ position: 'relative', zIndex: 1 }}
533
- className={className}
534
- {...dataAttributes}
535
- >
536
- {children}
537
- </Component>
538
- </>,
539
- );
540
- }
541
-
542
- return React.cloneElement(element, {
543
- ref: setRef,
544
- ...getNonOverridingDataAttributes(element, {
545
- ...dataAttributes,
546
- 'data-slot': 'motion-highlight-item',
547
- }),
548
- ...commonHandlers,
549
- });
550
- }
551
-
552
- return enabled ? (
553
- <Component
554
- key={childValue}
555
- ref={localRef}
556
- data-slot="motion-highlight-item-container"
557
- className={cn(mode === 'children' && 'relative', className)}
558
- {...dataAttributes}
559
- {...props}
560
- {...commonHandlers}
561
- >
562
- {mode === 'children' && (
563
- <AnimatePresence initial={false} mode="wait">
564
- {isActive && !isDisabled && (
565
- <motion.div
566
- layoutId={`transition-background-${contextId}`}
567
- data-slot="motion-highlight"
568
- style={{
569
- position: 'absolute',
570
- zIndex: 0,
571
- ...contextStyle,
572
- ...style,
573
- }}
574
- className={cn(contextClassName, activeClassName)}
575
- transition={itemTransition}
576
- initial={{ opacity: 0 }}
577
- animate={{ opacity: 1 }}
578
- exit={{
579
- opacity: 0,
580
- transition: {
581
- ...itemTransition,
582
- delay: (itemTransition?.delay ?? 0) + (exitDelay ?? contextExitDelay ?? 0) / 1000,
583
- },
584
- }}
585
- {...dataAttributes}
586
- />
587
- )}
588
- </AnimatePresence>
589
- )}
590
-
591
- {React.cloneElement(element, {
592
- style: { position: 'relative', zIndex: 1 },
593
- className: element.props.className,
594
- ...getNonOverridingDataAttributes(element, {
595
- ...dataAttributes,
596
- 'data-slot': 'motion-highlight-item',
597
- }),
598
- })}
599
- </Component>
600
- ) : (
601
- children
602
- );
603
- }
604
-
605
- export { Highlight, HighlightItem, useHighlight, type HighlightProps, type HighlightItemProps };
@@ -1,55 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useRef } from 'react';
4
- import { useInView, useMotionValue, useSpring } from 'motion/react';
5
-
6
- import { cn } from '@/lib/utils';
7
-
8
- export default function NumberTicker({
9
- value,
10
- direction = 'up',
11
- delay = 0,
12
- subValue = '',
13
- preValue = '',
14
- className,
15
- }: {
16
- value: number;
17
- direction?: 'up' | 'down';
18
- className?: string;
19
- delay?: number; // delay in s
20
- subValue?: string;
21
- preValue?: string;
22
- }) {
23
- const ref = useRef<HTMLSpanElement>(null);
24
- const motionValue = useMotionValue(direction === 'down' ? value : 0);
25
- const springValue = useSpring(motionValue, {
26
- damping: 60,
27
- stiffness: 100,
28
- });
29
- const isInView = useInView(ref, { once: true, margin: '0px' });
30
-
31
- useEffect(() => {
32
- if (isInView) {
33
- setTimeout(() => {
34
- motionValue.set(direction === 'down' ? 0 : value);
35
- }, delay * 1000);
36
- }
37
- }, [motionValue, isInView, delay, value, direction]);
38
-
39
- useEffect(() => {
40
- const updateText = (val: number) => {
41
- if (ref.current) {
42
- ref.current.textContent = `${preValue}${Intl.NumberFormat('en-US').format(Math.round(val))}${subValue}`;
43
- }
44
- };
45
-
46
- const initialValue = direction === 'down' ? value : 0;
47
- updateText(initialValue);
48
-
49
- return springValue.on('change', (latest: number) => {
50
- updateText(latest);
51
- });
52
- }, [preValue, springValue, subValue, value, direction]);
53
-
54
- return <span className={cn('text-primary-50 inline-block tabular-nums', className)} ref={ref} />;
55
- }