@muja-ui/native 0.2.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,2399 @@
1
+ import { createContext, forwardRef, useState, useContext, useCallback, useMemo, useRef, useEffect } from 'react';
2
+ import { View, Text as Text$1, Pressable, ActivityIndicator, TextInput, Platform, useColorScheme, StyleSheet, useWindowDimensions, Animated, Easing, PanResponder, Modal as Modal$1, ScrollView, RefreshControl, Image } from 'react-native';
3
+ import { darkTheme, lightTheme, getIcon } from '@muja-ui/core';
4
+ export { createTheme, darkTheme, lightTheme, registerIcons } from '@muja-ui/core';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import { warnOnce, clamp } from '@muja-ui/utils';
7
+ import Svg, { Path } from 'react-native-svg';
8
+ import { MinusIcon, CheckIcon, ChevronDownIcon, InfoIcon, AlertCircleIcon, AlertTriangleIcon, XIcon, ChevronRightIcon, ChevronLeftIcon } from '@muja-ui/icons';
9
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
10
+
11
+ // src/components/Box.tsx
12
+ var ThemeContext = createContext(null);
13
+ function ThemeProvider({
14
+ theme = lightTheme,
15
+ darkTheme: darkTheme2 = darkTheme,
16
+ defaultColorMode = "system",
17
+ colorMode: colorModeProp,
18
+ onColorModeChange,
19
+ children
20
+ }) {
21
+ const [uncontrolledMode, setUncontrolledMode] = useState(defaultColorMode);
22
+ const colorMode = colorModeProp ?? uncontrolledMode;
23
+ const systemScheme = useColorScheme();
24
+ const resolvedColorMode = colorMode === "system" ? systemScheme === "dark" ? "dark" : "light" : colorMode;
25
+ const setColorMode = useCallback(
26
+ (mode) => {
27
+ if (colorModeProp === void 0) setUncontrolledMode(mode);
28
+ onColorModeChange?.(mode);
29
+ },
30
+ [colorModeProp, onColorModeChange]
31
+ );
32
+ const toggleColorMode = useCallback(() => {
33
+ setColorMode(resolvedColorMode === "dark" ? "light" : "dark");
34
+ }, [resolvedColorMode, setColorMode]);
35
+ const value = useMemo(
36
+ () => ({
37
+ theme: resolvedColorMode === "dark" ? darkTheme2 : theme,
38
+ colorMode,
39
+ resolvedColorMode,
40
+ setColorMode,
41
+ toggleColorMode
42
+ }),
43
+ [theme, darkTheme2, colorMode, resolvedColorMode, setColorMode, toggleColorMode]
44
+ );
45
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
46
+ }
47
+ function useThemeContext(hookName) {
48
+ const context = useContext(ThemeContext);
49
+ if (!context) {
50
+ throw new Error(`[muja-ui] ${hookName} must be used within a <ThemeProvider>.`);
51
+ }
52
+ return context;
53
+ }
54
+ function useTheme() {
55
+ return useThemeContext("useTheme").theme;
56
+ }
57
+ function useColorMode() {
58
+ const { colorMode, resolvedColorMode, setColorMode, toggleColorMode } = useThemeContext("useColorMode");
59
+ return { colorMode, resolvedColorMode, setColorMode, toggleColorMode };
60
+ }
61
+ function useColorModeValue(lightValue, darkValue) {
62
+ return useThemeContext("useColorModeValue").resolvedColorMode === "dark" ? darkValue : lightValue;
63
+ }
64
+ function shadowStyle(value) {
65
+ const layer = value.layers[0];
66
+ if (!layer) return Platform.OS === "android" ? { elevation: 0 } : { shadowOpacity: 0 };
67
+ const alpha = /rgba?\([^)]*,\s*([\d.]+)\s*\)/.exec(layer.color)?.[1];
68
+ return {
69
+ shadowColor: layer.color.replace(/rgba?\(([^)]*?),\s*[\d.]+\s*\)/, "rgb($1)"),
70
+ shadowOffset: { width: layer.x, height: layer.y },
71
+ shadowOpacity: alpha ? Number(alpha) : 0.1,
72
+ shadowRadius: layer.blur / 2,
73
+ // Android's single-number model: approximate the blur as elevation.
74
+ elevation: Math.round(layer.blur / 2)
75
+ };
76
+ }
77
+ var space = (key) => (value, theme) => ({ [key]: theme.space[value] });
78
+ var pair = (a, b) => (value, theme) => ({ [a]: theme.space[value], [b]: theme.space[value] });
79
+ var passthrough = (key) => (value) => ({ [key]: value });
80
+ var resolvers = {
81
+ m: space("margin"),
82
+ mt: space("marginTop"),
83
+ mr: space("marginRight"),
84
+ mb: space("marginBottom"),
85
+ ml: space("marginLeft"),
86
+ mx: pair("marginLeft", "marginRight"),
87
+ my: pair("marginTop", "marginBottom"),
88
+ p: space("padding"),
89
+ pt: space("paddingTop"),
90
+ pr: space("paddingRight"),
91
+ pb: space("paddingBottom"),
92
+ pl: space("paddingLeft"),
93
+ px: pair("paddingLeft", "paddingRight"),
94
+ py: pair("paddingTop", "paddingBottom"),
95
+ bg: ((value, theme) => ({
96
+ backgroundColor: theme.colors[value]
97
+ })),
98
+ borderColor: ((value, theme) => ({
99
+ borderColor: theme.colors[value]
100
+ })),
101
+ borderWidth: ((value, theme) => ({
102
+ borderWidth: theme.borderWidth[value]
103
+ })),
104
+ borderTopWidth: ((value, theme) => ({
105
+ borderTopWidth: theme.borderWidth[value]
106
+ })),
107
+ borderBottomWidth: ((value, theme) => ({
108
+ borderBottomWidth: theme.borderWidth[value]
109
+ })),
110
+ radius: ((value, theme) => ({
111
+ borderRadius: theme.radius[value]
112
+ })),
113
+ shadow: ((value, theme) => shadowStyle(theme.shadow[value])),
114
+ zIndex: ((value, theme) => ({ zIndex: theme.zIndex[value] })),
115
+ opacity: passthrough("opacity"),
116
+ w: passthrough("width"),
117
+ h: passthrough("height"),
118
+ minW: passthrough("minWidth"),
119
+ maxW: passthrough("maxWidth"),
120
+ minH: passthrough("minHeight"),
121
+ maxH: passthrough("maxHeight"),
122
+ flex: passthrough("flex"),
123
+ alignSelf: passthrough("alignSelf"),
124
+ position: passthrough("position"),
125
+ top: passthrough("top"),
126
+ right: passthrough("right"),
127
+ bottom: passthrough("bottom"),
128
+ left: passthrough("left"),
129
+ overflow: passthrough("overflow")
130
+ };
131
+ function isStyleProp(key) {
132
+ return key in resolvers;
133
+ }
134
+ function splitStyleProps(props, theme) {
135
+ const style = {};
136
+ const rest = {};
137
+ for (const key of Object.keys(props)) {
138
+ const value = props[key];
139
+ if (value !== void 0 && isStyleProp(key)) {
140
+ Object.assign(style, resolvers[key](value, theme));
141
+ } else {
142
+ rest[key] = value;
143
+ }
144
+ }
145
+ return { style, rest };
146
+ }
147
+ var Box = forwardRef(function Box2(props, ref) {
148
+ const theme = useTheme();
149
+ const { style: styleProp, ...restProps } = props;
150
+ const { style, rest } = splitStyleProps(restProps, theme);
151
+ return /* @__PURE__ */ jsx(View, { ref, ...rest, style: [style, styleProp] });
152
+ });
153
+ var Flex = forwardRef(function Flex2({ direction = "row", align, justify, wrap, gap, rowGap, columnGap, style, ...rest }, ref) {
154
+ const theme = useTheme();
155
+ return /* @__PURE__ */ jsx(
156
+ Box,
157
+ {
158
+ ref,
159
+ ...rest,
160
+ style: [
161
+ {
162
+ flexDirection: direction,
163
+ alignItems: align,
164
+ justifyContent: justify,
165
+ flexWrap: wrap,
166
+ gap: gap !== void 0 ? theme.space[gap] : void 0,
167
+ rowGap: rowGap !== void 0 ? theme.space[rowGap] : void 0,
168
+ columnGap: columnGap !== void 0 ? theme.space[columnGap] : void 0
169
+ },
170
+ style
171
+ ]
172
+ }
173
+ );
174
+ });
175
+ var Stack = forwardRef(function Stack2({ direction = "column", gap = 4, ...rest }, ref) {
176
+ return /* @__PURE__ */ jsx(Flex, { ref, direction, gap, ...rest });
177
+ });
178
+ var HStack = forwardRef(function HStack2(props, ref) {
179
+ return /* @__PURE__ */ jsx(Stack, { ref, direction: "row", align: "center", ...props });
180
+ });
181
+ function Spacer({ size, axis = "vertical" }) {
182
+ const theme = useTheme();
183
+ if (size === void 0) return /* @__PURE__ */ jsx(View, { style: { flex: 1 } });
184
+ const length = theme.space[size];
185
+ return /* @__PURE__ */ jsx(View, { style: axis === "vertical" ? { height: length } : { width: length } });
186
+ }
187
+ function Divider({ orientation = "horizontal", color = "border", style }) {
188
+ const theme = useTheme();
189
+ return /* @__PURE__ */ jsx(
190
+ View,
191
+ {
192
+ accessibilityElementsHidden: true,
193
+ importantForAccessibility: "no-hide-descendants",
194
+ style: [
195
+ orientation === "horizontal" ? { height: StyleSheet.hairlineWidth, alignSelf: "stretch" } : { width: StyleSheet.hairlineWidth, alignSelf: "stretch" },
196
+ { backgroundColor: theme.colors[color] },
197
+ style
198
+ ]
199
+ }
200
+ );
201
+ }
202
+ var Text = forwardRef(function Text2(props, ref) {
203
+ const theme = useTheme();
204
+ const {
205
+ size = "md",
206
+ weight,
207
+ family = "sans",
208
+ leading,
209
+ tracking,
210
+ align,
211
+ color = "text",
212
+ truncate,
213
+ style: styleProp,
214
+ numberOfLines,
215
+ ...restProps
216
+ } = props;
217
+ const { style: tokenStyle, rest } = splitStyleProps(
218
+ restProps,
219
+ theme
220
+ );
221
+ const fontSize = theme.typography.fontSize[size];
222
+ const textStyle = {
223
+ fontFamily: theme.typography.fontFamily[family],
224
+ fontSize,
225
+ color: theme.colors[color],
226
+ fontWeight: weight ? theme.typography.fontWeight[weight] : void 0,
227
+ lineHeight: leading ? Math.round(fontSize * theme.typography.lineHeight[leading]) : void 0,
228
+ letterSpacing: tracking ? theme.typography.letterSpacing[tracking] : void 0,
229
+ textAlign: align
230
+ };
231
+ return /* @__PURE__ */ jsx(
232
+ Text$1,
233
+ {
234
+ ref,
235
+ numberOfLines: truncate ? 1 : numberOfLines,
236
+ ellipsizeMode: truncate ? "tail" : void 0,
237
+ ...rest,
238
+ style: [textStyle, tokenStyle, styleProp]
239
+ }
240
+ );
241
+ });
242
+ var defaultSize = {
243
+ 1: "3xl",
244
+ 2: "2xl",
245
+ 3: "xl",
246
+ 4: "lg",
247
+ 5: "md",
248
+ 6: "sm"
249
+ };
250
+ var Heading = forwardRef(function Heading2({ level = 2, size, weight = "semibold", leading = "tight", ...rest }, ref) {
251
+ return /* @__PURE__ */ jsx(
252
+ Text,
253
+ {
254
+ ref,
255
+ accessibilityRole: "header",
256
+ "aria-level": level,
257
+ size: size ?? defaultSize[level],
258
+ weight,
259
+ leading,
260
+ ...rest
261
+ }
262
+ );
263
+ });
264
+ function Icon({
265
+ icon,
266
+ size = 20,
267
+ color = "text",
268
+ label,
269
+ strokeWidth = 2,
270
+ style
271
+ }) {
272
+ const theme = useTheme();
273
+ const definition = typeof icon === "string" ? getIcon(icon) : icon;
274
+ if (!definition) {
275
+ warnOnce(
276
+ `Icon "${String(icon)}" is not registered. Call registerIcons() from @muja-ui/core or pass an IconDefinition directly.`
277
+ );
278
+ return null;
279
+ }
280
+ return /* @__PURE__ */ jsx(
281
+ Svg,
282
+ {
283
+ viewBox: definition.viewBox ?? "0 0 24 24",
284
+ width: size,
285
+ height: size,
286
+ fill: "none",
287
+ style,
288
+ accessibilityRole: label ? "image" : void 0,
289
+ accessibilityLabel: label,
290
+ accessibilityElementsHidden: label ? void 0 : true,
291
+ importantForAccessibility: label ? void 0 : "no-hide-descendants",
292
+ children: definition.paths.map((d, index) => /* @__PURE__ */ jsx(
293
+ Path,
294
+ {
295
+ d,
296
+ stroke: theme.colors[color],
297
+ strokeWidth,
298
+ strokeLinecap: "round",
299
+ strokeLinejoin: "round"
300
+ },
301
+ index
302
+ ))
303
+ }
304
+ );
305
+ }
306
+
307
+ // src/system/variants.ts
308
+ function variantColors(variant, theme) {
309
+ const { colors, borderWidth } = theme;
310
+ switch (variant) {
311
+ case "primary":
312
+ return {
313
+ background: colors.primary,
314
+ backgroundPressed: colors.primaryActive,
315
+ foreground: "onPrimary"
316
+ };
317
+ case "secondary":
318
+ return {
319
+ background: colors.secondary,
320
+ backgroundPressed: colors.secondaryActive,
321
+ foreground: "onSecondary"
322
+ };
323
+ case "accent":
324
+ return {
325
+ background: colors.accent,
326
+ backgroundPressed: colors.accentActive,
327
+ foreground: "onAccent"
328
+ };
329
+ case "danger":
330
+ return {
331
+ background: colors.danger,
332
+ backgroundPressed: colors.dangerActive,
333
+ foreground: "onDanger"
334
+ };
335
+ case "outline":
336
+ return {
337
+ background: "transparent",
338
+ backgroundPressed: colors.surfaceActive,
339
+ foreground: "text",
340
+ borderColor: colors.borderStrong,
341
+ borderWidth: borderWidth.thin
342
+ };
343
+ case "ghost":
344
+ return {
345
+ background: "transparent",
346
+ backgroundPressed: colors.surfaceActive,
347
+ foreground: "text"
348
+ };
349
+ case "link":
350
+ return {
351
+ background: "transparent",
352
+ backgroundPressed: "transparent",
353
+ foreground: "primaryText",
354
+ underline: true
355
+ };
356
+ }
357
+ }
358
+ function sizeMetrics(size, theme) {
359
+ switch (size) {
360
+ case "sm":
361
+ return {
362
+ height: 36,
363
+ paddingHorizontal: theme.space[3],
364
+ fontSize: "sm",
365
+ gap: theme.space[1.5],
366
+ iconSize: 16
367
+ };
368
+ case "lg":
369
+ return {
370
+ height: 52,
371
+ paddingHorizontal: theme.space[6],
372
+ fontSize: "lg",
373
+ gap: theme.space[2.5],
374
+ iconSize: 22
375
+ };
376
+ case "md":
377
+ default:
378
+ return {
379
+ height: 44,
380
+ paddingHorizontal: theme.space[4],
381
+ fontSize: "md",
382
+ gap: theme.space[2],
383
+ iconSize: 18
384
+ };
385
+ }
386
+ }
387
+ var Button = forwardRef(function Button2({
388
+ variant = "primary",
389
+ size = "md",
390
+ loading = false,
391
+ fullWidth = false,
392
+ leftIcon,
393
+ rightIcon,
394
+ disabled = false,
395
+ children,
396
+ style,
397
+ accessibilityLabel,
398
+ ...rest
399
+ }, ref) {
400
+ const theme = useTheme();
401
+ const colors = variantColors(variant, theme);
402
+ const metrics = sizeMetrics(size, theme);
403
+ const isInert = disabled || loading;
404
+ const isLink = variant === "link";
405
+ return /* @__PURE__ */ jsxs(
406
+ Pressable,
407
+ {
408
+ ref,
409
+ accessibilityRole: "button",
410
+ accessibilityState: { disabled: isInert, busy: loading },
411
+ accessibilityLabel,
412
+ disabled: isInert,
413
+ style: ({ pressed }) => [
414
+ {
415
+ flexDirection: "row",
416
+ alignItems: "center",
417
+ justifyContent: "center",
418
+ gap: metrics.gap,
419
+ height: isLink ? void 0 : metrics.height,
420
+ paddingHorizontal: isLink ? 0 : metrics.paddingHorizontal,
421
+ borderRadius: isLink ? 0 : theme.radius.md,
422
+ backgroundColor: pressed ? colors.backgroundPressed : colors.background,
423
+ borderColor: colors.borderColor,
424
+ borderWidth: colors.borderWidth,
425
+ alignSelf: fullWidth ? "stretch" : "flex-start",
426
+ opacity: isInert ? theme.opacity.disabled : 1
427
+ },
428
+ style
429
+ ],
430
+ ...rest,
431
+ children: [
432
+ loading ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: theme.colors[colors.foreground] }) : leftIcon ? /* @__PURE__ */ jsx(View, { children: leftIcon }) : null,
433
+ children != null && children !== false ? typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(
434
+ Text,
435
+ {
436
+ size: metrics.fontSize,
437
+ weight: "semibold",
438
+ color: colors.foreground,
439
+ style: colors.underline ? { textDecorationLine: "underline" } : void 0,
440
+ children
441
+ }
442
+ ) : children : null,
443
+ rightIcon && !loading ? /* @__PURE__ */ jsx(View, { children: rightIcon }) : null
444
+ ]
445
+ }
446
+ );
447
+ });
448
+ var boxSize = { sm: 36, md: 44, lg: 52 };
449
+ var IconButton = forwardRef(function IconButton2({
450
+ icon,
451
+ variant = "ghost",
452
+ size = "md",
453
+ loading = false,
454
+ round = false,
455
+ disabled = false,
456
+ style,
457
+ ...rest
458
+ }, ref) {
459
+ const theme = useTheme();
460
+ const colors = variantColors(variant, theme);
461
+ const dimension = boxSize[size];
462
+ const isInert = disabled || loading;
463
+ return /* @__PURE__ */ jsx(
464
+ Pressable,
465
+ {
466
+ ref,
467
+ accessibilityRole: "button",
468
+ accessibilityState: { disabled: isInert, busy: loading },
469
+ disabled: isInert,
470
+ hitSlop: dimension < 44 ? (44 - dimension) / 2 : void 0,
471
+ style: ({ pressed }) => [
472
+ {
473
+ width: dimension,
474
+ height: dimension,
475
+ alignItems: "center",
476
+ justifyContent: "center",
477
+ borderRadius: round ? theme.radius.full : theme.radius.md,
478
+ backgroundColor: pressed ? colors.backgroundPressed : colors.background,
479
+ borderColor: colors.borderColor,
480
+ borderWidth: colors.borderWidth,
481
+ opacity: isInert ? theme.opacity.disabled : 1
482
+ },
483
+ style
484
+ ],
485
+ ...rest,
486
+ children: loading ? /* @__PURE__ */ jsx(ActivityIndicator, { size: "small", color: theme.colors[colors.foreground] }) : icon
487
+ }
488
+ );
489
+ });
490
+ var Input = forwardRef(function Input2({
491
+ size = "md",
492
+ invalid = false,
493
+ disabled = false,
494
+ leftElement,
495
+ rightElement,
496
+ style,
497
+ onFocus,
498
+ onBlur,
499
+ ...rest
500
+ }, ref) {
501
+ const theme = useTheme();
502
+ const metrics = sizeMetrics(size, theme);
503
+ const [focused, setFocused] = useState(false);
504
+ const borderColor = invalid ? theme.colors.danger : focused ? theme.colors.focusRing : theme.colors.border;
505
+ return /* @__PURE__ */ jsxs(
506
+ View,
507
+ {
508
+ style: [
509
+ {
510
+ flexDirection: "row",
511
+ alignItems: "center",
512
+ gap: metrics.gap,
513
+ height: metrics.height,
514
+ paddingHorizontal: metrics.paddingHorizontal,
515
+ borderRadius: theme.radius.md,
516
+ borderWidth: focused || invalid ? theme.borderWidth.medium : theme.borderWidth.thin,
517
+ borderColor,
518
+ backgroundColor: disabled ? theme.colors.bgMuted : theme.colors.surface
519
+ },
520
+ style
521
+ ],
522
+ children: [
523
+ leftElement,
524
+ /* @__PURE__ */ jsx(
525
+ TextInput,
526
+ {
527
+ ref,
528
+ editable: !disabled,
529
+ "aria-invalid": invalid || void 0,
530
+ "aria-disabled": disabled || void 0,
531
+ placeholderTextColor: theme.colors.textMuted,
532
+ selectionColor: theme.colors.primary,
533
+ onFocus: (event) => {
534
+ setFocused(true);
535
+ onFocus?.(event);
536
+ },
537
+ onBlur: (event) => {
538
+ setFocused(false);
539
+ onBlur?.(event);
540
+ },
541
+ style: {
542
+ flex: 1,
543
+ // Android adds its own vertical padding; zero it so `height` wins.
544
+ paddingVertical: 0,
545
+ fontFamily: theme.typography.fontFamily.sans,
546
+ fontSize: theme.typography.fontSize[metrics.fontSize],
547
+ color: disabled ? theme.colors.textDisabled : theme.colors.text
548
+ },
549
+ ...rest
550
+ }
551
+ ),
552
+ rightElement
553
+ ]
554
+ }
555
+ );
556
+ });
557
+ var Textarea = forwardRef(function Textarea2({ invalid = false, disabled = false, rows = 4, style, onFocus, onBlur, ...rest }, ref) {
558
+ const theme = useTheme();
559
+ const [focused, setFocused] = useState(false);
560
+ const lineHeight = Math.round(
561
+ theme.typography.fontSize.md * theme.typography.lineHeight.normal
562
+ );
563
+ return /* @__PURE__ */ jsx(
564
+ TextInput,
565
+ {
566
+ ref,
567
+ multiline: true,
568
+ textAlignVertical: "top",
569
+ editable: !disabled,
570
+ "aria-invalid": invalid || void 0,
571
+ placeholderTextColor: theme.colors.textMuted,
572
+ selectionColor: theme.colors.primary,
573
+ onFocus: (event) => {
574
+ setFocused(true);
575
+ onFocus?.(event);
576
+ },
577
+ onBlur: (event) => {
578
+ setFocused(false);
579
+ onBlur?.(event);
580
+ },
581
+ style: [
582
+ {
583
+ minHeight: rows * lineHeight + theme.space[4],
584
+ padding: theme.space[3],
585
+ borderRadius: theme.radius.md,
586
+ borderWidth: focused || invalid ? theme.borderWidth.medium : theme.borderWidth.thin,
587
+ borderColor: invalid ? theme.colors.danger : focused ? theme.colors.focusRing : theme.colors.border,
588
+ backgroundColor: disabled ? theme.colors.bgMuted : theme.colors.surface,
589
+ fontFamily: theme.typography.fontFamily.sans,
590
+ fontSize: theme.typography.fontSize.md,
591
+ lineHeight,
592
+ color: disabled ? theme.colors.textDisabled : theme.colors.text
593
+ },
594
+ style
595
+ ],
596
+ ...rest
597
+ }
598
+ );
599
+ });
600
+ var Label = forwardRef(function Label2({ required = false, size = "sm", weight = "medium", children, ...rest }, ref) {
601
+ return /* @__PURE__ */ jsxs(Text, { ref, size, weight, ...rest, children: [
602
+ children,
603
+ required ? /* @__PURE__ */ jsx(Text, { size, weight, color: "danger", accessibilityLabel: "required", children: " *" }) : null
604
+ ] });
605
+ });
606
+ function FormField({
607
+ label,
608
+ required = false,
609
+ help,
610
+ error,
611
+ children,
612
+ style
613
+ }) {
614
+ const theme = useTheme();
615
+ return /* @__PURE__ */ jsxs(View, { style: [{ gap: theme.space[1.5] }, style], children: [
616
+ label ? /* @__PURE__ */ jsx(Label, { required, color: "textSecondary", children: label }) : null,
617
+ children,
618
+ error ? /* @__PURE__ */ jsx(Text, { size: "xs", color: "dangerText", accessibilityLiveRegion: "polite", children: error }) : help ? /* @__PURE__ */ jsx(Text, { size: "xs", color: "textMuted", children: help }) : null
619
+ ] });
620
+ }
621
+ function useControllableState(controlled, defaultValue, onChange) {
622
+ const [uncontrolled, setUncontrolled] = useState(defaultValue);
623
+ const value = controlled !== void 0 ? controlled : uncontrolled;
624
+ const setValue = useCallback(
625
+ (next) => {
626
+ if (controlled === void 0) setUncontrolled(next);
627
+ onChange?.(next);
628
+ },
629
+ [controlled, onChange]
630
+ );
631
+ return [value, setValue];
632
+ }
633
+ var boxSize2 = { sm: 18, md: 22, lg: 26 };
634
+ function Checkbox({
635
+ checked: controlledChecked,
636
+ defaultChecked = false,
637
+ onChange,
638
+ indeterminate = false,
639
+ size = "md",
640
+ invalid = false,
641
+ disabled = false,
642
+ children,
643
+ style
644
+ }) {
645
+ const theme = useTheme();
646
+ const [checked, setChecked] = useControllableState(
647
+ controlledChecked,
648
+ defaultChecked,
649
+ onChange
650
+ );
651
+ const dimension = boxSize2[size];
652
+ const filled = checked || indeterminate;
653
+ return /* @__PURE__ */ jsxs(
654
+ Pressable,
655
+ {
656
+ accessibilityRole: "checkbox",
657
+ accessibilityState: {
658
+ checked: indeterminate ? "mixed" : checked,
659
+ disabled
660
+ },
661
+ "aria-invalid": invalid || void 0,
662
+ disabled,
663
+ onPress: () => setChecked(!checked),
664
+ style: [
665
+ {
666
+ flexDirection: "row",
667
+ alignItems: "center",
668
+ gap: theme.space[2.5],
669
+ opacity: disabled ? theme.opacity.disabled : 1
670
+ },
671
+ style
672
+ ],
673
+ children: [
674
+ /* @__PURE__ */ jsx(
675
+ View,
676
+ {
677
+ style: {
678
+ width: dimension,
679
+ height: dimension,
680
+ alignItems: "center",
681
+ justifyContent: "center",
682
+ borderRadius: theme.radius.sm,
683
+ borderWidth: theme.borderWidth.medium,
684
+ borderColor: invalid ? theme.colors.danger : filled ? theme.colors.primary : theme.colors.borderStrong,
685
+ backgroundColor: filled ? theme.colors.primary : theme.colors.surface
686
+ },
687
+ children: filled ? /* @__PURE__ */ jsx(
688
+ Icon,
689
+ {
690
+ icon: indeterminate ? MinusIcon : CheckIcon,
691
+ size: dimension - 6,
692
+ color: "onPrimary",
693
+ strokeWidth: 3
694
+ }
695
+ ) : null
696
+ }
697
+ ),
698
+ children != null ? typeof children === "string" ? /* @__PURE__ */ jsx(Text, { size: size === "lg" ? "md" : "sm", style: { flexShrink: 1 }, children }) : children : null
699
+ ]
700
+ }
701
+ );
702
+ }
703
+ var track = {
704
+ sm: { width: 36, height: 20, thumb: 16 },
705
+ md: { width: 44, height: 26, thumb: 22 },
706
+ lg: { width: 52, height: 32, thumb: 28 }
707
+ };
708
+ function Switch({
709
+ checked: controlledChecked,
710
+ defaultChecked = false,
711
+ onChange,
712
+ size = "md",
713
+ disabled = false,
714
+ children,
715
+ style
716
+ }) {
717
+ const theme = useTheme();
718
+ const [checked, setChecked] = useControllableState(
719
+ controlledChecked,
720
+ defaultChecked,
721
+ onChange
722
+ );
723
+ const metrics = track[size];
724
+ const inset = (metrics.height - metrics.thumb) / 2;
725
+ return /* @__PURE__ */ jsxs(
726
+ Pressable,
727
+ {
728
+ accessibilityRole: "switch",
729
+ accessibilityState: { checked, disabled },
730
+ disabled,
731
+ onPress: () => setChecked(!checked),
732
+ style: [
733
+ {
734
+ flexDirection: "row",
735
+ alignItems: "center",
736
+ justifyContent: "space-between",
737
+ gap: theme.space[3],
738
+ opacity: disabled ? theme.opacity.disabled : 1
739
+ },
740
+ style
741
+ ],
742
+ children: [
743
+ children != null ? typeof children === "string" ? /* @__PURE__ */ jsx(Text, { size: "sm", style: { flexShrink: 1 }, children }) : children : null,
744
+ /* @__PURE__ */ jsx(
745
+ View,
746
+ {
747
+ style: {
748
+ width: metrics.width,
749
+ height: metrics.height,
750
+ borderRadius: theme.radius.full,
751
+ padding: inset,
752
+ justifyContent: "center",
753
+ backgroundColor: checked ? theme.colors.primary : theme.colors.secondaryActive
754
+ },
755
+ children: /* @__PURE__ */ jsx(
756
+ View,
757
+ {
758
+ style: {
759
+ width: metrics.thumb,
760
+ height: metrics.thumb,
761
+ borderRadius: theme.radius.full,
762
+ backgroundColor: theme.colors.surface,
763
+ transform: [{ translateX: checked ? metrics.width - metrics.thumb - inset * 2 : 0 }]
764
+ }
765
+ }
766
+ )
767
+ }
768
+ )
769
+ ]
770
+ }
771
+ );
772
+ }
773
+ var RadioGroupContext = createContext(null);
774
+ function RadioGroup({
775
+ value: controlledValue,
776
+ defaultValue,
777
+ onChange,
778
+ size = "md",
779
+ disabled = false,
780
+ orientation = "vertical",
781
+ children,
782
+ style,
783
+ accessibilityLabel
784
+ }) {
785
+ const theme = useTheme();
786
+ const [value, setValue] = useControllableState(
787
+ controlledValue,
788
+ defaultValue,
789
+ onChange
790
+ );
791
+ return /* @__PURE__ */ jsx(
792
+ RadioGroupContext.Provider,
793
+ {
794
+ value: { value, setValue, size, disabled },
795
+ children: /* @__PURE__ */ jsx(
796
+ View,
797
+ {
798
+ accessibilityRole: "radiogroup",
799
+ accessibilityLabel,
800
+ style: [
801
+ {
802
+ flexDirection: orientation === "horizontal" ? "row" : "column",
803
+ flexWrap: orientation === "horizontal" ? "wrap" : "nowrap",
804
+ gap: theme.space[3]
805
+ },
806
+ style
807
+ ],
808
+ children
809
+ }
810
+ )
811
+ }
812
+ );
813
+ }
814
+ var boxSize3 = { sm: 18, md: 22, lg: 26 };
815
+ function Radio({ value, disabled = false, children, style }) {
816
+ const theme = useTheme();
817
+ const group = useContext(RadioGroupContext);
818
+ if (!group) {
819
+ warnOnce("<Radio> must be rendered inside a <RadioGroup>.");
820
+ return null;
821
+ }
822
+ const selected = group.value === value;
823
+ const isDisabled = disabled || group.disabled;
824
+ const dimension = boxSize3[group.size];
825
+ return /* @__PURE__ */ jsxs(
826
+ Pressable,
827
+ {
828
+ accessibilityRole: "radio",
829
+ accessibilityState: { checked: selected, disabled: isDisabled },
830
+ disabled: isDisabled,
831
+ onPress: () => group.setValue(value),
832
+ style: [
833
+ {
834
+ flexDirection: "row",
835
+ alignItems: "center",
836
+ gap: theme.space[2.5],
837
+ opacity: isDisabled ? theme.opacity.disabled : 1
838
+ },
839
+ style
840
+ ],
841
+ children: [
842
+ /* @__PURE__ */ jsx(
843
+ View,
844
+ {
845
+ style: {
846
+ width: dimension,
847
+ height: dimension,
848
+ alignItems: "center",
849
+ justifyContent: "center",
850
+ borderRadius: theme.radius.full,
851
+ borderWidth: theme.borderWidth.medium,
852
+ borderColor: selected ? theme.colors.primary : theme.colors.borderStrong,
853
+ backgroundColor: theme.colors.surface
854
+ },
855
+ children: selected ? /* @__PURE__ */ jsx(
856
+ View,
857
+ {
858
+ style: {
859
+ width: dimension / 2,
860
+ height: dimension / 2,
861
+ borderRadius: theme.radius.full,
862
+ backgroundColor: theme.colors.primary
863
+ }
864
+ }
865
+ ) : null
866
+ }
867
+ ),
868
+ children != null ? typeof children === "string" ? /* @__PURE__ */ jsx(Text, { size: group.size === "lg" ? "md" : "sm", style: { flexShrink: 1 }, children }) : children : null
869
+ ]
870
+ }
871
+ );
872
+ }
873
+ var DISMISS_THRESHOLD = 96;
874
+ function BottomSheet({
875
+ open,
876
+ onClose,
877
+ closeOnOverlayPress = true,
878
+ title,
879
+ accessibilityLabel,
880
+ maxHeightRatio = 0.9,
881
+ children,
882
+ style
883
+ }) {
884
+ const theme = useTheme();
885
+ const insets = useSafeAreaInsets();
886
+ const { height } = useWindowDimensions();
887
+ const translateY = useRef(new Animated.Value(height)).current;
888
+ const dragY = useRef(0);
889
+ useEffect(() => {
890
+ Animated.timing(translateY, {
891
+ toValue: open ? 0 : height,
892
+ duration: open ? theme.motion.duration.normal : theme.motion.duration.fast,
893
+ easing: Easing.out(Easing.cubic),
894
+ useNativeDriver: true
895
+ }).start();
896
+ }, [open, height, translateY, theme.motion.duration]);
897
+ const panResponder = useRef(
898
+ PanResponder.create({
899
+ onMoveShouldSetPanResponder: (_event, gesture) => gesture.dy > 4,
900
+ onPanResponderMove: (_event, gesture) => {
901
+ if (gesture.dy > 0) {
902
+ dragY.current = gesture.dy;
903
+ translateY.setValue(gesture.dy);
904
+ }
905
+ },
906
+ onPanResponderRelease: () => {
907
+ if (dragY.current > DISMISS_THRESHOLD) {
908
+ onClose();
909
+ } else {
910
+ Animated.spring(translateY, { toValue: 0, useNativeDriver: true }).start();
911
+ }
912
+ dragY.current = 0;
913
+ }
914
+ })
915
+ ).current;
916
+ return /* @__PURE__ */ jsx(
917
+ Modal$1,
918
+ {
919
+ visible: open,
920
+ transparent: true,
921
+ animationType: "none",
922
+ statusBarTranslucent: true,
923
+ onRequestClose: onClose,
924
+ children: /* @__PURE__ */ jsxs(View, { style: { flex: 1, justifyContent: "flex-end" }, children: [
925
+ /* @__PURE__ */ jsx(
926
+ Pressable,
927
+ {
928
+ accessibilityLabel: closeOnOverlayPress ? "Close" : void 0,
929
+ onPress: closeOnOverlayPress ? onClose : void 0,
930
+ style: { flex: 1, backgroundColor: theme.colors.overlay }
931
+ }
932
+ ),
933
+ /* @__PURE__ */ jsxs(
934
+ Animated.View,
935
+ {
936
+ accessibilityViewIsModal: true,
937
+ accessibilityLabel: accessibilityLabel ?? title,
938
+ style: [
939
+ {
940
+ maxHeight: height * maxHeightRatio,
941
+ paddingBottom: insets.bottom + theme.space[4],
942
+ borderTopLeftRadius: theme.radius["2xl"],
943
+ borderTopRightRadius: theme.radius["2xl"],
944
+ backgroundColor: theme.colors.surface,
945
+ transform: [{ translateY }]
946
+ },
947
+ style
948
+ ],
949
+ children: [
950
+ /* @__PURE__ */ jsxs(View, { ...panResponder.panHandlers, style: { paddingTop: theme.space[3] }, children: [
951
+ /* @__PURE__ */ jsx(
952
+ View,
953
+ {
954
+ style: {
955
+ alignSelf: "center",
956
+ width: 40,
957
+ height: 4,
958
+ borderRadius: theme.radius.full,
959
+ backgroundColor: theme.colors.borderStrong
960
+ }
961
+ }
962
+ ),
963
+ title ? /* @__PURE__ */ jsx(
964
+ Heading,
965
+ {
966
+ level: 3,
967
+ size: "lg",
968
+ style: {
969
+ paddingHorizontal: theme.space[5],
970
+ paddingTop: theme.space[4]
971
+ },
972
+ children: title
973
+ }
974
+ ) : null
975
+ ] }),
976
+ children
977
+ ]
978
+ }
979
+ )
980
+ ] })
981
+ }
982
+ );
983
+ }
984
+ function Select({
985
+ options,
986
+ value,
987
+ onChange,
988
+ placeholder,
989
+ title,
990
+ size = "md",
991
+ invalid = false,
992
+ disabled = false,
993
+ style,
994
+ accessibilityLabel
995
+ }) {
996
+ const theme = useTheme();
997
+ const metrics = sizeMetrics(size, theme);
998
+ const [open, setOpen] = useState(false);
999
+ const selected = options.find((option) => option.value === value);
1000
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1001
+ /* @__PURE__ */ jsxs(
1002
+ Pressable,
1003
+ {
1004
+ accessibilityRole: "button",
1005
+ accessibilityLabel: accessibilityLabel ?? title ?? placeholder,
1006
+ accessibilityValue: { text: selected?.label },
1007
+ accessibilityState: { disabled, expanded: open },
1008
+ "aria-invalid": invalid || void 0,
1009
+ disabled,
1010
+ onPress: () => setOpen(true),
1011
+ style: [
1012
+ {
1013
+ flexDirection: "row",
1014
+ alignItems: "center",
1015
+ justifyContent: "space-between",
1016
+ gap: metrics.gap,
1017
+ height: metrics.height,
1018
+ paddingHorizontal: metrics.paddingHorizontal,
1019
+ borderRadius: theme.radius.md,
1020
+ borderWidth: invalid ? theme.borderWidth.medium : theme.borderWidth.thin,
1021
+ borderColor: invalid ? theme.colors.danger : theme.colors.border,
1022
+ backgroundColor: disabled ? theme.colors.bgMuted : theme.colors.surface,
1023
+ opacity: disabled ? theme.opacity.disabled : 1
1024
+ },
1025
+ style
1026
+ ],
1027
+ children: [
1028
+ /* @__PURE__ */ jsx(
1029
+ Text,
1030
+ {
1031
+ size: metrics.fontSize,
1032
+ color: selected ? "text" : "textMuted",
1033
+ truncate: true,
1034
+ style: { flex: 1 },
1035
+ children: selected?.label ?? placeholder ?? ""
1036
+ }
1037
+ ),
1038
+ /* @__PURE__ */ jsx(Icon, { icon: ChevronDownIcon, size: metrics.iconSize, color: "textSecondary" })
1039
+ ]
1040
+ }
1041
+ ),
1042
+ /* @__PURE__ */ jsx(
1043
+ BottomSheet,
1044
+ {
1045
+ open,
1046
+ onClose: () => setOpen(false),
1047
+ title: title ?? placeholder,
1048
+ accessibilityLabel,
1049
+ children: /* @__PURE__ */ jsx(ScrollView, { style: { marginTop: theme.space[2] }, children: options.map((option) => {
1050
+ const isSelected = option.value === value;
1051
+ return /* @__PURE__ */ jsxs(
1052
+ Pressable,
1053
+ {
1054
+ accessibilityRole: "menuitem",
1055
+ accessibilityState: { selected: isSelected, disabled: option.disabled },
1056
+ disabled: option.disabled,
1057
+ onPress: () => {
1058
+ onChange?.(option.value);
1059
+ setOpen(false);
1060
+ },
1061
+ style: ({ pressed }) => ({
1062
+ flexDirection: "row",
1063
+ alignItems: "center",
1064
+ gap: theme.space[3],
1065
+ paddingHorizontal: theme.space[5],
1066
+ paddingVertical: theme.space[3.5],
1067
+ backgroundColor: pressed ? theme.colors.surfaceActive : "transparent",
1068
+ opacity: option.disabled ? theme.opacity.disabled : 1
1069
+ }),
1070
+ children: [
1071
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1 }, children: [
1072
+ /* @__PURE__ */ jsx(Text, { size: "md", weight: isSelected ? "semibold" : "regular", children: option.label }),
1073
+ option.description ? /* @__PURE__ */ jsx(Text, { size: "sm", color: "textMuted", children: option.description }) : null
1074
+ ] }),
1075
+ isSelected ? /* @__PURE__ */ jsx(Icon, { icon: CheckIcon, color: "primary" }) : null
1076
+ ]
1077
+ },
1078
+ option.value
1079
+ );
1080
+ }) })
1081
+ }
1082
+ )
1083
+ ] });
1084
+ }
1085
+ function Skeleton({
1086
+ width = "100%",
1087
+ height = 16,
1088
+ radius = "sm",
1089
+ animated = true,
1090
+ style
1091
+ }) {
1092
+ const theme = useTheme();
1093
+ const pulse = useRef(new Animated.Value(0.5)).current;
1094
+ useEffect(() => {
1095
+ if (!animated) return;
1096
+ const loop = Animated.loop(
1097
+ Animated.sequence([
1098
+ Animated.timing(pulse, {
1099
+ toValue: 1,
1100
+ duration: theme.motion.duration.slower,
1101
+ easing: Easing.inOut(Easing.ease),
1102
+ useNativeDriver: true
1103
+ }),
1104
+ Animated.timing(pulse, {
1105
+ toValue: 0.5,
1106
+ duration: theme.motion.duration.slower,
1107
+ easing: Easing.inOut(Easing.ease),
1108
+ useNativeDriver: true
1109
+ })
1110
+ ])
1111
+ );
1112
+ loop.start();
1113
+ return () => loop.stop();
1114
+ }, [animated, pulse, theme.motion.duration.slower]);
1115
+ return /* @__PURE__ */ jsx(
1116
+ Animated.View,
1117
+ {
1118
+ accessibilityElementsHidden: true,
1119
+ importantForAccessibility: "no-hide-descendants",
1120
+ style: [
1121
+ {
1122
+ width,
1123
+ height,
1124
+ borderRadius: theme.radius[radius],
1125
+ backgroundColor: theme.colors.bgMuted,
1126
+ opacity: animated ? pulse : 0.7
1127
+ },
1128
+ style
1129
+ ]
1130
+ }
1131
+ );
1132
+ }
1133
+ function Spinner({ size = "md", color = "primary", label, style }) {
1134
+ const theme = useTheme();
1135
+ return /* @__PURE__ */ jsx(
1136
+ ActivityIndicator,
1137
+ {
1138
+ size: size === "sm" ? "small" : "large",
1139
+ color: theme.colors[color],
1140
+ accessibilityRole: "progressbar",
1141
+ accessibilityLabel: label,
1142
+ style: [size === "md" ? { transform: [{ scale: 0.8 }] } : null, style]
1143
+ }
1144
+ );
1145
+ }
1146
+ function Progress({
1147
+ value,
1148
+ max = 100,
1149
+ height = 8,
1150
+ color = "primary",
1151
+ trackColor = "bgMuted",
1152
+ label,
1153
+ style
1154
+ }) {
1155
+ const theme = useTheme();
1156
+ const ratio = max > 0 ? clamp(value / max, 0, 1) : 0;
1157
+ return /* @__PURE__ */ jsx(
1158
+ View,
1159
+ {
1160
+ accessibilityRole: "progressbar",
1161
+ accessibilityLabel: label,
1162
+ accessibilityValue: { min: 0, max, now: Math.round(value) },
1163
+ style: [
1164
+ {
1165
+ height,
1166
+ borderRadius: theme.radius.full,
1167
+ backgroundColor: theme.colors[trackColor],
1168
+ overflow: "hidden"
1169
+ },
1170
+ style
1171
+ ],
1172
+ children: /* @__PURE__ */ jsx(
1173
+ View,
1174
+ {
1175
+ style: {
1176
+ width: `${ratio * 100}%`,
1177
+ height: "100%",
1178
+ borderRadius: theme.radius.full,
1179
+ backgroundColor: theme.colors[color]
1180
+ }
1181
+ }
1182
+ )
1183
+ }
1184
+ );
1185
+ }
1186
+ var ToastContext = createContext(null);
1187
+ var DEFAULT_DURATION = 4e3;
1188
+ var toneIcon = {
1189
+ neutral: InfoIcon,
1190
+ success: CheckIcon,
1191
+ warning: AlertTriangleIcon,
1192
+ danger: AlertCircleIcon,
1193
+ info: InfoIcon
1194
+ };
1195
+ var toneColor = {
1196
+ neutral: "textSecondary",
1197
+ success: "success",
1198
+ warning: "warning",
1199
+ danger: "danger",
1200
+ info: "info"
1201
+ };
1202
+ function ToastProvider({ children, placement = "top", max = 3 }) {
1203
+ const theme = useTheme();
1204
+ const insets = useSafeAreaInsets();
1205
+ const [entries, setEntries] = useState([]);
1206
+ const nextId = useRef(0);
1207
+ const dismiss = useCallback((id) => {
1208
+ setEntries((current) => current.filter((entry) => entry.id !== id));
1209
+ }, []);
1210
+ const toast = useCallback(
1211
+ (options) => {
1212
+ const id = nextId.current++;
1213
+ setEntries((current) => [...current, { ...options, id }].slice(-max));
1214
+ },
1215
+ [max]
1216
+ );
1217
+ const value = useMemo(() => ({ toast, dismiss }), [toast, dismiss]);
1218
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value, children: [
1219
+ children,
1220
+ /* @__PURE__ */ jsx(
1221
+ View,
1222
+ {
1223
+ pointerEvents: "box-none",
1224
+ style: {
1225
+ position: "absolute",
1226
+ left: 0,
1227
+ right: 0,
1228
+ [placement]: (placement === "top" ? insets.top : insets.bottom) + theme.space[2],
1229
+ paddingHorizontal: theme.space[4],
1230
+ gap: theme.space[2],
1231
+ zIndex: theme.zIndex.toast
1232
+ },
1233
+ children: entries.map((entry) => /* @__PURE__ */ jsx(ToastCard, { entry, onDismiss: dismiss, placement }, entry.id))
1234
+ }
1235
+ )
1236
+ ] });
1237
+ }
1238
+ function ToastCard({
1239
+ entry,
1240
+ onDismiss,
1241
+ placement
1242
+ }) {
1243
+ const theme = useTheme();
1244
+ const tone = entry.tone ?? "neutral";
1245
+ const enter = useRef(new Animated.Value(0)).current;
1246
+ useEffect(() => {
1247
+ Animated.timing(enter, {
1248
+ toValue: 1,
1249
+ duration: theme.motion.duration.normal,
1250
+ easing: Easing.out(Easing.cubic),
1251
+ useNativeDriver: true
1252
+ }).start();
1253
+ }, [enter, theme.motion.duration.normal]);
1254
+ useEffect(() => {
1255
+ const duration = entry.duration ?? DEFAULT_DURATION;
1256
+ if (duration <= 0) return;
1257
+ const timer = setTimeout(() => onDismiss(entry.id), duration);
1258
+ return () => clearTimeout(timer);
1259
+ }, [entry.duration, entry.id, onDismiss]);
1260
+ return /* @__PURE__ */ jsxs(
1261
+ Animated.View,
1262
+ {
1263
+ accessibilityLiveRegion: "polite",
1264
+ accessibilityRole: "alert",
1265
+ style: {
1266
+ flexDirection: "row",
1267
+ alignItems: "flex-start",
1268
+ gap: theme.space[3],
1269
+ padding: theme.space[3.5],
1270
+ borderRadius: theme.radius.lg,
1271
+ borderWidth: theme.borderWidth.thin,
1272
+ borderColor: theme.colors.border,
1273
+ backgroundColor: theme.colors.surface,
1274
+ opacity: enter,
1275
+ transform: [
1276
+ {
1277
+ translateY: enter.interpolate({
1278
+ inputRange: [0, 1],
1279
+ outputRange: [placement === "top" ? -16 : 16, 0]
1280
+ })
1281
+ }
1282
+ ],
1283
+ ...shadowStyle(theme.shadow.lg)
1284
+ },
1285
+ children: [
1286
+ /* @__PURE__ */ jsx(Icon, { icon: toneIcon[tone], size: 18, color: toneColor[tone] }),
1287
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: theme.space[0.5] }, children: [
1288
+ /* @__PURE__ */ jsx(Text, { size: "sm", weight: "semibold", children: entry.title }),
1289
+ entry.description ? /* @__PURE__ */ jsx(Text, { size: "sm", color: "textSecondary", children: entry.description }) : null
1290
+ ] }),
1291
+ entry.action ? /* @__PURE__ */ jsx(
1292
+ Pressable,
1293
+ {
1294
+ accessibilityRole: "button",
1295
+ onPress: () => {
1296
+ entry.action?.onPress();
1297
+ onDismiss(entry.id);
1298
+ },
1299
+ hitSlop: 8,
1300
+ children: /* @__PURE__ */ jsx(Text, { size: "sm", weight: "semibold", color: "primaryText", children: entry.action.label })
1301
+ }
1302
+ ) : /* @__PURE__ */ jsx(
1303
+ Pressable,
1304
+ {
1305
+ accessibilityRole: "button",
1306
+ accessibilityLabel: "Dismiss",
1307
+ onPress: () => onDismiss(entry.id),
1308
+ hitSlop: 8,
1309
+ children: /* @__PURE__ */ jsx(Icon, { icon: XIcon, size: 16, color: "textMuted" })
1310
+ }
1311
+ )
1312
+ ]
1313
+ }
1314
+ );
1315
+ }
1316
+ function useToast() {
1317
+ const context = useContext(ToastContext);
1318
+ if (!context) {
1319
+ throw new Error("[muja-ui] useToast must be used within a <ToastProvider>.");
1320
+ }
1321
+ return context;
1322
+ }
1323
+ function EmptyState({ title, description, icon, action, style }) {
1324
+ const theme = useTheme();
1325
+ return /* @__PURE__ */ jsxs(
1326
+ View,
1327
+ {
1328
+ style: [
1329
+ {
1330
+ alignItems: "center",
1331
+ justifyContent: "center",
1332
+ gap: theme.space[2],
1333
+ paddingVertical: theme.space[10],
1334
+ paddingHorizontal: theme.space[6]
1335
+ },
1336
+ style
1337
+ ],
1338
+ children: [
1339
+ icon ? /* @__PURE__ */ jsx(View, { style: { marginBottom: theme.space[2] }, children: icon }) : null,
1340
+ /* @__PURE__ */ jsx(Heading, { level: 3, size: "md", align: "center", children: title }),
1341
+ description ? /* @__PURE__ */ jsx(Text, { size: "sm", color: "textMuted", align: "center", leading: "normal", children: description }) : null,
1342
+ action ? /* @__PURE__ */ jsx(View, { style: { marginTop: theme.space[3] }, children: action }) : null
1343
+ ]
1344
+ }
1345
+ );
1346
+ }
1347
+ var tones = {
1348
+ neutral: {
1349
+ solidBg: "secondaryActive",
1350
+ solidFg: "onSecondary",
1351
+ subtleBg: "bgMuted",
1352
+ subtleFg: "textSecondary"
1353
+ },
1354
+ primary: {
1355
+ solidBg: "primary",
1356
+ solidFg: "onPrimary",
1357
+ subtleBg: "primarySubtle",
1358
+ subtleFg: "primaryText"
1359
+ },
1360
+ accent: { solidBg: "accent", solidFg: "onAccent", subtleBg: "accentSubtle", subtleFg: "accentText" },
1361
+ success: {
1362
+ solidBg: "success",
1363
+ solidFg: "onSuccess",
1364
+ subtleBg: "successSubtle",
1365
+ subtleFg: "successText"
1366
+ },
1367
+ warning: {
1368
+ solidBg: "warning",
1369
+ solidFg: "onWarning",
1370
+ subtleBg: "warningSubtle",
1371
+ subtleFg: "warningText"
1372
+ },
1373
+ danger: { solidBg: "danger", solidFg: "onDanger", subtleBg: "dangerSubtle", subtleFg: "dangerText" },
1374
+ info: { solidBg: "info", solidFg: "onInfo", subtleBg: "infoSubtle", subtleFg: "infoText" }
1375
+ };
1376
+ function Badge({
1377
+ tone = "neutral",
1378
+ variant = "subtle",
1379
+ children,
1380
+ leftIcon,
1381
+ style
1382
+ }) {
1383
+ const theme = useTheme();
1384
+ const palette = tones[tone];
1385
+ const background = variant === "solid" ? theme.colors[palette.solidBg] : variant === "subtle" ? theme.colors[palette.subtleBg] : "transparent";
1386
+ const foreground = variant === "solid" ? palette.solidFg : palette.subtleFg;
1387
+ return /* @__PURE__ */ jsxs(
1388
+ View,
1389
+ {
1390
+ style: [
1391
+ {
1392
+ flexDirection: "row",
1393
+ alignItems: "center",
1394
+ alignSelf: "flex-start",
1395
+ gap: theme.space[1],
1396
+ paddingHorizontal: theme.space[2],
1397
+ paddingVertical: theme.space[0.5],
1398
+ borderRadius: theme.radius.full,
1399
+ backgroundColor: background,
1400
+ borderWidth: variant === "outline" ? theme.borderWidth.thin : 0,
1401
+ borderColor: variant === "outline" ? theme.colors[palette.solidBg] : void 0
1402
+ },
1403
+ style
1404
+ ],
1405
+ children: [
1406
+ leftIcon,
1407
+ typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(Text, { size: "xs", weight: "medium", color: foreground, children }) : children
1408
+ ]
1409
+ }
1410
+ );
1411
+ }
1412
+ function Chip({
1413
+ children,
1414
+ selected = false,
1415
+ disabled = false,
1416
+ onPress,
1417
+ onRemove,
1418
+ leftIcon,
1419
+ style
1420
+ }) {
1421
+ const theme = useTheme();
1422
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
1423
+ leftIcon,
1424
+ typeof children === "string" || typeof children === "number" ? /* @__PURE__ */ jsx(Text, { size: "sm", weight: "medium", color: selected ? "onPrimary" : "textSecondary", children }) : children,
1425
+ onRemove ? /* @__PURE__ */ jsx(
1426
+ Pressable,
1427
+ {
1428
+ accessibilityRole: "button",
1429
+ accessibilityLabel: "Remove",
1430
+ hitSlop: 8,
1431
+ onPress: onRemove,
1432
+ children: /* @__PURE__ */ jsx(Icon, { icon: XIcon, size: 14, color: selected ? "onPrimary" : "textSecondary" })
1433
+ }
1434
+ ) : null
1435
+ ] });
1436
+ const shape = (pressed) => ({
1437
+ flexDirection: "row",
1438
+ alignItems: "center",
1439
+ alignSelf: "flex-start",
1440
+ gap: theme.space[1.5],
1441
+ height: 32,
1442
+ paddingHorizontal: theme.space[3],
1443
+ borderRadius: theme.radius.full,
1444
+ borderWidth: theme.borderWidth.thin,
1445
+ borderColor: selected ? theme.colors.primary : theme.colors.border,
1446
+ backgroundColor: selected ? theme.colors.primary : pressed ? theme.colors.surfaceActive : theme.colors.surface,
1447
+ opacity: disabled ? theme.opacity.disabled : 1
1448
+ });
1449
+ if (!onPress) return /* @__PURE__ */ jsx(View, { style: [shape(false), style], children: body });
1450
+ return /* @__PURE__ */ jsx(
1451
+ Pressable,
1452
+ {
1453
+ accessibilityRole: "button",
1454
+ accessibilityState: { selected, disabled },
1455
+ disabled,
1456
+ onPress,
1457
+ style: ({ pressed }) => [shape(pressed), style],
1458
+ children: body
1459
+ }
1460
+ );
1461
+ }
1462
+ var Card = forwardRef(function Card2({ variant = "outline", onPress, accessibilityLabel, children, style }, ref) {
1463
+ const theme = useTheme();
1464
+ const base = {
1465
+ borderRadius: theme.radius.lg,
1466
+ backgroundColor: variant === "filled" ? theme.colors.bgSubtle : theme.colors.surface,
1467
+ borderWidth: variant === "outline" ? theme.borderWidth.thin : 0,
1468
+ borderColor: theme.colors.border,
1469
+ ...variant === "elevated" ? shadowStyle(theme.shadow.sm) : null
1470
+ };
1471
+ if (!onPress) {
1472
+ return /* @__PURE__ */ jsx(View, { ref, style: [base, style], children });
1473
+ }
1474
+ return /* @__PURE__ */ jsx(
1475
+ Pressable,
1476
+ {
1477
+ ref,
1478
+ accessibilityRole: "button",
1479
+ accessibilityLabel,
1480
+ onPress,
1481
+ style: ({ pressed }) => [
1482
+ base,
1483
+ pressed ? { backgroundColor: theme.colors.surfaceActive } : null,
1484
+ style
1485
+ ],
1486
+ children
1487
+ }
1488
+ );
1489
+ });
1490
+ function CardHeader({ children, style }) {
1491
+ const theme = useTheme();
1492
+ return /* @__PURE__ */ jsx(View, { style: [{ padding: theme.space[4], paddingBottom: theme.space[2], gap: theme.space[1] }, style], children });
1493
+ }
1494
+ function CardTitle({ children, style }) {
1495
+ return /* @__PURE__ */ jsx(Heading, { level: 3, size: "md", style, children });
1496
+ }
1497
+ function CardDescription({ children, style }) {
1498
+ return /* @__PURE__ */ jsx(Text, { size: "sm", color: "textMuted", style, children });
1499
+ }
1500
+ function CardContent({ children, style }) {
1501
+ const theme = useTheme();
1502
+ return /* @__PURE__ */ jsx(View, { style: [{ padding: theme.space[4], paddingTop: theme.space[2] }, style], children });
1503
+ }
1504
+ function CardFooter({ children, style }) {
1505
+ const theme = useTheme();
1506
+ return /* @__PURE__ */ jsx(
1507
+ View,
1508
+ {
1509
+ style: [
1510
+ {
1511
+ flexDirection: "row",
1512
+ alignItems: "center",
1513
+ gap: theme.space[2],
1514
+ padding: theme.space[4],
1515
+ paddingTop: 0
1516
+ },
1517
+ style
1518
+ ],
1519
+ children
1520
+ }
1521
+ );
1522
+ }
1523
+ function Container({ gutter = 4, maxWidth: maxWidth2, children, style }) {
1524
+ const theme = useTheme();
1525
+ return /* @__PURE__ */ jsx(
1526
+ View,
1527
+ {
1528
+ style: [
1529
+ {
1530
+ paddingHorizontal: theme.space[gutter],
1531
+ width: "100%",
1532
+ maxWidth: maxWidth2,
1533
+ alignSelf: maxWidth2 ? "center" : void 0
1534
+ },
1535
+ style
1536
+ ],
1537
+ children
1538
+ }
1539
+ );
1540
+ }
1541
+ function Section({ title, description, action, gap = 3, children, style }) {
1542
+ const theme = useTheme();
1543
+ return /* @__PURE__ */ jsxs(View, { style: [{ gap: theme.space[gap] }, style], children: [
1544
+ title || action ? /* @__PURE__ */ jsxs(
1545
+ View,
1546
+ {
1547
+ style: {
1548
+ flexDirection: "row",
1549
+ alignItems: "center",
1550
+ justifyContent: "space-between",
1551
+ gap: theme.space[3]
1552
+ },
1553
+ children: [
1554
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: theme.space[0.5] }, children: [
1555
+ title ? /* @__PURE__ */ jsx(Heading, { level: 2, size: "lg", children: title }) : null,
1556
+ description ? /* @__PURE__ */ jsx(Text, { size: "sm", color: "textMuted", children: description }) : null
1557
+ ] }),
1558
+ action
1559
+ ]
1560
+ }
1561
+ ) : null,
1562
+ children
1563
+ ] });
1564
+ }
1565
+ function Screen({
1566
+ children,
1567
+ bg = "bg",
1568
+ scrollable = false,
1569
+ refreshing,
1570
+ onRefresh,
1571
+ edges = ["top", "bottom"],
1572
+ bottomInset = 0,
1573
+ contentContainerStyle,
1574
+ style
1575
+ }) {
1576
+ const theme = useTheme();
1577
+ const insets = useSafeAreaInsets();
1578
+ const padding = {
1579
+ paddingTop: edges.includes("top") ? insets.top : 0,
1580
+ paddingBottom: (edges.includes("bottom") ? insets.bottom : 0) + bottomInset
1581
+ };
1582
+ if (!scrollable) {
1583
+ return /* @__PURE__ */ jsx(View, { style: [{ flex: 1, backgroundColor: theme.colors[bg] }, padding, style], children });
1584
+ }
1585
+ return /* @__PURE__ */ jsx(
1586
+ ScrollView,
1587
+ {
1588
+ style: [{ flex: 1, backgroundColor: theme.colors[bg] }, style],
1589
+ contentContainerStyle: [padding, contentContainerStyle],
1590
+ keyboardShouldPersistTaps: "handled",
1591
+ refreshControl: onRefresh ? /* @__PURE__ */ jsx(
1592
+ RefreshControl,
1593
+ {
1594
+ refreshing: refreshing ?? false,
1595
+ onRefresh,
1596
+ tintColor: theme.colors.primary,
1597
+ colors: [theme.colors.primary]
1598
+ }
1599
+ ) : void 0,
1600
+ children
1601
+ }
1602
+ );
1603
+ }
1604
+ function ListRow({
1605
+ title,
1606
+ subtitle,
1607
+ left,
1608
+ right,
1609
+ onPress,
1610
+ disabled = false,
1611
+ hideChevron = false,
1612
+ style
1613
+ }) {
1614
+ const theme = useTheme();
1615
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1616
+ left,
1617
+ /* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: theme.space[0.5] }, children: [
1618
+ /* @__PURE__ */ jsx(Text, { size: "md", numberOfLines: 1, children: title }),
1619
+ subtitle ? /* @__PURE__ */ jsx(Text, { size: "sm", color: "textMuted", numberOfLines: 2, children: subtitle }) : null
1620
+ ] }),
1621
+ right ?? (onPress && !hideChevron ? /* @__PURE__ */ jsx(Icon, { icon: ChevronRightIcon, size: 18, color: "textMuted" }) : null)
1622
+ ] });
1623
+ const layout = {
1624
+ flexDirection: "row",
1625
+ alignItems: "center",
1626
+ gap: theme.space[3],
1627
+ paddingHorizontal: theme.space[4],
1628
+ paddingVertical: theme.space[3.5],
1629
+ minHeight: 56,
1630
+ opacity: disabled ? theme.opacity.disabled : 1
1631
+ };
1632
+ if (!onPress) return /* @__PURE__ */ jsx(View, { style: [layout, style], children: content });
1633
+ return /* @__PURE__ */ jsx(
1634
+ Pressable,
1635
+ {
1636
+ accessibilityRole: "button",
1637
+ accessibilityState: { disabled },
1638
+ disabled,
1639
+ onPress,
1640
+ style: ({ pressed }) => [
1641
+ layout,
1642
+ pressed ? { backgroundColor: theme.colors.surfaceActive } : null,
1643
+ style
1644
+ ],
1645
+ children: content
1646
+ }
1647
+ );
1648
+ }
1649
+ var sizeMap = { sm: 32, md: 40, lg: 56 };
1650
+ function initials(name) {
1651
+ return name.trim().split(/\s+/).slice(0, 2).map((word) => word.charAt(0).toUpperCase()).join("");
1652
+ }
1653
+ function Avatar({ source, name, size = "md", style }) {
1654
+ const theme = useTheme();
1655
+ const [failed, setFailed] = useState(false);
1656
+ const dimension = typeof size === "number" ? size : sizeMap[size];
1657
+ const showImage = !!source && !failed;
1658
+ return /* @__PURE__ */ jsx(
1659
+ View,
1660
+ {
1661
+ style: [
1662
+ {
1663
+ width: dimension,
1664
+ height: dimension,
1665
+ borderRadius: theme.radius.full,
1666
+ backgroundColor: theme.colors.primarySubtle,
1667
+ alignItems: "center",
1668
+ justifyContent: "center",
1669
+ overflow: "hidden"
1670
+ },
1671
+ style
1672
+ ],
1673
+ children: showImage ? /* @__PURE__ */ jsx(
1674
+ Image,
1675
+ {
1676
+ source: { uri: source },
1677
+ accessibilityLabel: name,
1678
+ onError: () => setFailed(true),
1679
+ style: { width: "100%", height: "100%" }
1680
+ }
1681
+ ) : /* @__PURE__ */ jsx(
1682
+ Text,
1683
+ {
1684
+ size: dimension >= 56 ? "lg" : dimension >= 40 ? "md" : "sm",
1685
+ weight: "semibold",
1686
+ color: "primaryText",
1687
+ accessibilityLabel: name,
1688
+ children: name ? initials(name) : "?"
1689
+ }
1690
+ )
1691
+ }
1692
+ );
1693
+ }
1694
+ function Tabs({
1695
+ items,
1696
+ value: controlledValue,
1697
+ defaultValue,
1698
+ onChange,
1699
+ variant = "underline",
1700
+ scrollable = false,
1701
+ style,
1702
+ accessibilityLabel
1703
+ }) {
1704
+ const theme = useTheme();
1705
+ const [value, setValue] = useControllableState(
1706
+ controlledValue,
1707
+ defaultValue ?? items[0]?.value,
1708
+ onChange
1709
+ );
1710
+ const isSegmented = variant === "segmented";
1711
+ const tabs = items.map((item) => {
1712
+ const selected = item.value === value;
1713
+ return /* @__PURE__ */ jsxs(
1714
+ Pressable,
1715
+ {
1716
+ accessibilityRole: "tab",
1717
+ accessibilityState: { selected },
1718
+ onPress: () => setValue(item.value),
1719
+ style: {
1720
+ flex: scrollable ? void 0 : 1,
1721
+ flexDirection: "row",
1722
+ alignItems: "center",
1723
+ justifyContent: "center",
1724
+ gap: theme.space[1.5],
1725
+ paddingHorizontal: theme.space[3],
1726
+ paddingVertical: isSegmented ? theme.space[2] : theme.space[3],
1727
+ borderRadius: isSegmented ? theme.radius.md : 0,
1728
+ backgroundColor: isSegmented && selected ? theme.colors.surface : "transparent",
1729
+ borderBottomWidth: isSegmented ? 0 : theme.borderWidth.medium,
1730
+ borderBottomColor: selected ? theme.colors.primary : "transparent"
1731
+ },
1732
+ children: [
1733
+ /* @__PURE__ */ jsx(
1734
+ Text,
1735
+ {
1736
+ size: "sm",
1737
+ weight: selected ? "semibold" : "medium",
1738
+ color: selected ? isSegmented ? "text" : "primaryText" : "textMuted",
1739
+ numberOfLines: 1,
1740
+ children: item.label
1741
+ }
1742
+ ),
1743
+ item.badge !== void 0 ? /* @__PURE__ */ jsx(Text, { size: "xs", color: "textMuted", children: item.badge }) : null
1744
+ ]
1745
+ },
1746
+ item.value
1747
+ );
1748
+ });
1749
+ const container = isSegmented ? {
1750
+ flexDirection: "row",
1751
+ gap: theme.space[1],
1752
+ padding: theme.space[1],
1753
+ borderRadius: theme.radius.lg,
1754
+ backgroundColor: theme.colors.bgMuted
1755
+ } : {
1756
+ flexDirection: "row",
1757
+ borderBottomWidth: theme.borderWidth.thin,
1758
+ borderBottomColor: theme.colors.border
1759
+ };
1760
+ if (scrollable) {
1761
+ return /* @__PURE__ */ jsx(
1762
+ ScrollView,
1763
+ {
1764
+ horizontal: true,
1765
+ showsHorizontalScrollIndicator: false,
1766
+ accessibilityRole: "tablist",
1767
+ accessibilityLabel,
1768
+ contentContainerStyle: [container, style],
1769
+ children: tabs
1770
+ }
1771
+ );
1772
+ }
1773
+ return /* @__PURE__ */ jsx(View, { accessibilityRole: "tablist", accessibilityLabel, style: [container, style], children: tabs });
1774
+ }
1775
+ function Accordion({
1776
+ items,
1777
+ value: controlledValue,
1778
+ defaultValue = [],
1779
+ onChange,
1780
+ single = false,
1781
+ style
1782
+ }) {
1783
+ const theme = useTheme();
1784
+ const [open, setOpen] = useControllableState(controlledValue, defaultValue, onChange);
1785
+ const toggle = (itemValue) => {
1786
+ const isOpen = open.includes(itemValue);
1787
+ if (single) {
1788
+ setOpen(isOpen ? [] : [itemValue]);
1789
+ return;
1790
+ }
1791
+ setOpen(isOpen ? open.filter((entry) => entry !== itemValue) : [...open, itemValue]);
1792
+ };
1793
+ return /* @__PURE__ */ jsx(View, { style, children: items.map((item, index) => {
1794
+ const isOpen = open.includes(item.value);
1795
+ return /* @__PURE__ */ jsxs(
1796
+ View,
1797
+ {
1798
+ style: {
1799
+ borderTopWidth: index === 0 ? 0 : theme.borderWidth.thin,
1800
+ borderTopColor: theme.colors.border
1801
+ },
1802
+ children: [
1803
+ /* @__PURE__ */ jsxs(
1804
+ Pressable,
1805
+ {
1806
+ accessibilityRole: "button",
1807
+ accessibilityState: { expanded: isOpen },
1808
+ onPress: () => toggle(item.value),
1809
+ style: ({ pressed }) => ({
1810
+ flexDirection: "row",
1811
+ alignItems: "center",
1812
+ justifyContent: "space-between",
1813
+ gap: theme.space[3],
1814
+ paddingVertical: theme.space[4],
1815
+ backgroundColor: pressed ? theme.colors.surfaceActive : "transparent"
1816
+ }),
1817
+ children: [
1818
+ /* @__PURE__ */ jsx(Text, { size: "md", weight: "medium", style: { flex: 1 }, children: item.title }),
1819
+ /* @__PURE__ */ jsx(View, { style: { transform: [{ rotate: isOpen ? "180deg" : "0deg" }] }, children: /* @__PURE__ */ jsx(Icon, { icon: ChevronDownIcon, size: 18, color: "textSecondary" }) })
1820
+ ]
1821
+ }
1822
+ ),
1823
+ isOpen ? /* @__PURE__ */ jsx(View, { style: { paddingBottom: theme.space[4] }, children: item.content }) : null
1824
+ ]
1825
+ },
1826
+ item.value
1827
+ );
1828
+ }) });
1829
+ }
1830
+ function Collapse({ title, defaultOpen = false, children, style }) {
1831
+ const [open, setOpen] = useState(defaultOpen);
1832
+ return /* @__PURE__ */ jsx(
1833
+ Accordion,
1834
+ {
1835
+ items: [{ value: "only", title, content: children }],
1836
+ value: open ? ["only"] : [],
1837
+ onChange: (next) => setOpen(next.length > 0),
1838
+ style
1839
+ }
1840
+ );
1841
+ }
1842
+
1843
+ // src/internal/date.ts
1844
+ function startOfDay(date) {
1845
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate());
1846
+ }
1847
+ function startOfMonth(date) {
1848
+ return new Date(date.getFullYear(), date.getMonth(), 1);
1849
+ }
1850
+ function addDays(date, amount) {
1851
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount);
1852
+ }
1853
+ function addMonths(date, amount) {
1854
+ return new Date(date.getFullYear(), date.getMonth() + amount, 1);
1855
+ }
1856
+ function isSameDay(a, b) {
1857
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1858
+ }
1859
+ function dayKey(date) {
1860
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1861
+ const day = String(date.getDate()).padStart(2, "0");
1862
+ return `${date.getFullYear()}-${month}-${day}`;
1863
+ }
1864
+ function monthGrid(month, weekStartsOn) {
1865
+ const first = startOfMonth(month);
1866
+ const offset = (first.getDay() - weekStartsOn + 7) % 7;
1867
+ const start = addDays(first, -offset);
1868
+ return Array.from({ length: 42 }, (_, index) => addDays(start, index));
1869
+ }
1870
+ function Calendar({
1871
+ value: controlledValue,
1872
+ defaultValue,
1873
+ onChange,
1874
+ defaultMonth,
1875
+ minDate,
1876
+ maxDate,
1877
+ weekStartsOn = 1,
1878
+ locale,
1879
+ markedDates,
1880
+ onMonthChange,
1881
+ style
1882
+ }) {
1883
+ const theme = useTheme();
1884
+ const [selected, setSelected] = useControllableState(
1885
+ controlledValue,
1886
+ defaultValue,
1887
+ onChange
1888
+ );
1889
+ const [month, setMonth] = useState(() => defaultMonth ?? selected ?? /* @__PURE__ */ new Date());
1890
+ const today = startOfDay(/* @__PURE__ */ new Date());
1891
+ const days = useMemo(() => monthGrid(month, weekStartsOn), [month, weekStartsOn]);
1892
+ const monthLabel = useMemo(
1893
+ () => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric" }).format(month),
1894
+ [month, locale]
1895
+ );
1896
+ const weekdayLabels = useMemo(() => {
1897
+ const formatter = new Intl.DateTimeFormat(locale, { weekday: "short" });
1898
+ const anchor = new Date(2024, 0, 7);
1899
+ return Array.from(
1900
+ { length: 7 },
1901
+ (_, index) => formatter.format(addDays(anchor, index + weekStartsOn))
1902
+ );
1903
+ }, [locale, weekStartsOn]);
1904
+ const goToMonth = (amount) => {
1905
+ const next = addMonths(month, amount);
1906
+ setMonth(next);
1907
+ onMonthChange?.(next);
1908
+ };
1909
+ const isDisabled = (date) => minDate !== void 0 && date < startOfDay(minDate) || maxDate !== void 0 && date > startOfDay(maxDate);
1910
+ return /* @__PURE__ */ jsxs(View, { style, children: [
1911
+ /* @__PURE__ */ jsxs(
1912
+ View,
1913
+ {
1914
+ style: {
1915
+ flexDirection: "row",
1916
+ alignItems: "center",
1917
+ justifyContent: "space-between",
1918
+ paddingBottom: theme.space[2]
1919
+ },
1920
+ children: [
1921
+ /* @__PURE__ */ jsx(
1922
+ IconButton,
1923
+ {
1924
+ icon: /* @__PURE__ */ jsx(Icon, { icon: ChevronLeftIcon, color: "textSecondary" }),
1925
+ accessibilityLabel: "Previous month",
1926
+ size: "sm",
1927
+ onPress: () => goToMonth(-1)
1928
+ }
1929
+ ),
1930
+ /* @__PURE__ */ jsx(Text, { size: "md", weight: "semibold", accessibilityLiveRegion: "polite", children: monthLabel }),
1931
+ /* @__PURE__ */ jsx(
1932
+ IconButton,
1933
+ {
1934
+ icon: /* @__PURE__ */ jsx(Icon, { icon: ChevronRightIcon, color: "textSecondary" }),
1935
+ accessibilityLabel: "Next month",
1936
+ size: "sm",
1937
+ onPress: () => goToMonth(1)
1938
+ }
1939
+ )
1940
+ ]
1941
+ }
1942
+ ),
1943
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row" }, children: weekdayLabels.map((label) => /* @__PURE__ */ jsx(
1944
+ Text,
1945
+ {
1946
+ size: "xs",
1947
+ weight: "medium",
1948
+ color: "textMuted",
1949
+ align: "center",
1950
+ style: { flex: 1, paddingVertical: theme.space[1.5] },
1951
+ children: label
1952
+ },
1953
+ label
1954
+ )) }),
1955
+ /* @__PURE__ */ jsx(View, { style: { flexDirection: "row", flexWrap: "wrap" }, children: days.map((day) => {
1956
+ const outside = day.getMonth() !== month.getMonth();
1957
+ const isSelected = selected !== void 0 && isSameDay(day, selected);
1958
+ const isToday = isSameDay(day, today);
1959
+ const disabled = isDisabled(day);
1960
+ const mark = markedDates?.[dayKey(day)];
1961
+ return /* @__PURE__ */ jsxs(
1962
+ Pressable,
1963
+ {
1964
+ accessibilityRole: "button",
1965
+ accessibilityLabel: new Intl.DateTimeFormat(locale, { dateStyle: "full" }).format(day),
1966
+ accessibilityState: { selected: isSelected, disabled },
1967
+ disabled,
1968
+ onPress: () => setSelected(day),
1969
+ style: {
1970
+ width: `${100 / 7}%`,
1971
+ aspectRatio: 1,
1972
+ alignItems: "center",
1973
+ justifyContent: "center",
1974
+ gap: 2
1975
+ },
1976
+ children: [
1977
+ /* @__PURE__ */ jsx(
1978
+ View,
1979
+ {
1980
+ style: {
1981
+ width: 34,
1982
+ height: 34,
1983
+ alignItems: "center",
1984
+ justifyContent: "center",
1985
+ borderRadius: theme.radius.full,
1986
+ backgroundColor: isSelected ? theme.colors.primary : "transparent",
1987
+ borderWidth: !isSelected && isToday ? theme.borderWidth.thin : 0,
1988
+ borderColor: theme.colors.primary,
1989
+ opacity: disabled ? theme.opacity.disabled : outside ? 0.4 : 1
1990
+ },
1991
+ children: /* @__PURE__ */ jsx(
1992
+ Text,
1993
+ {
1994
+ size: "sm",
1995
+ weight: isSelected || isToday ? "semibold" : "regular",
1996
+ color: isSelected ? "onPrimary" : outside ? "textMuted" : "text",
1997
+ children: day.getDate()
1998
+ }
1999
+ )
2000
+ }
2001
+ ),
2002
+ mark && !isSelected ? /* @__PURE__ */ jsx(
2003
+ View,
2004
+ {
2005
+ style: {
2006
+ width: 4,
2007
+ height: 4,
2008
+ borderRadius: theme.radius.full,
2009
+ backgroundColor: theme.colors[mark]
2010
+ }
2011
+ }
2012
+ ) : /* @__PURE__ */ jsx(View, { style: { height: 4 } })
2013
+ ]
2014
+ },
2015
+ dayKey(day)
2016
+ );
2017
+ }) })
2018
+ ] });
2019
+ }
2020
+ function Carousel({
2021
+ children,
2022
+ slideWidth,
2023
+ gutter = 16,
2024
+ dots = true,
2025
+ onSlideChange,
2026
+ style,
2027
+ accessibilityLabel
2028
+ }) {
2029
+ const theme = useTheme();
2030
+ const { width } = useWindowDimensions();
2031
+ const itemWidth = slideWidth ?? width - gutter * 2;
2032
+ const [index, setIndex] = useState(0);
2033
+ const lastIndex = useRef(0);
2034
+ const handleScroll = (event) => {
2035
+ const next = Math.round(event.nativeEvent.contentOffset.x / (itemWidth + theme.space[3]));
2036
+ if (next === lastIndex.current) return;
2037
+ lastIndex.current = next;
2038
+ setIndex(next);
2039
+ onSlideChange?.(next);
2040
+ };
2041
+ return /* @__PURE__ */ jsxs(View, { style, children: [
2042
+ /* @__PURE__ */ jsx(
2043
+ ScrollView,
2044
+ {
2045
+ horizontal: true,
2046
+ showsHorizontalScrollIndicator: false,
2047
+ decelerationRate: "fast",
2048
+ snapToInterval: itemWidth + theme.space[3],
2049
+ snapToAlignment: "start",
2050
+ onMomentumScrollEnd: handleScroll,
2051
+ accessibilityLabel,
2052
+ contentContainerStyle: { gap: theme.space[3], paddingHorizontal: gutter },
2053
+ children: children.map((child, slideIndex) => /* @__PURE__ */ jsx(View, { style: { width: itemWidth }, children: child }, slideIndex))
2054
+ }
2055
+ ),
2056
+ dots && children.length > 1 ? /* @__PURE__ */ jsx(
2057
+ View,
2058
+ {
2059
+ style: {
2060
+ flexDirection: "row",
2061
+ alignSelf: "center",
2062
+ gap: theme.space[1.5],
2063
+ paddingTop: theme.space[3]
2064
+ },
2065
+ children: children.map((_, dotIndex) => /* @__PURE__ */ jsx(
2066
+ View,
2067
+ {
2068
+ style: {
2069
+ width: dotIndex === index ? 18 : 6,
2070
+ height: 6,
2071
+ borderRadius: theme.radius.full,
2072
+ backgroundColor: dotIndex === index ? theme.colors.primary : theme.colors.borderStrong
2073
+ }
2074
+ },
2075
+ dotIndex
2076
+ ))
2077
+ }
2078
+ ) : null
2079
+ ] });
2080
+ }
2081
+ function Tooltip({ label, placement = "top", children, style }) {
2082
+ const theme = useTheme();
2083
+ const [visible, setVisible] = useState(false);
2084
+ return /* @__PURE__ */ jsxs(View, { style: [{ position: "relative" }, style], children: [
2085
+ /* @__PURE__ */ jsx(
2086
+ Pressable,
2087
+ {
2088
+ accessibilityHint: label,
2089
+ delayLongPress: 200,
2090
+ onLongPress: () => setVisible(true),
2091
+ onPressOut: () => setVisible(false),
2092
+ children
2093
+ }
2094
+ ),
2095
+ visible ? /* @__PURE__ */ jsx(
2096
+ View,
2097
+ {
2098
+ accessibilityRole: "alert",
2099
+ pointerEvents: "none",
2100
+ style: {
2101
+ position: "absolute",
2102
+ [placement === "top" ? "bottom" : "top"]: "100%",
2103
+ left: 0,
2104
+ marginVertical: theme.space[1.5],
2105
+ maxWidth: 240,
2106
+ paddingHorizontal: theme.space[2.5],
2107
+ paddingVertical: theme.space[1.5],
2108
+ borderRadius: theme.radius.md,
2109
+ backgroundColor: theme.colors.text,
2110
+ ...shadowStyle(theme.shadow.md)
2111
+ },
2112
+ children: /* @__PURE__ */ jsx(Text, { size: "xs", style: { color: theme.colors.textInverse }, children: label })
2113
+ }
2114
+ ) : null
2115
+ ] });
2116
+ }
2117
+ var maxWidth = { sm: 340, md: 420, lg: 560 };
2118
+ function Modal({
2119
+ open,
2120
+ onClose,
2121
+ size = "md",
2122
+ closeOnOverlayPress = true,
2123
+ accessibilityLabel,
2124
+ children,
2125
+ style
2126
+ }) {
2127
+ const theme = useTheme();
2128
+ const { width } = useWindowDimensions();
2129
+ return /* @__PURE__ */ jsx(
2130
+ Modal$1,
2131
+ {
2132
+ visible: open,
2133
+ transparent: true,
2134
+ animationType: "fade",
2135
+ statusBarTranslucent: true,
2136
+ onRequestClose: onClose,
2137
+ children: /* @__PURE__ */ jsx(
2138
+ Pressable,
2139
+ {
2140
+ accessibilityLabel: closeOnOverlayPress ? "Close" : void 0,
2141
+ onPress: closeOnOverlayPress ? onClose : void 0,
2142
+ style: {
2143
+ flex: 1,
2144
+ alignItems: "center",
2145
+ justifyContent: "center",
2146
+ padding: theme.space[5],
2147
+ backgroundColor: theme.colors.overlay
2148
+ },
2149
+ children: /* @__PURE__ */ jsx(
2150
+ Pressable,
2151
+ {
2152
+ accessibilityRole: "alert",
2153
+ accessibilityViewIsModal: true,
2154
+ accessibilityLabel,
2155
+ onPress: () => {
2156
+ },
2157
+ style: [
2158
+ {
2159
+ width: Math.min(maxWidth[size], width - theme.space[10]),
2160
+ maxHeight: "85%",
2161
+ borderRadius: theme.radius.xl,
2162
+ backgroundColor: theme.colors.surface,
2163
+ overflow: "hidden"
2164
+ },
2165
+ style
2166
+ ],
2167
+ children
2168
+ }
2169
+ )
2170
+ }
2171
+ )
2172
+ }
2173
+ );
2174
+ }
2175
+ function ModalHeader({ children, onClose, style }) {
2176
+ const theme = useTheme();
2177
+ return /* @__PURE__ */ jsxs(
2178
+ View,
2179
+ {
2180
+ style: [
2181
+ {
2182
+ flexDirection: "row",
2183
+ alignItems: "center",
2184
+ justifyContent: "space-between",
2185
+ gap: theme.space[3],
2186
+ paddingHorizontal: theme.space[5],
2187
+ paddingTop: theme.space[5],
2188
+ paddingBottom: theme.space[3]
2189
+ },
2190
+ style
2191
+ ],
2192
+ children: [
2193
+ /* @__PURE__ */ jsx(View, { style: { flex: 1 }, children }),
2194
+ onClose ? /* @__PURE__ */ jsx(
2195
+ IconButton,
2196
+ {
2197
+ icon: /* @__PURE__ */ jsx(Icon, { icon: XIcon, color: "textSecondary" }),
2198
+ accessibilityLabel: "Close",
2199
+ size: "sm",
2200
+ variant: "ghost",
2201
+ onPress: onClose
2202
+ }
2203
+ ) : null
2204
+ ]
2205
+ }
2206
+ );
2207
+ }
2208
+ function ModalTitle({ children }) {
2209
+ return /* @__PURE__ */ jsx(Heading, { level: 3, size: "lg", children });
2210
+ }
2211
+ function ModalBody({ children, scrollable = true, style }) {
2212
+ const theme = useTheme();
2213
+ const padding = { paddingHorizontal: theme.space[5], paddingBottom: theme.space[4] };
2214
+ if (!scrollable) return /* @__PURE__ */ jsx(View, { style: [padding, style], children });
2215
+ return /* @__PURE__ */ jsx(ScrollView, { contentContainerStyle: [padding, style], keyboardShouldPersistTaps: "handled", children });
2216
+ }
2217
+ function ModalFooter({
2218
+ children,
2219
+ style
2220
+ }) {
2221
+ const theme = useTheme();
2222
+ return /* @__PURE__ */ jsx(
2223
+ View,
2224
+ {
2225
+ style: [
2226
+ {
2227
+ flexDirection: "row",
2228
+ justifyContent: "flex-end",
2229
+ gap: theme.space[2],
2230
+ paddingHorizontal: theme.space[5],
2231
+ paddingTop: theme.space[3],
2232
+ paddingBottom: theme.space[5]
2233
+ },
2234
+ style
2235
+ ],
2236
+ children
2237
+ }
2238
+ );
2239
+ }
2240
+ function Drawer({
2241
+ open,
2242
+ onClose,
2243
+ side = "right",
2244
+ widthRatio = 0.85,
2245
+ title,
2246
+ accessibilityLabel,
2247
+ children,
2248
+ style
2249
+ }) {
2250
+ const theme = useTheme();
2251
+ const insets = useSafeAreaInsets();
2252
+ const { width } = useWindowDimensions();
2253
+ const panelWidth = width * widthRatio;
2254
+ const offset = side === "right" ? panelWidth : -panelWidth;
2255
+ const translateX = useRef(new Animated.Value(offset)).current;
2256
+ useEffect(() => {
2257
+ Animated.timing(translateX, {
2258
+ toValue: open ? 0 : offset,
2259
+ duration: open ? theme.motion.duration.normal : theme.motion.duration.fast,
2260
+ easing: Easing.out(Easing.cubic),
2261
+ useNativeDriver: true
2262
+ }).start();
2263
+ }, [open, offset, translateX, theme.motion.duration]);
2264
+ return /* @__PURE__ */ jsx(
2265
+ Modal$1,
2266
+ {
2267
+ visible: open,
2268
+ transparent: true,
2269
+ animationType: "none",
2270
+ statusBarTranslucent: true,
2271
+ onRequestClose: onClose,
2272
+ children: /* @__PURE__ */ jsxs(View, { style: { flex: 1, flexDirection: side === "right" ? "row" : "row-reverse" }, children: [
2273
+ /* @__PURE__ */ jsx(
2274
+ Pressable,
2275
+ {
2276
+ accessibilityLabel: "Close",
2277
+ onPress: onClose,
2278
+ style: { flex: 1, backgroundColor: theme.colors.overlay }
2279
+ }
2280
+ ),
2281
+ /* @__PURE__ */ jsxs(
2282
+ Animated.View,
2283
+ {
2284
+ accessibilityViewIsModal: true,
2285
+ accessibilityLabel: accessibilityLabel ?? title,
2286
+ style: [
2287
+ {
2288
+ width: panelWidth,
2289
+ paddingTop: insets.top,
2290
+ paddingBottom: insets.bottom,
2291
+ backgroundColor: theme.colors.surface,
2292
+ transform: [{ translateX }]
2293
+ },
2294
+ style
2295
+ ],
2296
+ children: [
2297
+ title ? /* @__PURE__ */ jsxs(
2298
+ View,
2299
+ {
2300
+ style: {
2301
+ flexDirection: "row",
2302
+ alignItems: "center",
2303
+ justifyContent: "space-between",
2304
+ gap: theme.space[3],
2305
+ padding: theme.space[4],
2306
+ borderBottomWidth: theme.borderWidth.thin,
2307
+ borderBottomColor: theme.colors.border
2308
+ },
2309
+ children: [
2310
+ /* @__PURE__ */ jsx(Heading, { level: 3, size: "lg", style: { flex: 1 }, children: title }),
2311
+ /* @__PURE__ */ jsx(
2312
+ IconButton,
2313
+ {
2314
+ icon: /* @__PURE__ */ jsx(Icon, { icon: XIcon, color: "textSecondary" }),
2315
+ accessibilityLabel: "Close",
2316
+ size: "sm",
2317
+ onPress: onClose
2318
+ }
2319
+ )
2320
+ ]
2321
+ }
2322
+ ) : null,
2323
+ children
2324
+ ]
2325
+ }
2326
+ )
2327
+ ] })
2328
+ }
2329
+ );
2330
+ }
2331
+ function ActionSheet({
2332
+ open,
2333
+ onClose,
2334
+ title,
2335
+ description,
2336
+ actions,
2337
+ cancelLabel = "Cancel",
2338
+ style
2339
+ }) {
2340
+ const theme = useTheme();
2341
+ return /* @__PURE__ */ jsxs(BottomSheet, { open, onClose, title, style, children: [
2342
+ description ? /* @__PURE__ */ jsx(
2343
+ Text,
2344
+ {
2345
+ size: "sm",
2346
+ color: "textMuted",
2347
+ style: { paddingHorizontal: theme.space[5], paddingTop: theme.space[2] },
2348
+ children: description
2349
+ }
2350
+ ) : null,
2351
+ /* @__PURE__ */ jsxs(View, { style: { paddingTop: theme.space[3] }, accessibilityRole: "menu", children: [
2352
+ actions.map((action) => /* @__PURE__ */ jsxs(
2353
+ Pressable,
2354
+ {
2355
+ accessibilityRole: "menuitem",
2356
+ accessibilityState: { disabled: action.disabled },
2357
+ disabled: action.disabled,
2358
+ onPress: () => {
2359
+ onClose();
2360
+ action.onPress();
2361
+ },
2362
+ style: ({ pressed }) => ({
2363
+ flexDirection: "row",
2364
+ alignItems: "center",
2365
+ gap: theme.space[3],
2366
+ paddingHorizontal: theme.space[5],
2367
+ paddingVertical: theme.space[4],
2368
+ backgroundColor: pressed ? theme.colors.surfaceActive : "transparent",
2369
+ opacity: action.disabled ? theme.opacity.disabled : 1
2370
+ }),
2371
+ children: [
2372
+ action.icon,
2373
+ /* @__PURE__ */ jsx(Text, { size: "md", weight: "medium", color: action.destructive ? "dangerText" : "text", children: action.label })
2374
+ ]
2375
+ },
2376
+ action.label
2377
+ )),
2378
+ cancelLabel ? /* @__PURE__ */ jsx(
2379
+ Pressable,
2380
+ {
2381
+ accessibilityRole: "button",
2382
+ onPress: onClose,
2383
+ style: ({ pressed }) => ({
2384
+ paddingHorizontal: theme.space[5],
2385
+ paddingVertical: theme.space[4],
2386
+ borderTopWidth: theme.borderWidth.thin,
2387
+ borderTopColor: theme.colors.border,
2388
+ backgroundColor: pressed ? theme.colors.surfaceActive : "transparent"
2389
+ }),
2390
+ children: /* @__PURE__ */ jsx(Text, { size: "md", weight: "semibold", color: "textSecondary", children: cancelLabel })
2391
+ }
2392
+ ) : null
2393
+ ] })
2394
+ ] });
2395
+ }
2396
+
2397
+ export { Accordion, ActionSheet, Avatar, Badge, BottomSheet, Box, Button, Calendar, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Carousel, Checkbox, Chip, Collapse, Container, Divider, Drawer, EmptyState, Flex, FormField, HStack, Heading, Icon, IconButton, Input, Label, ListRow, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle, Progress, Radio, RadioGroup, Screen, Section, Select, Skeleton, Spacer, Spinner, Stack, Switch, Tabs, Text, Textarea, ThemeProvider, ToastProvider, Tooltip, addDays, addMonths, dayKey, isSameDay, monthGrid, shadowStyle, sizeMetrics, splitStyleProps, startOfDay, startOfMonth, useColorMode, useColorModeValue, useTheme, useToast, variantColors };
2398
+ //# sourceMappingURL=index.js.map
2399
+ //# sourceMappingURL=index.js.map