@musecat/uikit 0.1.1

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 (180) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +51 -0
  3. package/index.ts +78 -0
  4. package/package.json +103 -0
  5. package/packages/Components/Box/Box.tsx +173 -0
  6. package/packages/Components/Box/Box.types.ts +48 -0
  7. package/packages/Components/Button/Button.tsx +275 -0
  8. package/packages/Components/Button/Button.types.ts +44 -0
  9. package/packages/Components/Card/Card.default.tsx +180 -0
  10. package/packages/Components/Card/Card.foldable.tsx +383 -0
  11. package/packages/Components/Card/Card.module.scss +23 -0
  12. package/packages/Components/Card/Card.tsx +8 -0
  13. package/packages/Components/Card/Card.types.ts +91 -0
  14. package/packages/Components/Card/Card.utils.ts +66 -0
  15. package/packages/Components/Checkbox/Checkbox.module.scss +11 -0
  16. package/packages/Components/Checkbox/Checkbox.tsx +104 -0
  17. package/packages/Components/Checkbox/Checkbox.types.ts +24 -0
  18. package/packages/Components/CodeBox/CodeBox.copy.tsx +43 -0
  19. package/packages/Components/CodeBox/CodeBox.module.scss +72 -0
  20. package/packages/Components/CodeBox/CodeBox.tsx +91 -0
  21. package/packages/Components/CodeBox/CodeBox.types.ts +17 -0
  22. package/packages/Components/ContextMenu/ContextMenu.module.scss +8 -0
  23. package/packages/Components/ContextMenu/ContextMenu.options.tsx +126 -0
  24. package/packages/Components/ContextMenu/ContextMenu.tsx +381 -0
  25. package/packages/Components/ContextMenu/ContextMenu.types.ts +46 -0
  26. package/packages/Components/ContributionGraph/ContributionGraph.module.scss +42 -0
  27. package/packages/Components/ContributionGraph/ContributionGraph.tsx +454 -0
  28. package/packages/Components/ContributionGraph/ContributionGraph.types.ts +27 -0
  29. package/packages/Components/DatePicker/DatePicker.calendar.tsx +161 -0
  30. package/packages/Components/DatePicker/DatePicker.core.tsx +582 -0
  31. package/packages/Components/DatePicker/DatePicker.module.scss +26 -0
  32. package/packages/Components/DatePicker/DatePicker.tsx +786 -0
  33. package/packages/Components/DatePicker/DatePicker.types.ts +36 -0
  34. package/packages/Components/DatePicker/DatePicker.utils.ts +80 -0
  35. package/packages/Components/DatePicker/hooks/useCalendar.ts +61 -0
  36. package/packages/Components/DatePicker/hooks/useSelection.ts +208 -0
  37. package/packages/Components/Divider/Divider.tsx +46 -0
  38. package/packages/Components/Divider/Divider.types.ts +12 -0
  39. package/packages/Components/Header/Header.module.scss +74 -0
  40. package/packages/Components/Header/Header.tsx +47 -0
  41. package/packages/Components/Header/Header.types.ts +8 -0
  42. package/packages/Components/Icon/Icon.group.tsx +122 -0
  43. package/packages/Components/Icon/Icon.group.types.ts +20 -0
  44. package/packages/Components/Icon/Icon.tsx +341 -0
  45. package/packages/Components/Icon/Icon.types.ts +59 -0
  46. package/packages/Components/Icon/SVG/data.tsx +232 -0
  47. package/packages/Components/Input/Input.module.scss +5 -0
  48. package/packages/Components/Input/Input.tsx +87 -0
  49. package/packages/Components/Input/Input.types.ts +14 -0
  50. package/packages/Components/Label/Label.module.scss +19 -0
  51. package/packages/Components/Label/Label.tsx +115 -0
  52. package/packages/Components/Label/Label.types.ts +28 -0
  53. package/packages/Components/Layout/Layout.docs.module.scss +42 -0
  54. package/packages/Components/Layout/Layout.docs.tsx +150 -0
  55. package/packages/Components/Layout/Layout.docs.types.ts +18 -0
  56. package/packages/Components/Layout/Layout.module.scss +91 -0
  57. package/packages/Components/Layout/Layout.tsx +306 -0
  58. package/packages/Components/Layout/Layout.types.ts +71 -0
  59. package/packages/Components/Maps/OSM/MapOSM.module.scss +11 -0
  60. package/packages/Components/Maps/OSM/MapOSM.shared.ts +121 -0
  61. package/packages/Components/Maps/OSM/MapOSM.tsx +279 -0
  62. package/packages/Components/Maps/OSM/MapOSM.types.ts +18 -0
  63. package/packages/Components/Nav/Nav.module.scss +137 -0
  64. package/packages/Components/Nav/Nav.tsx +304 -0
  65. package/packages/Components/Nav/Nav.types.ts +32 -0
  66. package/packages/Components/Nav/hooks/useNavDrag.ts +374 -0
  67. package/packages/Components/Nav/hooks/useNavIndicator.ts +137 -0
  68. package/packages/Components/Pagination/Pagination.tsx +254 -0
  69. package/packages/Components/Pagination/Pagination.types.ts +18 -0
  70. package/packages/Components/Pill/Pill.tsx +127 -0
  71. package/packages/Components/Pill/Pill.types.ts +33 -0
  72. package/packages/Components/Profile/Profile.shared.ts +74 -0
  73. package/packages/Components/Profile/Profile.tsx +382 -0
  74. package/packages/Components/Profile/Profile.types.ts +55 -0
  75. package/packages/Components/Progress/Progress.module.scss +15 -0
  76. package/packages/Components/Progress/Progress.tsx +88 -0
  77. package/packages/Components/Progress/Progress.types.ts +22 -0
  78. package/packages/Components/Radio/Radio.module.scss +11 -0
  79. package/packages/Components/Radio/Radio.tsx +121 -0
  80. package/packages/Components/Radio/Radio.types.ts +20 -0
  81. package/packages/Components/Select/Select.keyboard.tsx +76 -0
  82. package/packages/Components/Select/Select.trigger.tsx +88 -0
  83. package/packages/Components/Select/Select.tsx +11 -0
  84. package/packages/Components/Select/Select.types.ts +38 -0
  85. package/packages/Components/Select/Select.utils.ts +84 -0
  86. package/packages/Components/Select/hooks/useSelectNavigation.ts +461 -0
  87. package/packages/Components/Select/hooks/useSelectSelection.ts +142 -0
  88. package/packages/Components/Select/hooks/useSelectState.ts +314 -0
  89. package/packages/Components/Select/select.control.tsx +228 -0
  90. package/packages/Components/Select/select.inner.tsx +312 -0
  91. package/packages/Components/Skeleton/Skeleton.tsx +30 -0
  92. package/packages/Components/Skeleton/Skeleton.types.ts +12 -0
  93. package/packages/Components/Spinner/Spinner.module.scss +78 -0
  94. package/packages/Components/Spinner/Spinner.tsx +125 -0
  95. package/packages/Components/Spinner/Spinner.types.ts +19 -0
  96. package/packages/Components/Text/Text.module.scss +7 -0
  97. package/packages/Components/Text/Text.tsx +49 -0
  98. package/packages/Components/Text/Text.types.ts +33 -0
  99. package/packages/Components/TimePicker/TimePicker.core.tsx +448 -0
  100. package/packages/Components/TimePicker/TimePicker.module.scss +30 -0
  101. package/packages/Components/TimePicker/TimePicker.tsx +511 -0
  102. package/packages/Components/TimePicker/TimePicker.types.ts +22 -0
  103. package/packages/Components/Timeline/Timeline.tsx +111 -0
  104. package/packages/Components/Timeline/Timeline.types.ts +23 -0
  105. package/packages/Components/Title/Title.tsx +305 -0
  106. package/packages/Components/Title/Title.types.ts +64 -0
  107. package/packages/Components/Toggle/Toggle.module.scss +32 -0
  108. package/packages/Components/Toggle/Toggle.tsx +272 -0
  109. package/packages/Components/Toggle/Toggle.types.ts +17 -0
  110. package/packages/Components/Tooltip/Tooltip.module.scss +44 -0
  111. package/packages/Components/Tooltip/Tooltip.tsx +340 -0
  112. package/packages/Components/Tooltip/Tooltip.types.ts +38 -0
  113. package/packages/Frameworks/Dialog/Dialog.background.tsx +88 -0
  114. package/packages/Frameworks/Dialog/Dialog.boot.tsx +32 -0
  115. package/packages/Frameworks/Dialog/Dialog.store.ts +199 -0
  116. package/packages/Frameworks/Dialog/Dialog.tsx +185 -0
  117. package/packages/Frameworks/Dialog/Dialog.types.ts +264 -0
  118. package/packages/Frameworks/Dialog/Dialog.utils.ts +22 -0
  119. package/packages/Frameworks/Dialog/contents/Dialog.footer.tsx +79 -0
  120. package/packages/Frameworks/Dialog/contents/Dialog.funnel.tsx +260 -0
  121. package/packages/Frameworks/Dialog/contents/Dialog.header.tsx +137 -0
  122. package/packages/Frameworks/Dialog/hooks/useDialogPosition.ts +298 -0
  123. package/packages/Frameworks/Dialog/hooks/useSheetDrag.ts +325 -0
  124. package/packages/Frameworks/Dialog/hooks/useSheetGeometry.ts +201 -0
  125. package/packages/Frameworks/Dialog/hooks/useSheetProgressive.ts +98 -0
  126. package/packages/Frameworks/Dialog/renderers/renderModal.tsx +228 -0
  127. package/packages/Frameworks/Dialog/renderers/renderPopover.tsx +220 -0
  128. package/packages/Frameworks/Dialog/renderers/renderSheet.tsx +571 -0
  129. package/packages/Frameworks/EdgeEffect/EdgeEffect.tsx +52 -0
  130. package/packages/Frameworks/EdgeEffect/EdgeEffect.types.ts +5 -0
  131. package/packages/Frameworks/Motion/Motion.presets.ts +49 -0
  132. package/packages/Frameworks/Pressable/Pressable.module.scss +19 -0
  133. package/packages/Frameworks/Pressable/Pressable.tsx +651 -0
  134. package/packages/Frameworks/Pressable/Pressable.types.ts +73 -0
  135. package/packages/Frameworks/Squircle/Squircle.tsx +263 -0
  136. package/packages/Frameworks/Squircle/Squircle.types.ts +15 -0
  137. package/packages/Frameworks/Theme/Radius.types.ts +55 -0
  138. package/packages/Frameworks/Theme/Theme.types.ts +325 -0
  139. package/packages/Frameworks/Toaster/Toaster.boot.tsx +47 -0
  140. package/packages/Frameworks/Toaster/Toaster.module.scss +168 -0
  141. package/packages/Frameworks/Toaster/Toaster.types.ts +13 -0
  142. package/packages/Frameworks/View/DNDView/DNDView.module.scss +8 -0
  143. package/packages/Frameworks/View/DNDView/DNDView.tsx +470 -0
  144. package/packages/Frameworks/View/DNDView/DNDView.types.ts +61 -0
  145. package/packages/Frameworks/View/HScrollView/HScrollView.module.scss +59 -0
  146. package/packages/Frameworks/View/HScrollView/HScrollView.tsx +386 -0
  147. package/packages/Frameworks/View/HScrollView/HScrollView.types.ts +46 -0
  148. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.counter.tsx +27 -0
  149. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.footer.tsx +76 -0
  150. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.header.tsx +49 -0
  151. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.list.tsx +74 -0
  152. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.slide.tsx +71 -0
  153. package/packages/Frameworks/View/ImageView/Dialog/ImageDialog.tsx +175 -0
  154. package/packages/Frameworks/View/ImageView/Image.controls.tsx +39 -0
  155. package/packages/Frameworks/View/ImageView/Image.hooks.ts +82 -0
  156. package/packages/Frameworks/View/ImageView/Image.module.scss +7 -0
  157. package/packages/Frameworks/View/ImageView/Image.tsx +375 -0
  158. package/packages/Frameworks/View/ImageView/Image.types.ts +71 -0
  159. package/packages/Frameworks/View/ImageView/Image.utils.ts +79 -0
  160. package/packages/Frameworks/View/View.module.scss +34 -0
  161. package/packages/Frameworks/View/View.tsx +200 -0
  162. package/packages/Frameworks/View/View.types.ts +36 -0
  163. package/packages/Frameworks/_shared/Padding.types.ts +52 -0
  164. package/packages/Frameworks/_shared/StopParentInteraction.tsx +45 -0
  165. package/packages/Frameworks/_shared/Wind.types.ts +17 -0
  166. package/packages/Frameworks/_shared/bodyScrollLock.ts +127 -0
  167. package/packages/Frameworks/_shared/layer.constants.ts +8 -0
  168. package/packages/Frameworks/_shared/normalize.ts +39 -0
  169. package/packages/Frameworks/_shared/sizing.ts +55 -0
  170. package/packages/Frameworks/_shared/useControllableState.ts +29 -0
  171. package/packages/Styles/_animation.scss +42 -0
  172. package/packages/Styles/_color.scss +131 -0
  173. package/packages/Styles/_font.scss +30 -0
  174. package/packages/Styles/_icon.scss +782 -0
  175. package/packages/Styles/_importer.scss +9 -0
  176. package/packages/Styles/_system.scss +69 -0
  177. package/packages/Styles/_textstyle.scss +111 -0
  178. package/packages/Styles/_theme.scss +452 -0
  179. package/packages/Styles/_viewport-global.scss +99 -0
  180. package/packages/Styles/_viewport.scss +108 -0
