@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,129 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Box, Button, Text } from "@/design-system/elements";
5
+ import {
6
+ IconBan,
7
+ IconCCheck,
8
+ IconLock,
9
+ IconOWarning,
10
+ IconUserFocus,
11
+ } from "@/design-system/icons";
12
+ import { FamilyDrawerView } from "../index";
13
+
14
+ interface KeyViewProps {
15
+ setView: (view: FamilyDrawerView) => void;
16
+ }
17
+
18
+ export const KeyView: React.FC<KeyViewProps> = ({ setView }) => {
19
+ return (
20
+ <Box>
21
+ <ViewHeader
22
+ icon={<Box as={IconLock} size="2.5rem" color="secondary" />}
23
+ title="Private Key"
24
+ description="Your Private Key is the key used to back up your wallet. Keep it secret and secure at all times."
25
+ />
26
+
27
+ <Box
28
+ mt="medium"
29
+ pt="medium"
30
+ borderTop="1px solid"
31
+ borderColor="base"
32
+ >
33
+ <Box display="flex" flexDirection="column" gap="medium">
34
+ <SecurityItem
35
+ icon={<Box as={IconCCheck} size="1.25rem" />}
36
+ text="Keep your private key safe"
37
+ />
38
+ <SecurityItem
39
+ icon={<Box as={IconBan} size="1.25rem" />}
40
+ text="Don't share it with anyone else"
41
+ />
42
+ <SecurityItem
43
+ icon={<Box as={IconOWarning} size="1.25rem" />}
44
+ text="If you lose it, we can't recover it"
45
+ />
46
+ </Box>
47
+ </Box>
48
+
49
+ <Box mt="large" display="flex" gap="medium">
50
+ <Button
51
+ variant="ghost"
52
+ $size="medium"
53
+ onClick={() => setView("default")}
54
+ flex="1"
55
+ skin="translucent"
56
+ >
57
+ <Text fontSize="medium" fontWeight="semibold">
58
+ Cancel
59
+ </Text>
60
+ </Button>
61
+ <Button
62
+ variant="primary"
63
+ $size="medium"
64
+ onClick={() => setView("default")}
65
+ flex="1"
66
+ display="flex"
67
+ alignItems="center"
68
+ gap="small"
69
+ >
70
+ <Box as={IconUserFocus} size="1.25rem" />
71
+ <Text fontSize="medium" fontWeight="semibold">
72
+ Reveal
73
+ </Text>
74
+ </Button>
75
+ </Box>
76
+ </Box>
77
+ );
78
+ };
79
+
80
+ interface ViewHeaderProps {
81
+ icon: React.ReactNode;
82
+ title: string;
83
+ description: string;
84
+ }
85
+
86
+ const ViewHeader: React.FC<ViewHeaderProps> = ({ icon, title, description }) => {
87
+ return (
88
+ <Box>
89
+ <Box display="flex" alignItems="flex-start" justifyContent="space-between">
90
+ {icon}
91
+ </Box>
92
+ <Text
93
+ as="h2"
94
+ fontSize="xlarge"
95
+ fontWeight="semibold"
96
+ color="primary"
97
+ mt="small"
98
+ >
99
+ {title}
100
+ </Text>
101
+ <Text
102
+ fontSize="medium"
103
+ color="secondary"
104
+ lineHeight="relaxed"
105
+ mt="small"
106
+ >
107
+ {description}
108
+ </Text>
109
+ </Box>
110
+ );
111
+ };
112
+
113
+ interface SecurityItemProps {
114
+ icon: React.ReactNode;
115
+ text: string;
116
+ }
117
+
118
+ const SecurityItem: React.FC<SecurityItemProps> = ({ icon, text }) => {
119
+ return (
120
+ <Box display="flex" alignItems="center" gap="medium">
121
+ <Box color="secondary">
122
+ {icon}
123
+ </Box>
124
+ <Text fontSize="small" fontWeight="medium" color="secondary">
125
+ {text}
126
+ </Text>
127
+ </Box>
128
+ );
129
+ };
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Box, Button, Text } from "@/design-system/elements";
5
+ import {
6
+ IconBan,
7
+ IconCCheck,
8
+ IconOWarning,
9
+ IconText,
10
+ IconUserFocus,
11
+ } from "@/design-system/icons";
12
+ import { FamilyDrawerView } from "../index";
13
+
14
+ interface PhraseViewProps {
15
+ setView: (view: FamilyDrawerView) => void;
16
+ }
17
+
18
+ export const PhraseView: React.FC<PhraseViewProps> = ({ setView }) => {
19
+ return (
20
+ <Box>
21
+ <ViewHeader
22
+ icon={<Box as={IconText} size="2.5rem" color="secondary" />}
23
+ title="Secret Recovery Phrase"
24
+ description="Your Secret Recovery Phrase is the key used to back up your wallet. Keep it secret at all times."
25
+ />
26
+
27
+ <Box
28
+ mt="medium"
29
+ pt="medium"
30
+ borderTop="1px solid"
31
+ borderColor="base"
32
+ >
33
+ <Box display="flex" flexDirection="column" gap="medium">
34
+ <SecurityItem
35
+ icon={<Box as={IconCCheck} size="1.25rem" />}
36
+ text="Keep your Secret Phrase safe"
37
+ />
38
+ <SecurityItem
39
+ icon={<Box as={IconBan} size="1.25rem" />}
40
+ text="Don't share it with anyone else"
41
+ />
42
+ <SecurityItem
43
+ icon={<Box as={IconOWarning} size="1.25rem" />}
44
+ text="If you lose it, we can't recover it"
45
+ />
46
+ </Box>
47
+ </Box>
48
+
49
+ <Box mt="large" display="flex" gap="medium">
50
+ <Button
51
+ variant="ghost"
52
+ $size="medium"
53
+ onClick={() => setView("default")}
54
+ flex="1"
55
+ skin="translucent"
56
+ >
57
+ <Text fontSize="medium" fontWeight="semibold">
58
+ Cancel
59
+ </Text>
60
+ </Button>
61
+ <Button
62
+ variant="primary"
63
+ $size="medium"
64
+ onClick={() => setView("default")}
65
+ flex="1"
66
+ display="flex"
67
+ alignItems="center"
68
+ gap="small"
69
+ >
70
+ <Box as={IconUserFocus} size="1.25rem" />
71
+ <Text fontSize="medium" fontWeight="semibold">
72
+ Reveal
73
+ </Text>
74
+ </Button>
75
+ </Box>
76
+ </Box>
77
+ );
78
+ };
79
+
80
+ interface ViewHeaderProps {
81
+ icon: React.ReactNode;
82
+ title: string;
83
+ description: string;
84
+ }
85
+
86
+ const ViewHeader: React.FC<ViewHeaderProps> = ({ icon, title, description }) => {
87
+ return (
88
+ <Box>
89
+ <Box display="flex" alignItems="flex-start" justifyContent="space-between">
90
+ {icon}
91
+ </Box>
92
+ <Text
93
+ as="h2"
94
+ fontSize="xlarge"
95
+ fontWeight="semibold"
96
+ color="primary"
97
+ mt="small"
98
+ >
99
+ {title}
100
+ </Text>
101
+ <Text
102
+ fontSize="medium"
103
+ color="secondary"
104
+ lineHeight="relaxed"
105
+ mt="small"
106
+ >
107
+ {description}
108
+ </Text>
109
+ </Box>
110
+ );
111
+ };
112
+
113
+ interface SecurityItemProps {
114
+ icon: React.ReactNode;
115
+ text: string;
116
+ }
117
+
118
+ const SecurityItem: React.FC<SecurityItemProps> = ({ icon, text }) => {
119
+ return (
120
+ <Box display="flex" alignItems="center" gap="medium">
121
+ <Box color="secondary">
122
+ {icon}
123
+ </Box>
124
+ <Text fontSize="small" fontWeight="medium" color="secondary">
125
+ {text}
126
+ </Text>
127
+ </Box>
128
+ );
129
+ };
@@ -0,0 +1,81 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Box, Button, Text } from "@/design-system/elements";
5
+ import { IconOWarning } from "@/design-system/icons";
6
+ import { FamilyDrawerView } from "../index";
7
+
8
+ interface RemoveViewProps {
9
+ setView: (view: FamilyDrawerView) => void;
10
+ }
11
+
12
+ export const RemoveView: React.FC<RemoveViewProps> = ({ setView }) => {
13
+ return (
14
+ <Box>
15
+ <ViewHeader
16
+ icon={<Box as={IconOWarning} size="2.5rem" color="danger" />}
17
+ title="Are you sure?"
18
+ description="You haven't backed up your wallet yet. If you remove it, you could lose access forever. We suggest tapping and backing up your wallet first with a valid recovery method."
19
+ />
20
+
21
+ <Box mt="large" display="flex" gap="medium">
22
+ <Button
23
+ variant="ghost"
24
+ $size="medium"
25
+ onClick={() => setView("default")}
26
+ flex="1"
27
+ skin="translucent"
28
+ >
29
+ <Text fontSize="medium" fontWeight="semibold">
30
+ Cancel
31
+ </Text>
32
+ </Button>
33
+ <Button
34
+ variant="primary"
35
+ $size="medium"
36
+ onClick={() => setView("default")}
37
+ flex="1"
38
+ skin="danger"
39
+ color="white"
40
+ >
41
+ <Text fontSize="medium" fontWeight="semibold">
42
+ Continue
43
+ </Text>
44
+ </Button>
45
+ </Box>
46
+ </Box>
47
+ );
48
+ };
49
+
50
+ interface ViewHeaderProps {
51
+ icon: React.ReactNode;
52
+ title: string;
53
+ description: string;
54
+ }
55
+
56
+ const ViewHeader: React.FC<ViewHeaderProps> = ({ icon, title, description }) => {
57
+ return (
58
+ <Box>
59
+ <Box display="flex" alignItems="flex-start" justifyContent="space-between">
60
+ {icon}
61
+ </Box>
62
+ <Text
63
+ as="h2"
64
+ fontSize="xlarge"
65
+ fontWeight="semibold"
66
+ color="primary"
67
+ mt="small"
68
+ >
69
+ {title}
70
+ </Text>
71
+ <Text
72
+ fontSize="medium"
73
+ color="secondary"
74
+ lineHeight="relaxed"
75
+ mt="small"
76
+ >
77
+ {description}
78
+ </Text>
79
+ </Box>
80
+ );
81
+ };
@@ -0,0 +1,173 @@
1
+ "use client";
2
+
3
+ import { Box, Text } from "@/design-system/elements";
4
+ import Field, { FieldProps } from "@/design-system/elements/field";
5
+ import { TextareaHTMLAttributes, forwardRef } from "react";
6
+
7
+ type CommonProps = {
8
+ error?: any;
9
+ as?: React.ElementType;
10
+ label: string | React.ReactNode;
11
+ description?: string;
12
+ children?: React.ReactNode;
13
+ watch?: any;
14
+ required?: boolean;
15
+ name: string;
16
+ layout?: "column" | "row";
17
+ fontSize?: string | number;
18
+ labelWidth?: string | number;
19
+ containerProps?: {
20
+ [key: string]: any;
21
+ };
22
+ [key: string]: any;
23
+ };
24
+
25
+ type FieldSetProps = CommonProps &
26
+ (
27
+ | Omit<FieldProps, "color">
28
+ | Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "color">
29
+ );
30
+
31
+ const FieldSet = forwardRef<any, FieldSetProps>(function FieldSet(
32
+ {
33
+ name,
34
+ error,
35
+ label,
36
+ required,
37
+ description,
38
+ children,
39
+ watch,
40
+ as,
41
+ layout = "column",
42
+ fontSize = "medium",
43
+ labelWidth,
44
+ containerProps = {},
45
+ ...rest
46
+ },
47
+ ref,
48
+ ) {
49
+ const isRowLayout = layout === "row";
50
+
51
+ const labelElement = (
52
+ <Text
53
+ as="label"
54
+ htmlFor={name}
55
+ fontWeight={600}
56
+ mb={isRowLayout ? "0" : description ? "0" : "xsmall"}
57
+ color="currentColor"
58
+ fontSize={fontSize}
59
+ width={isRowLayout ? labelWidth : undefined}
60
+ flex={isRowLayout && !labelWidth ? "none" : undefined}
61
+ minWidth={isRowLayout ? "fit-content" : undefined}
62
+ mr={isRowLayout ? "small" : undefined}
63
+ >
64
+ {typeof label === "string" ? (
65
+ label + (required ? " *" : "")
66
+ ) : (
67
+ <>
68
+ {label}
69
+ {required && (
70
+ <Text as="span" color="currentColor">
71
+ {" *"}
72
+ </Text>
73
+ )}
74
+ </>
75
+ )}
76
+ </Text>
77
+ );
78
+
79
+ const fieldElement = watch ? (
80
+ <Box display="flex" alignItems="center" gap="small" width="100%">
81
+ <Field
82
+ ref={ref}
83
+ id={name}
84
+ name={name}
85
+ as={as || "input"}
86
+ variant="default"
87
+ $size={fontSize}
88
+ width={"100%"}
89
+ {...rest}
90
+ >
91
+ {as && as !== "input" ? children : null}
92
+ </Field>
93
+ <Text
94
+ height="2rem"
95
+ width="2.5rem"
96
+ fontSize="small"
97
+ flex="none"
98
+ bg="transparent.light.1"
99
+ display="flex"
100
+ alignItems="center"
101
+ justifyContent={"center"}
102
+ textAlign="center"
103
+ shape="rounded"
104
+ >
105
+ {watch}
106
+ </Text>
107
+ </Box>
108
+ ) : (
109
+ <Field
110
+ ref={ref}
111
+ id={name}
112
+ name={name}
113
+ as={as || "input"}
114
+ variant="default"
115
+ $size={fontSize}
116
+ width={"100%"}
117
+ {...rest}
118
+ >
119
+ {as && as !== "input" ? children : null}
120
+ </Field>
121
+ );
122
+
123
+ return (
124
+ <Box
125
+ as="fieldset"
126
+ m="0"
127
+ p="0"
128
+ border="none"
129
+ display={"flex"}
130
+ flexDirection={isRowLayout ? "row" : "column"}
131
+ alignItems={isRowLayout ? "center" : "stretch"}
132
+ width="100%"
133
+ gap={isRowLayout ? "none" : undefined}
134
+ {...containerProps}
135
+ >
136
+ {isRowLayout ? (
137
+ <>
138
+ {labelElement}
139
+ <Box display="flex" flexDirection="column" width="100%">
140
+ {description && (
141
+ <Text as="p" fontSize="small" color="secondary" mb="xsmall">
142
+ {description}
143
+ </Text>
144
+ )}
145
+ {fieldElement}
146
+ {error && (
147
+ <Text pt="xxsmall" fontSize="xsmall" color="red">
148
+ {error}
149
+ </Text>
150
+ )}
151
+ </Box>
152
+ </>
153
+ ) : (
154
+ <>
155
+ {labelElement}
156
+ {description && (
157
+ <Text as="p" fontSize="xsmall" color="secondary" mb="xsmall">
158
+ {description}
159
+ </Text>
160
+ )}
161
+ {fieldElement}
162
+ {error && (
163
+ <Text pt="xxsmall" fontSize="xsmall" color="red">
164
+ {error}
165
+ </Text>
166
+ )}
167
+ </>
168
+ )}
169
+ </Box>
170
+ );
171
+ });
172
+
173
+ export default FieldSet;
@@ -0,0 +1,198 @@
1
+ "use client"
2
+
3
+ import React, { useState } from "react"
4
+ import { AnimatePresence, motion } from "motion/react"
5
+ import { Box, Button, Text } from "@/design-system/elements"
6
+ import {
7
+ Icon24Folder,
8
+ IconArrowSmRight,
9
+ IconDocFolder,
10
+ IconFile,
11
+ } from "@/design-system/icons"
12
+
13
+ export type FilesystemNode = {
14
+ name: string
15
+ nodes?: FilesystemNode[]
16
+ id?: string
17
+ }
18
+
19
+ export interface FilesystemProps {
20
+ nodes: FilesystemNode[]
21
+ initiallyOpenNames?: string[]
22
+ onSelectNode?: (node: FilesystemNode) => void
23
+ renderNode?: (
24
+ node: FilesystemNode,
25
+ defaultContent: React.ReactNode
26
+ ) => React.ReactNode
27
+ }
28
+
29
+ interface FilesystemItemProps {
30
+ node: FilesystemNode
31
+ initiallyOpenNames?: string[]
32
+ onSelectNode?: (node: FilesystemNode) => void
33
+ renderNode?: (
34
+ node: FilesystemNode,
35
+ defaultContent: React.ReactNode
36
+ ) => React.ReactNode
37
+ depth?: number
38
+ }
39
+
40
+ const TOGGLE_ICON_SIZE = "1.25rem"
41
+ const ITEM_ICON_SIZE = "1.125rem"
42
+ const CHILDREN_VARIANTS = {
43
+ open: { opacity: 1, height: "auto" },
44
+ collapsed: { opacity: 0, height: 0 },
45
+ }
46
+
47
+ const TRANSITION = { duration: 0.15, ease: [0.4, 0, 0.2, 1] }
48
+
49
+ function FilesystemItem({
50
+ node,
51
+ initiallyOpenNames,
52
+ onSelectNode,
53
+ renderNode,
54
+ depth = 0,
55
+ }: FilesystemItemProps) {
56
+ const [isOpen, setIsOpen] = useState(
57
+ () => !!initiallyOpenNames && initiallyOpenNames.includes(node.name)
58
+ )
59
+
60
+ const hasChildren = !!node.nodes && node.nodes.length > 0
61
+ const isFolder = !!node.nodes
62
+
63
+ return (
64
+ <Box as={motion.li} layout transition={TRANSITION}>
65
+ <Box
66
+ as="div"
67
+ display="flex"
68
+ alignItems="center"
69
+ gap="xxs"
70
+ py="xxs"
71
+ cursor="pointer"
72
+ title={node.name}
73
+ onClick={() => {
74
+ if (hasChildren) {
75
+ setIsOpen((open) => !open)
76
+ }
77
+ if (onSelectNode) {
78
+ onSelectNode(node)
79
+ }
80
+ }}
81
+ >
82
+ {hasChildren ? (
83
+ <Button
84
+ as="button"
85
+ type="button"
86
+ onClick={(event: Event) => {
87
+ event.stopPropagation()
88
+ setIsOpen((open) => !open)
89
+ if (onSelectNode) {
90
+ onSelectNode(node)
91
+ }
92
+ }}
93
+ variant=""
94
+ $size=""
95
+ size={TOGGLE_ICON_SIZE}
96
+ >
97
+ <Box
98
+ as={motion.span}
99
+ animate={{ rotate: isOpen ? 90 : 0 }}
100
+ transition={TRANSITION}
101
+ >
102
+ <Box
103
+ as={IconArrowSmRight}
104
+ size={TOGGLE_ICON_SIZE}
105
+ color="tertiary"
106
+ />
107
+ </Box>
108
+ </Button>
109
+ ) : (
110
+ // Spacer so folder / file icons line up with rows that have toggles
111
+ <Box size={TOGGLE_ICON_SIZE} flex="none" />
112
+ )}
113
+ {renderNode ? (
114
+ renderNode(
115
+ node,
116
+ <>
117
+ <Box
118
+ as={isFolder ? Icon24Folder : IconFile}
119
+ size={ITEM_ICON_SIZE}
120
+ color={"secondary"}
121
+ flex="none"
122
+ />
123
+ <Text as="span" fontSize="s" color="primary" lineClamp={1}>
124
+ {node.name}
125
+ </Text>
126
+ </>
127
+ )
128
+ ) : (
129
+ <>
130
+ <Box
131
+ as={isFolder ? Icon24Folder : IconFile}
132
+ size={ITEM_ICON_SIZE}
133
+ color={"secondary"}
134
+ flex="none"
135
+ />
136
+ <Text as="span" fontSize="s" color="primary" lineClamp={1}>
137
+ {node.name}
138
+ </Text>
139
+ </>
140
+ )}
141
+ </Box>
142
+
143
+ <AnimatePresence initial={false}>
144
+ {isOpen && hasChildren && (
145
+ <Box
146
+ as={motion.ul}
147
+ m={0}
148
+ p={0}
149
+ listStyleType="none"
150
+ initial="collapsed"
151
+ animate="open"
152
+ exit="collapsed"
153
+ variants={CHILDREN_VARIANTS}
154
+ transition={TRANSITION}
155
+ overflow="hidden"
156
+ >
157
+ {node.nodes!.map((child, index) => (
158
+ <FilesystemItem
159
+ key={`${child.id ?? child.name}-${index}`}
160
+ node={child}
161
+ initiallyOpenNames={initiallyOpenNames}
162
+ onSelectNode={onSelectNode}
163
+ renderNode={renderNode}
164
+ depth={depth + 1}
165
+ />
166
+ ))}
167
+ </Box>
168
+ )}
169
+ </AnimatePresence>
170
+ </Box>
171
+ )
172
+ }
173
+
174
+ const Filesystem: React.FC<FilesystemProps> = ({
175
+ nodes,
176
+ initiallyOpenNames,
177
+ onSelectNode,
178
+ renderNode,
179
+ }) => {
180
+ if (!nodes || nodes.length === 0) return null
181
+
182
+ return (
183
+ <Box as="ul" m={0} p={0} style={{ listStyle: "none" }}>
184
+ {nodes.map((node, index) => (
185
+ <FilesystemItem
186
+ key={`${node.id ?? node.name}-${index}`}
187
+ node={node}
188
+ initiallyOpenNames={initiallyOpenNames}
189
+ onSelectNode={onSelectNode}
190
+ renderNode={renderNode}
191
+ depth={0}
192
+ />
193
+ ))}
194
+ </Box>
195
+ )
196
+ }
197
+
198
+ export default Filesystem