@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,77 @@
1
+ "use client";
2
+ import Box from "@/design-system/elements/box";
3
+ import Text from "@/design-system/elements/text";
4
+ import { BoxProps } from "@/design-system/elements/box";
5
+ import React from "react";
6
+ import { IconERemove } from "@/design-system/icons";
7
+ import { Button } from "@/design-system/elements";
8
+
9
+ interface ToastProps extends BoxProps {
10
+ message: React.ReactNode;
11
+ description?: string;
12
+ variant?: "default" | "success" | "error" | "info" | "warning";
13
+ icon?: React.ReactNode;
14
+ onClose?: () => void;
15
+ }
16
+
17
+ export default function Toast({
18
+ message,
19
+ description,
20
+ variant = "default",
21
+ icon,
22
+ onClose,
23
+ ...props
24
+ }: ToastProps) {
25
+ const variantStyles = {
26
+ default: { skin: "translucent", color: "palette.grays.11" },
27
+ success: { bg: "palette.greens.0", color: "palette.greens.11" },
28
+ error: { bg: "palette.reds.0", color: "palette.reds.11" },
29
+ info: { bg: "palette.brands.0", color: "palette.brands.11" },
30
+ warning: { bg: "palette.yellows.0", color: "palette.yellows.11" },
31
+ };
32
+
33
+ const currentStyle = variantStyles[variant];
34
+
35
+ return (
36
+ <Box
37
+ display="flex"
38
+ alignItems="center"
39
+ gap="xs"
40
+ p="s"
41
+ py="xs"
42
+ shape="rounded"
43
+ width="22.5rem"
44
+ boxShadow="rgba(0, 0, 0, 0.125) 0px 4px 12px"
45
+ {...currentStyle}
46
+ {...props}
47
+ >
48
+ {icon && (
49
+ <Box display="flex" alignItems="center" pl="xxxs">
50
+ {icon}
51
+ </Box>
52
+ )}
53
+ <Box display="flex" flexDirection="column" gap="xxs" flex="auto">
54
+ <Text fontSize="13px" fontWeight="500" color="inherit">
55
+ {message}
56
+ </Text>
57
+ {description && (
58
+ <Text fontSize="xs" color="inherit" opacity={0.8}>
59
+ {description}
60
+ </Text>
61
+ )}
62
+ </Box>
63
+ {onClose && (
64
+ <Button
65
+ as="button"
66
+ onClick={onClose}
67
+ shape="circle"
68
+ $size="icon.xsmall"
69
+ variant="ghost.dim"
70
+ flex="none"
71
+ >
72
+ <Text as={IconERemove} size="0.875rem" color="inherit"></Text>
73
+ </Button>
74
+ )}
75
+ </Box>
76
+ );
77
+ }
@@ -0,0 +1,174 @@
1
+ "use client";
2
+
3
+ import { Box, Text } from "@/design-system/elements";
4
+ import { AnimatePresence, motion } from "motion/react";
5
+ import React, { useEffect, useState } from "react";
6
+ import { createPortal } from "react-dom";
7
+ import {
8
+ autoUpdate,
9
+ flip,
10
+ offset as floatingOffset,
11
+ shift,
12
+ useFloating,
13
+ type Placement as FloatingPlacement,
14
+ } from "@floating-ui/react";
15
+
16
+ interface TooltipProps {
17
+ children: React.ReactNode;
18
+ content: React.ReactNode;
19
+ placement?: "top" | "bottom" | "left" | "right";
20
+ offset?: number;
21
+ portal?: boolean;
22
+ possiblePlacements?: string[];
23
+ [key: string]: any;
24
+ }
25
+
26
+ export const Tooltip = ({
27
+ children,
28
+ content,
29
+ placement = "bottom",
30
+ offset = 8,
31
+ portal = true,
32
+ possiblePlacements,
33
+ ...props
34
+ }: TooltipProps) => {
35
+ const [mounted, setMounted] = useState(false);
36
+ const [isVisible, setIsVisible] = useState(false);
37
+
38
+ type Side = "top" | "bottom" | "left" | "right";
39
+
40
+ const validFallbacks = (possiblePlacements || []).filter((p): p is Side =>
41
+ ["top", "bottom", "left", "right"].includes(p)
42
+ );
43
+
44
+ const {
45
+ refs,
46
+ x,
47
+ y,
48
+ strategy,
49
+ placement: resolvedPlacement,
50
+ } = useFloating({
51
+ placement: placement as FloatingPlacement,
52
+ middleware: [
53
+ floatingOffset(offset),
54
+ flip(
55
+ validFallbacks.length
56
+ ? { fallbackPlacements: validFallbacks as FloatingPlacement[] }
57
+ : {}
58
+ ),
59
+ shift({ padding: 8 }),
60
+ ],
61
+ // Keep fixed positioning to match previous behavior
62
+ strategy: "fixed",
63
+ whileElementsMounted: autoUpdate,
64
+ });
65
+
66
+ const floatingStyles: React.CSSProperties = {
67
+ position: strategy,
68
+ top: y ?? 0,
69
+ left: x ?? 0,
70
+ };
71
+
72
+ const actualSide = (resolvedPlacement.split("-")[0] as Side) || placement;
73
+
74
+ useEffect(() => {
75
+ setMounted(true);
76
+ }, []);
77
+
78
+ const showTooltip = () => {
79
+ setIsVisible(true);
80
+ };
81
+
82
+ const hideTooltip = () => {
83
+ setIsVisible(false);
84
+ };
85
+
86
+ const getArrowStyles = () => {
87
+ switch (actualSide) {
88
+ case "bottom":
89
+ return { top: "-4px", left: "calc(50% - 4px)" };
90
+ case "top":
91
+ return { bottom: "-4px", left: "calc(50% - 4px)" };
92
+ case "right":
93
+ return { left: "-4px", top: "calc(50% - 4px)" };
94
+ case "left":
95
+ return { right: "-4px", top: "calc(50% - 4px)" };
96
+ default:
97
+ return {};
98
+ }
99
+ };
100
+
101
+ const tooltipContent = (
102
+ <AnimatePresence>
103
+ {isVisible && (
104
+ <Text
105
+ as={motion.div}
106
+ ref={refs.setFloating}
107
+ fontSize="small"
108
+ zIndex={99999}
109
+ initial={{ opacity: 0, y: actualSide === "top" ? 10 : -10 }}
110
+ animate={{ opacity: 1, y: 0 }}
111
+ exit={{ opacity: 0, y: actualSide === "top" ? 10 : -10 }}
112
+ transition={{ duration: 0.2 }}
113
+ style={{
114
+ pointerEvents: "none",
115
+ ...floatingStyles,
116
+ ...(props.style || {}),
117
+ }}
118
+ {...props}
119
+ >
120
+ <Box
121
+ skin="translucent.dark"
122
+ shape="roundedLarge"
123
+ $shadow="medium"
124
+ p="small"
125
+ width="fit-content"
126
+ maxWidth="20rem"
127
+ position="relative"
128
+ >
129
+ {/* Arrow */}
130
+ <Box
131
+ position="absolute"
132
+ width="8px"
133
+ height="8px"
134
+ bg="inherit"
135
+ style={{
136
+ transform: "rotate(45deg)",
137
+ ...getArrowStyles(),
138
+ }}
139
+ />
140
+
141
+ {/* Content */}
142
+
143
+ <Text
144
+ color="inherit"
145
+ // position="relative"
146
+ // zIndex={1}
147
+ >
148
+ {content}
149
+ </Text>
150
+ </Box>
151
+ </Text>
152
+ )}
153
+ </AnimatePresence>
154
+ );
155
+
156
+ return (
157
+ <>
158
+ <Box
159
+ ref={refs.setReference}
160
+ onMouseEnter={showTooltip}
161
+ onMouseLeave={hideTooltip}
162
+ display="flex"
163
+ {...props}
164
+ >
165
+ {children}
166
+ </Box>
167
+ {mounted && portal
168
+ ? createPortal(tooltipContent, document.body)
169
+ : tooltipContent}
170
+ </>
171
+ );
172
+ };
173
+
174
+ export default Tooltip;
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import { Box } from "@/design-system/elements";
4
+ import styled from "styled-components";
5
+ import { useEffect, useRef } from "react";
6
+
7
+ interface UnderlayProps {
8
+ backgroundColor?: string;
9
+ dotColor?: string;
10
+ dotSize?: number;
11
+ gridSize?: number;
12
+ blurAmount?: number;
13
+ maskGradient?: string;
14
+ hasMask?: boolean;
15
+ stop1?: string;
16
+ stop2?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ const StyledBox = styled(Box).withConfig({
21
+ shouldForwardProp: (prop) =>
22
+ ![
23
+ "dotColor",
24
+ "dotSize",
25
+ "gridSize",
26
+ "blurAmount",
27
+ "maskGradient",
28
+ "hasMask",
29
+ "stop1",
30
+ "stop2",
31
+ ].includes(prop),
32
+ })<UnderlayProps>`
33
+ ${(props) => `backdrop-filter: blur(${props.blurAmount || 8}px);`}
34
+ ${(props) =>
35
+ props.hasMask &&
36
+ `
37
+ mask: linear-gradient(
38
+ ${props.maskGradient || "rgb(0, 0, 0) 33%, rgba(0, 0, 0, 0) 100%"}
39
+ );
40
+ `}
41
+ transform: translateZ(0);
42
+ will-change: backdrop-filter;
43
+ `;
44
+
45
+ const Underlay = ({
46
+ backgroundColor,
47
+ dotColor,
48
+ dotSize = 1,
49
+ gridSize = 4,
50
+ blurAmount = 12,
51
+ maskGradient,
52
+ hasMask = true,
53
+ stop1,
54
+ stop2,
55
+ ...props
56
+ }: UnderlayProps) => {
57
+ const boxRef = useRef<HTMLDivElement>(null);
58
+
59
+ // Force blur application after sidebar state changes
60
+ useEffect(() => {
61
+ // This triggers a reflow to ensure the blur is applied
62
+ if (boxRef.current) {
63
+ // Force a repaint to ensure the backdrop-filter is applied
64
+ const currentBlur = boxRef.current.style.backdropFilter;
65
+ boxRef.current.style.backdropFilter = "none";
66
+ // Force reflow
67
+ void boxRef.current.offsetHeight;
68
+ // Restore the blur
69
+ boxRef.current.style.backdropFilter =
70
+ currentBlur || `blur(${blurAmount}px)`;
71
+ }
72
+ }, [blurAmount, props.width, props.height, props.expanded]);
73
+
74
+ // Determine if we're using dots or a gradient
75
+ const useGradient = stop1 && stop2;
76
+
77
+ // Generate the background image based on whether we're using dots or gradient
78
+ const backgroundImage = useGradient
79
+ ? `linear-gradient(to bottom right, ${stop1}, ${stop2})`
80
+ : `radial-gradient(
81
+ ${dotColor || "transparent"} 1px,
82
+ ${backgroundColor || "currentColor"} ${dotSize}px
83
+ )`;
84
+
85
+ // If using gradient, don't apply grid background size
86
+ const backgroundSize = useGradient
87
+ ? undefined
88
+ : `${gridSize}px ${gridSize}px`;
89
+
90
+ return (
91
+ <StyledBox
92
+ ref={boxRef}
93
+ bg="transparent"
94
+ backgroundSize={backgroundSize}
95
+ backgroundImage={backgroundImage}
96
+ blurAmount={blurAmount}
97
+ hasMask={hasMask}
98
+ maskGradient={maskGradient}
99
+ {...props}
100
+ />
101
+ );
102
+ };
103
+
104
+ export default Underlay;
@@ -0,0 +1,92 @@
1
+ "use client";
2
+ import React from "react";
3
+ import styled from "styled-components";
4
+ import { Box, Text } from "../../elements";
5
+ import Image from "next/image";
6
+ import { UploadContainerProps } from "./types";
7
+
8
+ const getColor = (props: any) => {
9
+ if (props.isDragAccept) {
10
+ return "success.subtle";
11
+ }
12
+
13
+ if (props.isDragReject) {
14
+ return "error.subtle";
15
+ }
16
+
17
+ if (props.isDragActive) {
18
+ return "info.subtle";
19
+ }
20
+
21
+ return "brand.subtle";
22
+ };
23
+
24
+ const DropzoneContainer = styled(Box)`
25
+ flex: 1;
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ justify-content: center;
30
+ text-align: center;
31
+ padding: 0.25rem;
32
+ border-width: 1.5px;
33
+ border-radius: 8px;
34
+ border-style: dashed;
35
+ outline: none;
36
+ transition: border 0.24s ease-in-out;
37
+ `;
38
+
39
+ function Dropzone({
40
+ getRootProps,
41
+ isDragActive,
42
+ isDragAccept,
43
+ isDragReject,
44
+ label = "",
45
+ dropzoneProps = {},
46
+ children,
47
+ dropzoneContainerProps = {},
48
+ }: UploadContainerProps) {
49
+ const { imgSrc, showPreview, icon, title, description } = dropzoneProps;
50
+
51
+ return (
52
+ <DropzoneContainer
53
+ {...getRootProps()}
54
+ skin={getColor({ isDragActive, isDragAccept, isDragReject })}
55
+ {...dropzoneContainerProps}
56
+ style={{
57
+ cursor: "pointer",
58
+ padding: showPreview && imgSrc ? 0 : "2rem",
59
+ ...dropzoneContainerProps?.style,
60
+ }}
61
+ >
62
+ {showPreview && imgSrc ? (
63
+ <Image src={imgSrc} alt="dropzone-result" style={{ width: "100%" }} />
64
+ ) : (
65
+ <>
66
+ {icon && <Box mb="s">{icon}</Box>}
67
+ {title && (
68
+ <Text
69
+ as="h3"
70
+ fontSize="m"
71
+ fontWeight={600}
72
+ mb={description ? "xs" : "0"}
73
+ color="primary"
74
+ >
75
+ {title}
76
+ </Text>
77
+ )}
78
+ <Text fontSize="small" color="secondary">
79
+ {description || label || (
80
+ <>
81
+ Drag and drop files here, or <b>click to select files</b>
82
+ </>
83
+ )}
84
+ </Text>
85
+ </>
86
+ )}
87
+ {children}
88
+ </DropzoneContainer>
89
+ );
90
+ }
91
+
92
+ export default Dropzone;
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import { Button, Box } from "@/design-system/elements";
3
+ import { UploadContainerProps } from "./types";
4
+ import { IconDotsAnim, IconUpload } from "@/design-system/icons";
5
+
6
+ function UploadBtn({
7
+ children,
8
+ getRootProps,
9
+ label,
10
+ loading,
11
+ icon,
12
+ buttonProps = {},
13
+ }: UploadContainerProps) {
14
+ return (
15
+ <Button
16
+ variant="primary"
17
+ $size="small"
18
+ disabled={loading}
19
+ {...buttonProps}
20
+ {...getRootProps()}
21
+ >
22
+ {loading ? (
23
+ <>
24
+ <Box as={IconDotsAnim} size="1rem" /> Working
25
+ </>
26
+ ) : (
27
+ <>
28
+ <Box as={icon || IconUpload} size="1rem" />
29
+ {label && label}
30
+ </>
31
+ )}
32
+
33
+ {children}
34
+ </Button>
35
+ );
36
+ }
37
+
38
+ export default UploadBtn;
@@ -0,0 +1,61 @@
1
+ import React, { useMemo } from "react";
2
+ import { useDropzone } from "react-dropzone";
3
+ import Dropzone from "./Dropzone";
4
+ import UploadBtn from "./UploadBtn";
5
+ import { UploadProps } from "./types";
6
+
7
+ function Upload({
8
+ type = "button",
9
+ label,
10
+ icon,
11
+ accept,
12
+ onDropAccepted,
13
+ onDropRejected,
14
+ dropzoneProps,
15
+ loading,
16
+ buttonProps,
17
+ dropzoneContainerProps,
18
+ ...rest
19
+ }: UploadProps) {
20
+ const {
21
+ getRootProps,
22
+ getInputProps,
23
+ isDragActive,
24
+ isDragAccept,
25
+ isDragReject,
26
+ } = useDropzone({
27
+ accept,
28
+ onDropAccepted,
29
+ onDropRejected,
30
+ disabled: loading,
31
+ ...rest,
32
+ });
33
+
34
+ const UploadContainer = useMemo(() => {
35
+ switch (type) {
36
+ case "dropzone":
37
+ return Dropzone;
38
+ case "button":
39
+ return UploadBtn;
40
+ }
41
+ }, [type]);
42
+
43
+ return (
44
+ <UploadContainer
45
+ label={label}
46
+ icon={icon}
47
+ dropzoneProps={dropzoneProps}
48
+ loading={loading}
49
+ getRootProps={getRootProps}
50
+ isDragActive={isDragActive}
51
+ isDragAccept={isDragAccept}
52
+ isDragReject={isDragReject}
53
+ buttonProps={buttonProps}
54
+ dropzoneContainerProps={dropzoneContainerProps}
55
+ >
56
+ <input {...getInputProps()} />
57
+ </UploadContainer>
58
+ );
59
+ }
60
+
61
+ export default Upload;
@@ -0,0 +1,37 @@
1
+ import { DropzoneOptions, DropzoneRootProps } from "react-dropzone";
2
+
3
+ export type UploadComponent = "button" | "dropzone";
4
+ export type DropzoneProps = {
5
+ showPreview?: boolean;
6
+ imgSrc?: string;
7
+ /** Optional icon node to render inside the dropzone empty state */
8
+ icon?: React.ReactNode;
9
+ /** Optional title/heading for the dropzone empty state */
10
+ title?: string | React.ReactNode;
11
+ /** Optional description text under the title; falls back to label */
12
+ description?: string | React.ReactNode;
13
+ };
14
+
15
+ export type UploadContainerProps = {
16
+ getRootProps: <T extends DropzoneRootProps>(props?: T) => T;
17
+ label?: string | React.ReactNode;
18
+ isDragActive: boolean;
19
+ isDragAccept: boolean;
20
+ isDragReject: boolean;
21
+ dropzoneProps?: Partial<DropzoneProps>;
22
+ children: any;
23
+ loading?: boolean;
24
+ icon?: React.ReactNode;
25
+ buttonProps?: any;
26
+ dropzoneContainerProps?: any;
27
+ };
28
+
29
+ export type UploadProps = Partial<DropzoneOptions> & {
30
+ type?: UploadComponent;
31
+ label?: string | React.ReactNode;
32
+ icon?: any;
33
+ dropzoneProps?: Partial<DropzoneProps>;
34
+ loading?: boolean;
35
+ buttonProps?: any;
36
+ dropzoneContainerProps?: any;
37
+ };