@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,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
+ }