@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,257 @@
1
+ "use client";
2
+ import Image from "next/image";
3
+ import { AnimatePresence, motion, MotionConfig } from "motion/react";
4
+ import { useState } from "react";
5
+ import { variants } from "../utils/animationVariants";
6
+ import downloadPhoto from "../utils/downloadPhoto";
7
+ import { useSwipeable } from "react-swipeable";
8
+ import Box from "@/design-system/elements/box";
9
+ import {
10
+ IconArrowLeft,
11
+ IconArrowRight,
12
+ IconDownloadData,
13
+ IconLaunch,
14
+ } from "@/design-system/icons";
15
+ import { useKeypress } from "@/design-system/hooks/useKeypress";
16
+ import { Control } from "../../Controls/Control";
17
+
18
+ export default function Carousel({
19
+ photos,
20
+ current,
21
+ navigation = true,
22
+ }: {
23
+ photos: any;
24
+ current: number;
25
+ navigation?: boolean;
26
+ }) {
27
+ // Handle undefined or empty photos array
28
+ if (!photos || photos.length === 0) {
29
+ return null;
30
+ }
31
+
32
+ const [index, setIndex] = useState(Number(current));
33
+
34
+ const photo: any = photos[index];
35
+ const [direction, setDirection] = useState(0);
36
+ const filteredImages = photos;
37
+
38
+ // console.log(photo);
39
+
40
+ function changePhotoId(newVal: number) {
41
+ if (newVal > index) {
42
+ setDirection(1);
43
+ } else {
44
+ setDirection(-1);
45
+ }
46
+ setIndex(Number(newVal));
47
+ }
48
+
49
+ const handlers = useSwipeable({
50
+ onSwipedLeft: () => {
51
+ if (index < photos?.length - 1) {
52
+ changePhotoId(index + 1);
53
+ }
54
+ },
55
+ onSwipedRight: () => {
56
+ if (index > 0) {
57
+ changePhotoId(index - 1);
58
+ }
59
+ },
60
+ trackMouse: true,
61
+ });
62
+
63
+ useKeypress("ArrowRight", () => {
64
+ if (index + 1 < photos.length) {
65
+ changePhotoId(index + 1);
66
+ }
67
+ });
68
+
69
+ useKeypress("ArrowLeft", () => {
70
+ if (index > 0) {
71
+ changePhotoId(index - 1);
72
+ }
73
+ });
74
+
75
+ return (
76
+ <>
77
+ <MotionConfig
78
+ transition={{
79
+ x: { type: "spring", stiffness: 300, damping: 30 },
80
+ opacity: { duration: 0.2 },
81
+ }}
82
+ >
83
+ {/* Main image */}
84
+
85
+ <Box
86
+ position="relative"
87
+ aspect={3 / 2}
88
+ display="flex"
89
+ alignItems="center"
90
+ justifyContent="center"
91
+ width="100%"
92
+ maxWidth="80rem"
93
+ maxHeight="90vh"
94
+ mx="auto"
95
+ overflow="hidden"
96
+ {...handlers}
97
+ >
98
+ <AnimatePresence initial={false} custom={direction}>
99
+ <Box
100
+ as={motion.div}
101
+ key={photo.url}
102
+ initial="enter"
103
+ animate="center"
104
+ exit="exit"
105
+ custom={direction}
106
+ variants={variants}
107
+ position="absolute"
108
+ width="100%"
109
+ mx="auto"
110
+ aspect={1 / 1}
111
+ //onClick={() => router.push(`/gallery/photos/${photo.id}`)}
112
+ >
113
+ <Image
114
+ src={photo?.url}
115
+ fill
116
+ sizes="100vw"
117
+ style={{ objectFit: "contain" }}
118
+ priority
119
+ alt="Image"
120
+ />
121
+ </Box>
122
+ </AnimatePresence>
123
+ {/* Buttons */}
124
+
125
+ <Box as={motion.div}>
126
+ {navigation && (
127
+ <>
128
+ {index > 0 && (
129
+ <Control ml="small" onClick={() => changePhotoId(index - 1)}>
130
+ <Box as={IconArrowLeft} size="1.125rem" />
131
+ </Control>
132
+ )}
133
+ {index + 1 < photos.length && (
134
+ <Control
135
+ mr="small"
136
+ left="auto"
137
+ right="0"
138
+ onClick={() => changePhotoId(index + 1)}
139
+ >
140
+ <Box as={IconArrowRight} size="1.125rem" />
141
+ </Control>
142
+ )}
143
+ </>
144
+ )}
145
+ <Box
146
+ display="flex"
147
+ position="absolute"
148
+ alignItems={"center"}
149
+ justifyContent={"space-between"}
150
+ width={"100%"}
151
+ top="0"
152
+ right="0"
153
+ gap="xxsmall"
154
+ p="small"
155
+ //mr="small"
156
+ >
157
+ <Box color="white" flex="auto">
158
+ Title
159
+ </Box>
160
+ <Control
161
+ as={motion.a}
162
+ position={"static"}
163
+ href={photo.url}
164
+ target="_blank"
165
+ title="Open fullsize version"
166
+ rel="noreferrer"
167
+ >
168
+ <Box as={IconLaunch} size="1.125rem" />
169
+ </Control>
170
+
171
+ <Control
172
+ position={"static"}
173
+ onClick={() => downloadPhoto(`${photo.url}`, `${index}.jpg`)}
174
+ title="Download fullsize version"
175
+ >
176
+ <Box as={IconDownloadData} size="1.125rem" />
177
+ </Control>
178
+ </Box>
179
+ </Box>
180
+ </Box>
181
+
182
+ {navigation && (
183
+ <Box
184
+ position={"fixed"}
185
+ width={"100%"}
186
+ bottom="0"
187
+ left="0"
188
+ backgroundImage="linear-gradient(to bottom, rgba(0,0,0,0), rgba(0,0,0,0.6))"
189
+ height="6rem"
190
+ zIndex={40}
191
+ display={"flex"}
192
+ overflow={"hidden"}
193
+ >
194
+ <Box
195
+ as={motion.div}
196
+ initial={false}
197
+ aspect={3 / 2}
198
+ mx="auto"
199
+ my="small"
200
+ display={"flex"}
201
+ >
202
+ {/* Bottom Nav bar */}
203
+ <AnimatePresence initial={false}>
204
+ {filteredImages?.map((image: { url: string }, i: number) => (
205
+ <Box
206
+ as={motion.div}
207
+ key={image.url + i}
208
+ initial={{
209
+ width: "0%",
210
+ x: `${Math.max((index - 1) * -100, 15 * -100)}%`,
211
+ }}
212
+ exit={{ width: "0%" }}
213
+ whileHover={{ opacity: 1 }}
214
+ animate={{
215
+ scale: Number(i) === index ? 1.25 : 1,
216
+ zIndex: Number(i) === index ? 19 : 1,
217
+ opacity: Number(i) === index ? 1 : 0.9,
218
+ width: "100%",
219
+ x: `${Math.max(index * -100, 15 * -100)}%`,
220
+ }}
221
+ onClick={() => changePhotoId(Number(i))}
222
+ opacity={0.9}
223
+ position={"relative"}
224
+ display={"inline-block"}
225
+ shape={
226
+ Number(i) === index
227
+ ? "rounded"
228
+ : Number(i) === 0
229
+ ? "roundedLeft"
230
+ : Number(i) === filteredImages.length - 1
231
+ ? "roundedRight"
232
+ : "square"
233
+ }
234
+ overflow="hidden"
235
+ flex="none"
236
+ style={{ transform: "translate3d(0, 0, 0)" }}
237
+ cursor="pointer"
238
+ bg="black"
239
+ boxShadow="medium"
240
+ >
241
+ <Image
242
+ alt="small photos on the bottom"
243
+ fill
244
+ src={image.url}
245
+ sizes="100vw"
246
+ style={{ objectFit: "cover" }}
247
+ />
248
+ </Box>
249
+ ))}
250
+ </AnimatePresence>
251
+ </Box>
252
+ </Box>
253
+ )}
254
+ </MotionConfig>
255
+ </>
256
+ );
257
+ }
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import {
3
+ Dialog,
4
+ DialogProps,
5
+ DialogPanel,
6
+ DialogBackdrop,
7
+ } from "@headlessui/react";
8
+ import { useRef, useState } from "react";
9
+ import Box, { BoxProps } from "@/design-system/elements/box";
10
+ import { useRouter } from "next/navigation";
11
+
12
+ type ModalProps = DialogProps<any> &
13
+ BoxProps & {
14
+ initial?: boolean;
15
+ goBack?: boolean;
16
+ children: React.ReactNode;
17
+ onClose?: any;
18
+ panelProps?: any;
19
+ backdropProps?: any;
20
+ };
21
+
22
+ export default function Modal({
23
+ initial = false,
24
+ goBack = true,
25
+ children,
26
+ onClose = () => null,
27
+ panelProps,
28
+ backdropProps,
29
+ ...props
30
+ }: ModalProps) {
31
+ const [isOpen, setIsOpen] = useState(initial);
32
+ const overlayRef = useRef(null);
33
+ const router = useRouter();
34
+
35
+ function handleClose() {
36
+ if (goBack) return router.back();
37
+ setIsOpen(false);
38
+ }
39
+
40
+ return (
41
+ <Box
42
+ as={Dialog}
43
+ open={isOpen}
44
+ onClose={onClose || handleClose}
45
+ position="fixed"
46
+ top="0"
47
+ left="0"
48
+ width="100%"
49
+ height="100%"
50
+ display="flex"
51
+ alignItems="center"
52
+ justifyContent="center"
53
+ zIndex="99999"
54
+ {...props}
55
+ >
56
+ <Box
57
+ as={DialogPanel}
58
+ position="relative"
59
+ width="100%"
60
+ maxWidth="80rem"
61
+ maxHeight="90vh"
62
+ mx="auto"
63
+ {...panelProps}
64
+ >
65
+ {children}
66
+ </Box>
67
+ <Box
68
+ as={DialogBackdrop}
69
+ ref={overlayRef}
70
+ key="backdrop"
71
+ bg="rgba(0,0,0,0.6)"
72
+ position="fixed"
73
+ top="0"
74
+ left="0"
75
+ width="100%"
76
+ height="100%"
77
+ zIndex="-1"
78
+ style={{ backdropFilter: "blur(10px)" }}
79
+ {...backdropProps}
80
+ />
81
+ </Box>
82
+ );
83
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+ import Image from "next/image";
3
+ import Box from "@/design-system/elements/box";
4
+ import { useState } from "react";
5
+ import Modal from "./Modal";
6
+ import Carousel from "./Carousel";
7
+ import Collection from "../Collection";
8
+ import { motion } from "motion/react";
9
+ import type { ImageType } from "@/design-system/types";
10
+
11
+ type GalleryProps = {
12
+ images: ImageType[];
13
+ };
14
+
15
+ export default function Gallery({ images }: GalleryProps) {
16
+ const [open, setIsOpen] = useState(false);
17
+ const [current, setCurrent] = useState(Number(0));
18
+
19
+ return (
20
+ <Collection colsize="medium">
21
+ {images?.map((image: ImageType, i: number) => {
22
+ return (
23
+ <Box
24
+ key={i}
25
+ as={motion.div}
26
+ whileHover={{ scale: 1.04 }}
27
+ whileTap={{ scale: 1 }}
28
+ aspect={3 / 2}
29
+ position="relative"
30
+ shape="rounded"
31
+ overflow="hidden"
32
+ cursor="pointer"
33
+ $shadow="small"
34
+ onClick={() => {
35
+ setIsOpen(!open);
36
+ setCurrent(Number(i));
37
+ }}
38
+ >
39
+ <Image
40
+ alt="image"
41
+ src={image.url}
42
+ fill
43
+ style={{ objectFit: "cover" }}
44
+ sizes="(max-width: 640px) 100vw,
45
+ (max-width: 1280px) 50vw,
46
+ (max-width: 1536px) 33vw,
47
+ 25vw"
48
+ />
49
+ </Box>
50
+ );
51
+ })}
52
+ <Modal open={open} onClose={() => setIsOpen(false)}>
53
+ <Carousel current={current} photos={images} />
54
+ </Modal>
55
+ </Collection>
56
+ );
57
+ }
@@ -0,0 +1,18 @@
1
+ export const variants = {
2
+ enter: (direction: number) => {
3
+ return {
4
+ x: direction > 0 ? 1000 : -1000,
5
+ opacity: 0,
6
+ };
7
+ },
8
+ center: {
9
+ x: 0,
10
+ opacity: 1,
11
+ },
12
+ exit: (direction: number) => {
13
+ return {
14
+ x: direction < 0 ? 1000 : -1000,
15
+ opacity: 0,
16
+ };
17
+ },
18
+ };
@@ -0,0 +1,14 @@
1
+ export default function getAspectRatio(image: any) {
2
+ const w = image.naturalWidth;
3
+ const h = image.naturalHeight;
4
+
5
+ let aspectRatio;
6
+
7
+ if (w > h) {
8
+ aspectRatio = w / h;
9
+ } else {
10
+ aspectRatio = h / w;
11
+ }
12
+
13
+ return aspectRatio;
14
+ }
@@ -0,0 +1,24 @@
1
+ function forceDownload(blobUrl: string, filename: string) {
2
+ const a: any = document.createElement("a");
3
+ a.download = filename;
4
+ a.href = blobUrl;
5
+ document.body.appendChild(a);
6
+ a.click();
7
+ a.remove();
8
+ }
9
+
10
+ export default function downloadPhoto(url: any, filename: any) {
11
+ if (!filename) filename = url.split("\\").pop().split("/").pop();
12
+ fetch(url, {
13
+ headers: new Headers({
14
+ Origin: location.origin,
15
+ }),
16
+ mode: "cors",
17
+ })
18
+ .then((response) => response.blob())
19
+ .then((blob) => {
20
+ const blobUrl = window.URL.createObjectURL(blob);
21
+ forceDownload(blobUrl, filename);
22
+ })
23
+ .catch((e) => console.error(e));
24
+ }
@@ -0,0 +1,11 @@
1
+ export const range = (start: number, end: number) => {
2
+ const output = [];
3
+ if (typeof end === "undefined") {
4
+ end = start;
5
+ start = 0;
6
+ }
7
+ for (let i = start; i < end; i += 1) {
8
+ output.push(i);
9
+ }
10
+ return output;
11
+ };
@@ -0,0 +1,106 @@
1
+ "use client";
2
+
3
+ import { MeshGradient } from "@paper-design/shaders-react";
4
+ import { Box } from "@/design-system/elements";
5
+ import styled from "styled-components";
6
+ import { useReducedMotion } from "@/design-system/hooks/useReducedMotion";
7
+
8
+ interface GradientMeshProps {
9
+ /** Animation intensity level */
10
+ intensity?: "low" | "medium" | "high";
11
+ /** Custom colors for the gradient (brand cyan, accent purple, bridge blue) */
12
+ colors?: {
13
+ primary?: string;
14
+ secondary?: string;
15
+ tertiary?: string;
16
+ };
17
+ /** Animation speed multiplier (default: 1) */
18
+ speed?: number;
19
+ /** Whether the animation is enabled (default: true) */
20
+ animated?: boolean;
21
+ /** Distortion amount (0-1) */
22
+ distortion?: number;
23
+ /** Swirl amount (0-1) */
24
+ swirl?: number;
25
+ /** Grain mixer amount (0-1) */
26
+ grainMixer?: number;
27
+ /** Grain overlay amount (0-1) */
28
+ grainOverlay?: number;
29
+ /** Scale factor */
30
+ scale?: number;
31
+ /** Rotation in degrees */
32
+ rotation?: number;
33
+ /** X offset */
34
+ offsetX?: number;
35
+ /** Y offset */
36
+ offsetY?: number;
37
+ /** Additional props passed to the container */
38
+ [key: string]: any;
39
+ }
40
+
41
+ // Intensity presets mapped to shader parameters
42
+ const intensityConfig = {
43
+ low: { distortion: 0.3, swirl: 0.05 },
44
+ medium: { distortion: 0.6, swirl: 0.1 },
45
+ high: { distortion: 0.9, swirl: 0.2 },
46
+ };
47
+
48
+ const Container = styled(Box)`
49
+ overflow: hidden;
50
+ pointer-events: none;
51
+ `;
52
+
53
+ /**
54
+ * GradientMesh - Animated gradient mesh background
55
+ *
56
+ * Creates a flowing, organic gradient animation using WebGL shaders.
57
+ * Uses the @paper-design/shaders-react MeshGradient component.
58
+ */
59
+ export default function GradientMesh({
60
+ intensity = "medium",
61
+ colors,
62
+ speed = 1,
63
+ animated = true,
64
+ distortion,
65
+ swirl,
66
+ grainMixer = 0,
67
+ grainOverlay = 0,
68
+ scale = 1,
69
+ rotation = 0,
70
+ offsetX = 0,
71
+ offsetY = 0,
72
+ ...props
73
+ }: GradientMeshProps) {
74
+ const prefersReducedMotion = useReducedMotion();
75
+ const config = intensityConfig[intensity];
76
+ const isPaused = prefersReducedMotion || !animated;
77
+
78
+ // Brand colors converted to hex
79
+ // Brand cyan: hsl(192, 100%, 38%) → #0099c2
80
+ // Accent purple: hsl(256, 90%, 58%) → #7c4dff
81
+ // Bridge blue: hsl(224, 95%, 52%) → #1565ef
82
+ const colorArray = [
83
+ colors?.primary || "#0099c2",
84
+ colors?.secondary || "#7c4dff",
85
+ colors?.tertiary || "#1565ef",
86
+ colors?.primary || "#0099c2",
87
+ ];
88
+
89
+ return (
90
+ <Container {...props}>
91
+ <MeshGradient
92
+ style={{ width: "100%", height: "100%" }}
93
+ colors={colorArray}
94
+ distortion={distortion ?? config.distortion}
95
+ swirl={swirl ?? config.swirl}
96
+ speed={isPaused ? 0 : speed}
97
+ grainMixer={grainMixer}
98
+ grainOverlay={grainOverlay}
99
+ scale={scale}
100
+ rotation={rotation}
101
+ offsetX={offsetX}
102
+ offsetY={offsetY}
103
+ />
104
+ </Container>
105
+ );
106
+ }