@reactberry/system 2.0.0-beta

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 (165) hide show
  1. package/README.md +48 -0
  2. package/package.json +74 -0
  3. package/src/blocks/Accordion/index.tsx +158 -0
  4. package/src/blocks/AnimatedCarousel/index.tsx +188 -0
  5. package/src/blocks/AppleGlow/index.tsx +144 -0
  6. package/src/blocks/Avatar/index.tsx +167 -0
  7. package/src/blocks/Await/index.tsx +45 -0
  8. package/src/blocks/Cards/AnimatedCard/index.tsx +175 -0
  9. package/src/blocks/Cards/FluorescentCard/index.tsx +180 -0
  10. package/src/blocks/Cards/InfoCard/index.tsx +206 -0
  11. package/src/blocks/Cards/TickerCard/index.tsx +125 -0
  12. package/src/blocks/Carousel/index.tsx +216 -0
  13. package/src/blocks/Checkbox/index.tsx +101 -0
  14. package/src/blocks/Collection/index.tsx +59 -0
  15. package/src/blocks/Container/index.tsx +55 -0
  16. package/src/blocks/Controls/Control.tsx +67 -0
  17. package/src/blocks/Controls/index.tsx +11 -0
  18. package/src/blocks/CyclingNumber/index.tsx +78 -0
  19. package/src/blocks/DisplaySet/index.tsx +42 -0
  20. package/src/blocks/Divider/index.tsx +14 -0
  21. package/src/blocks/Draggable/index.tsx +266 -0
  22. package/src/blocks/Drawer/index.tsx +136 -0
  23. package/src/blocks/DynamicIsland/DynamicIsland.tsx +89 -0
  24. package/src/blocks/DynamicIsland/index.tsx +2 -0
  25. package/src/blocks/Fader/index.tsx +145 -0
  26. package/src/blocks/FamilyDrawer/README.md +116 -0
  27. package/src/blocks/FamilyDrawer/example.tsx +108 -0
  28. package/src/blocks/FamilyDrawer/index.tsx +119 -0
  29. package/src/blocks/FamilyDrawer/views/DefaultView.tsx +93 -0
  30. package/src/blocks/FamilyDrawer/views/KeyView.tsx +129 -0
  31. package/src/blocks/FamilyDrawer/views/PhraseView.tsx +129 -0
  32. package/src/blocks/FamilyDrawer/views/RemoveView.tsx +81 -0
  33. package/src/blocks/FieldSet/index.tsx +173 -0
  34. package/src/blocks/Filesystem/index.tsx +198 -0
  35. package/src/blocks/Gallery/Carousel/index.tsx +257 -0
  36. package/src/blocks/Gallery/Modal/index.tsx +83 -0
  37. package/src/blocks/Gallery/index.tsx +57 -0
  38. package/src/blocks/Gallery/utils/animationVariants.ts +18 -0
  39. package/src/blocks/Gallery/utils/aspectRatio.ts +14 -0
  40. package/src/blocks/Gallery/utils/downloadPhoto.ts +24 -0
  41. package/src/blocks/Gallery/utils/range.ts +11 -0
  42. package/src/blocks/GradientMesh/index.tsx +106 -0
  43. package/src/blocks/Group/index.tsx +152 -0
  44. package/src/blocks/Heading/index.tsx +111 -0
  45. package/src/blocks/HorizontalScroller/index.tsx +135 -0
  46. package/src/blocks/Icon/index.tsx +45 -0
  47. package/src/blocks/Indicator/index.tsx +27 -0
  48. package/src/blocks/InlineEditor/index.tsx +216 -0
  49. package/src/blocks/List/index.tsx +657 -0
  50. package/src/blocks/Main/index.tsx +17 -0
  51. package/src/blocks/Marquee/index.tsx +116 -0
  52. package/src/blocks/MaskedField/index.tsx +199 -0
  53. package/src/blocks/Menu/MenuContent.tsx +246 -0
  54. package/src/blocks/Menu/MenuContext.tsx +34 -0
  55. package/src/blocks/Menu/MenuItem.tsx +104 -0
  56. package/src/blocks/Menu/index.tsx +60 -0
  57. package/src/blocks/Modal/index.tsx +268 -0
  58. package/src/blocks/MorphingPopover/index.tsx +294 -0
  59. package/src/blocks/Overlay/Backdrop.tsx +48 -0
  60. package/src/blocks/Overlay/OverscrollGuard.tsx +36 -0
  61. package/src/blocks/Overlay/index.ts +2 -0
  62. package/src/blocks/Parallax/index.tsx +117 -0
  63. package/src/blocks/ParallaxSection/index.tsx +61 -0
  64. package/src/blocks/Placeholder/index.tsx +48 -0
  65. package/src/blocks/Popover/index.tsx +402 -0
  66. package/src/blocks/Progress/getProgressColor.ts +61 -0
  67. package/src/blocks/Progress/index.tsx +179 -0
  68. package/src/blocks/ProgressiveBlur/index.tsx +75 -0
  69. package/src/blocks/README.md +15 -0
  70. package/src/blocks/RenderAsset/index.tsx +18 -0
  71. package/src/blocks/ScrollContainer/index.tsx +93 -0
  72. package/src/blocks/ShinyText/index.tsx +72 -0
  73. package/src/blocks/Skeleton/index.tsx +71 -0
  74. package/src/blocks/Slider/SliderControls.tsx +119 -0
  75. package/src/blocks/Slider/index.tsx +140 -0
  76. package/src/blocks/Slider/useSlider.ts +126 -0
  77. package/src/blocks/Slideshow/index.tsx +177 -0
  78. package/src/blocks/Spotlight/index.tsx +144 -0
  79. package/src/blocks/Steps/StepIndicator.tsx +149 -0
  80. package/src/blocks/Steps/StepProgress.tsx +164 -0
  81. package/src/blocks/Steps/Steps.tsx +197 -0
  82. package/src/blocks/Steps/StepsNav.tsx +30 -0
  83. package/src/blocks/Steps/StepsTracker.tsx +80 -0
  84. package/src/blocks/Steps/hooks.ts +71 -0
  85. package/src/blocks/Steps/index.tsx +16 -0
  86. package/src/blocks/Steps/types.ts +71 -0
  87. package/src/blocks/StickySectionStack/index.tsx +136 -0
  88. package/src/blocks/Switch/index.tsx +85 -0
  89. package/src/blocks/SystemNotice/index.tsx +81 -0
  90. package/src/blocks/Table/README.md +251 -0
  91. package/src/blocks/Table/Table.tsx +207 -0
  92. package/src/blocks/Table/TablePagination.tsx +189 -0
  93. package/src/blocks/Table/index.ts +33 -0
  94. package/src/blocks/Table/useTableControls.ts +331 -0
  95. package/src/blocks/Tag/index.tsx +27 -0
  96. package/src/blocks/TextBreak/index.tsx +96 -0
  97. package/src/blocks/TextReveal/index.tsx +104 -0
  98. package/src/blocks/Thumbnail/index.tsx +26 -0
  99. package/src/blocks/Ticker/index.tsx +112 -0
  100. package/src/blocks/Toast/index.tsx +77 -0
  101. package/src/blocks/Tooltip/index.tsx +174 -0
  102. package/src/blocks/Underlay/index.tsx +104 -0
  103. package/src/blocks/Upload/Dropzone.tsx +92 -0
  104. package/src/blocks/Upload/UploadBtn.tsx +38 -0
  105. package/src/blocks/Upload/index.tsx +61 -0
  106. package/src/blocks/Upload/types.ts +37 -0
  107. package/src/blocks/VideoMarquee/index.tsx +511 -0
  108. package/src/blocks/index.ts +119 -0
  109. package/src/blocks/pagination/Pagination.tsx +148 -0
  110. package/src/blocks/pagination/PaginationList.tsx +41 -0
  111. package/src/blocks/pagination/index.ts +2 -0
  112. package/src/charts/BarChart.tsx +63 -0
  113. package/src/charts/PieChart.tsx +39 -0
  114. package/src/charts/index.ts +3 -0
  115. package/src/charts/utils.ts +103 -0
  116. package/src/docs/README.md +373 -0
  117. package/src/docs/reference/README.md +299 -0
  118. package/src/elements/box.ts +163 -0
  119. package/src/elements/button.ts +49 -0
  120. package/src/elements/field.ts +129 -0
  121. package/src/elements/index.ts +8 -0
  122. package/src/elements/text.ts +47 -0
  123. package/src/elements/utils.js +97 -0
  124. package/src/hooks/use-copy-to-clipboard.tsx +33 -0
  125. package/src/hooks/use-enter-submit.tsx +23 -0
  126. package/src/hooks/use-local-storage.ts +42 -0
  127. package/src/hooks/use-sidebar.tsx +109 -0
  128. package/src/hooks/useAnimatedText.ts +32 -0
  129. package/src/hooks/useAutosizeTextArea.ts +45 -0
  130. package/src/hooks/useBreakpoint.tsx +123 -0
  131. package/src/hooks/useClickOutside.tsx +38 -0
  132. package/src/hooks/useHover.tsx +33 -0
  133. package/src/hooks/useHoverList.tsx +17 -0
  134. package/src/hooks/useKeyboardShortcuts.ts +91 -0
  135. package/src/hooks/useKeypress.ts +27 -0
  136. package/src/hooks/useOverlay.ts +32 -0
  137. package/src/hooks/useReducedMotion.ts +25 -0
  138. package/src/hooks/useStandaloneMode.ts +35 -0
  139. package/src/hooks/useTouchDevice.ts +34 -0
  140. package/src/icons/index.tsx +129 -0
  141. package/src/index.ts +12 -0
  142. package/src/providers/DesignSystemProvider.tsx +35 -0
  143. package/src/providers/StyledComponentsRegistry.tsx +30 -0
  144. package/src/providers/index.ts +2 -0
  145. package/src/themes/README.md +30 -0
  146. package/src/themes/default/assets/badge-avatar.tsx +45 -0
  147. package/src/themes/default/assets/logo.tsx +42 -0
  148. package/src/themes/default/global.ts +138 -0
  149. package/src/themes/default/modes/dark/config.js +49 -0
  150. package/src/themes/default/modes/dark/skins.js +631 -0
  151. package/src/themes/default/modes/dark/theme.js +87 -0
  152. package/src/themes/default/modes/light/config.js +48 -0
  153. package/src/themes/default/modes/light/skins.js +1026 -0
  154. package/src/themes/default/modes/light/theme.js +74 -0
  155. package/src/themes/default/tokens/controls.js +53 -0
  156. package/src/themes/default/tokens/shadows.js +63 -0
  157. package/src/themes/default/tokens/shapes.js +37 -0
  158. package/src/themes/default/tokens/space.js +143 -0
  159. package/src/themes/default/tokens/spectre.js +16 -0
  160. package/src/themes/default/utils.js +523 -0
  161. package/src/themes/index.ts +11 -0
  162. package/src/types.ts +394 -0
  163. package/src/utils/overlayTheme.ts +61 -0
  164. package/src/utils/pickColor.ts +15 -0
  165. package/tsconfig.json +24 -0
