@luanthnh/cntt-ui 0.1.5

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 (255) hide show
  1. package/.storybook/globals.d.ts +1 -0
  2. package/.storybook/main.ts +29 -0
  3. package/.storybook/preview.ts +32 -0
  4. package/README.md +86 -0
  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 +1 -0
  86. package/assets/icons/file.svg +1 -0
  87. package/assets/icons/globe.svg +1 -0
  88. package/assets/icons/logo-line.svg +1 -0
  89. package/assets/icons/next.svg +1 -0
  90. package/assets/icons/panel-left-expand.svg +1 -0
  91. package/assets/icons/placeholder.svg +57 -0
  92. package/assets/icons/vercel.svg +1 -0
  93. package/assets/icons/window.svg +1 -0
  94. package/assets/lotties/error-404.json +19642 -0
  95. package/assets/lotties/error.json +2414 -0
  96. package/assets/lotties/loader.json +305 -0
  97. package/components/Welcome.mdx +74 -0
  98. package/components/lenis/index.tsx +48 -0
  99. package/components/motion/auto-height.tsx +56 -0
  100. package/components/motion/cursor.tsx +108 -0
  101. package/components/motion/highlight.tsx +605 -0
  102. package/components/motion/number-ticker.tsx +55 -0
  103. package/components/motion/slot.tsx +106 -0
  104. package/components/motion/waves.tsx +417 -0
  105. package/components/primitives/tabs.tsx +174 -0
  106. package/components/ui/Accordion/index.stories.tsx +39 -0
  107. package/components/ui/Accordion/index.tsx +170 -0
  108. package/components/ui/Alert/index.stories.tsx +39 -0
  109. package/components/ui/Alert/index.tsx +60 -0
  110. package/components/ui/AlertDialog/index.stories.tsx +47 -0
  111. package/components/ui/AlertDialog/index.tsx +172 -0
  112. package/components/ui/AspectRatio/index.stories.tsx +40 -0
  113. package/components/ui/AspectRatio/index.tsx +9 -0
  114. package/components/ui/Avatar/index.stories.tsx +39 -0
  115. package/components/ui/Avatar/index.tsx +44 -0
  116. package/components/ui/Badge/index.stories.tsx +64 -0
  117. package/components/ui/Badge/index.tsx +46 -0
  118. package/components/ui/Breadcrumb/index.stories.tsx +64 -0
  119. package/components/ui/Breadcrumb/index.tsx +102 -0
  120. package/components/ui/Button/index.stories.tsx +232 -0
  121. package/components/ui/Button/index.tsx +114 -0
  122. package/components/ui/Calendar/index.stories.tsx +20 -0
  123. package/components/ui/Calendar/index.tsx +149 -0
  124. package/components/ui/Card/index.stories.tsx +39 -0
  125. package/components/ui/Card/index.tsx +65 -0
  126. package/components/ui/Carousel/index.stories.tsx +37 -0
  127. package/components/ui/Carousel/index.tsx +242 -0
  128. package/components/ui/Chart/index.stories.tsx +53 -0
  129. package/components/ui/Chart/index.tsx +322 -0
  130. package/components/ui/Checkbox/index.stories.tsx +56 -0
  131. package/components/ui/Checkbox/index.tsx +167 -0
  132. package/components/ui/CircleProcess/index.stories.tsx +29 -0
  133. package/components/ui/CircleProcess/index.tsx +50 -0
  134. package/components/ui/Collapsible/index.stories.tsx +33 -0
  135. package/components/ui/Collapsible/index.tsx +124 -0
  136. package/components/ui/Command/index.stories.tsx +65 -0
  137. package/components/ui/Command/index.tsx +161 -0
  138. package/components/ui/Container/index.stories.tsx +22 -0
  139. package/components/ui/Container/index.tsx +30 -0
  140. package/components/ui/ContextMenu/index.stories.tsx +51 -0
  141. package/components/ui/ContextMenu/index.tsx +224 -0
  142. package/components/ui/Dialog/index.stories.tsx +44 -0
  143. package/components/ui/Dialog/index.tsx +156 -0
  144. package/components/ui/Drawer/index.stories.tsx +54 -0
  145. package/components/ui/Drawer/index.tsx +124 -0
  146. package/components/ui/DropdownMenu/index.stories.tsx +83 -0
  147. package/components/ui/DropdownMenu/index.tsx +231 -0
  148. package/components/ui/Dropzone/index.stories.tsx +18 -0
  149. package/components/ui/Dropzone/index.tsx +47 -0
  150. package/components/ui/Form/date-field.tsx +77 -0
  151. package/components/ui/Form/index.stories.tsx +67 -0
  152. package/components/ui/Form/index.tsx +188 -0
  153. package/components/ui/Form/select-field.tsx +55 -0
  154. package/components/ui/Form/text-area-field.tsx +37 -0
  155. package/components/ui/Form/text-field.tsx +72 -0
  156. package/components/ui/HStack/index.stories.tsx +48 -0
  157. package/components/ui/HStack/index.tsx +73 -0
  158. package/components/ui/HoverCard/index.stories.tsx +38 -0
  159. package/components/ui/HoverCard/index.tsx +38 -0
  160. package/components/ui/Icons/index.stories.tsx +27 -0
  161. package/components/ui/Icons/index.tsx +33 -0
  162. package/components/ui/ImageWithFallback/index.stories.tsx +32 -0
  163. package/components/ui/ImageWithFallback/index.tsx +34 -0
  164. package/components/ui/Input/index.stories.tsx +47 -0
  165. package/components/ui/Input/index.tsx +21 -0
  166. package/components/ui/InputOtp/index.stories.tsx +35 -0
  167. package/components/ui/InputOtp/index.tsx +70 -0
  168. package/components/ui/Label/index.stories.tsx +18 -0
  169. package/components/ui/Label/index.tsx +21 -0
  170. package/components/ui/Marquee/index.stories.tsx +71 -0
  171. package/components/ui/Marquee/index.tsx +65 -0
  172. package/components/ui/Menubar/index.stories.tsx +116 -0
  173. package/components/ui/Menubar/index.tsx +252 -0
  174. package/components/ui/NavigationMenu/index.stories.tsx +112 -0
  175. package/components/ui/NavigationMenu/index.tsx +185 -0
  176. package/components/ui/NoData/index.stories.tsx +24 -0
  177. package/components/ui/NoData/index.tsx +19 -0
  178. package/components/ui/Pagination/index.stories.tsx +53 -0
  179. package/components/ui/Pagination/index.tsx +114 -0
  180. package/components/ui/Popover/index.stories.tsx +31 -0
  181. package/components/ui/Popover/index.tsx +42 -0
  182. package/components/ui/Progress/index.stories.tsx +35 -0
  183. package/components/ui/Progress/index.tsx +28 -0
  184. package/components/ui/RadioGroup/index.stories.tsx +28 -0
  185. package/components/ui/RadioGroup/index.tsx +45 -0
  186. package/components/ui/Resizable/index.stories.tsx +44 -0
  187. package/components/ui/Resizable/index.tsx +54 -0
  188. package/components/ui/ScrollArea/index.stories.tsx +31 -0
  189. package/components/ui/ScrollArea/index.tsx +56 -0
  190. package/components/ui/Select/index.stories.tsx +64 -0
  191. package/components/ui/Select/index.tsx +170 -0
  192. package/components/ui/Separator/index.stories.tsx +31 -0
  193. package/components/ui/Separator/index.tsx +28 -0
  194. package/components/ui/Sheet/index.stories.tsx +45 -0
  195. package/components/ui/Sheet/index.tsx +130 -0
  196. package/components/ui/Sidebar/index.stories.tsx +82 -0
  197. package/components/ui/Sidebar/index.tsx +676 -0
  198. package/components/ui/Skeleton/index.stories.tsx +36 -0
  199. package/components/ui/Skeleton/index.tsx +13 -0
  200. package/components/ui/Slider/index.stories.tsx +48 -0
  201. package/components/ui/Slider/index.tsx +82 -0
  202. package/components/ui/Slot/index.stories.tsx +29 -0
  203. package/components/ui/Slot/index.tsx +106 -0
  204. package/components/ui/Sonner/index.stories.tsx +36 -0
  205. package/components/ui/Sonner/index.tsx +31 -0
  206. package/components/ui/Switch/index.stories.tsx +33 -0
  207. package/components/ui/Switch/index.tsx +28 -0
  208. package/components/ui/Table/index.stories.tsx +74 -0
  209. package/components/ui/Table/index.tsx +95 -0
  210. package/components/ui/Tabs/index.stories.tsx +38 -0
  211. package/components/ui/Tabs/index.tsx +78 -0
  212. package/components/ui/Text/index.stories.tsx +53 -0
  213. package/components/ui/Text/index.tsx +138 -0
  214. package/components/ui/Textarea/index.stories.tsx +25 -0
  215. package/components/ui/Textarea/index.tsx +18 -0
  216. package/components/ui/Toggle/index.stories.tsx +52 -0
  217. package/components/ui/Toggle/index.tsx +46 -0
  218. package/components/ui/ToggleGroup/index.stories.tsx +52 -0
  219. package/components/ui/ToggleGroup/index.tsx +69 -0
  220. package/components/ui/Tooltip/index.stories.tsx +29 -0
  221. package/components/ui/Tooltip/index.tsx +35 -0
  222. package/components/ui/VStack/index.stories.tsx +45 -0
  223. package/components/ui/VStack/index.tsx +69 -0
  224. package/components/ui/colors.stories.tsx +148 -0
  225. package/dist/arrow-left-46B4CAEY.svg +1 -0
  226. package/dist/file-4IXBJF4J.svg +1 -0
  227. package/dist/globe-KVAXBN2U.svg +1 -0
  228. package/dist/index.cjs +6001 -0
  229. package/dist/index.cjs.map +1 -0
  230. package/dist/index.d.cts +693 -0
  231. package/dist/index.d.ts +693 -0
  232. package/dist/index.js +5714 -0
  233. package/dist/index.js.map +1 -0
  234. package/dist/logo-line-QLUD5DAV.svg +1 -0
  235. package/dist/next-HOXZBJQP.svg +1 -0
  236. package/dist/panel-left-expand-SIPFBG4J.svg +1 -0
  237. package/dist/placeholder-H3V4XYVI.svg +57 -0
  238. package/dist/vercel-KFYFHF3A.svg +1 -0
  239. package/dist/window-JNUL4Q2E.svg +1 -0
  240. package/eslint.config.js +10 -0
  241. package/globals.css +994 -0
  242. package/hooks/index.ts +3 -0
  243. package/hooks/use-auto-height.tsx +99 -0
  244. package/hooks/use-controlled-state.tsx +32 -0
  245. package/hooks/use-mobile.ts +19 -0
  246. package/index.ts +58 -0
  247. package/lib/get-strict-context.ts +15 -0
  248. package/lib/utils.ts +10 -0
  249. package/package.json +107 -0
  250. package/scripts/generate-exports.ts +32 -0
  251. package/tsconfig.json +12 -0
  252. package/tsconfig.tsbuildinfo +1 -0
  253. package/tsup.config.ts +11 -0
  254. package/types/svg.d.ts +10 -0
  255. package/vercel.json +5 -0
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { isMotionComponent, motion, type HTMLMotionProps } from 'motion/react';
5
+
6
+ import { cn } from '@/lib/utils';
7
+
8
+ type AnyProps = Record<string, unknown>;
9
+
10
+ type DOMMotionProps<T extends HTMLElement = HTMLElement> = Omit<
11
+ HTMLMotionProps<keyof HTMLElementTagNameMap>,
12
+ 'ref'
13
+ > & {
14
+ ref?: React.Ref<T>;
15
+ };
16
+
17
+ type WithAsChild<Base extends object> =
18
+ | (Base & { asChild: true; children: React.ReactElement })
19
+ | (Base & { asChild?: false | undefined });
20
+
21
+ type SlotProps<T extends HTMLElement = HTMLElement> = {
22
+ children?: React.ReactElement;
23
+ } & DOMMotionProps<T>;
24
+
25
+ function mergeRefs<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {
26
+ return (node) => {
27
+ refs.forEach((ref) => {
28
+ if (!ref) return;
29
+ if (typeof ref === 'function') {
30
+ ref(node);
31
+ } else {
32
+ (ref as React.RefObject<T | null>).current = node;
33
+ }
34
+ });
35
+ };
36
+ }
37
+
38
+ const motionComponentCache = new Map<React.ElementType, React.ElementType>();
39
+
40
+ function getMotionComponent(Component: React.ElementType): React.ElementType {
41
+ if (!motionComponentCache.has(Component)) {
42
+ motionComponentCache.set(Component, motion.create(Component));
43
+ }
44
+ return motionComponentCache.get(Component)!;
45
+ }
46
+
47
+ function mergeProps<T extends HTMLElement>(
48
+ childProps: AnyProps,
49
+ slotProps: DOMMotionProps<T>,
50
+ ): AnyProps {
51
+ const merged: AnyProps = { ...childProps, ...slotProps };
52
+
53
+ if (childProps.className || slotProps.className) {
54
+ merged.className = cn(childProps.className as string, slotProps.className as string);
55
+ }
56
+
57
+ if (childProps.style || slotProps.style) {
58
+ merged.style = {
59
+ ...(childProps.style as React.CSSProperties),
60
+ ...(slotProps.style as React.CSSProperties),
61
+ };
62
+ }
63
+
64
+ return merged;
65
+ }
66
+
67
+ function Slot<T extends HTMLElement = HTMLElement>({ children, ...props }: SlotProps<T>) {
68
+ const isValidElement = React.isValidElement(children);
69
+ const childType = isValidElement ? children.type : null;
70
+ const isAlreadyMotion =
71
+ isValidElement &&
72
+ childType !== null &&
73
+ typeof childType === 'object' &&
74
+ isMotionComponent(childType);
75
+
76
+ const Base = React.useMemo(() => {
77
+ if (!isValidElement || !childType) return null;
78
+ return isAlreadyMotion
79
+ ? (childType as React.ElementType)
80
+ : getMotionComponent(childType as React.ElementType);
81
+ }, [isValidElement, isAlreadyMotion, childType]);
82
+
83
+ const childRef = isValidElement && children ? (children.props as AnyProps).ref : undefined;
84
+ const childProps =
85
+ isValidElement && children
86
+ ? (({ ref: _ref, ...rest }) => rest)(children.props as AnyProps)
87
+ : {};
88
+
89
+ const mergedRef = React.useCallback(
90
+ (node: T | null) => {
91
+ mergeRefs(childRef as React.Ref<T>, props.ref)(node);
92
+ },
93
+ [childRef, props.ref],
94
+ );
95
+
96
+ if (!isValidElement || !Base || !children) return null;
97
+
98
+ const mergedProps = mergeProps(childProps, props);
99
+
100
+ return React.createElement(Base, {
101
+ ...mergedProps,
102
+ ref: mergedRef,
103
+ });
104
+ }
105
+
106
+ export { Slot, type SlotProps, type WithAsChild, type DOMMotionProps, type AnyProps };
@@ -0,0 +1,417 @@
1
+ import React, { CSSProperties, useEffect, useRef } from 'react';
2
+
3
+ const RANDOM_SEED = Math.random();
4
+
5
+ class Grad {
6
+ x: number;
7
+ y: number;
8
+ z: number;
9
+ constructor(x: number, y: number, z: number) {
10
+ this.x = x;
11
+ this.y = y;
12
+ this.z = z;
13
+ }
14
+ dot2(x: number, y: number): number {
15
+ return this.x * x + this.y * y;
16
+ }
17
+ }
18
+
19
+ class Noise {
20
+ grad3: Grad[];
21
+ p: number[];
22
+ perm: number[];
23
+ gradP: Grad[];
24
+
25
+ constructor(seed = 0) {
26
+ this.grad3 = [
27
+ new Grad(1, 1, 0),
28
+ new Grad(-1, 1, 0),
29
+ new Grad(1, -1, 0),
30
+ new Grad(-1, -1, 0),
31
+ new Grad(1, 0, 1),
32
+ new Grad(-1, 0, 1),
33
+ new Grad(1, 0, -1),
34
+ new Grad(-1, 0, -1),
35
+ new Grad(0, 1, 1),
36
+ new Grad(0, -1, 1),
37
+ new Grad(0, 1, -1),
38
+ new Grad(0, -1, -1),
39
+ ];
40
+ this.p = [
41
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69,
42
+ 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219,
43
+ 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
44
+ 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230,
45
+ 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209,
46
+ 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198,
47
+ 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
48
+ 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44,
49
+ 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79,
50
+ 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12,
51
+ 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
52
+ 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29,
53
+ 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180,
54
+ ];
55
+ this.perm = new Array(512);
56
+ this.gradP = new Array(512);
57
+ this.seed(seed);
58
+ }
59
+ seed(seed: number) {
60
+ if (seed > 0 && seed < 1) seed *= 65536;
61
+ seed = Math.floor(seed);
62
+ if (seed < 256) seed |= seed << 8;
63
+ for (let i = 0; i < 256; i++) {
64
+ const v = i & 1 ? this.p[i] ^ (seed & 255) : this.p[i] ^ ((seed >> 8) & 255);
65
+ this.perm[i] = this.perm[i + 256] = v;
66
+ this.gradP[i] = this.gradP[i + 256] = this.grad3[v % 12];
67
+ }
68
+ }
69
+ fade(t: number): number {
70
+ return t * t * t * (t * (t * 6 - 15) + 10);
71
+ }
72
+ lerp(a: number, b: number, t: number): number {
73
+ return (1 - t) * a + t * b;
74
+ }
75
+ perlin2(x: number, y: number): number {
76
+ let X = Math.floor(x),
77
+ Y = Math.floor(y);
78
+ x -= X;
79
+ y -= Y;
80
+ X &= 255;
81
+ Y &= 255;
82
+ const n00 = this.gradP[X + this.perm[Y]].dot2(x, y);
83
+ const n01 = this.gradP[X + this.perm[Y + 1]].dot2(x, y - 1);
84
+ const n10 = this.gradP[X + 1 + this.perm[Y]].dot2(x - 1, y);
85
+ const n11 = this.gradP[X + 1 + this.perm[Y + 1]].dot2(x - 1, y - 1);
86
+ const u = this.fade(x);
87
+ return this.lerp(this.lerp(n00, n10, u), this.lerp(n01, n11, u), this.fade(y));
88
+ }
89
+ }
90
+
91
+ interface Point {
92
+ x: number;
93
+ y: number;
94
+ wave: { x: number; y: number };
95
+ cursor: { x: number; y: number; vx: number; vy: number };
96
+ }
97
+
98
+ interface Mouse {
99
+ x: number;
100
+ y: number;
101
+ lx: number;
102
+ ly: number;
103
+ sx: number;
104
+ sy: number;
105
+ v: number;
106
+ vs: number;
107
+ a: number;
108
+ set: boolean;
109
+ }
110
+
111
+ interface Config {
112
+ lineColor: string;
113
+ waveSpeedX: number;
114
+ waveSpeedY: number;
115
+ waveAmpX: number;
116
+ waveAmpY: number;
117
+ friction: number;
118
+ tension: number;
119
+ maxCursorMove: number;
120
+ xGap: number;
121
+ yGap: number;
122
+ }
123
+
124
+ interface WavesProps {
125
+ lineColor?: string;
126
+ backgroundColor?: string;
127
+ waveSpeedX?: number;
128
+ waveSpeedY?: number;
129
+ waveAmpX?: number;
130
+ waveAmpY?: number;
131
+ xGap?: number;
132
+ yGap?: number;
133
+ friction?: number;
134
+ tension?: number;
135
+ maxCursorMove?: number;
136
+ style?: CSSProperties;
137
+ className?: string;
138
+ }
139
+
140
+ const Waves: React.FC<WavesProps> = ({
141
+ lineColor = '#e4e7ff',
142
+ backgroundColor = 'transparent',
143
+ waveSpeedX = 0.0125,
144
+ waveSpeedY = 0.005,
145
+ waveAmpX = 32,
146
+ waveAmpY = 16,
147
+ xGap = 10,
148
+ yGap = 32,
149
+ friction = 0.925,
150
+ tension = 0.005,
151
+ maxCursorMove = 100,
152
+ style = {},
153
+ className = '',
154
+ }) => {
155
+ const containerRef = useRef<HTMLDivElement>(null);
156
+ const canvasRef = useRef<HTMLCanvasElement>(null);
157
+ const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
158
+ const boundingRef = useRef<{
159
+ width: number;
160
+ height: number;
161
+ left: number;
162
+ top: number;
163
+ }>({
164
+ width: 0,
165
+ height: 0,
166
+ left: 0,
167
+ top: 0,
168
+ });
169
+ const noiseRef = useRef(new Noise(RANDOM_SEED));
170
+ const linesRef = useRef<Point[][]>([]);
171
+ const mouseRef = useRef<Mouse>({
172
+ x: -10,
173
+ y: 0,
174
+ lx: 0,
175
+ ly: 0,
176
+ sx: 0,
177
+ sy: 0,
178
+ v: 0,
179
+ vs: 0,
180
+ a: 0,
181
+ set: false,
182
+ });
183
+
184
+ const configRef = useRef<Config>({
185
+ lineColor,
186
+ waveSpeedX,
187
+ waveSpeedY,
188
+ waveAmpX,
189
+ waveAmpY,
190
+ friction,
191
+ tension,
192
+ maxCursorMove,
193
+ xGap,
194
+ yGap,
195
+ });
196
+
197
+ const frameIdRef = useRef<number | null>(null);
198
+
199
+ useEffect(() => {
200
+ configRef.current = {
201
+ lineColor,
202
+ waveSpeedX,
203
+ waveSpeedY,
204
+ waveAmpX,
205
+ waveAmpY,
206
+ friction,
207
+ tension,
208
+ maxCursorMove,
209
+ xGap,
210
+ yGap,
211
+ };
212
+ }, [
213
+ lineColor,
214
+ waveSpeedX,
215
+ waveSpeedY,
216
+ waveAmpX,
217
+ waveAmpY,
218
+ friction,
219
+ tension,
220
+ maxCursorMove,
221
+ xGap,
222
+ yGap,
223
+ ]);
224
+
225
+ useEffect(() => {
226
+ const canvas = canvasRef.current;
227
+ const container = containerRef.current;
228
+ if (!canvas || !container) return;
229
+ ctxRef.current = canvas.getContext('2d');
230
+
231
+ function setSize() {
232
+ if (!container || !canvas) return;
233
+ const rect = container.getBoundingClientRect();
234
+ boundingRef.current = {
235
+ width: rect.width,
236
+ height: rect.height,
237
+ left: rect.left,
238
+ top: rect.top,
239
+ };
240
+ canvas.width = rect.width;
241
+ canvas.height = rect.height;
242
+ }
243
+
244
+ function setLines() {
245
+ const { width, height } = boundingRef.current;
246
+ linesRef.current = [];
247
+ const oWidth = width + 200,
248
+ oHeight = height + 30;
249
+ const { xGap, yGap } = configRef.current;
250
+ const totalLines = Math.ceil(oWidth / xGap);
251
+ const totalPoints = Math.ceil(oHeight / yGap);
252
+ const xStart = (width - xGap * totalLines) / 2;
253
+ const yStart = (height - yGap * totalPoints) / 2;
254
+ for (let i = 0; i <= totalLines; i++) {
255
+ const pts: Point[] = [];
256
+ for (let j = 0; j <= totalPoints; j++) {
257
+ pts.push({
258
+ x: xStart + xGap * i,
259
+ y: yStart + yGap * j,
260
+ wave: { x: 0, y: 0 },
261
+ cursor: { x: 0, y: 0, vx: 0, vy: 0 },
262
+ });
263
+ }
264
+ linesRef.current.push(pts);
265
+ }
266
+ }
267
+
268
+ function movePoints(time: number) {
269
+ const lines = linesRef.current;
270
+ const mouse = mouseRef.current;
271
+ const noise = noiseRef.current;
272
+ const { waveSpeedX, waveSpeedY, waveAmpX, waveAmpY, friction, tension, maxCursorMove } =
273
+ configRef.current;
274
+ lines.forEach((pts) => {
275
+ pts.forEach((p) => {
276
+ const move =
277
+ noise.perlin2((p.x + time * waveSpeedX) * 0.002, (p.y + time * waveSpeedY) * 0.0015) *
278
+ 12;
279
+ p.wave.x = Math.cos(move) * waveAmpX;
280
+ p.wave.y = Math.sin(move) * waveAmpY;
281
+
282
+ const dx = p.x - mouse.sx,
283
+ dy = p.y - mouse.sy;
284
+ const dist = Math.hypot(dx, dy);
285
+ const l = Math.max(175, mouse.vs);
286
+ if (dist < l) {
287
+ const s = 1 - dist / l;
288
+ const f = Math.cos(dist * 0.001) * s;
289
+ p.cursor.vx += Math.cos(mouse.a) * f * l * mouse.vs * 0.00065;
290
+ p.cursor.vy += Math.sin(mouse.a) * f * l * mouse.vs * 0.00065;
291
+ }
292
+
293
+ p.cursor.vx += (0 - p.cursor.x) * tension;
294
+ p.cursor.vy += (0 - p.cursor.y) * tension;
295
+ p.cursor.vx *= friction;
296
+ p.cursor.vy *= friction;
297
+ p.cursor.x += p.cursor.vx * 2;
298
+ p.cursor.y += p.cursor.vy * 2;
299
+ p.cursor.x = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.x));
300
+ p.cursor.y = Math.min(maxCursorMove, Math.max(-maxCursorMove, p.cursor.y));
301
+ });
302
+ });
303
+ }
304
+
305
+ function moved(point: Point, withCursor = true): { x: number; y: number } {
306
+ const x = point.x + point.wave.x + (withCursor ? point.cursor.x : 0);
307
+ const y = point.y + point.wave.y + (withCursor ? point.cursor.y : 0);
308
+ return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
309
+ }
310
+
311
+ function drawLines() {
312
+ const { width, height } = boundingRef.current;
313
+ const ctx = ctxRef.current;
314
+ if (!ctx) return;
315
+ ctx.clearRect(0, 0, width, height);
316
+ ctx.beginPath();
317
+ ctx.strokeStyle = configRef.current.lineColor;
318
+ linesRef.current.forEach((points) => {
319
+ let p1 = moved(points[0], false);
320
+ ctx.moveTo(p1.x, p1.y);
321
+ points.forEach((p, idx) => {
322
+ const isLast = idx === points.length - 1;
323
+ p1 = moved(p, !isLast);
324
+ const p2 = moved(points[idx + 1] || points[points.length - 1], !isLast);
325
+ ctx.lineTo(p1.x, p1.y);
326
+ if (isLast) ctx.moveTo(p2.x, p2.y);
327
+ });
328
+ });
329
+ ctx.stroke();
330
+ }
331
+
332
+ function tick(t: number) {
333
+ if (!container) return;
334
+ const mouse = mouseRef.current;
335
+ mouse.sx += (mouse.x - mouse.sx) * 0.1;
336
+ mouse.sy += (mouse.y - mouse.sy) * 0.1;
337
+ const dx = mouse.x - mouse.lx,
338
+ dy = mouse.y - mouse.ly;
339
+ const d = Math.hypot(dx, dy);
340
+ mouse.v = d;
341
+ mouse.vs += (d - mouse.vs) * 0.1;
342
+ mouse.vs = Math.min(100, mouse.vs);
343
+ mouse.lx = mouse.x;
344
+ mouse.ly = mouse.y;
345
+ mouse.a = Math.atan2(dy, dx);
346
+ container.style.setProperty('--x', `${mouse.sx}px`);
347
+ container.style.setProperty('--y', `${mouse.sy}px`);
348
+
349
+ movePoints(t);
350
+ drawLines();
351
+ frameIdRef.current = requestAnimationFrame(tick);
352
+ }
353
+
354
+ function onResize() {
355
+ setSize();
356
+ setLines();
357
+ }
358
+ function onMouseMove(e: MouseEvent) {
359
+ updateMouse(e.clientX, e.clientY);
360
+ }
361
+ function onTouchMove(e: TouchEvent) {
362
+ const touch = e.touches[0];
363
+ updateMouse(touch.clientX, touch.clientY);
364
+ }
365
+ function updateMouse(x: number, y: number) {
366
+ const mouse = mouseRef.current;
367
+ const b = boundingRef.current;
368
+ mouse.x = x - b.left;
369
+ mouse.y = y - b.top;
370
+ if (!mouse.set) {
371
+ mouse.sx = mouse.x;
372
+ mouse.sy = mouse.y;
373
+ mouse.lx = mouse.x;
374
+ mouse.ly = mouse.y;
375
+ mouse.set = true;
376
+ }
377
+ }
378
+
379
+ setSize();
380
+ setLines();
381
+ frameIdRef.current = requestAnimationFrame(tick);
382
+ window.addEventListener('resize', onResize);
383
+ window.addEventListener('mousemove', onMouseMove);
384
+ window.addEventListener('touchmove', onTouchMove, { passive: false });
385
+
386
+ return () => {
387
+ window.removeEventListener('resize', onResize);
388
+ window.removeEventListener('mousemove', onMouseMove);
389
+ window.removeEventListener('touchmove', onTouchMove);
390
+ if (frameIdRef.current !== null) {
391
+ cancelAnimationFrame(frameIdRef.current);
392
+ }
393
+ };
394
+ }, []);
395
+
396
+ return (
397
+ <div
398
+ ref={containerRef}
399
+ style={{
400
+ backgroundColor,
401
+ ...style,
402
+ }}
403
+ className={`absolute top-0 left-0 h-full w-full overflow-hidden ${className}`}
404
+ >
405
+ <div
406
+ className="absolute top-0 left-0 h-[0.5rem] w-[0.5rem] rounded-full bg-[#160000]"
407
+ style={{
408
+ transform: 'translate3d(calc(var(--x) - 50%), calc(var(--y) - 50%), 0)',
409
+ willChange: 'transform',
410
+ }}
411
+ />
412
+ <canvas ref={canvasRef} className="block h-full w-full" />
413
+ </div>
414
+ );
415
+ };
416
+
417
+ export default Waves;
@@ -0,0 +1,174 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { AnimatePresence, motion, type HTMLMotionProps, type Transition } from 'motion/react';
5
+ import { Tabs as TabsPrimitive } from 'radix-ui';
6
+
7
+ import { getStrictContext } from '@/lib/get-strict-context';
8
+ import { useControlledState } from '@/hooks/use-controlled-state';
9
+ import { AutoHeight, type AutoHeightProps } from '@/components/motion/auto-height';
10
+ import {
11
+ Highlight,
12
+ HighlightItem,
13
+ type HighlightItemProps,
14
+ type HighlightProps,
15
+ } from '@/components/motion/highlight';
16
+
17
+ type TabsContextType = {
18
+ value: string | undefined;
19
+ setValue: TabsProps['onValueChange'];
20
+ };
21
+
22
+ const [TabsProvider, useTabs] = getStrictContext<TabsContextType>('TabsContext');
23
+
24
+ type TabsProps = React.ComponentProps<typeof TabsPrimitive.Root>;
25
+
26
+ function Tabs(props: TabsProps) {
27
+ const [value, setValue] = useControlledState({
28
+ value: props.value,
29
+ defaultValue: props.defaultValue,
30
+ onChange: props.onValueChange,
31
+ });
32
+
33
+ return (
34
+ <TabsProvider value={{ value, setValue }}>
35
+ <TabsPrimitive.Root data-slot="tabs" {...props} onValueChange={setValue} />
36
+ </TabsProvider>
37
+ );
38
+ }
39
+
40
+ type TabsHighlightProps = Omit<HighlightProps, 'controlledItems' | 'value'>;
41
+
42
+ function TabsHighlight({
43
+ transition = { type: 'spring', stiffness: 200, damping: 25 },
44
+ ...props
45
+ }: TabsHighlightProps) {
46
+ const { value } = useTabs();
47
+
48
+ return (
49
+ <Highlight
50
+ data-slot="tabs-highlight"
51
+ controlledItems
52
+ value={value}
53
+ transition={transition}
54
+ click={false}
55
+ {...props}
56
+ />
57
+ );
58
+ }
59
+
60
+ type TabsListProps = React.ComponentProps<typeof TabsPrimitive.List>;
61
+
62
+ function TabsList(props: TabsListProps) {
63
+ return <TabsPrimitive.List data-slot="tabs-list" {...props} />;
64
+ }
65
+
66
+ type TabsHighlightItemProps = HighlightItemProps & {
67
+ value: string;
68
+ };
69
+
70
+ function TabsHighlightItem(props: TabsHighlightItemProps) {
71
+ return <HighlightItem data-slot="tabs-highlight-item" {...props} />;
72
+ }
73
+
74
+ type TabsTriggerProps = React.ComponentProps<typeof TabsPrimitive.Trigger>;
75
+
76
+ function TabsTrigger(props: TabsTriggerProps) {
77
+ return <TabsPrimitive.Trigger data-slot="tabs-trigger" {...props} />;
78
+ }
79
+
80
+ type TabsContentProps = React.ComponentProps<typeof TabsPrimitive.Content> & HTMLMotionProps<'div'>;
81
+
82
+ function TabsContent({
83
+ value,
84
+ forceMount,
85
+ transition = { duration: 0.5, ease: 'easeInOut' },
86
+ ...props
87
+ }: TabsContentProps) {
88
+ return (
89
+ <AnimatePresence mode="wait">
90
+ <TabsPrimitive.Content asChild forceMount={forceMount} value={value}>
91
+ <motion.div
92
+ data-slot="tabs-content"
93
+ layout
94
+ layoutDependency={value}
95
+ initial={{ opacity: 0, filter: 'blur(4px)' }}
96
+ animate={{ opacity: 1, filter: 'blur(0px)' }}
97
+ exit={{ opacity: 0, filter: 'blur(4px)' }}
98
+ transition={transition}
99
+ {...props}
100
+ />
101
+ </TabsPrimitive.Content>
102
+ </AnimatePresence>
103
+ );
104
+ }
105
+
106
+ type TabsContentsAutoProps = AutoHeightProps & {
107
+ mode?: 'auto-height';
108
+ children: React.ReactNode;
109
+ transition?: Transition;
110
+ };
111
+
112
+ type TabsContentsLayoutProps = Omit<HTMLMotionProps<'div'>, 'transition'> & {
113
+ mode: 'layout';
114
+ children: React.ReactNode;
115
+ transition?: Transition;
116
+ };
117
+
118
+ type TabsContentsProps = TabsContentsAutoProps | TabsContentsLayoutProps;
119
+
120
+ const defaultTransition: Transition = {
121
+ type: 'spring',
122
+ stiffness: 200,
123
+ damping: 30,
124
+ };
125
+
126
+ function isAutoMode(props: TabsContentsProps): props is TabsContentsAutoProps {
127
+ return !('mode' in props) || props.mode === 'auto-height';
128
+ }
129
+
130
+ function TabsContents(props: TabsContentsProps) {
131
+ const { value } = useTabs();
132
+
133
+ if (isAutoMode(props)) {
134
+ const { transition = defaultTransition, ...autoProps } = props;
135
+
136
+ return (
137
+ <AutoHeight data-slot="tabs-contents" deps={[value]} transition={transition} {...autoProps} />
138
+ );
139
+ }
140
+
141
+ const {
142
+ transition = defaultTransition,
143
+ style,
144
+ ...layoutProps
145
+ } = props as TabsContentsLayoutProps;
146
+
147
+ return (
148
+ <motion.div
149
+ data-slot="tabs-contents"
150
+ layout="size"
151
+ layoutDependency={value}
152
+ style={{ overflow: 'hidden', ...style }}
153
+ transition={{ layout: transition }}
154
+ {...layoutProps}
155
+ />
156
+ );
157
+ }
158
+
159
+ export {
160
+ Tabs,
161
+ TabsHighlight,
162
+ TabsHighlightItem,
163
+ TabsList,
164
+ TabsTrigger,
165
+ TabsContent,
166
+ TabsContents,
167
+ type TabsProps,
168
+ type TabsHighlightProps,
169
+ type TabsHighlightItemProps,
170
+ type TabsListProps,
171
+ type TabsTriggerProps,
172
+ type TabsContentProps,
173
+ type TabsContentsProps,
174
+ };