@satellatickets/ui 0.0.0 → 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.
@@ -1,73 +1,37 @@
1
- import { UIContext, buttonIconSize, createUIContextValue, iconSizePx, spinnerSizePx, textVariantStyles, useBrand, useButton, useColorScheme, useTheme, useTheme as useTheme$1 } from "@satellatickets/core";
2
- import { Animated, Easing, Pressable, StyleSheet, Text as Text$1, View, useColorScheme as useColorScheme$1 } from "react-native";
3
- import { jsx, jsxs } from "react/jsx-runtime";
4
- import { Children, useEffect, useMemo, useState } from "react";
1
+ import { FormFieldContext, ToastContext, UIContext, buttonIconSize, createFormFieldValue, createToastStore, createUIContextValue, firstEnabledTab, iconButtonIconSize, iconSizePx, inputIconSize, isFeedbackTone, spinnerSizePx, textVariantStyles, useBrand, useButton, useColorScheme, useControllableState, useFormFieldControl, useLink, useTheme, useTheme as useTheme$1, useToast } from "@satellatickets/core";
2
+ import { AccessibilityInfo, Animated, Easing, Linking, Modal as Modal$1, Pressable, ScrollView, StyleSheet, Text as Text$1, TextInput, View, useColorScheme as useColorScheme$1 } from "react-native";
3
+ import { Children, createContext, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import Svg, { Circle, Line, Path, Rect } from "react-native-svg";
6
- //#region src/badge/Badge.native.tsx
7
- const variantColors = {
8
- success: (t) => t.color.feedback.success,
9
- warning: (t) => t.color.feedback.warning,
10
- danger: (t) => t.color.feedback.danger,
11
- info: (t) => t.color.feedback.info
6
+ //#region src/_internal/feedback.ts
7
+ /** El icono refuerza el color: el tono no depende solo de él. */
8
+ const feedbackIcon = {
9
+ success: "circle-check",
10
+ warning: "triangle-alert",
11
+ danger: "circle-x",
12
+ info: "info"
13
+ };
14
+ /**
15
+ * Si el tono interrumpe al lector de pantalla (`alert`, `assertive`) o espera a que
16
+ * termine de leer (`status`, `polite`).
17
+ */
18
+ const feedbackUrgent = {
19
+ success: false,
20
+ warning: true,
21
+ danger: true,
22
+ info: false
12
23
  };
13
- const styles$1 = StyleSheet.create({ base: {
14
- flexDirection: "row",
15
- alignItems: "center"
16
- } });
17
- function Badge({ variant = "info", style, testID, children }) {
18
- const t = useTheme$1();
19
- const colors = variantColors[variant](t);
20
- const container = {
21
- paddingVertical: t.space[1],
22
- paddingHorizontal: t.space[2],
23
- borderRadius: t.radius.full,
24
- borderWidth: t.borderWidth.thin,
25
- borderColor: colors.border,
26
- backgroundColor: colors.bg
27
- };
28
- const label = {
29
- flexShrink: 1,
30
- fontSize: t.font.size.xs,
31
- lineHeight: t.font.lineHeight.xs,
32
- fontWeight: t.font.weight.medium,
33
- color: colors.text
34
- };
35
- const family = t.font.family.sans;
36
- if (family !== void 0) label.fontFamily = family;
37
- return /* @__PURE__ */ jsx(View, {
38
- testID,
39
- style: [
40
- styles$1.base,
41
- container,
42
- style
43
- ],
44
- children: /* @__PURE__ */ jsx(Text$1, {
45
- style: label,
46
- children
47
- })
48
- });
49
- }
50
24
  //#endregion
51
- //#region src/box/Box.native.tsx
52
- function Box({ padding, paddingX, paddingY, background, radius, borderColor, shadow, flex, style, testID, children }) {
53
- const t = useTheme$1();
54
- const computed = {};
55
- if (padding !== void 0) computed.padding = t.space[padding];
56
- if (paddingX !== void 0) computed.paddingHorizontal = t.space[paddingX];
57
- if (paddingY !== void 0) computed.paddingVertical = t.space[paddingY];
58
- if (background !== void 0) computed.backgroundColor = t.color.bg[background];
59
- if (radius !== void 0) computed.borderRadius = t.radius[radius];
60
- if (borderColor !== void 0) {
61
- computed.borderWidth = t.borderWidth.thin;
62
- computed.borderColor = t.color.border[borderColor];
63
- }
64
- if (shadow !== void 0) computed.boxShadow = t.shadow[shadow];
65
- if (flex !== void 0) computed.flex = flex;
66
- return /* @__PURE__ */ jsx(View, {
67
- style: [computed, style],
68
- testID,
25
+ //#region src/_internal/label.native.tsx
26
+ /**
27
+ * El texto como `children` se envuelve en `<Text>`, porque React Native no admite texto
28
+ * suelto dentro de una vista; cualquier otro contenido se deja tal cual (CLAUDE.md §5).
29
+ */
30
+ function renderLabel(children, style) {
31
+ return Children.toArray(children).every((child) => typeof child === "string" || typeof child === "number") ? /* @__PURE__ */ jsx(Text$1, {
32
+ style,
69
33
  children
70
- });
34
+ }) : children;
71
35
  }
72
36
  //#endregion
73
37
  //#region ../icons/src/icons.ts
@@ -385,7 +349,7 @@ function Icon({ name, size = "md", color = "primary", label, style, testID }) {
385
349
  height: px,
386
350
  viewBox: `0 0 24 24`,
387
351
  fill: "none",
388
- stroke: t.color.text[color],
352
+ stroke: isFeedbackTone(color) ? t.color.feedback[color].icon : t.color.text[color],
389
353
  strokeWidth: 2,
390
354
  strokeLinecap: "round",
391
355
  strokeLinejoin: "round",
@@ -394,17 +358,39 @@ function Icon({ name, size = "md", color = "primary", label, style, testID }) {
394
358
  });
395
359
  }
396
360
  //#endregion
361
+ //#region src/_internal/useReducedMotion.native.ts
362
+ /**
363
+ * `true` si el usuario ha pedido reducir el movimiento en los ajustes del sistema. Es
364
+ * el equivalente nativo de `@media (prefers-reduced-motion: reduce)` en web.
365
+ */
366
+ function useReducedMotion() {
367
+ const [reduced, setReduced] = useState(false);
368
+ useEffect(() => {
369
+ let mounted = true;
370
+ AccessibilityInfo.isReduceMotionEnabled().then((enabled) => {
371
+ if (mounted && enabled) setReduced(true);
372
+ });
373
+ const subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", setReduced);
374
+ return () => {
375
+ mounted = false;
376
+ subscription.remove();
377
+ };
378
+ }, []);
379
+ return reduced;
380
+ }
381
+ //#endregion
397
382
  //#region src/spinner/Spinner.native.tsx
398
383
  /**
399
- * Indicador de carga indeterminada. Versión mínima (ROADMAP Fase 3): el icono
400
- * `loader-circle` girando con `Animated` y el driver nativo, sin dependencias extra.
401
- * Con `label` es una barra de progreso indeterminada para los lectores de
402
- * pantalla; sin él, es decorativo.
384
+ * Indicador de carga indeterminada: el icono `loader-circle` girando con `Animated` y
385
+ * el driver nativo, sin dependencias extra. Con `label` es una barra de progreso
386
+ * indeterminada para los lectores de pantalla; sin él, es decorativo. Con movimiento
387
+ * reducido gira más despacio, pero no se detiene: parado no diría que algo está en curso.
403
388
  */
404
389
  function Spinner({ size = "md", color = "primary", label, style, testID }) {
405
390
  const t = useTheme$1();
406
391
  const [rotation] = useState(() => new Animated.Value(0));
407
- const duration = t.duration.slow * 2;
392
+ const reducedMotion = useReducedMotion();
393
+ const duration = t.duration.slow * (reducedMotion ? 8 : 2);
408
394
  useEffect(() => {
409
395
  const loop = Animated.loop(Animated.timing(rotation, {
410
396
  toValue: 1,
@@ -499,7 +485,7 @@ const sizeStyles = {
499
485
  lineHeight: t.font.lineHeight.md
500
486
  })
501
487
  };
502
- const styles = StyleSheet.create({
488
+ const styles$16 = StyleSheet.create({
503
489
  base: {
504
490
  flexDirection: "row",
505
491
  alignItems: "center",
@@ -523,13 +509,6 @@ const styles = StyleSheet.create({
523
509
  justifyContent: "center"
524
510
  }
525
511
  });
526
- /** El texto como `children` se envuelve en `<Text>`; cualquier otro contenido se deja tal cual. */
527
- function renderLabel(children, style) {
528
- return Children.toArray(children).every((child) => typeof child === "string" || typeof child === "number") ? /* @__PURE__ */ jsx(Text$1, {
529
- style,
530
- children
531
- }) : children;
532
- }
533
512
  function Button({ variant = "primary", size = "md", disabled = false, loading = false, iconStart, iconEnd, fullWidth = false, onPress, accessibilityLabel, style, testID, ref, children }) {
534
513
  const t = useTheme$1();
535
514
  const { interactive, press } = useButton({
@@ -582,18 +561,18 @@ function Button({ variant = "primary", size = "md", disabled = false, loading =
582
561
  onBlur: () => setFocused(false),
583
562
  testID,
584
563
  style: ({ pressed }) => [
585
- styles.base,
564
+ styles$16.base,
586
565
  container,
587
566
  { backgroundColor: disabled ? colors.disabled : pressed ? colors.pressed : colors.background },
588
- fullWidth && styles.fullWidth,
567
+ fullWidth && styles$16.fullWidth,
589
568
  focused && focusRing,
590
569
  style
591
570
  ],
592
571
  children: [/* @__PURE__ */ jsxs(View, {
593
572
  style: [
594
- styles.content,
573
+ styles$16.content,
595
574
  { gap: t.space[2] },
596
- loading && styles.hidden
575
+ loading && styles$16.hidden
597
576
  ],
598
577
  children: [
599
578
  iconStart === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
@@ -609,7 +588,7 @@ function Button({ variant = "primary", size = "md", disabled = false, loading =
609
588
  })
610
589
  ]
611
590
  }), loading ? /* @__PURE__ */ jsx(View, {
612
- style: styles.spinner,
591
+ style: styles$16.spinner,
613
592
  children: /* @__PURE__ */ jsx(Spinner, {
614
593
  size: iconSize,
615
594
  color: content
@@ -618,82 +597,1740 @@ function Button({ variant = "primary", size = "md", disabled = false, loading =
618
597
  });
619
598
  }
620
599
  //#endregion
621
- //#region src/stack/Stack.native.tsx
622
- const alignItems = {
623
- stretch: "stretch",
624
- start: "flex-start",
625
- center: "center",
626
- end: "flex-end"
627
- };
628
- const justifyContent = {
629
- start: "flex-start",
630
- center: "center",
631
- end: "flex-end",
632
- between: "space-between",
633
- around: "space-around"
634
- };
635
- function Stack({ direction = "column", gap, align = "stretch", justify = "start", wrap = false, style, ...boxProps }) {
600
+ //#region src/icon-button/IconButton.native.tsx
601
+ const styles$15 = StyleSheet.create({ base: {
602
+ alignItems: "center",
603
+ justifyContent: "center",
604
+ flexShrink: 0
605
+ } });
606
+ function IconButton({ icon, label, variant = "ghost", size = "md", disabled = false, loading = false, onPress, style, testID, ref }) {
636
607
  const t = useTheme$1();
637
- const layout = {
638
- flexDirection: direction,
639
- alignItems: alignItems[align],
640
- justifyContent: justifyContent[justify],
641
- flexWrap: wrap ? "wrap" : "nowrap"
608
+ const { interactive, press } = useButton({
609
+ disabled,
610
+ loading,
611
+ onPress
612
+ });
613
+ const [focused, setFocused] = useState(false);
614
+ const colors = variantStyles[variant](t);
615
+ const side = t.size.control[size];
616
+ const content = disabled ? "disabled" : colors.content;
617
+ const iconSize = iconButtonIconSize[size];
618
+ const container = {
619
+ width: side,
620
+ height: side,
621
+ borderRadius: t.radius.md,
622
+ borderWidth: t.borderWidth.thin,
623
+ borderColor: disabled ? "transparent" : colors.border
642
624
  };
643
- if (gap !== void 0) layout.gap = t.space[gap];
644
- return /* @__PURE__ */ jsx(Box, {
645
- ...boxProps,
646
- style: [layout, style]
625
+ const focusRing = {
626
+ outlineStyle: "solid",
627
+ outlineColor: t.color.border.focus,
628
+ outlineWidth: t.borderWidth.thick,
629
+ outlineOffset: t.borderWidth.thick
630
+ };
631
+ const hitSlop = Math.max(0, (t.size.control.md - side) / 2);
632
+ return /* @__PURE__ */ jsx(Pressable, {
633
+ ref,
634
+ accessibilityRole: "button",
635
+ accessibilityLabel: label,
636
+ accessibilityState: {
637
+ disabled: !interactive,
638
+ busy: loading
639
+ },
640
+ disabled: !interactive,
641
+ hitSlop,
642
+ onPress: press,
643
+ onFocus: () => setFocused(true),
644
+ onBlur: () => setFocused(false),
645
+ testID,
646
+ style: ({ pressed }) => [
647
+ styles$15.base,
648
+ container,
649
+ { backgroundColor: disabled ? colors.disabled : pressed ? colors.pressed : colors.background },
650
+ focused && focusRing,
651
+ style
652
+ ],
653
+ children: loading ? /* @__PURE__ */ jsx(Spinner, {
654
+ size: iconSize,
655
+ color: content
656
+ }) : /* @__PURE__ */ jsx(Icon, {
657
+ name: icon,
658
+ size: iconSize,
659
+ color: content
660
+ })
647
661
  });
648
662
  }
649
663
  //#endregion
650
- //#region src/text/Text.native.tsx
651
- const textAlign = {
652
- left: "left",
653
- center: "center",
654
- right: "right"
664
+ //#region src/alert/Alert.native.tsx
665
+ const styles$14 = StyleSheet.create({
666
+ root: {
667
+ flexDirection: "row",
668
+ alignItems: "flex-start",
669
+ alignSelf: "stretch"
670
+ },
671
+ message: {
672
+ flex: 1,
673
+ minWidth: 0,
674
+ flexDirection: "row",
675
+ alignItems: "flex-start"
676
+ },
677
+ icon: {
678
+ justifyContent: "center",
679
+ flexShrink: 0
680
+ },
681
+ content: {
682
+ flex: 1,
683
+ minWidth: 0
684
+ },
685
+ close: { flexShrink: 0 }
686
+ });
687
+ function Alert({ tone = "info", title, onClose, closeLabel, style, testID, children }) {
688
+ const t = useTheme$1();
689
+ const colors = t.color.feedback[tone];
690
+ const hasDescription = children !== void 0 && children !== null;
691
+ const titleLine = t.font.lineHeight.md;
692
+ const descriptionLine = t.font.lineHeight.sm;
693
+ const container = {
694
+ gap: t.space[3],
695
+ paddingVertical: t.space[3],
696
+ paddingHorizontal: t.space[4],
697
+ borderWidth: t.borderWidth.thin,
698
+ borderRadius: t.radius.md,
699
+ borderColor: colors.border,
700
+ backgroundColor: colors.bg
701
+ };
702
+ const titleStyle = {
703
+ fontSize: t.font.size.md,
704
+ lineHeight: titleLine,
705
+ fontWeight: t.font.weight.semibold,
706
+ color: colors.text
707
+ };
708
+ const descriptionStyle = {
709
+ fontSize: t.font.size.sm,
710
+ lineHeight: descriptionLine,
711
+ fontWeight: t.font.weight.regular,
712
+ color: colors.text
713
+ };
714
+ const family = t.font.family.sans;
715
+ if (family !== void 0) {
716
+ titleStyle.fontFamily = family;
717
+ descriptionStyle.fontFamily = family;
718
+ }
719
+ const closeOffset = (titleLine - t.size.control.sm) / 2;
720
+ return /* @__PURE__ */ jsxs(View, {
721
+ style: [
722
+ styles$14.root,
723
+ container,
724
+ style
725
+ ],
726
+ testID,
727
+ children: [/* @__PURE__ */ jsxs(View, {
728
+ accessible: true,
729
+ accessibilityRole: "alert",
730
+ accessibilityLiveRegion: feedbackUrgent[tone] ? "assertive" : "polite",
731
+ style: [styles$14.message, { gap: t.space[3] }],
732
+ children: [/* @__PURE__ */ jsx(View, {
733
+ style: [styles$14.icon, { height: titleLine }],
734
+ children: /* @__PURE__ */ jsx(Icon, {
735
+ name: feedbackIcon[tone],
736
+ size: "md",
737
+ color: tone
738
+ })
739
+ }), /* @__PURE__ */ jsxs(View, {
740
+ style: styles$14.content,
741
+ children: [title === void 0 ? null : /* @__PURE__ */ jsx(Text$1, {
742
+ style: titleStyle,
743
+ children: title
744
+ }), hasDescription ? /* @__PURE__ */ jsx(View, {
745
+ style: title === void 0 ? { paddingVertical: (titleLine - descriptionLine) / 2 } : null,
746
+ children: renderLabel(children, descriptionStyle)
747
+ }) : null]
748
+ })]
749
+ }), onClose === void 0 ? null : /* @__PURE__ */ jsx(IconButton, {
750
+ icon: "x",
751
+ label: closeLabel,
752
+ size: "sm",
753
+ variant: "ghost",
754
+ onPress: onClose,
755
+ style: [styles$14.close, {
756
+ marginVertical: closeOffset,
757
+ marginRight: -t.space[2]
758
+ }]
759
+ })]
760
+ });
761
+ }
762
+ //#endregion
763
+ //#region src/badge/Badge.native.tsx
764
+ const variantColors = {
765
+ success: (t) => t.color.feedback.success,
766
+ warning: (t) => t.color.feedback.warning,
767
+ danger: (t) => t.color.feedback.danger,
768
+ info: (t) => t.color.feedback.info
655
769
  };
656
- function Text({ variant = "body", color = "primary", align, truncate = false, numberOfLines, style, testID, children }) {
770
+ const styles$13 = StyleSheet.create({ base: {
771
+ flexDirection: "row",
772
+ alignItems: "center"
773
+ } });
774
+ function Badge({ variant = "info", style, testID, children }) {
657
775
  const t = useTheme$1();
658
- const spec = textVariantStyles[variant];
659
- const computed = {
660
- fontSize: t.font.size[spec.size],
661
- lineHeight: t.font.lineHeight[spec.size],
662
- fontWeight: t.font.weight[spec.weight],
663
- color: t.color.text[color]
776
+ const colors = variantColors[variant](t);
777
+ const container = {
778
+ paddingVertical: t.space[1],
779
+ paddingHorizontal: t.space[2],
780
+ borderRadius: t.radius.full,
781
+ borderWidth: t.borderWidth.thin,
782
+ borderColor: colors.border,
783
+ backgroundColor: colors.bg
664
784
  };
665
- const family = t.font.family[spec.family];
666
- if (family !== void 0) computed.fontFamily = family;
667
- if (align !== void 0) computed.textAlign = textAlign[align];
668
- if (spec.uppercase) computed.textTransform = "uppercase";
669
- if (spec.letterSpacing !== void 0) computed.letterSpacing = spec.letterSpacing;
670
- return /* @__PURE__ */ jsx(Text$1, {
671
- style: [computed, style],
672
- numberOfLines: truncate ? 1 : numberOfLines,
673
- ellipsizeMode: "tail",
674
- accessibilityRole: spec.headingLevel === void 0 ? void 0 : "header",
675
- "aria-level": spec.headingLevel,
785
+ const label = {
786
+ flexShrink: 1,
787
+ fontSize: t.font.size.xs,
788
+ lineHeight: t.font.lineHeight.xs,
789
+ fontWeight: t.font.weight.medium,
790
+ color: colors.text
791
+ };
792
+ const family = t.font.family.sans;
793
+ if (family !== void 0) label.fontFamily = family;
794
+ return /* @__PURE__ */ jsx(View, {
676
795
  testID,
677
- children
796
+ style: [
797
+ styles$13.base,
798
+ container,
799
+ style
800
+ ],
801
+ children: /* @__PURE__ */ jsx(Text$1, {
802
+ style: label,
803
+ children
804
+ })
678
805
  });
679
806
  }
680
807
  //#endregion
681
- //#region src/ui-provider/UIProvider.native.tsx
682
- /**
683
- * Activa tema y marca para todo lo que envuelve (ADR-010). Entrega el tema
684
- * resuelto por contexto; `system` sigue a `Appearance`.
685
- */
686
- function UIProvider({ theme = "system", brand, children }) {
687
- const system = useColorScheme$1() === "dark" ? "dark" : "light";
688
- const value = useMemo(() => createUIContextValue(theme, system, brand), [
689
- theme,
690
- system,
691
- brand
692
- ]);
693
- return /* @__PURE__ */ jsx(UIContext.Provider, {
694
- value,
695
- children
808
+ //#region src/box/Box.native.tsx
809
+ function Box({ padding, paddingX, paddingY, background, radius, borderColor, shadow, flex, style, testID, children }) {
810
+ const t = useTheme$1();
811
+ const computed = {};
812
+ if (padding !== void 0) computed.padding = t.space[padding];
813
+ if (paddingX !== void 0) computed.paddingHorizontal = t.space[paddingX];
814
+ if (paddingY !== void 0) computed.paddingVertical = t.space[paddingY];
815
+ if (background !== void 0) computed.backgroundColor = t.color.bg[background];
816
+ if (radius !== void 0) computed.borderRadius = t.radius[radius];
817
+ if (borderColor !== void 0) {
818
+ computed.borderWidth = t.borderWidth.thin;
819
+ computed.borderColor = t.color.border[borderColor];
820
+ }
821
+ if (shadow !== void 0) computed.boxShadow = t.shadow[shadow];
822
+ if (flex !== void 0) computed.flex = flex;
823
+ return /* @__PURE__ */ jsx(View, {
824
+ style: [computed, style],
825
+ testID,
826
+ children
827
+ });
828
+ }
829
+ //#endregion
830
+ //#region src/card/Card.native.tsx
831
+ const variantStyle = {
832
+ outlined: (t) => ({ backgroundColor: t.color.bg.surface }),
833
+ elevated: (t) => ({
834
+ backgroundColor: t.color.bg.elevated,
835
+ boxShadow: t.shadow.md
836
+ })
837
+ };
838
+ const styles$12 = StyleSheet.create({ root: {
839
+ alignSelf: "stretch",
840
+ overflow: "hidden"
841
+ } });
842
+ function Card({ variant = "outlined", padding = 4, onPress, accessibilityLabel, style, testID, ref, children }) {
843
+ const t = useTheme$1();
844
+ const [focused, setFocused] = useState(false);
845
+ const container = {
846
+ padding: t.space[padding],
847
+ borderWidth: t.borderWidth.thin,
848
+ borderRadius: t.radius.lg,
849
+ borderColor: t.color.border.default,
850
+ ...variantStyle[variant](t)
851
+ };
852
+ if (onPress === void 0) return /* @__PURE__ */ jsx(View, {
853
+ ref,
854
+ style: [
855
+ styles$12.root,
856
+ container,
857
+ style
858
+ ],
859
+ testID,
860
+ children
861
+ });
862
+ const focusRing = {
863
+ outlineStyle: "solid",
864
+ outlineColor: t.color.border.focus,
865
+ outlineWidth: t.borderWidth.thick,
866
+ outlineOffset: t.borderWidth.thick
867
+ };
868
+ return /* @__PURE__ */ jsx(Pressable, {
869
+ ref,
870
+ accessibilityRole: "button",
871
+ accessibilityLabel,
872
+ onPress,
873
+ onFocus: () => setFocused(true),
874
+ onBlur: () => setFocused(false),
875
+ testID,
876
+ style: ({ pressed }) => [
877
+ styles$12.root,
878
+ container,
879
+ pressed && { backgroundColor: t.color.bg.subtle },
880
+ focused && focusRing,
881
+ style
882
+ ],
883
+ children
884
+ });
885
+ }
886
+ //#endregion
887
+ //#region src/checkbox/Checkbox.native.tsx
888
+ const styles$11 = StyleSheet.create({
889
+ root: {
890
+ flexDirection: "row",
891
+ alignItems: "flex-start",
892
+ alignSelf: "flex-start"
893
+ },
894
+ box: {
895
+ alignItems: "center",
896
+ justifyContent: "center",
897
+ flexShrink: 0
898
+ },
899
+ label: { flexShrink: 1 }
900
+ });
901
+ function Checkbox({ checked, defaultChecked = false, indeterminate = false, onCheckedChange, disabled = false, invalid = false, accessibilityLabel, style, testID, ref, children }) {
902
+ const t = useTheme$1();
903
+ const [isChecked, setChecked] = useControllableState({
904
+ value: checked,
905
+ defaultValue: defaultChecked,
906
+ onChange: onCheckedChange
907
+ });
908
+ const [focused, setFocused] = useState(false);
909
+ const filled = isChecked || indeterminate;
910
+ const size = t.space[5];
911
+ const lineHeight = t.font.lineHeight.md;
912
+ const box = {
913
+ width: size,
914
+ height: size,
915
+ marginTop: (lineHeight - size) / 2,
916
+ borderRadius: t.radius.xs,
917
+ borderWidth: t.borderWidth.thin,
918
+ borderColor: disabled ? "transparent" : filled ? t.color.action.primary : invalid ? t.color.feedback.danger.icon : t.color.border.strong,
919
+ backgroundColor: disabled ? t.color.action.disabled : filled ? t.color.action.primary : t.color.bg.surface
920
+ };
921
+ const focusRing = {
922
+ outlineStyle: "solid",
923
+ outlineColor: t.color.border.focus,
924
+ outlineWidth: t.borderWidth.thick,
925
+ outlineOffset: t.borderWidth.thick
926
+ };
927
+ const label = {
928
+ flexShrink: 1,
929
+ fontSize: t.font.size.md,
930
+ lineHeight,
931
+ color: disabled ? t.color.text.disabled : t.color.text.primary
932
+ };
933
+ const family = t.font.family.sans;
934
+ if (family !== void 0) label.fontFamily = family;
935
+ const mark = disabled ? "disabled" : "onPrimary";
936
+ const minHeight = t.space[6];
937
+ const hitSlop = Math.max(0, (t.size.control.md - minHeight) / 2);
938
+ return /* @__PURE__ */ jsxs(Pressable, {
939
+ ref,
940
+ accessibilityRole: "checkbox",
941
+ accessibilityLabel,
942
+ accessibilityState: {
943
+ checked: indeterminate ? "mixed" : isChecked,
944
+ disabled
945
+ },
946
+ disabled,
947
+ hitSlop,
948
+ onPress: () => setChecked(!isChecked),
949
+ onFocus: () => setFocused(true),
950
+ onBlur: () => setFocused(false),
951
+ testID,
952
+ style: [
953
+ styles$11.root,
954
+ {
955
+ gap: t.space[2],
956
+ minHeight
957
+ },
958
+ style
959
+ ],
960
+ children: [/* @__PURE__ */ jsx(View, {
961
+ style: [
962
+ styles$11.box,
963
+ box,
964
+ focused && focusRing
965
+ ],
966
+ children: indeterminate ? /* @__PURE__ */ jsx(Icon, {
967
+ name: "minus",
968
+ size: "sm",
969
+ color: mark
970
+ }) : isChecked ? /* @__PURE__ */ jsx(Icon, {
971
+ name: "check",
972
+ size: "sm",
973
+ color: mark
974
+ }) : null
975
+ }), children === void 0 || children === null ? null : renderLabel(children, label)]
976
+ });
977
+ }
978
+ //#endregion
979
+ //#region src/divider/Divider.native.tsx
980
+ const orientationStyle = {
981
+ horizontal: (t) => ({
982
+ alignSelf: "stretch",
983
+ height: t.borderWidth.thin
984
+ }),
985
+ vertical: (t) => ({
986
+ alignSelf: "stretch",
987
+ width: t.borderWidth.thin
988
+ })
989
+ };
990
+ const styles$10 = StyleSheet.create({ root: { flexShrink: 0 } });
991
+ /** En nativo es decorativo: los lectores de pantalla no anuncian separadores. */
992
+ function Divider({ orientation = "horizontal", style, testID }) {
993
+ const t = useTheme$1();
994
+ return /* @__PURE__ */ jsx(View, {
995
+ accessibilityElementsHidden: true,
996
+ importantForAccessibility: "no",
997
+ style: [
998
+ styles$10.root,
999
+ orientationStyle[orientation](t),
1000
+ { backgroundColor: t.color.border.default },
1001
+ style
1002
+ ],
1003
+ testID
1004
+ });
1005
+ }
1006
+ //#endregion
1007
+ //#region src/form-field/FormField.native.tsx
1008
+ const styles$9 = StyleSheet.create({
1009
+ root: { alignSelf: "stretch" },
1010
+ label: { alignSelf: "flex-start" },
1011
+ error: {
1012
+ flexDirection: "row",
1013
+ alignItems: "flex-start"
1014
+ },
1015
+ errorIcon: { justifyContent: "center" },
1016
+ errorText: { flex: 1 }
1017
+ });
1018
+ const hiddenFromScreenReaders$1 = {
1019
+ accessibilityElementsHidden: true,
1020
+ importantForAccessibility: "no"
1021
+ };
1022
+ /**
1023
+ * Etiqueta, ayuda y error de un control (ADR-037). En nativo no hay `htmlFor`: el
1024
+ * control toma la etiqueta como nombre accesible y la ayuda y el error como pista.
1025
+ */
1026
+ function FormField({ label, help, error, required = false, disabled = false, style, testID, children }) {
1027
+ const t = useTheme$1();
1028
+ const baseId = useId();
1029
+ const field = useMemo(() => createFormFieldValue(baseId, {
1030
+ label,
1031
+ help,
1032
+ error,
1033
+ disabled,
1034
+ required
1035
+ }), [
1036
+ baseId,
1037
+ label,
1038
+ help,
1039
+ error,
1040
+ disabled,
1041
+ required
1042
+ ]);
1043
+ const small = {
1044
+ fontSize: t.font.size.sm,
1045
+ lineHeight: t.font.lineHeight.sm
1046
+ };
1047
+ const family = t.font.family.sans;
1048
+ if (family !== void 0) small.fontFamily = family;
1049
+ return /* @__PURE__ */ jsx(FormFieldContext.Provider, {
1050
+ value: field,
1051
+ children: /* @__PURE__ */ jsxs(View, {
1052
+ style: [
1053
+ styles$9.root,
1054
+ { gap: t.space[1] },
1055
+ style
1056
+ ],
1057
+ testID,
1058
+ children: [
1059
+ /* @__PURE__ */ jsxs(Text$1, {
1060
+ style: [
1061
+ styles$9.label,
1062
+ small,
1063
+ {
1064
+ fontWeight: t.font.weight.medium,
1065
+ color: disabled ? t.color.text.disabled : t.color.text.primary
1066
+ }
1067
+ ],
1068
+ ...hiddenFromScreenReaders$1,
1069
+ children: [label, required ? /* @__PURE__ */ jsx(Text$1, {
1070
+ style: { color: t.color.feedback.danger.text },
1071
+ children: " *"
1072
+ }) : null]
1073
+ }),
1074
+ children,
1075
+ field.help === void 0 ? null : /* @__PURE__ */ jsx(Text$1, {
1076
+ style: [small, { color: t.color.text.muted }],
1077
+ ...hiddenFromScreenReaders$1,
1078
+ children: field.help
1079
+ }),
1080
+ field.error === void 0 ? null : /* @__PURE__ */ jsxs(View, {
1081
+ style: [styles$9.error, { gap: t.space[1] }],
1082
+ accessibilityLiveRegion: "polite",
1083
+ children: [/* @__PURE__ */ jsx(View, {
1084
+ style: [styles$9.errorIcon, { height: t.font.lineHeight.sm }],
1085
+ children: /* @__PURE__ */ jsx(Icon, {
1086
+ name: "circle-alert",
1087
+ size: "sm",
1088
+ color: "danger"
1089
+ })
1090
+ }), /* @__PURE__ */ jsx(Text$1, {
1091
+ style: [
1092
+ styles$9.errorText,
1093
+ small,
1094
+ { color: t.color.feedback.danger.text }
1095
+ ],
1096
+ children: field.error
1097
+ })]
1098
+ })
1099
+ ]
1100
+ })
1101
+ });
1102
+ }
1103
+ //#endregion
1104
+ //#region src/_internal/field.native.ts
1105
+ /**
1106
+ * La caja de los controles de texto (`Input`, `TextArea`, `Select`): borde, fondo y
1107
+ * estados. Es el equivalente nativo de `field.module.css`.
1108
+ *
1109
+ * El borde en reposo es `border.strong`: el contorno de un campo tiene que
1110
+ * distinguirse del fondo con un contraste de 3:1.
1111
+ */
1112
+ function fieldContainerStyle(t, { focused, invalid, disabled, readOnly }) {
1113
+ const accent = invalid ? t.color.feedback.danger.icon : t.color.border.focus;
1114
+ const style = {
1115
+ borderWidth: t.borderWidth.thin,
1116
+ borderRadius: t.radius.md,
1117
+ borderColor: disabled ? "transparent" : invalid || focused ? accent : t.color.border.strong,
1118
+ backgroundColor: disabled ? t.color.action.disabled : readOnly ? t.color.bg.subtle : t.color.bg.surface
1119
+ };
1120
+ if (focused && !disabled) {
1121
+ style.outlineStyle = "solid";
1122
+ style.outlineColor = accent;
1123
+ style.outlineWidth = t.borderWidth.thin;
1124
+ }
1125
+ return style;
1126
+ }
1127
+ /** Tipografía del texto que se escribe o se muestra dentro de la caja. */
1128
+ function fieldTextStyle(t, disabled) {
1129
+ const style = {
1130
+ fontSize: t.font.size.md,
1131
+ fontWeight: t.font.weight.regular,
1132
+ color: disabled ? t.color.text.disabled : t.color.text.primary
1133
+ };
1134
+ const family = t.font.family.sans;
1135
+ if (family !== void 0) style.fontFamily = family;
1136
+ return style;
1137
+ }
1138
+ //#endregion
1139
+ //#region src/input/Input.native.tsx
1140
+ const typeProps = {
1141
+ text: {},
1142
+ email: {
1143
+ keyboardType: "email-address",
1144
+ autoCapitalize: "none",
1145
+ autoCorrect: false,
1146
+ autoComplete: "email",
1147
+ textContentType: "emailAddress"
1148
+ },
1149
+ password: {
1150
+ secureTextEntry: true,
1151
+ autoCapitalize: "none",
1152
+ autoCorrect: false,
1153
+ autoComplete: "current-password",
1154
+ textContentType: "password"
1155
+ },
1156
+ search: {
1157
+ returnKeyType: "search",
1158
+ autoCapitalize: "none"
1159
+ },
1160
+ tel: {
1161
+ keyboardType: "phone-pad",
1162
+ autoComplete: "tel",
1163
+ textContentType: "telephoneNumber"
1164
+ },
1165
+ url: {
1166
+ keyboardType: "url",
1167
+ autoCapitalize: "none",
1168
+ autoCorrect: false,
1169
+ autoComplete: "url",
1170
+ textContentType: "URL"
1171
+ },
1172
+ number: { keyboardType: "decimal-pad" }
1173
+ };
1174
+ const paddingHorizontal$1 = {
1175
+ sm: (t) => t.space[3],
1176
+ md: (t) => t.space[3],
1177
+ lg: (t) => t.space[4]
1178
+ };
1179
+ const styles$8 = StyleSheet.create({
1180
+ base: {
1181
+ flexDirection: "row",
1182
+ alignItems: "center",
1183
+ alignSelf: "stretch"
1184
+ },
1185
+ input: {
1186
+ flex: 1,
1187
+ minWidth: 0,
1188
+ alignSelf: "stretch",
1189
+ paddingVertical: 0,
1190
+ paddingHorizontal: 0
1191
+ }
1192
+ });
1193
+ function Input({ value, defaultValue, onChangeText, placeholder, type = "text", size = "md", disabled, readOnly = false, invalid, required, iconStart, iconEnd, maxLength, onFocus, onBlur, onSubmit, accessibilityLabel, style, testID, ref }) {
1194
+ const t = useTheme$1();
1195
+ const control = useFormFieldControl({
1196
+ invalid,
1197
+ disabled,
1198
+ required,
1199
+ accessibilityLabel
1200
+ });
1201
+ const [focused, setFocused] = useState(false);
1202
+ const iconSize = inputIconSize[size];
1203
+ const iconColor = control.disabled ? "disabled" : "muted";
1204
+ const translated = typeProps[type];
1205
+ return /* @__PURE__ */ jsxs(View, {
1206
+ style: [
1207
+ styles$8.base,
1208
+ fieldContainerStyle(t, {
1209
+ focused,
1210
+ invalid: control.invalid,
1211
+ disabled: control.disabled,
1212
+ readOnly
1213
+ }),
1214
+ {
1215
+ minHeight: t.size.control[size],
1216
+ paddingHorizontal: paddingHorizontal$1[size](t),
1217
+ gap: t.space[2]
1218
+ },
1219
+ style
1220
+ ],
1221
+ children: [
1222
+ iconStart === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
1223
+ name: iconStart,
1224
+ size: iconSize,
1225
+ color: iconColor
1226
+ }),
1227
+ /* @__PURE__ */ jsx(TextInput, {
1228
+ ref,
1229
+ nativeID: control.id,
1230
+ style: [styles$8.input, fieldTextStyle(t, control.disabled)],
1231
+ ...translated,
1232
+ ...value === void 0 ? defaultValue === void 0 ? {} : { defaultValue } : { value },
1233
+ onChangeText,
1234
+ placeholder,
1235
+ placeholderTextColor: t.color.text.muted,
1236
+ selectionColor: t.color.border.focus,
1237
+ editable: !control.disabled && !readOnly,
1238
+ maxLength,
1239
+ accessibilityLabel: control.accessibilityLabel,
1240
+ accessibilityHint: control.accessibilityHint,
1241
+ accessibilityState: { disabled: control.disabled },
1242
+ "aria-required": control.required ? true : void 0,
1243
+ testID,
1244
+ onFocus: () => {
1245
+ setFocused(true);
1246
+ onFocus?.();
1247
+ },
1248
+ onBlur: () => {
1249
+ setFocused(false);
1250
+ onBlur?.();
1251
+ },
1252
+ onSubmitEditing: onSubmit
1253
+ }),
1254
+ iconEnd === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
1255
+ name: iconEnd,
1256
+ size: iconSize,
1257
+ color: iconColor
1258
+ })
1259
+ ]
1260
+ });
1261
+ }
1262
+ //#endregion
1263
+ //#region src/text/TextAncestor.native.ts
1264
+ /**
1265
+ * `true` dentro de un `Text` de la librería. React Native propaga la tipografía de un
1266
+ * `Text` a los `Text` que contiene; `Link` lo consulta para heredarla en vez de fijar
1267
+ * la suya. En web no hace falta: CSS ya hereda.
1268
+ */
1269
+ const TextAncestorContext = createContext(false);
1270
+ //#endregion
1271
+ //#region src/link/Link.native.tsx
1272
+ const underlinedAtRest = {
1273
+ always: true,
1274
+ hover: false
1275
+ };
1276
+ function Link({ href, variant, color = "link", underline = "always", onPress, accessibilityLabel, style, testID, ref, children }) {
1277
+ const t = useTheme$1();
1278
+ const insideText = useContext(TextAncestorContext);
1279
+ const { press } = useLink({ onPress });
1280
+ const [pressed, setPressed] = useState(false);
1281
+ const spec = variant === void 0 ? insideText ? void 0 : textVariantStyles.body : textVariantStyles[variant];
1282
+ const computed = {
1283
+ color: t.color.text[pressed ? "primary" : color],
1284
+ textDecorationLine: underlinedAtRest[underline] || pressed ? "underline" : "none"
1285
+ };
1286
+ if (spec !== void 0) {
1287
+ computed.fontSize = t.font.size[spec.size];
1288
+ computed.lineHeight = t.font.lineHeight[spec.size];
1289
+ computed.fontWeight = t.font.weight[spec.weight];
1290
+ const family = t.font.family[spec.family];
1291
+ if (family !== void 0) computed.fontFamily = family;
1292
+ if (spec.uppercase) computed.textTransform = "uppercase";
1293
+ if (spec.letterSpacing !== void 0) computed.letterSpacing = spec.letterSpacing;
1294
+ }
1295
+ function handlePress() {
1296
+ if (!press()) return;
1297
+ Linking.openURL(href).catch((error) => {
1298
+ if (__DEV__) console.warn(`Link: no se pudo abrir "${href}".`, error);
1299
+ });
1300
+ }
1301
+ return /* @__PURE__ */ jsx(Text$1, {
1302
+ ref,
1303
+ accessibilityRole: "link",
1304
+ accessibilityLabel,
1305
+ onPress: handlePress,
1306
+ onPressIn: () => setPressed(true),
1307
+ onPressOut: () => setPressed(false),
1308
+ suppressHighlighting: true,
1309
+ style: [computed, style],
1310
+ testID,
1311
+ children: /* @__PURE__ */ jsx(TextAncestorContext.Provider, {
1312
+ value: true,
1313
+ children
1314
+ })
1315
+ });
1316
+ }
1317
+ //#endregion
1318
+ //#region src/modal/ModalDialog.native.tsx
1319
+ const presentationStyle = {
1320
+ dialog: (t) => ({
1321
+ animation: "fade",
1322
+ overlay: {
1323
+ justifyContent: "center",
1324
+ padding: t.space[4]
1325
+ },
1326
+ surface: {
1327
+ maxWidth: t.space[24] * 5,
1328
+ maxHeight: "90%",
1329
+ borderRadius: t.radius.xl,
1330
+ borderWidth: t.borderWidth.thin
1331
+ }
1332
+ }),
1333
+ sheet: (t) => ({
1334
+ animation: "slide",
1335
+ overlay: { justifyContent: "flex-end" },
1336
+ surface: {
1337
+ maxHeight: "90%",
1338
+ borderTopLeftRadius: t.radius.xl,
1339
+ borderTopRightRadius: t.radius.xl,
1340
+ borderTopWidth: t.borderWidth.thin,
1341
+ paddingBottom: t.space[8]
1342
+ }
1343
+ })
1344
+ };
1345
+ const styles$7 = StyleSheet.create({
1346
+ overlay: { flex: 1 },
1347
+ backdrop: {
1348
+ position: "absolute",
1349
+ top: 0,
1350
+ right: 0,
1351
+ bottom: 0,
1352
+ left: 0
1353
+ },
1354
+ surface: {
1355
+ alignSelf: "center",
1356
+ width: "100%",
1357
+ overflow: "hidden"
1358
+ },
1359
+ header: {
1360
+ flexDirection: "row",
1361
+ alignItems: "flex-start"
1362
+ },
1363
+ titles: {
1364
+ flex: 1,
1365
+ minWidth: 0
1366
+ },
1367
+ close: { flexShrink: 0 },
1368
+ body: {
1369
+ flexGrow: 0,
1370
+ flexShrink: 1
1371
+ },
1372
+ footer: {
1373
+ flexDirection: "row",
1374
+ flexWrap: "wrap",
1375
+ justifyContent: "flex-end"
1376
+ }
1377
+ });
1378
+ /**
1379
+ * La implementación que comparten `Modal` y `Sheet` (ADR-040): el `Modal` de React
1380
+ * Native, que pinta una ventana por encima de la app y avisa del botón atrás de Android.
1381
+ */
1382
+ function ModalDialog({ presentation, open, onClose, title, description, closeLabel, dismissible = true, footer, style, testID, children }) {
1383
+ const t = useTheme$1();
1384
+ const shape = presentationStyle[presentation](t);
1385
+ const padding = t.space[5];
1386
+ const titleLine = t.font.lineHeight.lg;
1387
+ const titleStyle = {
1388
+ fontSize: t.font.size.lg,
1389
+ lineHeight: titleLine,
1390
+ fontWeight: t.font.weight.semibold,
1391
+ color: t.color.text.primary
1392
+ };
1393
+ const descriptionStyle = {
1394
+ marginTop: t.space[1],
1395
+ fontSize: t.font.size.sm,
1396
+ lineHeight: t.font.lineHeight.sm,
1397
+ color: t.color.text.secondary
1398
+ };
1399
+ const bodyText = {
1400
+ fontSize: t.font.size.md,
1401
+ lineHeight: t.font.lineHeight.md,
1402
+ color: t.color.text.primary
1403
+ };
1404
+ const family = t.font.family.sans;
1405
+ if (family !== void 0) {
1406
+ titleStyle.fontFamily = family;
1407
+ descriptionStyle.fontFamily = family;
1408
+ bodyText.fontFamily = family;
1409
+ }
1410
+ const hasBody = children !== void 0 && children !== null;
1411
+ const hasFooter = footer !== void 0 && footer !== null;
1412
+ const closeOffset = (titleLine - t.size.control.sm) / 2;
1413
+ return /* @__PURE__ */ jsx(Modal$1, {
1414
+ visible: open,
1415
+ transparent: true,
1416
+ animationType: shape.animation,
1417
+ statusBarTranslucent: true,
1418
+ onRequestClose: () => {
1419
+ if (dismissible) onClose();
1420
+ },
1421
+ testID: testID === void 0 ? void 0 : `${testID}-modal`,
1422
+ children: /* @__PURE__ */ jsxs(View, {
1423
+ style: [
1424
+ styles$7.overlay,
1425
+ shape.overlay,
1426
+ { backgroundColor: t.color.bg.overlay }
1427
+ ],
1428
+ children: [/* @__PURE__ */ jsx(Pressable, {
1429
+ style: styles$7.backdrop,
1430
+ onPress: dismissible ? onClose : void 0,
1431
+ accessible: false,
1432
+ importantForAccessibility: "no",
1433
+ testID: testID === void 0 ? void 0 : `${testID}-backdrop`
1434
+ }), /* @__PURE__ */ jsxs(View, {
1435
+ accessibilityViewIsModal: true,
1436
+ style: [
1437
+ styles$7.surface,
1438
+ {
1439
+ padding,
1440
+ gap: t.space[4],
1441
+ borderColor: t.color.border.default,
1442
+ backgroundColor: t.color.bg.elevated,
1443
+ boxShadow: t.shadow.lg
1444
+ },
1445
+ shape.surface,
1446
+ style
1447
+ ],
1448
+ testID,
1449
+ children: [
1450
+ /* @__PURE__ */ jsxs(View, {
1451
+ style: [styles$7.header, { gap: t.space[3] }],
1452
+ children: [/* @__PURE__ */ jsxs(View, {
1453
+ style: styles$7.titles,
1454
+ children: [/* @__PURE__ */ jsx(Text$1, {
1455
+ accessibilityRole: "header",
1456
+ style: titleStyle,
1457
+ children: title
1458
+ }), description === void 0 ? null : /* @__PURE__ */ jsx(Text$1, {
1459
+ style: descriptionStyle,
1460
+ children: description
1461
+ })]
1462
+ }), closeLabel === void 0 ? null : /* @__PURE__ */ jsx(IconButton, {
1463
+ icon: "x",
1464
+ label: closeLabel,
1465
+ size: "sm",
1466
+ variant: "ghost",
1467
+ onPress: onClose,
1468
+ style: [styles$7.close, {
1469
+ marginVertical: closeOffset,
1470
+ marginRight: -t.space[2]
1471
+ }]
1472
+ })]
1473
+ }),
1474
+ hasBody ? /* @__PURE__ */ jsx(ScrollView, {
1475
+ style: styles$7.body,
1476
+ children: renderLabel(children, bodyText)
1477
+ }) : null,
1478
+ hasFooter ? /* @__PURE__ */ jsx(View, {
1479
+ style: [styles$7.footer, { gap: t.space[3] }],
1480
+ children: footer
1481
+ }) : null
1482
+ ]
1483
+ })]
1484
+ })
1485
+ });
1486
+ }
1487
+ //#endregion
1488
+ //#region src/modal/Modal.native.tsx
1489
+ /** Un diálogo centrado (ADR-040). */
1490
+ function Modal(props) {
1491
+ return /* @__PURE__ */ jsx(ModalDialog, {
1492
+ ...props,
1493
+ presentation: "dialog"
1494
+ });
1495
+ }
1496
+ //#endregion
1497
+ //#region src/select/Select.native.tsx
1498
+ const paddingHorizontal = {
1499
+ sm: (t) => t.space[3],
1500
+ md: (t) => t.space[3],
1501
+ lg: (t) => t.space[4]
1502
+ };
1503
+ const styles$6 = StyleSheet.create({
1504
+ trigger: {
1505
+ flexDirection: "row",
1506
+ alignItems: "center",
1507
+ alignSelf: "stretch"
1508
+ },
1509
+ value: {
1510
+ flex: 1,
1511
+ minWidth: 0
1512
+ },
1513
+ overlay: {
1514
+ flex: 1,
1515
+ justifyContent: "center"
1516
+ },
1517
+ backdrop: {
1518
+ position: "absolute",
1519
+ top: 0,
1520
+ right: 0,
1521
+ bottom: 0,
1522
+ left: 0
1523
+ },
1524
+ list: {
1525
+ alignSelf: "stretch",
1526
+ maxHeight: "70%",
1527
+ overflow: "hidden"
1528
+ },
1529
+ option: {
1530
+ flexDirection: "row",
1531
+ alignItems: "center"
1532
+ },
1533
+ optionLabel: {
1534
+ flex: 1,
1535
+ minWidth: 0
1536
+ }
1537
+ });
1538
+ /**
1539
+ * Un disparador con aspecto de `Input` que abre una lista modal (ADR-038). React
1540
+ * Native no trae ningún selector, y así `Select` no añade dependencias.
1541
+ */
1542
+ function Select({ options, value, defaultValue, onValueChange, placeholder, size = "md", disabled, invalid, required, accessibilityLabel, style, testID, ref }) {
1543
+ const t = useTheme$1();
1544
+ const control = useFormFieldControl({
1545
+ invalid,
1546
+ disabled,
1547
+ required,
1548
+ accessibilityLabel
1549
+ });
1550
+ const [current, setCurrent] = useControllableState({
1551
+ value,
1552
+ defaultValue: defaultValue ?? "",
1553
+ onChange: onValueChange
1554
+ });
1555
+ const [open, setOpen] = useState(false);
1556
+ const [focused, setFocused] = useState(false);
1557
+ const selected = options.find((option) => option.value === current);
1558
+ const iconSize = inputIconSize[size];
1559
+ const text = fieldTextStyle(t, control.disabled);
1560
+ const list = {
1561
+ borderRadius: t.radius.lg,
1562
+ borderWidth: t.borderWidth.thin,
1563
+ borderColor: t.color.border.default,
1564
+ backgroundColor: t.color.bg.elevated,
1565
+ paddingVertical: t.space[2]
1566
+ };
1567
+ const title = {
1568
+ paddingHorizontal: t.space[4],
1569
+ paddingVertical: t.space[2],
1570
+ fontSize: t.font.size.sm,
1571
+ lineHeight: t.font.lineHeight.sm,
1572
+ fontWeight: t.font.weight.medium,
1573
+ color: t.color.text.muted
1574
+ };
1575
+ const family = t.font.family.sans;
1576
+ if (family !== void 0) title.fontFamily = family;
1577
+ function choose(next) {
1578
+ setCurrent(next);
1579
+ setOpen(false);
1580
+ }
1581
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Pressable, {
1582
+ ref,
1583
+ accessibilityRole: "combobox",
1584
+ accessibilityLabel: control.accessibilityLabel,
1585
+ accessibilityHint: control.accessibilityHint,
1586
+ accessibilityValue: { text: selected?.label ?? placeholder ?? "" },
1587
+ accessibilityState: {
1588
+ disabled: control.disabled,
1589
+ expanded: open
1590
+ },
1591
+ "aria-required": control.required ? true : void 0,
1592
+ disabled: control.disabled,
1593
+ onPress: () => setOpen(true),
1594
+ onFocus: () => setFocused(true),
1595
+ onBlur: () => setFocused(false),
1596
+ testID,
1597
+ style: [
1598
+ styles$6.trigger,
1599
+ fieldContainerStyle(t, {
1600
+ focused: focused || open,
1601
+ invalid: control.invalid,
1602
+ disabled: control.disabled,
1603
+ readOnly: false
1604
+ }),
1605
+ {
1606
+ minHeight: t.size.control[size],
1607
+ paddingHorizontal: paddingHorizontal[size](t),
1608
+ gap: t.space[2]
1609
+ },
1610
+ style
1611
+ ],
1612
+ children: [/* @__PURE__ */ jsx(Text$1, {
1613
+ numberOfLines: 1,
1614
+ style: [
1615
+ styles$6.value,
1616
+ text,
1617
+ selected === void 0 && !control.disabled && { color: t.color.text.muted }
1618
+ ],
1619
+ children: selected?.label ?? placeholder ?? ""
1620
+ }), /* @__PURE__ */ jsx(Icon, {
1621
+ name: "chevron-down",
1622
+ size: iconSize,
1623
+ color: control.disabled ? "disabled" : "muted"
1624
+ })]
1625
+ }), /* @__PURE__ */ jsx(Modal$1, {
1626
+ visible: open,
1627
+ transparent: true,
1628
+ animationType: "fade",
1629
+ statusBarTranslucent: true,
1630
+ onRequestClose: () => setOpen(false),
1631
+ testID: testID === void 0 ? void 0 : `${testID}-modal`,
1632
+ children: /* @__PURE__ */ jsxs(View, {
1633
+ style: [styles$6.overlay, {
1634
+ padding: t.space[6],
1635
+ backgroundColor: t.color.bg.overlay
1636
+ }],
1637
+ children: [/* @__PURE__ */ jsx(Pressable, {
1638
+ style: styles$6.backdrop,
1639
+ onPress: () => setOpen(false),
1640
+ accessible: false,
1641
+ importantForAccessibility: "no",
1642
+ testID: testID === void 0 ? void 0 : `${testID}-backdrop`
1643
+ }), /* @__PURE__ */ jsxs(View, {
1644
+ style: [styles$6.list, list],
1645
+ children: [control.accessibilityLabel === void 0 ? null : /* @__PURE__ */ jsx(Text$1, {
1646
+ style: title,
1647
+ accessibilityRole: "header",
1648
+ children: control.accessibilityLabel
1649
+ }), /* @__PURE__ */ jsx(ScrollView, {
1650
+ accessibilityRole: "radiogroup",
1651
+ children: options.map((option) => {
1652
+ const isSelected = option.value === current;
1653
+ const isDisabled = option.disabled === true;
1654
+ return /* @__PURE__ */ jsxs(Pressable, {
1655
+ accessibilityRole: "radio",
1656
+ accessibilityState: {
1657
+ checked: isSelected,
1658
+ disabled: isDisabled
1659
+ },
1660
+ disabled: isDisabled,
1661
+ onPress: () => choose(option.value),
1662
+ style: ({ pressed }) => [styles$6.option, {
1663
+ minHeight: t.size.control.md,
1664
+ paddingHorizontal: t.space[4],
1665
+ gap: t.space[3],
1666
+ backgroundColor: pressed ? t.color.bg.muted : "transparent"
1667
+ }],
1668
+ children: [/* @__PURE__ */ jsx(Text$1, {
1669
+ style: [styles$6.optionLabel, fieldTextStyle(t, isDisabled)],
1670
+ children: option.label
1671
+ }), isSelected ? /* @__PURE__ */ jsx(Icon, {
1672
+ name: "check",
1673
+ size: "md",
1674
+ color: "link"
1675
+ }) : null]
1676
+ }, option.value);
1677
+ })
1678
+ })]
1679
+ })]
1680
+ })
1681
+ })] });
1682
+ }
1683
+ //#endregion
1684
+ //#region src/sheet/Sheet.native.tsx
1685
+ /** Un diálogo anclado al borde inferior (ADR-040). Comparte todo con `Modal` salvo dónde se coloca. */
1686
+ function Sheet(props) {
1687
+ return /* @__PURE__ */ jsx(ModalDialog, {
1688
+ ...props,
1689
+ presentation: "sheet"
1690
+ });
1691
+ }
1692
+ //#endregion
1693
+ //#region src/skeleton/Skeleton.native.tsx
1694
+ const shapeRadius = {
1695
+ text: (t) => t.radius.xs,
1696
+ rectangle: (t) => t.radius.md,
1697
+ circle: (t) => t.radius.full
1698
+ };
1699
+ const styles$5 = StyleSheet.create({
1700
+ bar: { overflow: "hidden" },
1701
+ fill: {
1702
+ position: "absolute",
1703
+ top: 0,
1704
+ right: 0,
1705
+ bottom: 0,
1706
+ left: 0
1707
+ }
1708
+ });
1709
+ const hiddenFromScreenReaders = {
1710
+ accessibilityElementsHidden: true,
1711
+ importantForAccessibility: "no-hide-descendants"
1712
+ };
1713
+ /**
1714
+ * El hueco de un contenido que se está cargando. Late entre dos fondos del tema: una
1715
+ * capa con `bg.muted` aparece y desaparece sobre `bg.subtle`, con el driver nativo. Con
1716
+ * movimiento reducido se queda quieto.
1717
+ */
1718
+ function Skeleton({ shape = "text", width, height, variant = "body", lines = 1, style, testID }) {
1719
+ const t = useTheme$1();
1720
+ const reducedMotion = useReducedMotion();
1721
+ const [pulse] = useState(() => new Animated.Value(1));
1722
+ const duration = t.duration.slow * 4;
1723
+ useEffect(() => {
1724
+ if (reducedMotion) {
1725
+ pulse.setValue(1);
1726
+ return;
1727
+ }
1728
+ const loop = Animated.loop(Animated.sequence([Animated.timing(pulse, {
1729
+ toValue: 0,
1730
+ duration,
1731
+ easing: Easing.inOut(Easing.ease),
1732
+ useNativeDriver: true
1733
+ }), Animated.timing(pulse, {
1734
+ toValue: 1,
1735
+ duration,
1736
+ easing: Easing.inOut(Easing.ease),
1737
+ useNativeDriver: true
1738
+ })]));
1739
+ loop.start();
1740
+ return () => loop.stop();
1741
+ }, [
1742
+ pulse,
1743
+ duration,
1744
+ reducedMotion
1745
+ ]);
1746
+ const radius = shapeRadius[shape](t);
1747
+ function bar(dimensions, key) {
1748
+ return /* @__PURE__ */ jsx(View, {
1749
+ style: [
1750
+ styles$5.bar,
1751
+ {
1752
+ borderRadius: radius,
1753
+ backgroundColor: t.color.bg.subtle
1754
+ },
1755
+ dimensions
1756
+ ],
1757
+ children: /* @__PURE__ */ jsx(Animated.View, { style: [styles$5.fill, {
1758
+ backgroundColor: t.color.bg.muted,
1759
+ opacity: pulse
1760
+ }] })
1761
+ }, key);
1762
+ }
1763
+ if (shape === "text") {
1764
+ const spec = textVariantStyles[variant];
1765
+ const fontSize = t.font.size[spec.size];
1766
+ const marginVertical = (t.font.lineHeight[spec.size] - fontSize) / 2;
1767
+ const count = Math.max(1, lines);
1768
+ return /* @__PURE__ */ jsx(View, {
1769
+ style,
1770
+ testID,
1771
+ ...hiddenFromScreenReaders,
1772
+ children: Array.from({ length: count }, (_, index) => bar({
1773
+ height: fontSize,
1774
+ marginVertical,
1775
+ width: count > 1 && index === count - 1 ? "60%" : width ?? "100%"
1776
+ }, index))
1777
+ });
1778
+ }
1779
+ const fallback = t.size.control.md;
1780
+ const dimensions = shape === "circle" ? {
1781
+ width: width ?? height ?? fallback,
1782
+ height: height ?? fallback
1783
+ } : {
1784
+ width: width ?? "100%",
1785
+ height: height ?? fallback
1786
+ };
1787
+ if (shape === "circle" && height === void 0 && typeof width === "number") dimensions.height = width;
1788
+ return /* @__PURE__ */ jsx(View, {
1789
+ style,
1790
+ testID,
1791
+ ...hiddenFromScreenReaders,
1792
+ children: bar(dimensions)
1793
+ });
1794
+ }
1795
+ //#endregion
1796
+ //#region src/stack/Stack.native.tsx
1797
+ const alignItems = {
1798
+ stretch: "stretch",
1799
+ start: "flex-start",
1800
+ center: "center",
1801
+ end: "flex-end"
1802
+ };
1803
+ const justifyContent = {
1804
+ start: "flex-start",
1805
+ center: "center",
1806
+ end: "flex-end",
1807
+ between: "space-between",
1808
+ around: "space-around"
1809
+ };
1810
+ function Stack({ direction = "column", gap, align = "stretch", justify = "start", wrap = false, style, ...boxProps }) {
1811
+ const t = useTheme$1();
1812
+ const layout = {
1813
+ flexDirection: direction,
1814
+ alignItems: alignItems[align],
1815
+ justifyContent: justifyContent[justify],
1816
+ flexWrap: wrap ? "wrap" : "nowrap"
1817
+ };
1818
+ if (gap !== void 0) layout.gap = t.space[gap];
1819
+ return /* @__PURE__ */ jsx(Box, {
1820
+ ...boxProps,
1821
+ style: [layout, style]
1822
+ });
1823
+ }
1824
+ //#endregion
1825
+ //#region src/switch/Switch.native.tsx
1826
+ const styles$4 = StyleSheet.create({
1827
+ root: {
1828
+ flexDirection: "row",
1829
+ alignItems: "flex-start",
1830
+ alignSelf: "flex-start"
1831
+ },
1832
+ track: {
1833
+ justifyContent: "center",
1834
+ flexShrink: 0
1835
+ },
1836
+ label: { flexShrink: 1 }
1837
+ });
1838
+ function Switch({ checked, defaultChecked = false, onCheckedChange, disabled = false, accessibilityLabel, style, testID, ref, children }) {
1839
+ const t = useTheme$1();
1840
+ const [isChecked, setChecked] = useControllableState({
1841
+ value: checked,
1842
+ defaultValue: defaultChecked,
1843
+ onChange: onCheckedChange
1844
+ });
1845
+ const [focused, setFocused] = useState(false);
1846
+ const [progress] = useState(() => new Animated.Value(isChecked ? 1 : 0));
1847
+ const width = t.space[10];
1848
+ const height = t.space[6];
1849
+ const gap = t.borderWidth.thick;
1850
+ const thumb = height - gap * 2;
1851
+ const duration = useReducedMotion() ? 0 : t.duration.fast;
1852
+ useEffect(() => {
1853
+ const animation = Animated.timing(progress, {
1854
+ toValue: isChecked ? 1 : 0,
1855
+ duration,
1856
+ useNativeDriver: true
1857
+ });
1858
+ animation.start();
1859
+ return () => animation.stop();
1860
+ }, [
1861
+ progress,
1862
+ isChecked,
1863
+ duration
1864
+ ]);
1865
+ const translateX = progress.interpolate({
1866
+ inputRange: [0, 1],
1867
+ outputRange: [0, width - height]
1868
+ });
1869
+ const track = {
1870
+ width,
1871
+ height,
1872
+ padding: gap,
1873
+ borderRadius: t.radius.full,
1874
+ backgroundColor: disabled ? t.color.action.disabled : isChecked ? t.color.action.primary : t.color.border.strong
1875
+ };
1876
+ const focusRing = {
1877
+ outlineStyle: "solid",
1878
+ outlineColor: t.color.border.focus,
1879
+ outlineWidth: t.borderWidth.thick,
1880
+ outlineOffset: t.borderWidth.thick
1881
+ };
1882
+ const label = {
1883
+ flexShrink: 1,
1884
+ fontSize: t.font.size.md,
1885
+ lineHeight: t.font.lineHeight.md,
1886
+ color: disabled ? t.color.text.disabled : t.color.text.primary
1887
+ };
1888
+ const family = t.font.family.sans;
1889
+ if (family !== void 0) label.fontFamily = family;
1890
+ const hitSlop = Math.max(0, (t.size.control.md - height) / 2);
1891
+ return /* @__PURE__ */ jsxs(Pressable, {
1892
+ ref,
1893
+ accessibilityRole: "switch",
1894
+ accessibilityLabel,
1895
+ accessibilityState: {
1896
+ checked: isChecked,
1897
+ disabled
1898
+ },
1899
+ disabled,
1900
+ hitSlop,
1901
+ onPress: () => setChecked(!isChecked),
1902
+ onFocus: () => setFocused(true),
1903
+ onBlur: () => setFocused(false),
1904
+ testID,
1905
+ style: [
1906
+ styles$4.root,
1907
+ {
1908
+ gap: t.space[3],
1909
+ minHeight: height
1910
+ },
1911
+ style
1912
+ ],
1913
+ children: [/* @__PURE__ */ jsx(View, {
1914
+ style: [
1915
+ styles$4.track,
1916
+ track,
1917
+ focused && focusRing
1918
+ ],
1919
+ children: /* @__PURE__ */ jsx(Animated.View, { style: {
1920
+ width: thumb,
1921
+ height: thumb,
1922
+ borderRadius: t.radius.full,
1923
+ backgroundColor: disabled ? t.color.text.disabled : t.color.text.onPrimary,
1924
+ transform: [{ translateX }]
1925
+ } })
1926
+ }), children === void 0 || children === null ? null : renderLabel(children, label)]
1927
+ });
1928
+ }
1929
+ //#endregion
1930
+ //#region src/tabs/Tabs.native.tsx
1931
+ const styles$3 = StyleSheet.create({
1932
+ root: { alignSelf: "stretch" },
1933
+ list: { flexGrow: 0 },
1934
+ tab: {
1935
+ flexDirection: "row",
1936
+ alignItems: "center",
1937
+ justifyContent: "center"
1938
+ }
1939
+ });
1940
+ /**
1941
+ * Pestañas subrayadas. Si no caben, la lista se desplaza en horizontal. En nativo no hay
1942
+ * teclado de flechas: cada pestaña se pulsa.
1943
+ */
1944
+ function Tabs({ items, value, defaultValue, onValueChange, accessibilityLabel, style, testID }) {
1945
+ const t = useTheme$1();
1946
+ const [selected, setSelected] = useControllableState({
1947
+ value,
1948
+ defaultValue: defaultValue ?? firstEnabledTab(items) ?? "",
1949
+ onChange: onValueChange
1950
+ });
1951
+ const active = items.find((item) => item.value === selected);
1952
+ const label = {
1953
+ fontSize: t.font.size.sm,
1954
+ lineHeight: t.font.lineHeight.sm,
1955
+ fontWeight: t.font.weight.medium
1956
+ };
1957
+ const panelText = {
1958
+ fontSize: t.font.size.md,
1959
+ lineHeight: t.font.lineHeight.md,
1960
+ color: t.color.text.primary
1961
+ };
1962
+ const family = t.font.family.sans;
1963
+ if (family !== void 0) {
1964
+ label.fontFamily = family;
1965
+ panelText.fontFamily = family;
1966
+ }
1967
+ return /* @__PURE__ */ jsxs(View, {
1968
+ style: [styles$3.root, style],
1969
+ testID,
1970
+ children: [/* @__PURE__ */ jsx(ScrollView, {
1971
+ horizontal: true,
1972
+ showsHorizontalScrollIndicator: false,
1973
+ accessibilityRole: "tablist",
1974
+ accessibilityLabel,
1975
+ style: [styles$3.list, {
1976
+ borderBottomWidth: t.borderWidth.thin,
1977
+ borderBottomColor: t.color.border.default
1978
+ }],
1979
+ contentContainerStyle: { gap: t.space[1] },
1980
+ children: items.map((item) => {
1981
+ const isSelected = item.value === selected;
1982
+ const isDisabled = item.disabled === true;
1983
+ const color = isDisabled ? "disabled" : isSelected ? "primary" : "secondary";
1984
+ return /* @__PURE__ */ jsxs(Pressable, {
1985
+ accessibilityRole: "tab",
1986
+ accessibilityState: {
1987
+ selected: isSelected,
1988
+ disabled: isDisabled
1989
+ },
1990
+ disabled: isDisabled,
1991
+ onPress: () => setSelected(item.value),
1992
+ style: ({ pressed }) => [styles$3.tab, {
1993
+ minHeight: t.size.control.md,
1994
+ paddingHorizontal: t.space[3],
1995
+ gap: t.space[2],
1996
+ borderBottomWidth: t.borderWidth.thick,
1997
+ borderBottomColor: isSelected ? t.color.action.primary : "transparent",
1998
+ borderTopLeftRadius: t.radius.sm,
1999
+ borderTopRightRadius: t.radius.sm,
2000
+ backgroundColor: pressed ? t.color.bg.muted : "transparent"
2001
+ }],
2002
+ children: [item.icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
2003
+ name: item.icon,
2004
+ size: "sm",
2005
+ color
2006
+ }), /* @__PURE__ */ jsx(Text$1, {
2007
+ style: [label, { color: t.color.text[color] }],
2008
+ children: item.label
2009
+ })]
2010
+ }, item.value);
2011
+ })
2012
+ }), active?.content === void 0 || active.content === null ? null : /* @__PURE__ */ jsx(View, {
2013
+ style: { paddingTop: t.space[4] },
2014
+ children: renderLabel(active.content, panelText)
2015
+ })]
2016
+ });
2017
+ }
2018
+ //#endregion
2019
+ //#region src/text/Text.native.tsx
2020
+ const textAlign = {
2021
+ left: "left",
2022
+ center: "center",
2023
+ right: "right"
2024
+ };
2025
+ function Text({ variant = "body", color = "primary", align, truncate = false, numberOfLines, style, testID, children }) {
2026
+ const t = useTheme$1();
2027
+ const spec = textVariantStyles[variant];
2028
+ const computed = {
2029
+ fontSize: t.font.size[spec.size],
2030
+ lineHeight: t.font.lineHeight[spec.size],
2031
+ fontWeight: t.font.weight[spec.weight],
2032
+ color: t.color.text[color]
2033
+ };
2034
+ const family = t.font.family[spec.family];
2035
+ if (family !== void 0) computed.fontFamily = family;
2036
+ if (align !== void 0) computed.textAlign = textAlign[align];
2037
+ if (spec.uppercase) computed.textTransform = "uppercase";
2038
+ if (spec.letterSpacing !== void 0) computed.letterSpacing = spec.letterSpacing;
2039
+ return /* @__PURE__ */ jsx(Text$1, {
2040
+ style: [computed, style],
2041
+ numberOfLines: truncate ? 1 : numberOfLines,
2042
+ ellipsizeMode: "tail",
2043
+ accessibilityRole: spec.headingLevel === void 0 ? void 0 : "header",
2044
+ "aria-level": spec.headingLevel,
2045
+ testID,
2046
+ children: /* @__PURE__ */ jsx(TextAncestorContext.Provider, {
2047
+ value: true,
2048
+ children
2049
+ })
2050
+ });
2051
+ }
2052
+ //#endregion
2053
+ //#region src/text-area/TextArea.native.tsx
2054
+ const styles$2 = StyleSheet.create({
2055
+ base: { alignSelf: "stretch" },
2056
+ input: {
2057
+ paddingVertical: 0,
2058
+ paddingHorizontal: 0,
2059
+ textAlignVertical: "top"
2060
+ }
2061
+ });
2062
+ function TextArea({ value, defaultValue, onChangeText, placeholder, rows = 3, disabled, readOnly = false, invalid, required, maxLength, onFocus, onBlur, accessibilityLabel, style, testID, ref }) {
2063
+ const t = useTheme$1();
2064
+ const control = useFormFieldControl({
2065
+ invalid,
2066
+ disabled,
2067
+ required,
2068
+ accessibilityLabel
2069
+ });
2070
+ const [focused, setFocused] = useState(false);
2071
+ const lineHeight = t.font.lineHeight.md;
2072
+ return /* @__PURE__ */ jsx(View, {
2073
+ style: [
2074
+ styles$2.base,
2075
+ fieldContainerStyle(t, {
2076
+ focused,
2077
+ invalid: control.invalid,
2078
+ disabled: control.disabled,
2079
+ readOnly
2080
+ }),
2081
+ {
2082
+ paddingVertical: t.space[2],
2083
+ paddingHorizontal: t.space[3]
2084
+ },
2085
+ style
2086
+ ],
2087
+ children: /* @__PURE__ */ jsx(TextInput, {
2088
+ ref,
2089
+ nativeID: control.id,
2090
+ multiline: true,
2091
+ style: [
2092
+ styles$2.input,
2093
+ fieldTextStyle(t, control.disabled),
2094
+ {
2095
+ lineHeight,
2096
+ minHeight: lineHeight * rows
2097
+ }
2098
+ ],
2099
+ ...value === void 0 ? defaultValue === void 0 ? {} : { defaultValue } : { value },
2100
+ onChangeText,
2101
+ placeholder,
2102
+ placeholderTextColor: t.color.text.muted,
2103
+ selectionColor: t.color.border.focus,
2104
+ editable: !control.disabled && !readOnly,
2105
+ maxLength,
2106
+ accessibilityLabel: control.accessibilityLabel,
2107
+ accessibilityHint: control.accessibilityHint,
2108
+ accessibilityState: { disabled: control.disabled },
2109
+ "aria-required": control.required ? true : void 0,
2110
+ testID,
2111
+ onFocus: () => {
2112
+ setFocused(true);
2113
+ onFocus?.();
2114
+ },
2115
+ onBlur: () => {
2116
+ setFocused(false);
2117
+ onBlur?.();
2118
+ }
2119
+ })
2120
+ });
2121
+ }
2122
+ //#endregion
2123
+ //#region src/toast/Toast.native.tsx
2124
+ const styles$1 = StyleSheet.create({
2125
+ root: {
2126
+ flexDirection: "row",
2127
+ alignItems: "flex-start",
2128
+ width: "100%"
2129
+ },
2130
+ message: {
2131
+ flex: 1,
2132
+ minWidth: 0,
2133
+ flexDirection: "row",
2134
+ alignItems: "flex-start"
2135
+ },
2136
+ icon: {
2137
+ justifyContent: "center",
2138
+ flexShrink: 0
2139
+ },
2140
+ content: {
2141
+ flex: 1,
2142
+ minWidth: 0
2143
+ },
2144
+ actions: {
2145
+ flexDirection: "row",
2146
+ alignItems: "center",
2147
+ flexShrink: 0
2148
+ }
2149
+ });
2150
+ /**
2151
+ * La tarjeta de un toast. Es interna: los toasts se muestran con `useToast` y los
2152
+ * pinta `UIProvider` (ADR-039).
2153
+ */
2154
+ function Toast({ toast, onDismiss }) {
2155
+ const { tone, title, description, action, closeLabel } = toast;
2156
+ const t = useTheme$1();
2157
+ const reducedMotion = useReducedMotion();
2158
+ const [entered] = useState(() => new Animated.Value(0));
2159
+ const duration = reducedMotion ? 0 : t.duration.normal;
2160
+ useEffect(() => {
2161
+ const animation = Animated.timing(entered, {
2162
+ toValue: 1,
2163
+ duration,
2164
+ useNativeDriver: true
2165
+ });
2166
+ animation.start();
2167
+ return () => animation.stop();
2168
+ }, [entered, duration]);
2169
+ const titleLine = t.font.lineHeight.md;
2170
+ const container = {
2171
+ gap: t.space[3],
2172
+ maxWidth: t.space[24] * 4,
2173
+ paddingVertical: t.space[3],
2174
+ paddingHorizontal: t.space[4],
2175
+ borderWidth: t.borderWidth.thin,
2176
+ borderRadius: t.radius.lg,
2177
+ borderColor: t.color.border.strong,
2178
+ backgroundColor: t.color.bg.elevated,
2179
+ boxShadow: t.shadow.lg
2180
+ };
2181
+ const titleStyle = {
2182
+ fontSize: t.font.size.md,
2183
+ lineHeight: titleLine,
2184
+ fontWeight: t.font.weight.medium,
2185
+ color: t.color.text.primary
2186
+ };
2187
+ const descriptionStyle = {
2188
+ fontSize: t.font.size.sm,
2189
+ lineHeight: t.font.lineHeight.sm,
2190
+ color: t.color.text.secondary
2191
+ };
2192
+ const family = t.font.family.sans;
2193
+ if (family !== void 0) {
2194
+ titleStyle.fontFamily = family;
2195
+ descriptionStyle.fontFamily = family;
2196
+ }
2197
+ const actionsOffset = (titleLine - t.size.control.sm) / 2;
2198
+ return /* @__PURE__ */ jsxs(Animated.View, {
2199
+ style: [
2200
+ styles$1.root,
2201
+ container,
2202
+ {
2203
+ opacity: entered,
2204
+ transform: [{ translateY: entered.interpolate({
2205
+ inputRange: [0, 1],
2206
+ outputRange: [t.space[2], 0]
2207
+ }) }]
2208
+ }
2209
+ ],
2210
+ children: [/* @__PURE__ */ jsxs(View, {
2211
+ accessible: true,
2212
+ accessibilityRole: "alert",
2213
+ accessibilityLiveRegion: feedbackUrgent[tone] ? "assertive" : "polite",
2214
+ style: [styles$1.message, { gap: t.space[3] }],
2215
+ children: [/* @__PURE__ */ jsx(View, {
2216
+ style: [styles$1.icon, { height: titleLine }],
2217
+ children: /* @__PURE__ */ jsx(Icon, {
2218
+ name: feedbackIcon[tone],
2219
+ size: "md",
2220
+ color: tone
2221
+ })
2222
+ }), /* @__PURE__ */ jsxs(View, {
2223
+ style: styles$1.content,
2224
+ children: [/* @__PURE__ */ jsx(Text$1, {
2225
+ style: titleStyle,
2226
+ children: title
2227
+ }), description === void 0 ? null : /* @__PURE__ */ jsx(Text$1, {
2228
+ style: descriptionStyle,
2229
+ children: description
2230
+ })]
2231
+ })]
2232
+ }), action === void 0 && closeLabel === void 0 ? null : /* @__PURE__ */ jsxs(View, {
2233
+ style: [styles$1.actions, {
2234
+ gap: t.space[1],
2235
+ marginVertical: actionsOffset,
2236
+ marginRight: -t.space[2]
2237
+ }],
2238
+ children: [action === void 0 ? null : /* @__PURE__ */ jsx(Button, {
2239
+ variant: "ghost",
2240
+ size: "sm",
2241
+ onPress: () => {
2242
+ action.onPress();
2243
+ onDismiss();
2244
+ },
2245
+ children: action.label
2246
+ }), closeLabel === void 0 ? null : /* @__PURE__ */ jsx(IconButton, {
2247
+ icon: "x",
2248
+ label: closeLabel,
2249
+ size: "sm",
2250
+ variant: "ghost",
2251
+ onPress: onDismiss
2252
+ })]
2253
+ })]
2254
+ });
2255
+ }
2256
+ //#endregion
2257
+ //#region src/toast/ToastViewport.native.tsx
2258
+ const styles = StyleSheet.create({ viewport: {
2259
+ position: "absolute",
2260
+ right: 0,
2261
+ bottom: 0,
2262
+ left: 0,
2263
+ alignItems: "center"
2264
+ } });
2265
+ /**
2266
+ * La zona donde aparecen los toasts de un `UIProvider` (ADR-039). No recibe toques:
2267
+ * solo las tarjetas.
2268
+ */
2269
+ function ToastViewport({ store }) {
2270
+ const t = useTheme$1();
2271
+ const toasts = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
2272
+ const announced = useRef(/* @__PURE__ */ new Set());
2273
+ useEffect(() => {
2274
+ const visible = new Set(toasts.map((toast) => toast.id));
2275
+ for (const toast of toasts) {
2276
+ if (announced.current.has(toast.id)) continue;
2277
+ announced.current.add(toast.id);
2278
+ AccessibilityInfo.announceForAccessibility(toast.description === void 0 ? toast.title : `${toast.title}. ${toast.description}`);
2279
+ }
2280
+ for (const id of announced.current) if (!visible.has(id)) announced.current.delete(id);
2281
+ }, [toasts]);
2282
+ if (toasts.length === 0) return null;
2283
+ return /* @__PURE__ */ jsx(View, {
2284
+ pointerEvents: "box-none",
2285
+ style: [styles.viewport, {
2286
+ zIndex: t.zIndex.toast,
2287
+ gap: t.space[2],
2288
+ padding: t.space[4],
2289
+ paddingBottom: t.space[10]
2290
+ }],
2291
+ children: toasts.map((toast) => /* @__PURE__ */ jsx(Toast, {
2292
+ toast,
2293
+ onDismiss: () => store.dismiss(toast.id)
2294
+ }, toast.id))
2295
+ });
2296
+ }
2297
+ //#endregion
2298
+ //#region src/toast/useToastHost.ts
2299
+ /**
2300
+ * La cola de toasts de un `UIProvider` (ADR-039). Uno anidado, por ejemplo para cambiar
2301
+ * de marca en una sección, reutiliza la del de fuera: solo hay una zona de avisos por app.
2302
+ */
2303
+ function useToastHost() {
2304
+ const outer = useContext(ToastContext);
2305
+ const [own] = useState(() => outer === null ? createToastStore() : null);
2306
+ useEffect(() => () => own?.dismiss(), [own]);
2307
+ return {
2308
+ store: outer ?? own ?? createToastStore(),
2309
+ own: outer === null ? own : null
2310
+ };
2311
+ }
2312
+ //#endregion
2313
+ //#region src/ui-provider/UIProvider.native.tsx
2314
+ /**
2315
+ * Activa tema y marca para todo lo que envuelve (ADR-010). Entrega el tema
2316
+ * resuelto por contexto; `system` sigue a `Appearance`. También guarda la cola de
2317
+ * toasts y los pinta sobre la vista que lo contiene (ADR-039).
2318
+ */
2319
+ function UIProvider({ theme = "system", brand, children }) {
2320
+ const system = useColorScheme$1() === "dark" ? "dark" : "light";
2321
+ const value = useMemo(() => createUIContextValue(theme, system, brand), [
2322
+ theme,
2323
+ system,
2324
+ brand
2325
+ ]);
2326
+ const toasts = useToastHost();
2327
+ return /* @__PURE__ */ jsx(UIContext.Provider, {
2328
+ value,
2329
+ children: /* @__PURE__ */ jsxs(ToastContext.Provider, {
2330
+ value: toasts.store,
2331
+ children: [children, toasts.own === null ? null : /* @__PURE__ */ jsx(ToastViewport, { store: toasts.own })]
2332
+ })
696
2333
  });
697
2334
  }
698
2335
  //#endregion
699
- export { Badge, Box, Button, Icon, Spinner, Stack, Text, UIProvider, useBrand, useColorScheme, useTheme };
2336
+ export { Alert, Badge, Box, Button, Card, Checkbox, Divider, FormField, Icon, IconButton, Input, Link, Modal, Select, Sheet, Skeleton, Spinner, Stack, Switch, Tabs, Text, TextArea, UIProvider, useBrand, useColorScheme, useTheme, useToast };