@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,206 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Box, Button, Text } from "@/design-system/elements";
|
|
4
|
+
import { AnimatePresence, HTMLMotionProps, motion } from "motion/react";
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
import { useState } from "react";
|
|
7
|
+
|
|
8
|
+
const ANIMATION_DELAY = 0.3;
|
|
9
|
+
|
|
10
|
+
const InfoCard = ({
|
|
11
|
+
tintColor = "#000000",
|
|
12
|
+
title,
|
|
13
|
+
description = "",
|
|
14
|
+
detailContent = "",
|
|
15
|
+
imageSrc,
|
|
16
|
+
imageAlt,
|
|
17
|
+
imageSizes = "100vh",
|
|
18
|
+
children,
|
|
19
|
+
...props
|
|
20
|
+
}: {
|
|
21
|
+
tintColor?: string;
|
|
22
|
+
title: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
detailContent?: string;
|
|
25
|
+
imageSrc?: string;
|
|
26
|
+
imageAlt?: string;
|
|
27
|
+
imageSizes?: string;
|
|
28
|
+
children?: React.ReactNode;
|
|
29
|
+
} & HTMLMotionProps<"div">) => {
|
|
30
|
+
const [showDetail, setShowDetail] = useState(false);
|
|
31
|
+
const hasDetail = detailContent.length > 0;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Box
|
|
35
|
+
as={motion.div}
|
|
36
|
+
position={"relative"}
|
|
37
|
+
display={"flex"}
|
|
38
|
+
flexDirection={"column"}
|
|
39
|
+
gap={2}
|
|
40
|
+
aspect="4/3"
|
|
41
|
+
//minHeight={"400px"}
|
|
42
|
+
shape="rounded"
|
|
43
|
+
initial={false}
|
|
44
|
+
overflow={"hidden"}
|
|
45
|
+
animate={showDetail ? "detail" : "main"}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<Box position={"relative"} zIndex={"2"} p="large">
|
|
49
|
+
<Box
|
|
50
|
+
position={"absolute"}
|
|
51
|
+
top={0}
|
|
52
|
+
left={0}
|
|
53
|
+
width={"100%"}
|
|
54
|
+
height={"100%"}
|
|
55
|
+
zIndex={-1}
|
|
56
|
+
style={{
|
|
57
|
+
backdropFilter: "blur(8px)",
|
|
58
|
+
mask: "linear-gradient(rgb(0, 0, 0) 70%, rgba(0, 0, 0, 0) 100%)",
|
|
59
|
+
}}
|
|
60
|
+
></Box>
|
|
61
|
+
<Text
|
|
62
|
+
as={motion.h2}
|
|
63
|
+
fontSize={"xl"}
|
|
64
|
+
m="0"
|
|
65
|
+
fontWeight={"medium"}
|
|
66
|
+
color="primary"
|
|
67
|
+
>
|
|
68
|
+
{title}
|
|
69
|
+
</Text>
|
|
70
|
+
<Text>{description}</Text>
|
|
71
|
+
</Box>
|
|
72
|
+
{children ??
|
|
73
|
+
(imageSrc ? (
|
|
74
|
+
<Box
|
|
75
|
+
as={Image}
|
|
76
|
+
src={imageSrc}
|
|
77
|
+
alt={imageAlt ?? title}
|
|
78
|
+
fill
|
|
79
|
+
sizes={imageSizes}
|
|
80
|
+
zIndex={0}
|
|
81
|
+
style={{ objectFit: "cover" }}
|
|
82
|
+
/>
|
|
83
|
+
) : null)}
|
|
84
|
+
|
|
85
|
+
{hasDetail && (
|
|
86
|
+
<ToggleButton
|
|
87
|
+
tintColor={tintColor}
|
|
88
|
+
onClick={() => setShowDetail(!showDetail)}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
<AnimatePresence>
|
|
92
|
+
{showDetail && (
|
|
93
|
+
<DetailContainer tintColor={tintColor} content={detailContent} />
|
|
94
|
+
)}
|
|
95
|
+
</AnimatePresence>
|
|
96
|
+
</Box>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const DetailContainer = ({
|
|
101
|
+
tintColor,
|
|
102
|
+
content,
|
|
103
|
+
}: {
|
|
104
|
+
tintColor: string;
|
|
105
|
+
content: string;
|
|
106
|
+
}) => {
|
|
107
|
+
const animationVariants = {
|
|
108
|
+
container: {
|
|
109
|
+
initial: { opacity: 0 },
|
|
110
|
+
animate: { opacity: 1 },
|
|
111
|
+
exit: { opacity: 0, transition: { delay: ANIMATION_DELAY } },
|
|
112
|
+
},
|
|
113
|
+
content: {
|
|
114
|
+
initial: { y: -100, opacity: 0 },
|
|
115
|
+
animate: {
|
|
116
|
+
y: 0,
|
|
117
|
+
opacity: 1,
|
|
118
|
+
transition: { delay: ANIMATION_DELAY, bounce: 0 },
|
|
119
|
+
},
|
|
120
|
+
exit: { y: -100, opacity: 0 },
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Box
|
|
126
|
+
as={motion.div}
|
|
127
|
+
position={"absolute"}
|
|
128
|
+
display={"flex"}
|
|
129
|
+
flexDirection={"column"}
|
|
130
|
+
justifyContent={"center"}
|
|
131
|
+
width={"100%"}
|
|
132
|
+
height={"100%"}
|
|
133
|
+
top="0"
|
|
134
|
+
left="0"
|
|
135
|
+
zIndex={"2"}
|
|
136
|
+
p={"medium"}
|
|
137
|
+
bg={tintColor}
|
|
138
|
+
initial="initial"
|
|
139
|
+
animate="animate"
|
|
140
|
+
exit="exit"
|
|
141
|
+
variants={animationVariants["container"]}
|
|
142
|
+
>
|
|
143
|
+
<Text
|
|
144
|
+
as={motion.p}
|
|
145
|
+
maxWidth={"80ch"}
|
|
146
|
+
mx={"auto"}
|
|
147
|
+
color={"white"}
|
|
148
|
+
fontSize={"large"}
|
|
149
|
+
fontWeight={"medium"}
|
|
150
|
+
variants={animationVariants["content"]}
|
|
151
|
+
>
|
|
152
|
+
{content}
|
|
153
|
+
</Text>
|
|
154
|
+
</Box>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const ToggleButton = ({
|
|
159
|
+
tintColor,
|
|
160
|
+
...props
|
|
161
|
+
}: { tintColor: string } & HTMLMotionProps<"button">) => {
|
|
162
|
+
const animationVariants = {
|
|
163
|
+
main: {
|
|
164
|
+
rotate: 0,
|
|
165
|
+
backgroundColor: "#000000",
|
|
166
|
+
stroke: "#ffffff",
|
|
167
|
+
},
|
|
168
|
+
detail: {
|
|
169
|
+
rotate: 45,
|
|
170
|
+
backgroundColor: "#ffffff",
|
|
171
|
+
stroke: tintColor,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Button
|
|
177
|
+
as={motion.button}
|
|
178
|
+
position={"absolute"}
|
|
179
|
+
right={"1rem"}
|
|
180
|
+
bottom={"1rem"}
|
|
181
|
+
zIndex={"3"}
|
|
182
|
+
$size="icon.large"
|
|
183
|
+
shape="circle"
|
|
184
|
+
variants={animationVariants}
|
|
185
|
+
transition={{
|
|
186
|
+
bounce: 0,
|
|
187
|
+
}}
|
|
188
|
+
{...props}
|
|
189
|
+
>
|
|
190
|
+
<svg
|
|
191
|
+
viewBox="0 0 24 24"
|
|
192
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
193
|
+
width={"1.5rem"}
|
|
194
|
+
height={"1.5rem"}
|
|
195
|
+
>
|
|
196
|
+
<path
|
|
197
|
+
strokeLinecap="round"
|
|
198
|
+
strokeLinejoin="round"
|
|
199
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
200
|
+
/>
|
|
201
|
+
</svg>
|
|
202
|
+
</Button>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export default InfoCard;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
4
|
+
import { Box, Text } from "@/design-system/elements";
|
|
5
|
+
import Image from "next/image";
|
|
6
|
+
|
|
7
|
+
interface TickerCardProps {
|
|
8
|
+
image?: string;
|
|
9
|
+
width?: number | string;
|
|
10
|
+
height?: number | string;
|
|
11
|
+
overlayContent?: React.ReactNode | string;
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function TickerCard({
|
|
17
|
+
image,
|
|
18
|
+
overlayContent = "Explore Now",
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}: TickerCardProps) {
|
|
22
|
+
const [showOverlay, setShowOverlay] = React.useState(false);
|
|
23
|
+
|
|
24
|
+
const renderOverlayContent = () => {
|
|
25
|
+
if (React.isValidElement(overlayContent)) {
|
|
26
|
+
return overlayContent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Box
|
|
31
|
+
as={motion.div}
|
|
32
|
+
backgroundColor="white"
|
|
33
|
+
paddingX={3}
|
|
34
|
+
paddingY={2}
|
|
35
|
+
borderRadius="full"
|
|
36
|
+
display="flex"
|
|
37
|
+
alignItems="center"
|
|
38
|
+
gap="4px"
|
|
39
|
+
initial={{ y: 10 }}
|
|
40
|
+
animate={{ y: 0 }}
|
|
41
|
+
exit={{ y: 10 }}
|
|
42
|
+
>
|
|
43
|
+
<Text fontSize="sm" fontWeight="semibold">
|
|
44
|
+
{overlayContent}
|
|
45
|
+
</Text>
|
|
46
|
+
</Box>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Box
|
|
52
|
+
as={motion.div}
|
|
53
|
+
position="relative"
|
|
54
|
+
overflow="hidden"
|
|
55
|
+
aspect={"1/1"}
|
|
56
|
+
display="flex"
|
|
57
|
+
justifyContent="center"
|
|
58
|
+
alignItems="center"
|
|
59
|
+
onHoverStart={() => setShowOverlay(true)}
|
|
60
|
+
onHoverEnd={() => setShowOverlay(false)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{/* Children content in overlay */}
|
|
64
|
+
{children}
|
|
65
|
+
|
|
66
|
+
<AnimatePresence>
|
|
67
|
+
{showOverlay && (
|
|
68
|
+
<Box
|
|
69
|
+
as={motion.div}
|
|
70
|
+
position="absolute"
|
|
71
|
+
left={0}
|
|
72
|
+
top={0}
|
|
73
|
+
bottom={0}
|
|
74
|
+
right={0}
|
|
75
|
+
zIndex={10}
|
|
76
|
+
display="flex"
|
|
77
|
+
justifyContent="center"
|
|
78
|
+
alignItems="center"
|
|
79
|
+
initial={{ opacity: 0 }}
|
|
80
|
+
animate={{ opacity: 1 }}
|
|
81
|
+
exit={{ opacity: 0 }}
|
|
82
|
+
>
|
|
83
|
+
{/* Overlay background */}
|
|
84
|
+
<Box
|
|
85
|
+
position="absolute"
|
|
86
|
+
bg="black"
|
|
87
|
+
opacity={0.7}
|
|
88
|
+
height="100%"
|
|
89
|
+
width="100%"
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
{/* Overlay Content */}
|
|
93
|
+
<Box
|
|
94
|
+
position="relative"
|
|
95
|
+
zIndex={10}
|
|
96
|
+
display="flex"
|
|
97
|
+
flexDirection="column"
|
|
98
|
+
alignItems="center"
|
|
99
|
+
gap={2}
|
|
100
|
+
>
|
|
101
|
+
{renderOverlayContent()}
|
|
102
|
+
</Box>
|
|
103
|
+
</Box>
|
|
104
|
+
)}
|
|
105
|
+
</AnimatePresence>
|
|
106
|
+
|
|
107
|
+
<Box
|
|
108
|
+
position="absolute"
|
|
109
|
+
width="100%"
|
|
110
|
+
height="100%"
|
|
111
|
+
zIndex={0}
|
|
112
|
+
top="0"
|
|
113
|
+
left="0"
|
|
114
|
+
>
|
|
115
|
+
<Image
|
|
116
|
+
src={image as string}
|
|
117
|
+
alt="image"
|
|
118
|
+
fill
|
|
119
|
+
sizes="100vh"
|
|
120
|
+
style={{ objectFit: "cover" }}
|
|
121
|
+
/>
|
|
122
|
+
</Box>
|
|
123
|
+
</Box>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useRef, useState } from "react";
|
|
4
|
+
import styled from "styled-components";
|
|
5
|
+
import { AnimatePresence, useScroll, useMotionValueEvent } from "motion/react";
|
|
6
|
+
import { animate } from "motion";
|
|
7
|
+
import { Box } from "@/design-system/elements";
|
|
8
|
+
import { ControlLeft, ControlRight } from "../Controls/Control";
|
|
9
|
+
import Group from "../Group";
|
|
10
|
+
|
|
11
|
+
interface CarouselProps {
|
|
12
|
+
items: React.ReactNode[];
|
|
13
|
+
gap?: string | number;
|
|
14
|
+
onScroll?: (index: number) => void;
|
|
15
|
+
showScrollbar?: boolean;
|
|
16
|
+
showArrows?: boolean;
|
|
17
|
+
containerProps?: { [key: string]: any };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SCROLL_PADDING_REM = 0.5;
|
|
21
|
+
const SCROLL_PADDING_PX = SCROLL_PADDING_REM * 16;
|
|
22
|
+
const GAP_PX = 16;
|
|
23
|
+
const SCROLL_AMOUNT = 300;
|
|
24
|
+
const THRESHOLD_RATIO = 0.5;
|
|
25
|
+
|
|
26
|
+
const ScrollContainerStyled = styled(Box)<{
|
|
27
|
+
$showScrollbar?: boolean;
|
|
28
|
+
}>`
|
|
29
|
+
display: flex;
|
|
30
|
+
width: 100%;
|
|
31
|
+
overflow-x: auto;
|
|
32
|
+
scroll-snap-type: x mandatory;
|
|
33
|
+
scroll-behavior: smooth;
|
|
34
|
+
scroll-padding: ${SCROLL_PADDING_REM}rem;
|
|
35
|
+
-webkit-overflow-scrolling: touch;
|
|
36
|
+
scrollbar-width: ${(props) => (props.$showScrollbar ? "auto" : "none")};
|
|
37
|
+
|
|
38
|
+
&::-webkit-scrollbar {
|
|
39
|
+
height: 8px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&::-webkit-scrollbar-track {
|
|
43
|
+
background: transparent;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&::-webkit-scrollbar-thumb {
|
|
47
|
+
background: rgba(0, 0, 0, 0.2);
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
|
|
50
|
+
&:hover {
|
|
51
|
+
background: rgba(0, 0, 0, 0.3);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
${(props) =>
|
|
56
|
+
!props.$showScrollbar &&
|
|
57
|
+
`
|
|
58
|
+
-ms-overflow-style: none;
|
|
59
|
+
&::-webkit-scrollbar {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
`}
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const SnapItem = styled(Box)`
|
|
66
|
+
display: flex;
|
|
67
|
+
scroll-snap-align: start;
|
|
68
|
+
scroll-snap-stop: always;
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
min-width: fit-content;
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
const Carousel = ({
|
|
74
|
+
items,
|
|
75
|
+
gap = "s",
|
|
76
|
+
onScroll,
|
|
77
|
+
showScrollbar = false,
|
|
78
|
+
showArrows = true,
|
|
79
|
+
containerProps,
|
|
80
|
+
}: CarouselProps) => {
|
|
81
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
82
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
83
|
+
const { scrollX } = useScroll({ container: containerRef });
|
|
84
|
+
|
|
85
|
+
// Reset carousel scroll position on mount using Motion's animate API
|
|
86
|
+
// This uses Motion's animate() to set scrollLeft to 0; duration 0 keeps it instantaneous.
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
const el = containerRef.current;
|
|
89
|
+
if (!el) return;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Use Motion's animate to reset scrollLeft on the element
|
|
93
|
+
// duration: 0 ensures it's an immediate reset (no visible animation)
|
|
94
|
+
animate(el, { scrollLeft: 0 }, { duration: 0 });
|
|
95
|
+
} catch {
|
|
96
|
+
// Fallback to direct assignment if animate is unavailable
|
|
97
|
+
el.scrollLeft = 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Prevent browser scroll restoration from interfering while mounted.
|
|
101
|
+
// Store previous value and restore on cleanup.
|
|
102
|
+
const prev =
|
|
103
|
+
typeof window !== "undefined" && "history" in window
|
|
104
|
+
? (window.history as any).scrollRestoration
|
|
105
|
+
: undefined;
|
|
106
|
+
if (typeof window !== "undefined" && "history" in window) {
|
|
107
|
+
try {
|
|
108
|
+
(window.history as any).scrollRestoration = "manual";
|
|
109
|
+
} catch {
|
|
110
|
+
/* ignore */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
if (
|
|
116
|
+
typeof window !== "undefined" &&
|
|
117
|
+
"history" in window &&
|
|
118
|
+
prev !== undefined
|
|
119
|
+
) {
|
|
120
|
+
try {
|
|
121
|
+
(window.history as any).scrollRestoration = prev;
|
|
122
|
+
} catch {
|
|
123
|
+
/* ignore */
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
useMotionValueEvent(scrollX, "change", (latest) => {
|
|
130
|
+
const container = containerRef.current;
|
|
131
|
+
if (!container) return;
|
|
132
|
+
|
|
133
|
+
const itemWidth =
|
|
134
|
+
container.firstElementChild?.getBoundingClientRect().width || 0;
|
|
135
|
+
if (itemWidth <= 0) return;
|
|
136
|
+
|
|
137
|
+
const adjustedScrollLeft = Math.max(0, latest - SCROLL_PADDING_PX);
|
|
138
|
+
const threshold = itemWidth * THRESHOLD_RATIO;
|
|
139
|
+
const newIndex =
|
|
140
|
+
adjustedScrollLeft > threshold
|
|
141
|
+
? Math.floor((adjustedScrollLeft - threshold) / (itemWidth + GAP_PX)) +
|
|
142
|
+
1
|
|
143
|
+
: 0;
|
|
144
|
+
|
|
145
|
+
setCurrentIndex(newIndex);
|
|
146
|
+
onScroll?.(newIndex);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const scroll = (direction: "left" | "right") => {
|
|
150
|
+
if (!containerRef.current) return;
|
|
151
|
+
containerRef.current.scrollBy({
|
|
152
|
+
left: direction === "left" ? -SCROLL_AMOUNT : SCROLL_AMOUNT,
|
|
153
|
+
behavior: "smooth",
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<Box display="flex" flexDirection="column" gap="s" width="100%">
|
|
159
|
+
<ScrollContainerStyled
|
|
160
|
+
ref={containerRef}
|
|
161
|
+
gap={gap}
|
|
162
|
+
p="xs"
|
|
163
|
+
{...containerProps}
|
|
164
|
+
$showScrollbar={showScrollbar}
|
|
165
|
+
>
|
|
166
|
+
{items.map((item, index) => (
|
|
167
|
+
<SnapItem key={index}>{item}</SnapItem>
|
|
168
|
+
))}
|
|
169
|
+
</ScrollContainerStyled>
|
|
170
|
+
|
|
171
|
+
{showArrows && items.length > 1 && (
|
|
172
|
+
<Group
|
|
173
|
+
justifyContent="end"
|
|
174
|
+
gap="m"
|
|
175
|
+
position="relative"
|
|
176
|
+
width="100%"
|
|
177
|
+
height="2.5rem"
|
|
178
|
+
pb="s"
|
|
179
|
+
>
|
|
180
|
+
<AnimatePresence mode="sync" initial={false}>
|
|
181
|
+
{currentIndex > 0 && (
|
|
182
|
+
<ControlLeft
|
|
183
|
+
key="control-left"
|
|
184
|
+
initial={{ opacity: 0 }}
|
|
185
|
+
animate={{ opacity: 0.8 }}
|
|
186
|
+
exit={{ opacity: 0, pointerEvents: "none" }}
|
|
187
|
+
whileTap={{ opacity: 1, scale: 0.92 }}
|
|
188
|
+
onClick={() => scroll("left")}
|
|
189
|
+
position="relative"
|
|
190
|
+
top="auto"
|
|
191
|
+
left="auto"
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
<ControlRight
|
|
196
|
+
key="control-right"
|
|
197
|
+
initial={{ opacity: 0 }}
|
|
198
|
+
animate={{
|
|
199
|
+
opacity: currentIndex >= items.length - 1 ? 0.3 : 0.8,
|
|
200
|
+
}}
|
|
201
|
+
exit={{ opacity: 0, pointerEvents: "none" }}
|
|
202
|
+
whileTap={{ opacity: 1, scale: 0.92 }}
|
|
203
|
+
onClick={() => scroll("right")}
|
|
204
|
+
position="relative"
|
|
205
|
+
disabled={currentIndex === items.length - 1}
|
|
206
|
+
top="auto"
|
|
207
|
+
right="auto"
|
|
208
|
+
/>
|
|
209
|
+
</AnimatePresence>
|
|
210
|
+
</Group>
|
|
211
|
+
)}
|
|
212
|
+
</Box>
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export default Carousel;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { motion } from "motion/react"
|
|
5
|
+
import { Box } from "@/design-system/elements"
|
|
6
|
+
import Group from "@/design-system/blocks/Group"
|
|
7
|
+
import { IconDotsAnim } from "@/design-system/icons"
|
|
8
|
+
|
|
9
|
+
export interface CheckboxProps {
|
|
10
|
+
/** Whether the checkbox is checked */
|
|
11
|
+
checked?: boolean
|
|
12
|
+
/** Callback when the checkbox is toggled */
|
|
13
|
+
onChange?: () => void
|
|
14
|
+
/** Whether the checkbox is in a loading state */
|
|
15
|
+
isLoading?: boolean
|
|
16
|
+
/** Whether the checkbox is disabled */
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
/** Size of the checkbox (default: "1.375rem") */
|
|
19
|
+
size?: string
|
|
20
|
+
/** Title attribute for accessibility */
|
|
21
|
+
title?: string
|
|
22
|
+
/** Additional props for the container */
|
|
23
|
+
containerProps?: React.ComponentProps<typeof Group>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const tickVariants = {
|
|
27
|
+
checked: {
|
|
28
|
+
pathLength: 1,
|
|
29
|
+
opacity: 1,
|
|
30
|
+
transition: { duration: 0.2, delay: 0.2 },
|
|
31
|
+
},
|
|
32
|
+
unchecked: {
|
|
33
|
+
pathLength: 0,
|
|
34
|
+
opacity: 0,
|
|
35
|
+
transition: { duration: 0.2 },
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default function Checkbox({
|
|
40
|
+
checked = false,
|
|
41
|
+
onChange,
|
|
42
|
+
isLoading = false,
|
|
43
|
+
disabled = false,
|
|
44
|
+
size = "1.375rem",
|
|
45
|
+
title,
|
|
46
|
+
containerProps,
|
|
47
|
+
}: CheckboxProps) {
|
|
48
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
e.stopPropagation()
|
|
51
|
+
if (!isLoading && !disabled && onChange) {
|
|
52
|
+
onChange()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const resolvedTitle =
|
|
57
|
+
title ?? (checked ? "Mark as incomplete" : "Mark as complete")
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Group
|
|
61
|
+
as={motion.button}
|
|
62
|
+
size={size}
|
|
63
|
+
shape="rounded"
|
|
64
|
+
justifyContent="center"
|
|
65
|
+
border="1.5px solid"
|
|
66
|
+
skin={checked ? "success.static" : "panel"}
|
|
67
|
+
cursor={isLoading || disabled ? "not-allowed" : "pointer"}
|
|
68
|
+
onClick={handleClick}
|
|
69
|
+
disabled={isLoading || disabled}
|
|
70
|
+
title={resolvedTitle}
|
|
71
|
+
whileTap={{ scale: 0.9 }}
|
|
72
|
+
transition={{ duration: 0.15 }}
|
|
73
|
+
position="relative"
|
|
74
|
+
flex="none"
|
|
75
|
+
{...containerProps}
|
|
76
|
+
>
|
|
77
|
+
{isLoading && (
|
|
78
|
+
<Box as={IconDotsAnim} size=".875rem" position="absolute" />
|
|
79
|
+
)}
|
|
80
|
+
<Box
|
|
81
|
+
as={motion.svg}
|
|
82
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
83
|
+
fill="none"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
strokeWidth="3.5"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
size={"0.875rem"}
|
|
88
|
+
initial={false}
|
|
89
|
+
animate={checked ? "checked" : "unchecked"}
|
|
90
|
+
opacity={isLoading ? 0 : 1}
|
|
91
|
+
>
|
|
92
|
+
<motion.path
|
|
93
|
+
strokeLinecap="round"
|
|
94
|
+
strokeLinejoin="round"
|
|
95
|
+
d="M4.5 12.75l6 6 9-13.5"
|
|
96
|
+
variants={tickVariants}
|
|
97
|
+
/>
|
|
98
|
+
</Box>
|
|
99
|
+
</Group>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import Box, { BoxProps } from "@/design-system/elements/box";
|
|
3
|
+
import { ThemeContext } from "styled-components";
|
|
4
|
+
import { useContext } from "react";
|
|
5
|
+
|
|
6
|
+
type CollectionProps = BoxProps & {
|
|
7
|
+
colsize?: string;
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default function Collection({
|
|
13
|
+
colsize = "small",
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: CollectionProps) {
|
|
17
|
+
const theme: any = useContext(ThemeContext);
|
|
18
|
+
|
|
19
|
+
const colSizes: any = {
|
|
20
|
+
xsmall: {
|
|
21
|
+
_: "repeat(auto-fill,minmax(6rem,1fr))",
|
|
22
|
+
sm: "repeat(auto-fill,minmax(10rem,1fr))",
|
|
23
|
+
},
|
|
24
|
+
small: {
|
|
25
|
+
_: "repeat(auto-fill,minmax(10rem,1fr))",
|
|
26
|
+
md: "repeat(auto-fill,minmax(12rem,1fr))",
|
|
27
|
+
},
|
|
28
|
+
medium: {
|
|
29
|
+
_: "repeat(auto-fill,minmax(auto,1fr))",
|
|
30
|
+
md: "repeat(auto-fill,minmax(18rem,1fr))",
|
|
31
|
+
},
|
|
32
|
+
large: { _: "repeat(auto-fill,minmax(20rem,1fr))" },
|
|
33
|
+
xlarge: {
|
|
34
|
+
_: "repeat(auto-fill,minmax(20rem,1fr))",
|
|
35
|
+
sm: "repeat(auto-fill,minmax(25rem,1fr))",
|
|
36
|
+
},
|
|
37
|
+
xxlarge: {
|
|
38
|
+
_: "repeat(auto-fill,minmax(30rem,1fr))",
|
|
39
|
+
},
|
|
40
|
+
auto: {
|
|
41
|
+
_: "repeat(auto-fill,minmax(50cqb,1fr))",
|
|
42
|
+
},
|
|
43
|
+
row: {
|
|
44
|
+
_: "1fr",
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<Box
|
|
50
|
+
display="grid"
|
|
51
|
+
gap="small"
|
|
52
|
+
gridTemplateColumns={colSizes[colsize]}
|
|
53
|
+
{...theme?.collection}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{children}
|
|
57
|
+
</Box>
|
|
58
|
+
);
|
|
59
|
+
}
|