@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.
- package/README.md +48 -0
- package/package.json +74 -0
- package/src/blocks/Accordion/index.tsx +158 -0
- package/src/blocks/AnimatedCarousel/index.tsx +188 -0
- package/src/blocks/AppleGlow/index.tsx +144 -0
- package/src/blocks/Avatar/index.tsx +167 -0
- package/src/blocks/Await/index.tsx +45 -0
- package/src/blocks/Cards/AnimatedCard/index.tsx +175 -0
- package/src/blocks/Cards/FluorescentCard/index.tsx +180 -0
- package/src/blocks/Cards/InfoCard/index.tsx +206 -0
- package/src/blocks/Cards/TickerCard/index.tsx +125 -0
- package/src/blocks/Carousel/index.tsx +216 -0
- package/src/blocks/Checkbox/index.tsx +101 -0
- package/src/blocks/Collection/index.tsx +59 -0
- package/src/blocks/Container/index.tsx +55 -0
- package/src/blocks/Controls/Control.tsx +67 -0
- package/src/blocks/Controls/index.tsx +11 -0
- package/src/blocks/CyclingNumber/index.tsx +78 -0
- package/src/blocks/DisplaySet/index.tsx +42 -0
- package/src/blocks/Divider/index.tsx +14 -0
- package/src/blocks/Draggable/index.tsx +266 -0
- package/src/blocks/Drawer/index.tsx +136 -0
- package/src/blocks/DynamicIsland/DynamicIsland.tsx +89 -0
- package/src/blocks/DynamicIsland/index.tsx +2 -0
- package/src/blocks/Fader/index.tsx +145 -0
- package/src/blocks/FamilyDrawer/README.md +116 -0
- package/src/blocks/FamilyDrawer/example.tsx +108 -0
- package/src/blocks/FamilyDrawer/index.tsx +119 -0
- package/src/blocks/FamilyDrawer/views/DefaultView.tsx +93 -0
- package/src/blocks/FamilyDrawer/views/KeyView.tsx +129 -0
- package/src/blocks/FamilyDrawer/views/PhraseView.tsx +129 -0
- package/src/blocks/FamilyDrawer/views/RemoveView.tsx +81 -0
- package/src/blocks/FieldSet/index.tsx +173 -0
- package/src/blocks/Filesystem/index.tsx +198 -0
- package/src/blocks/Gallery/Carousel/index.tsx +257 -0
- package/src/blocks/Gallery/Modal/index.tsx +83 -0
- package/src/blocks/Gallery/index.tsx +57 -0
- package/src/blocks/Gallery/utils/animationVariants.ts +18 -0
- package/src/blocks/Gallery/utils/aspectRatio.ts +14 -0
- package/src/blocks/Gallery/utils/downloadPhoto.ts +24 -0
- package/src/blocks/Gallery/utils/range.ts +11 -0
- package/src/blocks/GradientMesh/index.tsx +106 -0
- package/src/blocks/Group/index.tsx +152 -0
- package/src/blocks/Heading/index.tsx +111 -0
- package/src/blocks/HorizontalScroller/index.tsx +135 -0
- package/src/blocks/Icon/index.tsx +45 -0
- package/src/blocks/Indicator/index.tsx +27 -0
- package/src/blocks/InlineEditor/index.tsx +216 -0
- package/src/blocks/List/index.tsx +657 -0
- package/src/blocks/Main/index.tsx +17 -0
- package/src/blocks/Marquee/index.tsx +116 -0
- package/src/blocks/MaskedField/index.tsx +199 -0
- package/src/blocks/Menu/MenuContent.tsx +246 -0
- package/src/blocks/Menu/MenuContext.tsx +34 -0
- package/src/blocks/Menu/MenuItem.tsx +104 -0
- package/src/blocks/Menu/index.tsx +60 -0
- package/src/blocks/Modal/index.tsx +268 -0
- package/src/blocks/MorphingPopover/index.tsx +294 -0
- package/src/blocks/Overlay/Backdrop.tsx +48 -0
- package/src/blocks/Overlay/OverscrollGuard.tsx +36 -0
- package/src/blocks/Overlay/index.ts +2 -0
- package/src/blocks/Parallax/index.tsx +117 -0
- package/src/blocks/ParallaxSection/index.tsx +61 -0
- package/src/blocks/Placeholder/index.tsx +48 -0
- package/src/blocks/Popover/index.tsx +402 -0
- package/src/blocks/Progress/getProgressColor.ts +61 -0
- package/src/blocks/Progress/index.tsx +179 -0
- package/src/blocks/ProgressiveBlur/index.tsx +75 -0
- package/src/blocks/README.md +15 -0
- package/src/blocks/RenderAsset/index.tsx +18 -0
- package/src/blocks/ScrollContainer/index.tsx +93 -0
- package/src/blocks/ShinyText/index.tsx +72 -0
- package/src/blocks/Skeleton/index.tsx +71 -0
- package/src/blocks/Slider/SliderControls.tsx +119 -0
- package/src/blocks/Slider/index.tsx +140 -0
- package/src/blocks/Slider/useSlider.ts +126 -0
- package/src/blocks/Slideshow/index.tsx +177 -0
- package/src/blocks/Spotlight/index.tsx +144 -0
- package/src/blocks/Steps/StepIndicator.tsx +149 -0
- package/src/blocks/Steps/StepProgress.tsx +164 -0
- package/src/blocks/Steps/Steps.tsx +197 -0
- package/src/blocks/Steps/StepsNav.tsx +30 -0
- package/src/blocks/Steps/StepsTracker.tsx +80 -0
- package/src/blocks/Steps/hooks.ts +71 -0
- package/src/blocks/Steps/index.tsx +16 -0
- package/src/blocks/Steps/types.ts +71 -0
- package/src/blocks/StickySectionStack/index.tsx +136 -0
- package/src/blocks/Switch/index.tsx +85 -0
- package/src/blocks/SystemNotice/index.tsx +81 -0
- package/src/blocks/Table/README.md +251 -0
- package/src/blocks/Table/Table.tsx +207 -0
- package/src/blocks/Table/TablePagination.tsx +189 -0
- package/src/blocks/Table/index.ts +33 -0
- package/src/blocks/Table/useTableControls.ts +331 -0
- package/src/blocks/Tag/index.tsx +27 -0
- package/src/blocks/TextBreak/index.tsx +96 -0
- package/src/blocks/TextReveal/index.tsx +104 -0
- package/src/blocks/Thumbnail/index.tsx +26 -0
- package/src/blocks/Ticker/index.tsx +112 -0
- package/src/blocks/Toast/index.tsx +77 -0
- package/src/blocks/Tooltip/index.tsx +174 -0
- package/src/blocks/Underlay/index.tsx +104 -0
- package/src/blocks/Upload/Dropzone.tsx +92 -0
- package/src/blocks/Upload/UploadBtn.tsx +38 -0
- package/src/blocks/Upload/index.tsx +61 -0
- package/src/blocks/Upload/types.ts +37 -0
- package/src/blocks/VideoMarquee/index.tsx +511 -0
- package/src/blocks/index.ts +119 -0
- package/src/blocks/pagination/Pagination.tsx +148 -0
- package/src/blocks/pagination/PaginationList.tsx +41 -0
- package/src/blocks/pagination/index.ts +2 -0
- package/src/charts/BarChart.tsx +63 -0
- package/src/charts/PieChart.tsx +39 -0
- package/src/charts/index.ts +3 -0
- package/src/charts/utils.ts +103 -0
- package/src/docs/README.md +373 -0
- package/src/docs/reference/README.md +299 -0
- package/src/elements/box.ts +163 -0
- package/src/elements/button.ts +49 -0
- package/src/elements/field.ts +129 -0
- package/src/elements/index.ts +8 -0
- package/src/elements/text.ts +47 -0
- package/src/elements/utils.js +97 -0
- package/src/hooks/use-copy-to-clipboard.tsx +33 -0
- package/src/hooks/use-enter-submit.tsx +23 -0
- package/src/hooks/use-local-storage.ts +42 -0
- package/src/hooks/use-sidebar.tsx +109 -0
- package/src/hooks/useAnimatedText.ts +32 -0
- package/src/hooks/useAutosizeTextArea.ts +45 -0
- package/src/hooks/useBreakpoint.tsx +123 -0
- package/src/hooks/useClickOutside.tsx +38 -0
- package/src/hooks/useHover.tsx +33 -0
- package/src/hooks/useHoverList.tsx +17 -0
- package/src/hooks/useKeyboardShortcuts.ts +91 -0
- package/src/hooks/useKeypress.ts +27 -0
- package/src/hooks/useOverlay.ts +32 -0
- package/src/hooks/useReducedMotion.ts +25 -0
- package/src/hooks/useStandaloneMode.ts +35 -0
- package/src/hooks/useTouchDevice.ts +34 -0
- package/src/icons/index.tsx +129 -0
- package/src/index.ts +12 -0
- package/src/providers/DesignSystemProvider.tsx +35 -0
- package/src/providers/StyledComponentsRegistry.tsx +30 -0
- package/src/providers/index.ts +2 -0
- package/src/themes/README.md +30 -0
- package/src/themes/default/assets/badge-avatar.tsx +45 -0
- package/src/themes/default/assets/logo.tsx +42 -0
- package/src/themes/default/global.ts +138 -0
- package/src/themes/default/modes/dark/config.js +49 -0
- package/src/themes/default/modes/dark/skins.js +631 -0
- package/src/themes/default/modes/dark/theme.js +87 -0
- package/src/themes/default/modes/light/config.js +48 -0
- package/src/themes/default/modes/light/skins.js +1026 -0
- package/src/themes/default/modes/light/theme.js +74 -0
- package/src/themes/default/tokens/controls.js +53 -0
- package/src/themes/default/tokens/shadows.js +63 -0
- package/src/themes/default/tokens/shapes.js +37 -0
- package/src/themes/default/tokens/space.js +143 -0
- package/src/themes/default/tokens/spectre.js +16 -0
- package/src/themes/default/utils.js +523 -0
- package/src/themes/index.ts +11 -0
- package/src/types.ts +394 -0
- package/src/utils/overlayTheme.ts +61 -0
- package/src/utils/pickColor.ts +15 -0
- 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
|
+
}
|