@@ -0,0 +1,167 @@
1
+ "use client";
2
+ import Box, { BoxProps } from "@/design-system/elements/box";
3
+ import Text from "@/design-system/elements/text";
4
+ import { pickColor } from "@/design-system/utils/pickColor";
5
+ import Image from "next/image";
6
+
7
+ interface AvatarProps extends BoxProps {
8
+ autocolor?: boolean;
9
+ name?: string | undefined;
10
+ type?: "text" | "icon" | "image"; // Add "image" to possible types
11
+ src?: string; // Add src prop for image URL
12
+ alt?: string; // Add alt text for accessibility
13
+ withPresence?: boolean;
14
+ fontSize?: string;
15
+ status?: {
16
+ label: string;
17
+ color: string;
18
+ border: string;
19
+ } | null;
20
+ [key: string]: any;
21
+ }
22
+
23
+ const defaultProps = {
24
+ display: "inline-flex",
25
+ name: "Aa",
26
+ shape: "circle",
27
+ flex: "none",
28
+ overflow: "hidden",
29
+ };
30
+
31
+ function getUserInitials(name?: string) {
32
+ if (!name) return "Aa";
33
+ const [first, last] = name.split(" ");
34
+ return last
35
+ ? `${first[0].toUpperCase()}${last[0].toUpperCase()}`
36
+ : first.slice(0, 2).toUpperCase();
37
+ }
38
+
39
+ const Avatar: React.FC<AvatarProps> = (props) => {
40
+ const {
41
+ autocolor = true,
42
+ name = "Aa",
43
+ color,
44
+ type = "text",
45
+ src,
46
+ alt,
47
+ withPresence = false,
48
+ status = { label: "online", color: "green", border: "surface" },
49
+ size,
50
+ fontSize = "75%",
51
+ ...restProps
52
+ } = props;
53
+
54
+ return (
55
+ <Box display="inline-flex" alignItems={"center"} position={"relative"}>
56
+ <Box
57
+ {...defaultProps}
58
+ color={autocolor ? pickColor(String(name)) : color || "neutral"}
59
+ name={name}
60
+ position="relative"
61
+ size={size || "2.5rem"}
62
+ {...restProps}
63
+ >
64
+ {type === "image" && src && (
65
+ <Box
66
+ as={Image}
67
+ backgroundColor={"white"}
68
+ src={src}
69
+ loading="eager"
70
+ placeholder="blur"
71
+ blurDataURL={
72
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mM8feRIPQAHiQLUpr/inAAAAABJRU5ErkJggg=="
73
+ }
74
+ alt={alt || name || "avatar"}
75
+ fill
76
+ sizes="100%"
77
+ style={{ objectFit: "cover" }}
78
+ />
79
+ )}
80
+
81
+ {/*old way*/}
82
+ {/*{type === "text" && (
83
+ <svg
84
+ fill="currentColor"
85
+ width="100%"
86
+ height="100%"
87
+ viewBox="0 0 100 100"
88
+ version="1.1"
89
+ xmlns="http://www.w3.org/2000/svg"
90
+ >
91
+ <g>
92
+ <rect x="0" y="0" width="100" height="100" />
93
+ <text
94
+ x="50%"
95
+ y="50%"
96
+ dy="4%"
97
+ //alignmentBaseline="baseline"
98
+ dominantBaseline="middle"
99
+ textAnchor="middle"
100
+ fill="white"
101
+ fontFamily="sans-serif"
102
+ fontWeight="700"
103
+ fontSize={fontSize}
104
+ >
105
+ {getUserInitials(name)}
106
+ </text>
107
+ </g>
108
+ </svg>
109
+ )}*/}
110
+
111
+ {type === "text" && (
112
+ <Box
113
+ width="100%"
114
+ height="100%"
115
+ bg="currentColor"
116
+ display={"flex"}
117
+ alignItems={"center"}
118
+ justifyContent={"center"}
119
+ >
120
+ <Text color="white" fontSize={fontSize} fontWeight="800">
121
+ {getUserInitials(name)}
122
+ </Text>
123
+ </Box>
124
+ )}
125
+
126
+ {type === "icon" && (
127
+ <svg
128
+ fill="currentColor"
129
+ width="100%"
130
+ height="100%"
131
+ viewBox="0 0 100 100"
132
+ version="1.1"
133
+ xmlns="http://www.w3.org/2000/svg"
134
+ >
135
+ <g>
136
+ <rect x="0" y="0" width="100" height="100" />
137
+ <path
138
+ d="M30.224 36.982C30.224 25.964 39.319 17 50.5 17c11.18 0 20.276 8.964 20.276 19.982v4.996c0 11.019-9.095 19.983-20.276 19.983-11.18 0-20.276-8.964-20.276-19.983v-4.996zM84 98.198c0 .924-.75 1.67-1.675 1.67h-63.65c-.925 0-1.675-.746-1.675-1.67v-3.34c0-14.737 12.023-26.726 26.8-26.726h13.4c14.777 0 26.8 11.99 26.8 26.725v3.341z"
139
+ fill="white"
140
+ fillRule="nonzero"
141
+ />
142
+ </g>
143
+ </svg>
144
+ )}
145
+ </Box>
146
+
147
+ {withPresence && status && (
148
+ <Box
149
+ bg={status.color}
150
+ position="absolute"
151
+ size={size ? `calc(${size} / 2.5)` : "0.625rem"}
152
+ maxWidth={"1rem"}
153
+ maxHeight={"1rem"}
154
+ shape="circle"
155
+ bottom="0"
156
+ left="auto"
157
+ right={`calc(${size} / 7.5 * -1)`}
158
+ zIndex="1"
159
+ border="1.5px solid"
160
+ borderColor="surface"
161
+ />
162
+ )}
163
+ </Box>
164
+ );
165
+ };
166
+
167
+ export default Avatar;
@@ -0,0 +1,45 @@
1
+ import { JSX } from "react";
2
+
3
+ export async function Await<T>({
4
+ promise,
5
+ children,
6
+ }: {
7
+ promise: Promise<T>;
8
+ children: (result: T) => JSX.Element;
9
+ }) {
10
+ const result = await promise;
11
+
12
+ return children(result);
13
+ }
14
+
15
+ // Example usage
16
+ // // src/app/stocks/[stockId]/page.tsx
17
+ // import { Await } from "@/design-system/blocks";
18
+
19
+ // export default async function Stock({
20
+ // params,
21
+ // }: {
22
+ // params: { stockId: string };
23
+ // }) {
24
+ // // Page blocks on this data:
25
+ // const stock = await getStock(params.stockId);
26
+
27
+ // return (
28
+ // <div>
29
+ // <h1>{stock.name}</h1>
30
+
31
+ // <Suspense fallback={<Spinner />}>
32
+ // {/* ...but renders a spinner for this slower data: */}
33
+ // <Await promise={getRecentNews(stock)}>
34
+ // {(stories) => (
35
+ // <ul>
36
+ // {stories.map((story) => (
37
+ // <li key={story.id}>{story.title}</li>
38
+ // ))}
39
+ // </ul>
40
+ // )}
41
+ // </Await>
42
+ // </Suspense>
43
+ // </div>
44
+ // );
45
+ // }
@@ -0,0 +1,175 @@
1
+ "use client";
2
+
3
+ import { useRef, useState } from "react";
4
+ import {
5
+ motion,
6
+ useMotionTemplate,
7
+ useMotionValue,
8
+ useSpring,
9
+ } from "motion/react";
10
+ import Box, { BoxProps } from "@/design-system/elements/box";
11
+ import { useReducedMotion } from "@/design-system/hooks/useReducedMotion";
12
+
13
+ interface AnimatedCardProps extends Omit<BoxProps, "ref" | "children"> {
14
+ children: React.ReactNode;
15
+ /** Spotlight color (default: "rgba(255, 255, 255, 0.2)") */
16
+ spotlightColor?: string;
17
+ /** Spotlight radius in pixels (default: 300) */
18
+ spotlightSize?: number;
19
+ /** Enable tilt effect (default: true) */
20
+ tiltEnabled?: boolean;
21
+ /** Enable spotlight effect (default: true) */
22
+ spotlightEnabled?: boolean;
23
+ /** Max rotation in degrees (default: 2.3) */
24
+ maxTilt?: number;
25
+ /** Spring stiffness (default: 300) */
26
+ stiffness?: number;
27
+ /** Spring damping (default: 30) */
28
+ damping?: number;
29
+ }
30
+
31
+ /**
32
+ * AnimatedCard - Interactive card with 3D tilt and spotlight effects
33
+ *
34
+ * A solid card with interactive 3D tilt and mouse-tracking spotlight.
35
+ * Combines the best features from TiltCard and InteractiveCard components.
36
+ */
37
+ export default function AnimatedCard({
38
+ children,
39
+ spotlightColor = "rgba(255, 255, 255, 0.2)",
40
+ spotlightSize = 300,
41
+ tiltEnabled = true,
42
+ spotlightEnabled = true,
43
+ maxTilt = 2.3,
44
+ stiffness = 300,
45
+ damping = 30,
46
+ ...props
47
+ }: AnimatedCardProps) {
48
+ const ref = useRef<HTMLDivElement>(null);
49
+ const [isHovered, setIsHovered] = useState(false);
50
+ const prefersReducedMotion = useReducedMotion();
51
+
52
+ // Spotlight values with springs for smoother animation
53
+ const spotlightX = useSpring(useMotionValue(0), { stiffness, damping });
54
+ const spotlightY = useSpring(useMotionValue(0), { stiffness, damping });
55
+
56
+ // Tilt values
57
+ const tiltX = useMotionValue(0);
58
+ const tiltY = useMotionValue(0);
59
+ const tiltXSpring = useSpring(tiltX, { stiffness, damping });
60
+ const tiltYSpring = useSpring(tiltY, { stiffness, damping });
61
+
62
+ // Transform template for 3D tilt
63
+ const transform = useMotionTemplate`perspective(1000px) rotateX(${tiltXSpring}deg) rotateY(${tiltYSpring}deg)`;
64
+
65
+ // Spotlight gradient
66
+ const spotlightBackground = useMotionTemplate`
67
+ radial-gradient(
68
+ ${spotlightSize}px circle at ${spotlightX}px ${spotlightY}px,
69
+ ${spotlightColor},
70
+ transparent 70%
71
+ )
72
+ `;
73
+
74
+ const shouldAnimate = !prefersReducedMotion && (tiltEnabled || spotlightEnabled);
75
+
76
+ function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
77
+ if (!ref.current || !shouldAnimate) return;
78
+
79
+ const rect = ref.current.getBoundingClientRect();
80
+
81
+ // Calculate mouse position relative to card center
82
+ const centerX = rect.left + rect.width / 2;
83
+ const centerY = rect.top + rect.height / 2;
84
+ const mouseX = e.clientX - centerX;
85
+ const mouseY = e.clientY - centerY;
86
+
87
+ // Update spotlight position
88
+ spotlightX.set(e.clientX - rect.left);
89
+ spotlightY.set(e.clientY - rect.top);
90
+
91
+ // Calculate rotation (clamped to maxTilt)
92
+ if (tiltEnabled) {
93
+ const rotateX = (mouseY / (rect.height / 2)) * -maxTilt;
94
+ const rotateY = (mouseX / (rect.width / 2)) * maxTilt;
95
+
96
+ tiltX.set(rotateX);
97
+ tiltY.set(rotateY);
98
+ }
99
+ }
100
+
101
+ function handleMouseEnter() {
102
+ setIsHovered(true);
103
+ }
104
+
105
+ function handleMouseLeave() {
106
+ setIsHovered(false);
107
+ // Reset tilt with animation
108
+ if (tiltEnabled) {
109
+ tiltX.set(0);
110
+ tiltY.set(0);
111
+ }
112
+ }
113
+
114
+ return (
115
+ <Box
116
+ as={motion.div}
117
+ ref={ref}
118
+ onMouseMove={handleMouseMove}
119
+ onMouseEnter={handleMouseEnter}
120
+ onMouseLeave={handleMouseLeave}
121
+ position="relative"
122
+ overflow="hidden"
123
+ shape="rounded"
124
+ skin="card"
125
+ $shadow="medium"
126
+ style={{
127
+ transformStyle: shouldAnimate ? "preserve-3d" : undefined,
128
+ transform: shouldAnimate ? transform : undefined,
129
+ transformOrigin: "center center",
130
+ backgroundColor: "rgba(255, 255, 255, 0.95)",
131
+ isolation: "isolate",
132
+ }}
133
+ {...props}
134
+ >
135
+ {/* Content */}
136
+ <Box
137
+ as={shouldAnimate ? motion.div : "div"}
138
+ position="relative"
139
+ height="100%"
140
+ width="100%"
141
+ zIndex={1}
142
+ style={
143
+ shouldAnimate
144
+ ? {
145
+ transform: isHovered ? "translateZ(24px)" : "translateZ(0px)",
146
+ transition: "transform 0.3s ease-out",
147
+ transformStyle: "preserve-3d",
148
+ }
149
+ : undefined
150
+ }
151
+ >
152
+ {children}
153
+ </Box>
154
+
155
+ {/* Spotlight effect - on top of content */}
156
+ {shouldAnimate && spotlightEnabled && (
157
+ <Box
158
+ as={motion.div}
159
+ position="absolute"
160
+ width="100%"
161
+ height="100%"
162
+ top="0"
163
+ left="0"
164
+ opacity={isHovered ? 1 : 0}
165
+ zIndex={2}
166
+ style={{
167
+ pointerEvents: "none",
168
+ transition: "opacity 0.3s ease-out",
169
+ background: spotlightBackground,
170
+ }}
171
+ />
172
+ )}
173
+ </Box>
174
+ );
175
+ }
@@ -0,0 +1,180 @@
1
+ "use client";
2
+
3
+ import { useRef, useState } from "react";
4
+ import {
5
+ motion,
6
+ useMotionTemplate,
7
+ useMotionValue,
8
+ useSpring,
9
+ } from "motion/react";
10
+ import Box, { BoxProps } from "@/design-system/elements/box";
11
+ import styled from "styled-components";
12
+ import { useReducedMotion } from "@/design-system/hooks/useReducedMotion";
13
+ import { useTouchDevice } from "@/design-system/hooks/useTouchDevice";
14
+
15
+ interface FluorescentCardProps extends Omit<BoxProps, "ref" | "children"> {
16
+ children: React.ReactNode;
17
+ /** Spotlight color (default: "rgba(255, 255, 255, 0.2)") */
18
+ spotlightColor?: string;
19
+ /** Spotlight radius in pixels (default: 300) */
20
+ spotlightSize?: number;
21
+ /** Enable tilt effect (default: true) */
22
+ tiltEnabled?: boolean;
23
+ /** Max rotation in degrees (default: 2.3) */
24
+ maxTilt?: number;
25
+ /** Spring stiffness (default: 300) */
26
+ stiffness?: number;
27
+ /** Spring damping (default: 30) */
28
+ damping?: number;
29
+ }
30
+
31
+ // Fluorescent/frosted glass styling
32
+ // Uses semi-transparent white with saturated backdrop for a glowing effect
33
+ const FluorescentContainer = styled(Box)`
34
+ background-color: rgba(255, 255, 255, 0.95);
35
+ backdrop-filter: saturate(1.5) brightness(1.75);
36
+ `;
37
+
38
+ /**
39
+ * FluorescentCard - Interactive card with frosted glass effect and 3D tilt
40
+ *
41
+ * A fluorescent/frosted glass card with interactive 3D tilt and mouse-tracking spotlight.
42
+ * Features a semi-transparent white background with saturated backdrop blur for a glowing effect.
43
+ * Ideal for use over gradient or colorful backgrounds.
44
+ */
45
+ export default function FluorescentCard({
46
+ children,
47
+ spotlightColor = "rgba(255, 255, 255, 0.2)",
48
+ spotlightSize = 300,
49
+ tiltEnabled = true,
50
+ maxTilt = 2.3,
51
+ stiffness = 300,
52
+ damping = 30,
53
+ ...props
54
+ }: FluorescentCardProps) {
55
+ const ref = useRef<HTMLDivElement>(null);
56
+ const [isHovered, setIsHovered] = useState(false);
57
+ const isTouchDevice = useTouchDevice();
58
+ const prefersReducedMotion = useReducedMotion();
59
+
60
+ // Spotlight values with springs for smoother animation
61
+ const spotlightX = useSpring(useMotionValue(0), { stiffness, damping });
62
+ const spotlightY = useSpring(useMotionValue(0), { stiffness, damping });
63
+
64
+ // Tilt values
65
+ const tiltX = useMotionValue(0);
66
+ const tiltY = useMotionValue(0);
67
+ const tiltXSpring = useSpring(tiltX, { stiffness, damping });
68
+ const tiltYSpring = useSpring(tiltY, { stiffness, damping });
69
+
70
+ // Transform template for 3D tilt
71
+ const transform = useMotionTemplate`perspective(1000px) rotateX(${tiltXSpring}deg) rotateY(${tiltYSpring}deg)`;
72
+
73
+ // Spotlight gradient
74
+ const spotlightBackground = useMotionTemplate`
75
+ radial-gradient(
76
+ ${spotlightSize}px circle at ${spotlightX}px ${spotlightY}px,
77
+ ${spotlightColor},
78
+ transparent 70%
79
+ )
80
+ `;
81
+
82
+ const shouldAnimate = !prefersReducedMotion && !isTouchDevice;
83
+
84
+ function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
85
+ if (!ref.current || !shouldAnimate) return;
86
+
87
+ const rect = ref.current.getBoundingClientRect();
88
+
89
+ // Calculate mouse position relative to card center
90
+ const centerX = rect.left + rect.width / 2;
91
+ const centerY = rect.top + rect.height / 2;
92
+ const mouseX = e.clientX - centerX;
93
+ const mouseY = e.clientY - centerY;
94
+
95
+ // Update spotlight position
96
+ spotlightX.set(e.clientX - rect.left);
97
+ spotlightY.set(e.clientY - rect.top);
98
+
99
+ // Calculate rotation (clamped to maxTilt)
100
+ if (tiltEnabled) {
101
+ const rotateX = (mouseY / (rect.height / 2)) * -maxTilt;
102
+ const rotateY = (mouseX / (rect.width / 2)) * maxTilt;
103
+
104
+ tiltX.set(rotateX);
105
+ tiltY.set(rotateY);
106
+ }
107
+ }
108
+
109
+ function handleMouseEnter() {
110
+ setIsHovered(true);
111
+ }
112
+
113
+ function handleMouseLeave() {
114
+ setIsHovered(false);
115
+ // Reset tilt with animation
116
+ if (tiltEnabled) {
117
+ tiltX.set(0);
118
+ tiltY.set(0);
119
+ }
120
+ }
121
+
122
+ return (
123
+ <FluorescentContainer
124
+ as={motion.div}
125
+ ref={ref}
126
+ onMouseMove={handleMouseMove}
127
+ onMouseEnter={handleMouseEnter}
128
+ onMouseLeave={handleMouseLeave}
129
+ position="relative"
130
+ overflow="hidden"
131
+ shape="rounded"
132
+ $shadow="medium"
133
+ style={{
134
+ transformStyle: shouldAnimate ? "preserve-3d" : undefined,
135
+ transform: shouldAnimate ? transform : undefined,
136
+ transformOrigin: "center center",
137
+ }}
138
+ {...props}
139
+ >
140
+ {/* Content */}
141
+ <Box
142
+ as={shouldAnimate ? motion.div : "div"}
143
+ position="relative"
144
+ height="100%"
145
+ width="100%"
146
+ zIndex={1}
147
+ style={
148
+ shouldAnimate
149
+ ? {
150
+ transform: isHovered ? "translateZ(24px)" : "translateZ(0px)",
151
+ transition: "transform 0.3s ease-out",
152
+ transformStyle: "preserve-3d",
153
+ }
154
+ : undefined
155
+ }
156
+ >
157
+ {children}
158
+ </Box>
159
+
160
+ {/* Spotlight effect - on top of content */}
161
+ {shouldAnimate && (
162
+ <Box
163
+ as={motion.div}
164
+ position="absolute"
165
+ width="100%"
166
+ height="100%"
167
+ top="0"
168
+ left="0"
169
+ opacity={isHovered ? 1 : 0}
170
+ zIndex={2}
171
+ style={{
172
+ pointerEvents: "none",
173
+ transition: "opacity 0.3s ease-out",
174
+ background: spotlightBackground,
175
+ }}
176
+ />
177
+ )}
178
+ </FluorescentContainer>
179
+ );
180
+ }