@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,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { motion, type PanInfo } from "motion/react";
|
|
3
|
+
import { ReactNode, type WheelEvent } from "react";
|
|
4
|
+
import useSlider, { ResponsiveConfig, SliderConfig } from "./useSlider";
|
|
5
|
+
import { Box } from "@/design-system/elements";
|
|
6
|
+
import SliderControls from "./SliderControls";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
config?: SliderConfig;
|
|
10
|
+
responsiveConfig?: ResponsiveConfig[];
|
|
11
|
+
children: ReactNode[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Slider = ({ config, responsiveConfig = [], children }: Props) => {
|
|
15
|
+
const {
|
|
16
|
+
currentSlide,
|
|
17
|
+
currentPage,
|
|
18
|
+
canGoNext,
|
|
19
|
+
canGoPrev,
|
|
20
|
+
scrollTo,
|
|
21
|
+
settings,
|
|
22
|
+
setCurrentPage,
|
|
23
|
+
nextSlide,
|
|
24
|
+
prevSlide,
|
|
25
|
+
setScrollTo,
|
|
26
|
+
slideWidth,
|
|
27
|
+
totalPages,
|
|
28
|
+
} = useSlider(children, config, responsiveConfig);
|
|
29
|
+
|
|
30
|
+
const showControls = totalPages > 1;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Box
|
|
34
|
+
width="100%"
|
|
35
|
+
position={"relative"}
|
|
36
|
+
overflow={"hidden"}
|
|
37
|
+
display={"flex"}
|
|
38
|
+
flexDirection={"column"}
|
|
39
|
+
px={`${settings.paddingX ?? 0}px`}
|
|
40
|
+
py={`${settings.paddingY ?? 0}px`}
|
|
41
|
+
>
|
|
42
|
+
<Box
|
|
43
|
+
as={motion.div}
|
|
44
|
+
initial={{ x: 0 }}
|
|
45
|
+
animate={{
|
|
46
|
+
x: `-${(100 / children.length) * currentSlide}%`,
|
|
47
|
+
width: `${slideWidth < 100 ? 100 : slideWidth}%`,
|
|
48
|
+
}}
|
|
49
|
+
transition={{
|
|
50
|
+
type: "spring",
|
|
51
|
+
visualDuration: 0.3,
|
|
52
|
+
bounce: 0.2,
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<Box
|
|
56
|
+
as={motion.div}
|
|
57
|
+
display={"flex"}
|
|
58
|
+
justifyContent={
|
|
59
|
+
children.length < settings.slidesToShow ? "flex-start" : "initial"
|
|
60
|
+
}
|
|
61
|
+
mr={`-${settings.spacing}px`}
|
|
62
|
+
ml={`-${settings.spacing}px`}
|
|
63
|
+
onWheel={(e: WheelEvent<HTMLDivElement>) => {
|
|
64
|
+
if (e.deltaX < -1) {
|
|
65
|
+
if (scrollTo) return;
|
|
66
|
+
prevSlide();
|
|
67
|
+
setScrollTo(true);
|
|
68
|
+
} else if (e.deltaX > 1) {
|
|
69
|
+
if (scrollTo) return;
|
|
70
|
+
nextSlide();
|
|
71
|
+
setScrollTo(true);
|
|
72
|
+
} else {
|
|
73
|
+
setScrollTo(false);
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
drag="x"
|
|
77
|
+
dragConstraints={{ left: 0, right: 0 }}
|
|
78
|
+
onDragEnd={(
|
|
79
|
+
_event: MouseEvent | TouchEvent | PointerEvent,
|
|
80
|
+
{ offset, velocity }: PanInfo,
|
|
81
|
+
) => {
|
|
82
|
+
const swipe = Math.abs(offset.x) > 50 && Math.abs(velocity.x) > 500;
|
|
83
|
+
if (swipe && offset.x > 0) prevSlide();
|
|
84
|
+
else if (swipe) nextSlide();
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{children.map((child, index) => {
|
|
88
|
+
// Calculate item width - use slidesToShow if we have enough items, otherwise use actual count
|
|
89
|
+
const itemsToShow = Math.min(
|
|
90
|
+
children.length,
|
|
91
|
+
settings.slidesToShow,
|
|
92
|
+
);
|
|
93
|
+
const calculatedWidth = 100 / itemsToShow;
|
|
94
|
+
|
|
95
|
+
// If maxItemWidth is set and we have fewer items than slidesToShow,
|
|
96
|
+
// respect the maxItemWidth to prevent stretching
|
|
97
|
+
const shouldRespectMaxWidth =
|
|
98
|
+
children.length < settings.slidesToShow && settings.maxItemWidth;
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<Box
|
|
102
|
+
key={index}
|
|
103
|
+
pl={`${settings.spacing}px`}
|
|
104
|
+
pr={`${settings.spacing}px`}
|
|
105
|
+
width={
|
|
106
|
+
shouldRespectMaxWidth ? undefined : `${calculatedWidth}%`
|
|
107
|
+
}
|
|
108
|
+
maxWidth={
|
|
109
|
+
shouldRespectMaxWidth
|
|
110
|
+
? `${settings.maxItemWidth}px`
|
|
111
|
+
: undefined
|
|
112
|
+
}
|
|
113
|
+
flex={shouldRespectMaxWidth ? "0 0 auto" : undefined}
|
|
114
|
+
>
|
|
115
|
+
{child}
|
|
116
|
+
</Box>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</Box>
|
|
120
|
+
</Box>
|
|
121
|
+
|
|
122
|
+
{showControls && (
|
|
123
|
+
<SliderControls
|
|
124
|
+
canGoPrev={canGoPrev}
|
|
125
|
+
canGoNext={canGoNext}
|
|
126
|
+
onPrev={prevSlide}
|
|
127
|
+
onNext={nextSlide}
|
|
128
|
+
slides={totalPages}
|
|
129
|
+
onChange={setCurrentPage}
|
|
130
|
+
activeSlide={currentPage}
|
|
131
|
+
showDots={settings.dots}
|
|
132
|
+
showArrows={settings.arrows}
|
|
133
|
+
paddingX={settings.paddingX}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
</Box>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export default Slider;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
|
|
3
|
+
export interface SliderConfig {
|
|
4
|
+
slidesToShow?: number;
|
|
5
|
+
slidesToScroll?: number;
|
|
6
|
+
infinite?: boolean;
|
|
7
|
+
dots?: boolean;
|
|
8
|
+
arrows?: boolean;
|
|
9
|
+
spacing?: number;
|
|
10
|
+
paddingX?: number;
|
|
11
|
+
paddingY?: number;
|
|
12
|
+
maxItemWidth?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ResponsiveConfig {
|
|
16
|
+
breakpoint: number;
|
|
17
|
+
settings: SliderConfig;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const useSlider = (
|
|
21
|
+
data: any[],
|
|
22
|
+
config: SliderConfig = {},
|
|
23
|
+
responsiveConfig: ResponsiveConfig[] = [],
|
|
24
|
+
) => {
|
|
25
|
+
const [currentPage, setCurrentPage] = useState(0);
|
|
26
|
+
const [canGoNext, setCanGoNext] = useState(false);
|
|
27
|
+
const [canGoPrev, setCanGoPrev] = useState(false);
|
|
28
|
+
const [scrollTo, setScrollTo] = useState<boolean>(false);
|
|
29
|
+
const [resized, setResized] = useState<boolean>(false);
|
|
30
|
+
|
|
31
|
+
const [settings, setSettings] = useState({
|
|
32
|
+
slidesToShow: 1,
|
|
33
|
+
slidesToScroll: 1,
|
|
34
|
+
infinite: false,
|
|
35
|
+
dots: true,
|
|
36
|
+
arrows: true,
|
|
37
|
+
spacing: 0,
|
|
38
|
+
paddingX: 0,
|
|
39
|
+
paddingY: 0,
|
|
40
|
+
maxItemWidth: 300,
|
|
41
|
+
responsive: [],
|
|
42
|
+
...config,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const totalPages = Math.max(
|
|
46
|
+
1,
|
|
47
|
+
Math.ceil(data.length / settings.slidesToShow),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const nextSlide = () => {
|
|
51
|
+
if (!canGoNext) return;
|
|
52
|
+
setCurrentPage((prev) => Math.min(prev + 1, totalPages - 1));
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const prevSlide = () => {
|
|
56
|
+
if (!canGoPrev) return;
|
|
57
|
+
setCurrentPage((prev) => Math.max(prev - 1, 0));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (settings.infinite) {
|
|
62
|
+
setCanGoNext(true);
|
|
63
|
+
setCanGoPrev(true);
|
|
64
|
+
} else {
|
|
65
|
+
setCanGoNext(currentPage < totalPages - 1);
|
|
66
|
+
setCanGoPrev(currentPage > 0);
|
|
67
|
+
}
|
|
68
|
+
}, [
|
|
69
|
+
settings.infinite,
|
|
70
|
+
settings.slidesToShow,
|
|
71
|
+
currentPage,
|
|
72
|
+
data.length,
|
|
73
|
+
totalPages,
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
const currentSlide = currentPage * settings.slidesToShow;
|
|
77
|
+
const slideWidth = (100 * data.length) / settings.slidesToShow;
|
|
78
|
+
|
|
79
|
+
const handleResize = () => {
|
|
80
|
+
const width = window.innerWidth;
|
|
81
|
+
if (responsiveConfig.length === 0) return;
|
|
82
|
+
|
|
83
|
+
const responsiveSettings = responsiveConfig.find(
|
|
84
|
+
(item) => item.breakpoint < width,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (responsiveSettings) {
|
|
88
|
+
setSettings({
|
|
89
|
+
...settings,
|
|
90
|
+
...responsiveSettings.settings,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!resized) {
|
|
97
|
+
handleResize();
|
|
98
|
+
setResized(true);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
}, [resized]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
window.addEventListener("resize", handleResize);
|
|
105
|
+
return () => {
|
|
106
|
+
window.removeEventListener("resize", handleResize);
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
currentSlide,
|
|
112
|
+
currentPage,
|
|
113
|
+
canGoNext,
|
|
114
|
+
canGoPrev,
|
|
115
|
+
scrollTo,
|
|
116
|
+
settings,
|
|
117
|
+
slideWidth,
|
|
118
|
+
totalPages,
|
|
119
|
+
setScrollTo,
|
|
120
|
+
setCurrentPage,
|
|
121
|
+
nextSlide,
|
|
122
|
+
prevSlide,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default useSlider;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Box } from "@/design-system/elements";
|
|
3
|
+
import type { ImageType } from "@/design-system/types";
|
|
4
|
+
import { motion, AnimatePresence } from "motion/react";
|
|
5
|
+
import { useState, useEffect } from "react";
|
|
6
|
+
|
|
7
|
+
export const wrap = (min: number, max: number, v: number) => {
|
|
8
|
+
const rangeSize = max - min;
|
|
9
|
+
return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type SlideshowProps = {
|
|
13
|
+
photos: ImageType[];
|
|
14
|
+
showProgress?: boolean;
|
|
15
|
+
duration?: number;
|
|
16
|
+
fade?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const imageVariants = {
|
|
20
|
+
enter: { opacity: 0, scale: 1.04, y: 10, transition: { duration: 0.6 } },
|
|
21
|
+
center: { opacity: 1, scale: 1, y: 0, transition: { duration: 0.6 } },
|
|
22
|
+
exit: { opacity: 0, scale: 1.04, y: -10, transition: { duration: 0.6 } },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const controlVariants = {
|
|
26
|
+
enter: { opacity: 0.2, width: ".5rem" },
|
|
27
|
+
center: { opacity: 0.7, width: "2rem" },
|
|
28
|
+
exit: { opacity: 0.1, width: ".5rem" },
|
|
29
|
+
hover: { opacity: 1, scale: 1.1 },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function Slideshow({
|
|
33
|
+
photos,
|
|
34
|
+
showProgress,
|
|
35
|
+
duration = 3,
|
|
36
|
+
fade = true,
|
|
37
|
+
}: SlideshowProps) {
|
|
38
|
+
const [page, setPage] = useState(0);
|
|
39
|
+
const [autoplayKey, setAutoplayKey] = useState(0);
|
|
40
|
+
const [mounted, setMounted] = useState(false);
|
|
41
|
+
const [instanceId] = useState(() => Math.random().toString(36).substr(2, 9)); // Prevents SSR mismatches
|
|
42
|
+
|
|
43
|
+
useEffect(() => setMounted(true), []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (photos.length > 1) {
|
|
47
|
+
const timer = setTimeout(
|
|
48
|
+
() => setPage((prev) => (prev + 1) % photos.length),
|
|
49
|
+
duration * 1000
|
|
50
|
+
);
|
|
51
|
+
return () => clearTimeout(timer);
|
|
52
|
+
}
|
|
53
|
+
}, [page, photos, duration, autoplayKey]);
|
|
54
|
+
|
|
55
|
+
if (!photos || photos.length === 0) {
|
|
56
|
+
return (
|
|
57
|
+
<Box
|
|
58
|
+
width="100%"
|
|
59
|
+
height="100%"
|
|
60
|
+
bg="base"
|
|
61
|
+
shape="rounded"
|
|
62
|
+
display="flex"
|
|
63
|
+
alignItems="center"
|
|
64
|
+
justifyContent="center"
|
|
65
|
+
>
|
|
66
|
+
No images available
|
|
67
|
+
</Box>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (photos.length === 1) {
|
|
72
|
+
return (
|
|
73
|
+
<Box
|
|
74
|
+
as={motion.div}
|
|
75
|
+
variants={imageVariants}
|
|
76
|
+
initial="enter"
|
|
77
|
+
animate="center"
|
|
78
|
+
exit="exit"
|
|
79
|
+
whileHover="initial"
|
|
80
|
+
width="100%"
|
|
81
|
+
height="100%"
|
|
82
|
+
position="absolute"
|
|
83
|
+
maxWidth="100%"
|
|
84
|
+
top="0"
|
|
85
|
+
left="0"
|
|
86
|
+
shape="rounded"
|
|
87
|
+
zIndex={1}
|
|
88
|
+
background={`url(${photos[0].url}) no-repeat center center / cover`}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const results = photos;
|
|
94
|
+
const dataIndex = wrap(0, results.length, page);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Box
|
|
98
|
+
as={motion.div}
|
|
99
|
+
overflow="hidden"
|
|
100
|
+
position="relative"
|
|
101
|
+
width="100%"
|
|
102
|
+
height="100%"
|
|
103
|
+
bg="base"
|
|
104
|
+
shape="rounded"
|
|
105
|
+
>
|
|
106
|
+
{fade && (
|
|
107
|
+
<Box
|
|
108
|
+
as={motion.div}
|
|
109
|
+
position="absolute"
|
|
110
|
+
width="100%"
|
|
111
|
+
height="100%"
|
|
112
|
+
top="0"
|
|
113
|
+
zIndex={2}
|
|
114
|
+
backgroundImage="linear-gradient(to bottom, transparent, currentColor 100%)"
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
<AnimatePresence>
|
|
119
|
+
{mounted && showProgress && (
|
|
120
|
+
<Box
|
|
121
|
+
position="absolute"
|
|
122
|
+
width="100%"
|
|
123
|
+
bottom="0"
|
|
124
|
+
left="0"
|
|
125
|
+
zIndex={2}
|
|
126
|
+
display="flex"
|
|
127
|
+
alignItems="center"
|
|
128
|
+
justifyContent="center"
|
|
129
|
+
gap="xsmall"
|
|
130
|
+
p="large"
|
|
131
|
+
backgroundImage="linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.6))"
|
|
132
|
+
>
|
|
133
|
+
{results.map((_, i) => (
|
|
134
|
+
<Box
|
|
135
|
+
as={motion.div}
|
|
136
|
+
key={i}
|
|
137
|
+
variants={controlVariants}
|
|
138
|
+
initial="enter"
|
|
139
|
+
animate={i === dataIndex ? "center" : "none"}
|
|
140
|
+
whileHover="hover"
|
|
141
|
+
exit="exit"
|
|
142
|
+
flex="none"
|
|
143
|
+
size="0.5rem"
|
|
144
|
+
bg="white"
|
|
145
|
+
shape="rounded"
|
|
146
|
+
cursor="pointer"
|
|
147
|
+
onClick={() => {
|
|
148
|
+
setPage(i);
|
|
149
|
+
setAutoplayKey((prev) => prev + 1);
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
))}
|
|
153
|
+
</Box>
|
|
154
|
+
)}
|
|
155
|
+
<Box
|
|
156
|
+
as={motion.div}
|
|
157
|
+
key={`slideshow-${page}-${instanceId}`}
|
|
158
|
+
variants={imageVariants}
|
|
159
|
+
initial="enter"
|
|
160
|
+
animate="center"
|
|
161
|
+
exit="exit"
|
|
162
|
+
whileHover="initial"
|
|
163
|
+
width="100%"
|
|
164
|
+
height="100%"
|
|
165
|
+
position="absolute"
|
|
166
|
+
maxWidth="100%"
|
|
167
|
+
top="0"
|
|
168
|
+
left="0"
|
|
169
|
+
zIndex={1}
|
|
170
|
+
background={`url(${
|
|
171
|
+
results[dataIndex].url || results[0].url
|
|
172
|
+
}) no-repeat center center / cover`}
|
|
173
|
+
/>
|
|
174
|
+
</AnimatePresence>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRef, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
motion,
|
|
5
|
+
useMotionTemplate,
|
|
6
|
+
useMotionValue,
|
|
7
|
+
useSpring,
|
|
8
|
+
} from "motion/react";
|
|
9
|
+
import Box, { BoxProps } from "@/design-system/elements/box";
|
|
10
|
+
|
|
11
|
+
interface SpotlightProps extends Omit<BoxProps, "children"> {
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
aspect?: number | number[] | string[] | {};
|
|
14
|
+
spotlightColor?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function Spotlight({
|
|
18
|
+
children,
|
|
19
|
+
aspect = "4/5",
|
|
20
|
+
spotlightColor = "transparent.brand.1",
|
|
21
|
+
...props
|
|
22
|
+
}: SpotlightProps) {
|
|
23
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
24
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
25
|
+
|
|
26
|
+
// Spotlight values with springs for smoother animation
|
|
27
|
+
const spotlightX = useSpring(useMotionValue(0));
|
|
28
|
+
const spotlightY = useSpring(useMotionValue(0));
|
|
29
|
+
|
|
30
|
+
// Tilt values with adjusted spring configuration
|
|
31
|
+
const tiltX = useMotionValue(0);
|
|
32
|
+
const tiltY = useMotionValue(0);
|
|
33
|
+
const tiltXSpring = useSpring(tiltX, {
|
|
34
|
+
stiffness: 300,
|
|
35
|
+
damping: 30,
|
|
36
|
+
});
|
|
37
|
+
const tiltYSpring = useSpring(tiltY, {
|
|
38
|
+
stiffness: 300,
|
|
39
|
+
damping: 30,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const transform = useMotionTemplate`perspective(1000px) rotateX(${tiltXSpring}deg) rotateY(${tiltYSpring}deg)`;
|
|
43
|
+
|
|
44
|
+
function handleMouseMove(e: React.MouseEvent<HTMLDivElement>) {
|
|
45
|
+
if (!ref.current) return;
|
|
46
|
+
|
|
47
|
+
const rect = ref.current.getBoundingClientRect();
|
|
48
|
+
|
|
49
|
+
// Calculate mouse position relative to card center
|
|
50
|
+
const centerX = rect.left + rect.width / 2;
|
|
51
|
+
const centerY = rect.top + rect.height / 2;
|
|
52
|
+
const mouseX = e.clientX - centerX;
|
|
53
|
+
const mouseY = e.clientY - centerY;
|
|
54
|
+
|
|
55
|
+
// Update spotlight position
|
|
56
|
+
spotlightX.set(e.clientX - rect.left);
|
|
57
|
+
spotlightY.set(e.clientY - rect.top);
|
|
58
|
+
|
|
59
|
+
// Calculate rotation (reduced range for subtler effect)
|
|
60
|
+
const rotateX = (mouseY / (rect.height / 2)) * -5;
|
|
61
|
+
const rotateY = (mouseX / (rect.width / 2)) * 5;
|
|
62
|
+
|
|
63
|
+
tiltX.set(rotateX);
|
|
64
|
+
tiltY.set(rotateY);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleMouseEnter() {
|
|
68
|
+
setIsHovered(true);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleMouseLeave() {
|
|
72
|
+
setIsHovered(false);
|
|
73
|
+
// Reset tilt with animation
|
|
74
|
+
tiltX.set(0);
|
|
75
|
+
tiltY.set(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Box
|
|
80
|
+
as={motion.div}
|
|
81
|
+
ref={ref}
|
|
82
|
+
onMouseMove={handleMouseMove}
|
|
83
|
+
onMouseEnter={handleMouseEnter}
|
|
84
|
+
onMouseLeave={handleMouseLeave}
|
|
85
|
+
position={"relative"}
|
|
86
|
+
style={{
|
|
87
|
+
transformStyle: "preserve-3d",
|
|
88
|
+
transform,
|
|
89
|
+
transition: "all 0.2s ease-out",
|
|
90
|
+
transformOrigin: "center center",
|
|
91
|
+
aspectRatio: aspect,
|
|
92
|
+
}}
|
|
93
|
+
border="1px solid"
|
|
94
|
+
skin="card"
|
|
95
|
+
shape="rounded"
|
|
96
|
+
borderColor="transparent.light.2"
|
|
97
|
+
p="medium"
|
|
98
|
+
$shadow="medium"
|
|
99
|
+
overflow="hidden"
|
|
100
|
+
{...props}
|
|
101
|
+
>
|
|
102
|
+
{/* Spotlight effect */}
|
|
103
|
+
<Box
|
|
104
|
+
as={motion.div}
|
|
105
|
+
position={"absolute"}
|
|
106
|
+
width="100%"
|
|
107
|
+
height="100%"
|
|
108
|
+
top="0"
|
|
109
|
+
left="0"
|
|
110
|
+
opacity={isHovered ? 1 : 0}
|
|
111
|
+
zIndex={0}
|
|
112
|
+
color={spotlightColor}
|
|
113
|
+
style={{
|
|
114
|
+
pointerEvents: "none",
|
|
115
|
+
transition: "opacity 0.2s ease-out",
|
|
116
|
+
background: useMotionTemplate`
|
|
117
|
+
radial-gradient(
|
|
118
|
+
300px circle at ${spotlightX}px ${spotlightY}px,
|
|
119
|
+
currentColor,
|
|
120
|
+
transparent 80%
|
|
121
|
+
)
|
|
122
|
+
`,
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
|
|
126
|
+
{/* Content */}
|
|
127
|
+
<Box
|
|
128
|
+
as={motion.div}
|
|
129
|
+
position={"relative"}
|
|
130
|
+
height={"100%"}
|
|
131
|
+
width={"100%"}
|
|
132
|
+
zIndex={1}
|
|
133
|
+
style={{
|
|
134
|
+
transform: isHovered ? "translateZ(48px)" : "translateZ(0px)",
|
|
135
|
+
scale: isHovered ? 1.04 : 1,
|
|
136
|
+
transition: "transform 0.2s ease-out, scale 0.2s ease-out",
|
|
137
|
+
transformStyle: "preserve-3d",
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</Box>
|
|
142
|
+
</Box>
|
|
143
|
+
);
|
|
144
|
+
}
|