@@ -0,0 +1,383 @@
1
+ "use client";
2
+
3
+ import clsx from "clsx";
4
+ import { animate, useMotionValue } from "motion/react";
5
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
6
+ import { useInBoxContent } from "@/packages/Components/Box/Box";
7
+ import styles from "@/packages/Components/Card/Card.module.scss";
8
+ import type { CardFoldableProps } from "@/packages/Components/Card/Card.types";
9
+ import { resolveState } from "@/packages/Components/Card/Card.types";
10
+ import { buildRenderKeys } from "@/packages/Components/Card/Card.utils";
11
+ import Icon from "@/packages/Components/Icon/Icon";
12
+ import Pill from "@/packages/Components/Pill/Pill";
13
+ import Text from "@/packages/Components/Text/Text";
14
+ import { normalizeCardIcon } from "@/packages/Components/Card/Card.utils";
15
+ import StopParentInteraction from "@/packages/Frameworks/_shared/StopParentInteraction";
16
+ import {
17
+ getComputedPixelValue,
18
+ SizePX,
19
+ type UIKitSizeValue,
20
+ } from "@/packages/Frameworks/_shared/sizing";
21
+ import Pressable from "@/packages/Frameworks/Pressable/Pressable";
22
+ import type { RadiusValue } from "@/packages/Frameworks/Theme/Radius.types";
23
+ import View from "@/packages/Frameworks/View/View";
24
+
25
+ function useFoldProgress(
26
+ activated: boolean,
27
+ enabled: boolean,
28
+ duration = 0.28,
29
+ ) {
30
+ const [progress, setProgress] = useState(activated ? 1 : 0);
31
+ const mv = useMotionValue(activated ? 1 : 0);
32
+
33
+ useEffect(() => {
34
+ if (!enabled) {
35
+ setProgress(activated ? 1 : 0);
36
+ return;
37
+ }
38
+ const controls = animate(mv, activated ? 1 : 0, {
39
+ duration,
40
+ ease: [0.4, 0, 0.2, 1],
41
+ onUpdate: (value) => setProgress(value),
42
+ });
43
+ return () => controls.stop();
44
+ }, [activated, enabled, duration, mv]);
45
+
46
+ return enabled ? progress : activated ? 1 : 0;
47
+ }
48
+
49
+ const CARD_ACCORDION_EVENT = "uikit-card-accordion-change";
50
+
51
+ type RadiusTupleValue = Extract<RadiusValue, readonly unknown[]>;
52
+
53
+ const isRadiusArrayValue = (radius: RadiusValue): radius is RadiusTupleValue =>
54
+ Array.isArray(radius);
55
+
56
+ const resolveFoldableHeaderRadius = (
57
+ radius: RadiusValue | undefined,
58
+ activated: boolean,
59
+ ): RadiusValue => {
60
+ if (!activated) return radius ?? "Regular";
61
+ if (!radius) return ["Regular", "Regular", "None", "None"];
62
+
63
+ if (isRadiusArrayValue(radius)) {
64
+ return [radius[0] ?? "Regular", radius[1] ?? radius[0], "None", "None"];
65
+ }
66
+
67
+ return [radius, radius, "None", "None"];
68
+ };
69
+
70
+ const resolveFoldableBodyRadius = (
71
+ radius: RadiusValue | undefined,
72
+ ): RadiusValue => {
73
+ if (!radius) return ["None", "None", "Regular", "Regular"];
74
+
75
+ if (isRadiusArrayValue(radius)) {
76
+ return [
77
+ "None",
78
+ "None",
79
+ radius[2] ?? radius[0] ?? "Regular",
80
+ radius[3] ?? radius[1] ?? radius[0] ?? "Regular",
81
+ ];
82
+ }
83
+
84
+ return ["None", "None", radius, radius];
85
+ };
86
+
87
+ export default function CardFoldable({
88
+ themePreset,
89
+ background,
90
+ color,
91
+ border,
92
+ shadow,
93
+ contained,
94
+ radius,
95
+ pressable,
96
+ style,
97
+ className,
98
+ title,
99
+ caption,
100
+ icon,
101
+ arrow,
102
+ pill,
103
+ customRight,
104
+ customRightAllowDefault,
105
+ accordion,
106
+ children,
107
+ "data-color-mode": dataTheme,
108
+ }: CardFoldableProps) {
109
+ const shouldBlockHostDefault = Boolean(pressable) && !customRightAllowDefault;
110
+ const inBoxContent = useInBoxContent();
111
+ const resolvedContained = contained ?? !inBoxContent;
112
+ const resolvedIcon = normalizeCardIcon(icon);
113
+ const pillItems = Array.isArray(pill) ? pill : pill ? [pill] : [];
114
+ const pillKeys = buildRenderKeys(
115
+ pillItems,
116
+ (pillItem, index) =>
117
+ pillItem.renderKey ||
118
+ [
119
+ pillItem.pressable?.href,
120
+ pillItem.icon?.icon,
121
+ pillItem.icon?.svg,
122
+ pillItem.rightIcon?.icon,
123
+ index,
124
+ ]
125
+ .filter(Boolean)
126
+ .join("-"),
127
+ );
128
+
129
+ const [activated, setActivated] = useState(
130
+ accordion.activated ?? accordion.defaultActivated ?? false,
131
+ );
132
+ const {
133
+ name: accordionName,
134
+ value: accordionValue,
135
+ onActivatedChange,
136
+ } = accordion;
137
+ const animateRadius = accordion.animateRadius ?? true;
138
+
139
+ const {
140
+ disabled: pressableDisabled,
141
+ onClick: pressableOnClick,
142
+ ...restPressable
143
+ } = pressable ?? {};
144
+ const isInteractionDisabled = Boolean(pressableDisabled);
145
+
146
+ const headerRef = useRef<HTMLElement | null>(null);
147
+ const [_measuredR, setMeasuredR] = useState<number | null>(null);
148
+
149
+ useLayoutEffect(() => {
150
+ if (!animateRadius || !headerRef.current) return;
151
+ const val = getComputedPixelValue(headerRef.current, "borderTopLeftRadius");
152
+ if (val !== undefined && val > 0) setMeasuredR(val);
153
+ }, [animateRadius]);
154
+
155
+ useEffect(() => {
156
+ if (accordion.activated === undefined) return;
157
+ setActivated(accordion.activated);
158
+ }, [accordion.activated]);
159
+
160
+ useEffect(() => {
161
+ const { name, value, onActivatedChange } = accordion;
162
+ if (!name || !value) return;
163
+
164
+ const handleAccordionChange = (event: Event) => {
165
+ const detail =
166
+ (
167
+ event as CustomEvent<{
168
+ name?: string;
169
+ value?: string;
170
+ }>
171
+ ).detail ?? {};
172
+
173
+ if (detail.name !== name || detail.value === value) return;
174
+
175
+ queueMicrotask(() => {
176
+ setActivated(false);
177
+ onActivatedChange?.(false);
178
+ });
179
+ };
180
+
181
+ window.addEventListener(CARD_ACCORDION_EVENT, handleAccordionChange);
182
+ return () => {
183
+ window.removeEventListener(CARD_ACCORDION_EVENT, handleAccordionChange);
184
+ };
185
+ }, [accordion]);
186
+
187
+ const resolvedTitle = resolveState(title, activated);
188
+ const resolvedCaption = resolveState(caption, activated);
189
+ const resolvedThemePreset = resolveState(themePreset, activated);
190
+ const showCaption = Boolean(resolvedCaption);
191
+ const showTitleGroup = Boolean(resolvedTitle) || showCaption;
192
+
193
+ const progress = useFoldProgress(activated, animateRadius);
194
+
195
+ const radiusUnit = isRadiusArrayValue(radius ?? "Regular")
196
+ ? (radius as RadiusTupleValue)[0]
197
+ : (radius as UIKitSizeValue | undefined);
198
+ const cornerUnit = SizePX(radiusUnit as UIKitSizeValue | undefined, 8);
199
+ const _headerRadius: RadiusValue = animateRadius
200
+ ? ([
201
+ cornerUnit,
202
+ cornerUnit,
203
+ cornerUnit * (1 - progress),
204
+ cornerUnit * (1 - progress),
205
+ ] as RadiusValue)
206
+ : resolveFoldableHeaderRadius(radius, activated);
207
+ const _bodyRadius = resolveFoldableBodyRadius(radius);
208
+
209
+ const handleToggle = (event: React.MouseEvent) => {
210
+ pressableOnClick?.(event);
211
+ if (isInteractionDisabled) return;
212
+
213
+ setActivated((prev) => {
214
+ const nextActivated = !prev;
215
+ onActivatedChange?.(nextActivated);
216
+
217
+ if (nextActivated && accordionName && accordionValue) {
218
+ window.dispatchEvent(
219
+ new CustomEvent(CARD_ACCORDION_EVENT, {
220
+ detail: {
221
+ name: accordionName,
222
+ value: accordionValue,
223
+ },
224
+ }),
225
+ );
226
+ }
227
+
228
+ return nextActivated;
229
+ });
230
+ };
231
+
232
+ return (
233
+ <View
234
+ column
235
+ gap={0}
236
+ data-color-mode={dataTheme}
237
+ radius={radius ?? "Regular"}
238
+ themePreset={
239
+ resolveState(themePreset, activated) ??
240
+ (activated ? "BaseFull" : "UIPrimary")
241
+ }
242
+ background={resolveState(background, activated)}
243
+ color={resolveState(color, activated)}
244
+ selected={activated}
245
+ disabled={isInteractionDisabled}
246
+ border={border}
247
+ shadow={shadow}
248
+ style={{ overflow: "hidden" }}
249
+ >
250
+ {}
251
+ <Pressable
252
+ gap={12}
253
+ alignItems="center"
254
+ justifyContent="space-between"
255
+ data-color-mode={dataTheme}
256
+ {...restPressable}
257
+ onClick={handleToggle}
258
+ fullWidth
259
+ className={clsx(
260
+ styles.Card,
261
+ resolvedContained && styles.Contained,
262
+ className,
263
+ )}
264
+ style={style}
265
+ >
266
+ <View gap={12} fullWidth style={{ minWidth: 0 }}>
267
+ {resolvedIcon && <Icon {...resolvedIcon} />}
268
+ {showTitleGroup && (
269
+ <View column justifyContent="center" gap={2} fullWidth>
270
+ {resolvedTitle && (
271
+ <Text type="Subheadline" weight={500}>
272
+ {resolvedTitle}
273
+ </Text>
274
+ )}
275
+ {showCaption && (
276
+ <Text type="Footnote" opacity={0.6}>
277
+ {resolvedCaption}
278
+ </Text>
279
+ )}
280
+ </View>
281
+ )}
282
+ </View>
283
+
284
+ <View gap={8}>
285
+ {pillItems.length > 0 && (
286
+ <StopParentInteraction
287
+ alignItems="center"
288
+ gap={6}
289
+ style={{ width: "max-content", maxWidth: "100%" }}
290
+ blockDefault={shouldBlockHostDefault}
291
+ data-stop-parent-interaction-state
292
+ >
293
+ {pillItems.map((pillItem, index) => (
294
+ <Pill
295
+ key={pillKeys[index]}
296
+ themePreset={
297
+ pillItem.themePreset ? resolvedThemePreset : "BaseFull"
298
+ }
299
+ padding={8}
300
+ {...pillItem}
301
+ />
302
+ ))}
303
+ </StopParentInteraction>
304
+ )}
305
+ {customRight && (
306
+ <StopParentInteraction
307
+ alignItems="center"
308
+ gap={12}
309
+ blockDefault={shouldBlockHostDefault}
310
+ data-stop-parent-interaction-state
311
+ >
312
+ {customRight}
313
+ </StopParentInteraction>
314
+ )}
315
+
316
+ {arrow &&
317
+ (() => {
318
+ if (typeof arrow === "boolean") {
319
+ return (
320
+ <Icon
321
+ icon={activated ? "iArrowKeyUp" : "iArrowKeyDown"}
322
+ iconFill
323
+ size={20}
324
+ opacity={0.6}
325
+ />
326
+ );
327
+ }
328
+ const { filled, degree, ...iconProps } = arrow;
329
+ return (
330
+ <Icon
331
+ icon={activated ? "iArrowKeyUp" : "iArrowKeyDown"}
332
+ iconFill
333
+ size={18}
334
+ {...iconProps}
335
+ box={iconProps.box ?? true}
336
+ boxOptions={iconProps.boxOptions ?? { padding: 6 }}
337
+ background={iconProps.background}
338
+ themePreset={
339
+ filled
340
+ ? iconProps.themePreset
341
+ ? resolvedThemePreset
342
+ : "BaseFull"
343
+ : iconProps.themePreset
344
+ }
345
+ style={{
346
+ transform: degree ? `rotate(${degree}deg)` : undefined,
347
+ ...iconProps.style,
348
+ }}
349
+ />
350
+ );
351
+ })()}
352
+ </View>
353
+ </Pressable>
354
+
355
+ <View
356
+ motion={{
357
+ initial: false,
358
+ animate: {
359
+ height: activated ? "auto" : 0,
360
+ opacity: activated ? 1 : 0,
361
+ },
362
+ transition: {
363
+ duration: 0.28,
364
+ ease: [0.4, 0, 0.2, 1],
365
+ },
366
+ }}
367
+ fullWidth
368
+ >
369
+ <View
370
+ column
371
+ gap={12}
372
+ className={clsx(
373
+ contained ? styles.containedInnerChildren : styles.InnerChildren,
374
+ accordion.innerClassName,
375
+ )}
376
+ style={accordion.innerStyle}
377
+ >
378
+ {children}
379
+ </View>
380
+ </View>
381
+ </View>
382
+ );
383
+ }
@@ -0,0 +1,23 @@
1
+ .Card {
2
+ padding: 1.2rem 1.6rem;
3
+
4
+ &.Contained {
5
+ padding: 1.6rem 2rem;
6
+
7
+ @media (max-width: 409.98px) {
8
+ padding: 1.2rem 1.6rem;
9
+ }
10
+ }
11
+ }
12
+
13
+ .containedInnerChildren {
14
+ padding: 1.6rem 2rem;
15
+
16
+ @media (max-width: 409.98px) {
17
+ padding: 1.2rem 1.6rem;
18
+ }
19
+ }
20
+
21
+ .InnerChildren {
22
+ padding: 1.2rem 1.6rem;
23
+ }
@@ -0,0 +1,8 @@
1
+ import type { CardProps } from "@/packages/Components/Card/Card.types";
2
+ import CardDefault from "@/packages/Components/Card/Card.default";
3
+ import CardFoldable from "@/packages/Components/Card/Card.foldable";
4
+
5
+ export default function Card(props: CardProps) {
6
+ if (props.accordion) return <CardFoldable {...props} />;
7
+ return <CardDefault {...props} />;
8
+ }
@@ -0,0 +1,91 @@
1
+ import type { IconProps } from "@/packages/Components/Icon/Icon.types";
2
+ import type { PillProps } from "@/packages/Components/Pill/Pill.types";
3
+ import type { PressableProps } from "@/packages/Frameworks/Pressable/Pressable.types";
4
+ import type { RadiusProps } from "@/packages/Frameworks/Theme/Radius.types";
5
+ import type {
6
+ BorderProps,
7
+ ThemeBackgroundPaint,
8
+ ThemePaint,
9
+ ThemePreset,
10
+ ThemeSystemProps,
11
+ } from "@/packages/Frameworks/Theme/Theme.types";
12
+
13
+ export type Stateful<T> = T | { normal?: T; activated?: T };
14
+
15
+ export function resolveState<T>(
16
+ value: Stateful<T> | undefined,
17
+ activated: boolean,
18
+ ): T | undefined {
19
+ if (value === undefined) return undefined;
20
+ if (
21
+ typeof value === "object" &&
22
+ value !== null &&
23
+ !Array.isArray(value) &&
24
+ ("normal" in value || "activated" in value)
25
+ ) {
26
+ return activated ? value.activated : value.normal;
27
+ }
28
+ return value as T;
29
+ }
30
+
31
+ export interface CardBaseProps extends BorderProps, RadiusProps {
32
+ "data-color-mode"?: string;
33
+ className?: string;
34
+ style?: React.CSSProperties;
35
+ vertical?: boolean;
36
+ contained?: boolean;
37
+
38
+ pressable?: PressableProps;
39
+
40
+ title?: React.ReactNode;
41
+ caption?: React.ReactNode;
42
+ titleReversed?: boolean;
43
+
44
+ icon?: IconProps;
45
+
46
+ arrow?:
47
+ | (IconProps & {
48
+ filled?: boolean;
49
+ degree?: 0 | 90 | 180 | 270;
50
+ })
51
+ | boolean;
52
+
53
+ pill?: PillProps[] | PillProps;
54
+
55
+ customRight?: React.ReactNode;
56
+ customRightAllowDefault?: boolean;
57
+ }
58
+
59
+ export interface CardDefaultProps extends CardBaseProps, ThemeSystemProps {
60
+ title?: React.ReactNode;
61
+ caption?: React.ReactNode;
62
+ accordion?: never;
63
+ }
64
+
65
+ export interface CardAccordionProps {
66
+ name?: string;
67
+ value?: string;
68
+ activated?: boolean;
69
+ defaultActivated?: boolean;
70
+ onActivatedChange?: (activated: boolean) => void;
71
+ innerClassName?: string;
72
+ innerStyle?: React.CSSProperties;
73
+ animateRadius?: boolean;
74
+ }
75
+
76
+ export interface CardFoldableProps
77
+ extends Omit<
78
+ CardBaseProps,
79
+ "title" | "caption" | "titleReversed" | "vertical"
80
+ >,
81
+ Omit<ThemeSystemProps, "themePreset" | "color" | "background"> {
82
+ children?: React.ReactNode;
83
+ accordion: CardAccordionProps;
84
+ themePreset?: Stateful<ThemePreset>;
85
+ color?: Stateful<ThemePaint>;
86
+ background?: Stateful<ThemeBackgroundPaint>;
87
+ title?: Stateful<React.ReactNode>;
88
+ caption?: Stateful<React.ReactNode>;
89
+ }
90
+
91
+ export type CardProps = CardDefaultProps | CardFoldableProps;
@@ -0,0 +1,66 @@
1
+ import type { IconProps } from "@/packages/Components/Icon/Icon.types";
2
+
3
+ const DEFAULT_CARD_BOX_ICON_OUTER_SIZE = 40;
4
+
5
+ export function normalizeCardIcon(icon?: IconProps) {
6
+ if (!icon) {
7
+ return undefined;
8
+ }
9
+
10
+ const hasExplicitSize =
11
+ icon.size !== undefined ||
12
+ icon.width !== undefined ||
13
+ icon.height !== undefined;
14
+ const normalizedIcon =
15
+ !icon.box && !hasExplicitSize
16
+ ? {
17
+ ...icon,
18
+ size: 20,
19
+ }
20
+ : icon;
21
+
22
+ if (!normalizedIcon.box || !normalizedIcon.image) return normalizedIcon;
23
+
24
+ const hasDirectionalPadding =
25
+ normalizedIcon.boxOptions?.paddingHorizontal !== undefined ||
26
+ normalizedIcon.boxOptions?.paddingVertical !== undefined;
27
+ if (hasDirectionalPadding) return normalizedIcon;
28
+
29
+ const contentSize =
30
+ typeof normalizedIcon.width === "number"
31
+ ? normalizedIcon.width
32
+ : typeof normalizedIcon.height === "number"
33
+ ? normalizedIcon.height
34
+ : typeof normalizedIcon.size === "number"
35
+ ? normalizedIcon.size
36
+ : 24;
37
+
38
+ const shouldNormalizePadding =
39
+ normalizedIcon.boxOptions?.padding === undefined;
40
+ if (!shouldNormalizePadding) return normalizedIcon;
41
+
42
+ return {
43
+ ...normalizedIcon,
44
+ boxOptions: {
45
+ ...normalizedIcon.boxOptions,
46
+ padding: Math.max(
47
+ 0,
48
+ (DEFAULT_CARD_BOX_ICON_OUTER_SIZE - contentSize) / 2,
49
+ ),
50
+ },
51
+ };
52
+ }
53
+
54
+ export const buildRenderKeys = <T>(
55
+ items: T[],
56
+ resolveBase: (item: T, index: number) => string,
57
+ ): string[] => {
58
+ const counts = new Map<string, number>();
59
+
60
+ return items.map((item, index) => {
61
+ const base = resolveBase(item, index) || "item";
62
+ const nextCount = (counts.get(base) ?? 0) + 1;
63
+ counts.set(base, nextCount);
64
+ return `${base}-${nextCount}`;
65
+ });
66
+ };
@@ -0,0 +1,11 @@
1
+ .CheckboxWrapper {
2
+ display: flex;
3
+ align-items: center;
4
+ width: max-content;
5
+ max-width: 100%;
6
+ background: transparent;
7
+
8
+ .Input {
9
+ display: none;
10
+ }
11
+ }
@@ -0,0 +1,104 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import styles from "@/packages/Components/Checkbox/Checkbox.module.scss";
5
+ import type { CheckboxProps } from "@/packages/Components/Checkbox/Checkbox.types";
6
+ import Icon from "@/packages/Components/Icon/Icon";
7
+ import Text from "@/packages/Components/Text/Text";
8
+ import { Size } from "@/packages/Frameworks/_shared/sizing";
9
+ import View from "@/packages/Frameworks/View/View";
10
+
11
+ export default function Checkbox({
12
+ title,
13
+ titleType,
14
+ titleSpaceBetween,
15
+ reversed,
16
+ readOnly,
17
+ "data-color-mode": dataTheme,
18
+ size,
19
+ radius,
20
+ border,
21
+ themePreset,
22
+ background,
23
+ color,
24
+ themeInteractive,
25
+ selected,
26
+ ...props
27
+ }: CheckboxProps) {
28
+ const isControlled = props.checked !== undefined;
29
+ const [internalChecked, setInternalChecked] = useState(
30
+ Boolean(props.defaultChecked),
31
+ );
32
+ const isChecked = isControlled ? Boolean(props.checked) : internalChecked;
33
+ const isInteractive = themeInteractive ?? (!readOnly && !props.disabled);
34
+
35
+ useEffect(() => {
36
+ if (!isControlled) return;
37
+ setInternalChecked(Boolean(props.checked));
38
+ }, [isControlled, props.checked]);
39
+
40
+ return (
41
+ <label
42
+ className={styles.CheckboxWrapper}
43
+ data-color-mode={dataTheme}
44
+ title=""
45
+ style={{
46
+ gap: size ? `calc(${Size(size)} / 3)` : ".8rem",
47
+ justifyContent: titleSpaceBetween ? "space-between" : undefined,
48
+ width: titleSpaceBetween ? "100%" : undefined,
49
+ }}
50
+ >
51
+ <input
52
+ type="checkbox"
53
+ readOnly={readOnly}
54
+ className={styles.Input}
55
+ aria-checked={isChecked}
56
+ {...props}
57
+ checked={isControlled ? props.checked : undefined}
58
+ defaultChecked={isControlled ? undefined : props.defaultChecked}
59
+ onChange={(event) => {
60
+ if (!isControlled) {
61
+ setInternalChecked(event.currentTarget.checked);
62
+ }
63
+ props.onChange?.(event);
64
+ }}
65
+ />
66
+ <View
67
+ alignItems="center"
68
+ justifyContent="center"
69
+ width={size ?? 24}
70
+ height={size ?? 24}
71
+ border={border}
72
+ radius={radius ?? "ExtraLight"}
73
+ themePreset={
74
+ themePreset ?? (isChecked ? "ReversedUISecondary" : "UIPrimary")
75
+ }
76
+ background={background}
77
+ color={color}
78
+ themeInteractive={isInteractive}
79
+ selected={selected}
80
+ data-disabled={props.disabled ? "true" : undefined}
81
+ data-readonly={readOnly ? "true" : undefined}
82
+ style={{
83
+ position: "relative",
84
+ order: reversed ? 1 : undefined,
85
+ }}
86
+ >
87
+ <Icon
88
+ icon="iCheck"
89
+ weight={700}
90
+ size={typeof size === "number" ? size * 0.75 : 20}
91
+ opacity={isChecked ? 1 : 0}
92
+ />
93
+ </View>
94
+ {title && (
95
+ <Text
96
+ style={{ minWidth: 0, flex: 1 }}
97
+ type={titleType ?? "Subheadline"}
98
+ >
99
+ {title}
100
+ </Text>
101
+ )}
102
+ </label>
103
+ );
104
+ }