orn-ui 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/AGENTS.md +354 -0
  2. package/README.md +43 -13
  3. package/lib/commonjs/organisms/DateField.js +1 -1
  4. package/lib/commonjs/organisms/DateField.js.map +1 -1
  5. package/lib/module/organisms/DateField.js +1 -1
  6. package/lib/module/organisms/DateField.js.map +1 -1
  7. package/lib/typescript/atoms/Badge.d.ts +1 -1
  8. package/lib/typescript/atoms/Badge.d.ts.map +1 -1
  9. package/lib/typescript/atoms/Image.d.ts +6 -6
  10. package/lib/typescript/atoms/Image.d.ts.map +1 -1
  11. package/lib/typescript/atoms/Input.d.ts +1 -1
  12. package/lib/typescript/atoms/Input.d.ts.map +1 -1
  13. package/lib/typescript/atoms/PressableScale.d.ts +1 -1
  14. package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
  15. package/lib/typescript/atoms/Skeleton.d.ts +5 -5
  16. package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
  17. package/lib/typescript/atoms/Spinner.d.ts +4 -4
  18. package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
  19. package/lib/typescript/atoms/Transition.d.ts +10 -10
  20. package/lib/typescript/atoms/Transition.d.ts.map +1 -1
  21. package/lib/typescript/molecules/AvatarHeader.d.ts +2 -2
  22. package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
  23. package/lib/typescript/molecules/FormActions.d.ts +1 -1
  24. package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
  25. package/lib/typescript/molecules/OptionCard.d.ts +2 -2
  26. package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
  27. package/lib/typescript/molecules/Stepper.d.ts +8 -8
  28. package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
  29. package/lib/typescript/molecules/Steps.d.ts +3 -3
  30. package/lib/typescript/molecules/Steps.d.ts.map +1 -1
  31. package/lib/typescript/molecules/SymmetricGrid.d.ts +4 -4
  32. package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
  33. package/lib/typescript/organisms/BottomSheet.d.ts +8 -8
  34. package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
  35. package/lib/typescript/organisms/DateField.d.ts +10 -10
  36. package/lib/typescript/organisms/DateField.d.ts.map +1 -1
  37. package/lib/typescript/organisms/DatePicker.d.ts +10 -10
  38. package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
  39. package/lib/typescript/organisms/List.d.ts +6 -6
  40. package/lib/typescript/organisms/List.d.ts.map +1 -1
  41. package/lib/typescript/organisms/NavigationBar.d.ts +2 -2
  42. package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
  43. package/lib/typescript/organisms/ReorderableList.d.ts +7 -7
  44. package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
  45. package/lib/typescript/organisms/Screen.d.ts +6 -6
  46. package/lib/typescript/organisms/SearchList.d.ts +8 -8
  47. package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
  48. package/lib/typescript/organisms/Select.d.ts +2 -2
  49. package/lib/typescript/organisms/Select.d.ts.map +1 -1
  50. package/lib/typescript/organisms/Toast.d.ts +2 -2
  51. package/lib/typescript/organisms/Toast.d.ts.map +1 -1
  52. package/lib/typescript/organisms/ToastProvider.d.ts +2 -2
  53. package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
  54. package/lib/typescript/organisms/Wizard.d.ts +7 -7
  55. package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
  56. package/lib/typescript/theme/UIProvider.d.ts +9 -9
  57. package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
  58. package/llms.txt +33 -0
  59. package/package.json +24 -6
  60. package/registry/avatar-header.json +1 -1
  61. package/registry/badge.json +1 -1
  62. package/registry/bottom-sheet.json +1 -1
  63. package/registry/core.json +1 -1
  64. package/registry/date-field.json +1 -1
  65. package/registry/date-picker.json +1 -1
  66. package/registry/form-actions.json +1 -1
  67. package/registry/image.json +1 -1
  68. package/registry/index.json +26 -26
  69. package/registry/input.json +1 -1
  70. package/registry/list.json +1 -1
  71. package/registry/navigation-bar.json +1 -1
  72. package/registry/option-card.json +1 -1
  73. package/registry/pressable-scale.json +1 -1
  74. package/registry/reorderable-list.json +1 -1
  75. package/registry/screen.json +1 -1
  76. package/registry/search-list.json +1 -1
  77. package/registry/select.json +1 -1
  78. package/registry/skeleton.json +1 -1
  79. package/registry/spinner.json +1 -1
  80. package/registry/stepper.json +1 -1
  81. package/registry/steps.json +1 -1
  82. package/registry/symmetric-grid.json +1 -1
  83. package/registry/toast-provider.json +1 -1
  84. package/registry/toast.json +1 -1
  85. package/registry/transition.json +1 -1
  86. package/registry/wizard.json +1 -1
  87. package/src/atoms/Badge.tsx +1 -1
  88. package/src/atoms/Image.tsx +6 -6
  89. package/src/atoms/Input.tsx +1 -1
  90. package/src/atoms/PressableScale.tsx +1 -1
  91. package/src/atoms/Skeleton.tsx +5 -5
  92. package/src/atoms/Spinner.tsx +4 -4
  93. package/src/atoms/Transition.tsx +10 -10
  94. package/src/molecules/AvatarHeader.tsx +2 -2
  95. package/src/molecules/FormActions.tsx +1 -1
  96. package/src/molecules/OptionCard.tsx +2 -2
  97. package/src/molecules/Stepper.tsx +8 -8
  98. package/src/molecules/Steps.tsx +3 -3
  99. package/src/molecules/SymmetricGrid.tsx +4 -4
  100. package/src/organisms/BottomSheet.tsx +8 -8
  101. package/src/organisms/DateField.tsx +11 -11
  102. package/src/organisms/DatePicker.tsx +10 -10
  103. package/src/organisms/List.tsx +6 -6
  104. package/src/organisms/NavigationBar.tsx +2 -2
  105. package/src/organisms/ReorderableList.tsx +7 -7
  106. package/src/organisms/Screen.tsx +6 -6
  107. package/src/organisms/SearchList.tsx +8 -8
  108. package/src/organisms/Select.tsx +2 -2
  109. package/src/organisms/Toast.tsx +2 -2
  110. package/src/organisms/ToastProvider.tsx +2 -2
  111. package/src/organisms/Wizard.tsx +7 -7
  112. package/src/theme/UIProvider.tsx +9 -9
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { type FlatListProps, type StyleProp, type ViewStyle } from 'react-native';
3
3
  import type { IconName } from '../icons/types';
4
4
  export interface SearchListProps<T> {
5
- /** Chrome opcional arriba del buscador (ej. back button + título). */
5
+ /** Optional chrome above the search bar (e.g. back button + title). */
6
6
  header?: React.ReactNode;
7
7
  searchValue: string;
8
8
  onSearchChange: (text: string) => void;
@@ -14,22 +14,22 @@ export interface SearchListProps<T> {
14
14
  data: T[];
15
15
  keyExtractor: (item: T, index: number) => string;
16
16
  renderItem: FlatListProps<T>['renderItem'];
17
- /** Carga inicial: mientras sea true y no haya datos, se muestran placeholders. */
17
+ /** Initial load: while true and there's no data, placeholders are shown. */
18
18
  isLoading?: boolean;
19
- /** 'replace' cambia toda la lista por un spinner/skeleton; 'overlay' lo muestra encima manteniendo el layout. */
19
+ /** 'replace' swaps the whole list for a spinner/skeleton; 'overlay' shows it on top while keeping the layout. */
20
20
  loadingMode?: 'replace' | 'overlay';
21
- /** Recarga con datos ya en pantalla (pull-to-refresh). @default false */
21
+ /** Reload with data already on screen (pull-to-refresh). @default false */
22
22
  isRefreshing?: boolean;
23
23
  isLoadingMore?: boolean;
24
- /** Controlado por el consumidor: mientras es false, se muestra un spinner de pantalla completa. @default true */
24
+ /** Controlled by the consumer: while false, a fullscreen spinner is shown. @default true */
25
25
  isReady?: boolean;
26
26
  loadingText?: string;
27
27
  loadingMoreText?: string;
28
28
  hasMore?: boolean;
29
29
  noMoreText?: string;
30
- /** Filas fantasma de la carga inicial. @default 6 */
30
+ /** Ghost rows for the initial load. @default 6 */
31
31
  skeletonCount?: number;
32
- /** Placeholder propio, para que calce con la forma real del ítem. */
32
+ /** Custom placeholder, to match the real shape of the item. */
33
33
  renderSkeletonItem?: () => React.ReactElement;
34
34
  onLoadMore?: () => void;
35
35
  onEndReached?: () => void;
@@ -42,7 +42,7 @@ export interface SearchListProps<T> {
42
42
  ListFooterComponent?: FlatListProps<T>['ListFooterComponent'];
43
43
  contentContainerStyle?: StyleProp<ViewStyle>;
44
44
  containerStyle?: ViewStyle;
45
- /** Componente de lista a usar (FlashList, etc). @default FlatList */
45
+ /** List component to use (FlashList, etc). @default FlatList */
46
46
  ListComponent?: React.ComponentType<any>;
47
47
  listProps?: Record<string, unknown>;
48
48
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SearchList.d.ts","sourceRoot":"","sources":["../../../src/organisms/SearchList.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchList.d.ts","sourceRoot":"","sources":["../../../src/organisms/SearchList.tsx"],"names":[],"mappings":"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"}
@@ -20,10 +20,10 @@ export interface SelectProps<T> {
20
20
  error?: string;
21
21
  isLoading?: boolean;
22
22
  disabled?: boolean;
23
- /** Agrega un buscador dentro del desplegable. @default false */
23
+ /** Adds a search box inside the dropdown. @default false */
24
24
  searchable?: boolean;
25
25
  searchPlaceholder?: string;
26
- /** Alto máximo del desplegable, si el espacio disponible lo permite. @default 280 */
26
+ /** Maximum height of the dropdown, if the available space allows it. @default 280 */
27
27
  maxHeight?: number;
28
28
  onOpenChange?: (open: boolean) => void;
29
29
  containerStyle?: StyleProp<ViewStyle>;
@@ -1 +1 @@
1
- {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/organisms/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA6ED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EAChD,KAAK,EACL,QAAgB,EAChB,OAAO,EACP,aAAa,EACb,QAAQ,EACR,WAAW,EACX,KAAK,EACL,SAAiB,EACjB,QAAgB,EAChB,UAAkB,EAClB,iBAAiB,EACjB,SAAe,EACf,YAAY,EACZ,cAAc,EACd,MAAM,GACP,EAAE,WAAW,CAAC,CAAC,CAAC,qBAyLhB"}
1
+ {"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../../src/organisms/Select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAWL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,CAAC,CAAC;IACT,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3B,aAAa,EAAE,CAAC,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AA6ED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,EAChD,KAAK,EACL,QAAgB,EAChB,OAAO,EACP,aAAa,EACb,QAAQ,EACR,WAAW,EACX,KAAK,EACL,SAAiB,EACjB,QAAgB,EAChB,UAAkB,EAClB,iBAAiB,EACjB,SAAe,EACf,YAAY,EACZ,cAAc,EACd,MAAM,GACP,EAAE,WAAW,CAAC,CAAC,CAAC,qBAyLhB"}
@@ -5,10 +5,10 @@ export interface ToastProps {
5
5
  title: string;
6
6
  message?: string;
7
7
  variant?: ToastVariant;
8
- /** Se dispara al tocar el toast (además de cerrarlo). */
8
+ /** Fires when the toast is tapped (in addition to closing it). */
9
9
  onPress?: () => void;
10
10
  onDismiss?: () => void;
11
- /** Oculta la X de cerrar. El toast igual se auto-descarta por tiempo. */
11
+ /** Hides the close X. The toast still auto-dismisses by timeout. */
12
12
  hideCloseButton?: boolean;
13
13
  style?: StyleProp<ViewStyle>;
14
14
  testID?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/organisms/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAOrF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwCD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,8GAC2E,UAAU,uBAkDtG,CAAC;AAGF,sFAAsF;AACtF,eAAO,MAAM,aAAa,qDACC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,uBAKvF,CAAC"}
1
+ {"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../../src/organisms/Toast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAAyB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAOrF,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAwCD;;;;;GAKG;AACH,eAAO,MAAM,KAAK,8GAC2E,UAAU,uBAkDtG,CAAC;AAGF,sFAAsF;AACtF,eAAO,MAAM,aAAa,qDACC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,uBAKvF,CAAC"}
@@ -27,9 +27,9 @@ export interface ToastProviderProps {
27
27
  children: React.ReactNode;
28
28
  /** @default 'top' */
29
29
  position?: 'top' | 'bottom';
30
- /** Máximo de toasts simultáneos; los más viejos se descartan. @default 3 */
30
+ /** Maximum simultaneous toasts; the oldest ones are dropped. @default 3 */
31
31
  maxVisible?: number;
32
- /** Duración por defecto en ms. @default 4000 */
32
+ /** Default duration in ms. @default 4000 */
33
33
  defaultDuration?: number;
34
34
  }
35
35
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../src/organisms/ToastProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuF,MAAM,OAAO,CAAC;AAE5G,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAKlE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,UAAU,iBAAiB;IACzB,kFAAkF;IAClF,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAgB,EAChB,UAAc,EACd,eAAsB,GACvB,EAAE,kBAAkB,qBA4GpB;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
1
+ {"version":3,"file":"ToastProvider.d.ts","sourceRoot":"","sources":["../../../src/organisms/ToastProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuF,MAAM,OAAO,CAAC;AAE5G,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAKlE,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,UAAU,iBAAiB;IACzB,kFAAkF;IAClF,IAAI,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,MAAM,CAAC;IACxC,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAKD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,qBAAqB;IACrB,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,QAAgB,EAChB,UAAc,EACd,eAAsB,GACvB,EAAE,kBAAkB,qBA4GpB;AAED,wBAAgB,QAAQ,IAAI,iBAAiB,CAM5C"}
@@ -8,28 +8,28 @@ export interface WizardStep extends StepItem {
8
8
  }
9
9
  export interface WizardProps {
10
10
  steps: WizardStep[];
11
- /** Paso actual (controlado). Si se omite, el Wizard maneja su propio estado. */
11
+ /** Current step (controlled). If omitted, the Wizard manages its own state. */
12
12
  current?: number;
13
13
  onStepChange?: (index: number) => void;
14
14
  onFinish?: () => void;
15
15
  /** @default 'horizontal' */
16
16
  orientation?: 'horizontal' | 'vertical';
17
17
  completedIndicator?: 'check' | 'number';
18
- /** Permite volver a un paso ya completado tocándolo en el indicador. @default true */
18
+ /** Allows returning to an already-completed step by tapping it in the indicator. @default true */
19
19
  allowStepNavigation?: boolean;
20
20
  backLabel?: string;
21
21
  nextLabel?: string;
22
22
  finishLabel?: string;
23
23
  /**
24
- * El contenido del paso scrollea cuando no entra. Ponelo en false sólo si
25
- * un paso renderiza su propia lista virtualizada (anidar una
26
- * VirtualizedList dentro de un ScrollView rompe la virtualización).
24
+ * The step content scrolls when it doesn't fit. Set to false only if
25
+ * a step renders its own virtualized list (nesting a
26
+ * VirtualizedList inside a ScrollView breaks virtualization).
27
27
  * @default true
28
28
  */
29
29
  scrollableContent?: boolean;
30
30
  /**
31
- * El contenido del paso entra deslizándose en el sentido de la navegación.
32
- * Sólo opacity y transform, así que la animación corre en el hilo nativo.
31
+ * The step content slides in following the navigation direction.
32
+ * Only opacity and transform, so the animation runs on the native thread.
33
33
  * @default true
34
34
  */
35
35
  animated?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Wizard.d.ts","sourceRoot":"","sources":["../../../src/organisms/Wizard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,kBAAkB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACxC,sFAAsF;IACtF,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAUD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EACZ,QAAQ,EACR,WAA0B,EAC1B,kBAA4B,EAC5B,mBAA0B,EAC1B,SAAkB,EAClB,SAAkB,EAClB,WAAsB,EACtB,iBAAwB,EACxB,QAAe,EACf,KAAK,GACN,EAAE,WAAW,qBAkGb"}
1
+ {"version":3,"file":"Wizard.d.ts","sourceRoot":"","sources":["../../../src/organisms/Wizard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAS,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAI1D,MAAM,WAAW,UAAW,SAAQ,QAAQ;IAC1C,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,YAAY,GAAG,UAAU,CAAC;IACxC,kBAAkB,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACxC,kGAAkG;IAClG,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAUD;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,OAAO,EAAE,iBAAiB,EAC1B,YAAY,EACZ,QAAQ,EACR,WAA0B,EAC1B,kBAA4B,EAC5B,mBAA0B,EAC1B,SAAkB,EAClB,SAAkB,EAClB,WAAsB,EACtB,iBAAwB,EACxB,QAAe,EACf,KAAK,GACN,EAAE,WAAW,qBAkGb"}
@@ -19,24 +19,24 @@ export interface ModalSafeAreaBoundaryProps {
19
19
  export type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;
20
20
  export interface UIProviderProps {
21
21
  children: React.ReactNode;
22
- /** Par light/dark. Por defecto el theme de marca de la librería (créalo con createTheme()). */
22
+ /** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */
23
23
  theme?: ThemePair;
24
- /** Modo inicial (no controlado) si no se pasa `mode`. @default 'system' */
24
+ /** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */
25
25
  defaultMode?: ThemeMode;
26
- /** Modo controlado. Si se pasa junto a onModeChange, el provider no gestiona estado propio. */
26
+ /** Controlled mode. If passed together with onModeChange, the provider doesn't manage its own state. */
27
27
  mode?: ThemeMode;
28
28
  onModeChange?: (mode: ThemeMode) => void;
29
- /** Renderer de íconos. Por defecto usa los glifos zero-dep incluidos. */
29
+ /** Icon renderer. Defaults to the included zero-dep glyphs. */
30
30
  icons?: IconRenderer;
31
- /** Insets de safe area (de useSafeAreaInsets()). Por defecto {0,0,0,0}. */
31
+ /** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */
32
32
  insets?: EdgeInsets;
33
33
  labels?: Partial<Labels>;
34
- /** @default false, para paridad con el resto de la librería (accesibilidad primero). */
34
+ /** @default false, for parity with the rest of the library (accessibility first). */
35
35
  allowFontScaling?: boolean;
36
36
  /**
37
- * Fuente de insets que usan `Modal`/`BottomSheet` para su propia ventana
38
- * nativa. No la pases a mano — `SafeAreaUIProvider` la cablea sola.
39
- * @default repite los insets ambientes (comportamiento sin safe-area-context)
37
+ * Source of insets used by `Modal`/`BottomSheet` for their own native
38
+ * window. Don't pass this by hand — `SafeAreaUIProvider` wires it up itself.
39
+ * @default repeats the ambient insets (behavior without safe-area-context)
40
40
  */
41
41
  ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;
42
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"UIProvider.d.ts","sourceRoot":"","sources":["../../../src/theme/UIProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4E,MAAM,OAAO,CAAC;AACjG,OAAO,EAAE,UAAU,EAAkB,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD;AACD,MAAM,MAAM,8BAA8B,GAAG,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;AAoB7F,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,+FAA+F;IAC/F,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,+FAA+F;IAC/F,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,yEAAyE;IACzE,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,wFAAwF;IACxF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,8BAA8B,CAAC;CACxD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,KAAoB,EACpB,WAAsB,EACtB,IAAI,EAAE,cAAc,EACpB,YAAY,EACZ,KAAyB,EACzB,MAAmB,EACnB,MAAM,EAAE,cAAc,EACtB,gBAAwB,EACxB,qBAAoD,GACrD,EAAE,eAAe,qBAwDjB;AAUD,wBAAgB,QAAQ,IAAI,KAAK,CAEhC;AAED,wBAAgB,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,CAE3C;AAED,wBAAgB,YAAY;;oBA9HV,SAAS,KAAK,IAAI;EAiInC;AAED,wBAAgB,QAAQ,IAAI,YAAY,CAEvC;AAED,wBAAgB,SAAS,IAAI,UAAU,CAEtC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,yGAAyG;AACzG,wBAAgB,wBAAwB,IAAI,8BAA8B,CAEzE;AAED,8FAA8F;AAC9F,eAAO,MAAM,WAAW;;CAAiB,CAAC"}
1
+ {"version":3,"file":"UIProvider.d.ts","sourceRoot":"","sources":["../../../src/theme/UIProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4E,MAAM,OAAO,CAAC;AACjG,OAAO,EAAE,UAAU,EAAkB,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACtD,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD;AACD,MAAM,MAAM,8BAA8B,GAAG,KAAK,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC;AAoB7F,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,8FAA8F;IAC9F,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,wGAAwG;IACxG,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACzC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,8BAA8B,CAAC;CACxD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,KAAoB,EACpB,WAAsB,EACtB,IAAI,EAAE,cAAc,EACpB,YAAY,EACZ,KAAyB,EACzB,MAAmB,EACnB,MAAM,EAAE,cAAc,EACtB,gBAAwB,EACxB,qBAAoD,GACrD,EAAE,eAAe,qBAwDjB;AAUD,wBAAgB,QAAQ,IAAI,KAAK,CAEhC;AAED,wBAAgB,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,CAE3C;AAED,wBAAgB,YAAY;;oBA9HV,SAAS,KAAK,IAAI;EAiInC;AAED,wBAAgB,QAAQ,IAAI,YAAY,CAEvC;AAED,wBAAgB,SAAS,IAAI,UAAU,CAEtC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED,yGAAyG;AACzG,wBAAgB,wBAAwB,IAAI,8BAA8B,CAEzE;AAED,8FAA8F;AAC9F,eAAO,MAAM,WAAW;;CAAiB,CAAC"}
package/llms.txt ADDED
@@ -0,0 +1,33 @@
1
+ # orn-ui
2
+
3
+ > React Native / Expo component library: 44 typed components (atoms, molecules,
4
+ > organisms), light/dark theming, zero runtime dependencies (only `react` and
5
+ > `react-native` as peers). Verified in CI against Expo SDK 54, 55, 56 and 57.
6
+ > Built to be used by AI coding agents: one component replaces dozens of lines
7
+ > of hand-written `View`/`Text`/`StyleSheet`, so a screen costs a fraction of
8
+ > the tokens and ships production-ready instead of prototype-only.
9
+
10
+ Install: `pnpm add orn-ui` — no native modules, no pods, no babel or metro
11
+ config, works in Expo Go and bare React Native >= 0.81.
12
+ Or copy the source into the project with no dependency: `npx orn-ui add button select`.
13
+
14
+ Import from subpaths (Metro does not tree-shake barrels):
15
+ `import { Button } from 'orn-ui/button'`, `import { UIProvider } from 'orn-ui/theme'`.
16
+
17
+ Wrap the app once: `<SafeAreaUIProvider defaultMode="system">` from `orn-ui/safe-area`
18
+ (or `<UIProvider insets={...}>` from `orn-ui/theme` to skip the optional
19
+ `react-native-safe-area-context` peer).
20
+
21
+ ## Docs
22
+
23
+ - [Agent guide](https://github.com/DavidTrujillo123/orn-ui/blob/main/packages/ui/AGENTS.md): full component + props cheat sheet, import rules, gotchas, recipes — shipped in the package as `node_modules/orn-ui/AGENTS.md`
24
+ - [README](https://github.com/DavidTrujillo123/orn-ui#readme): install modes, safe area, theming, compatibility matrix
25
+ - [Live docs](https://orn-ui-docs.vercel.app/): props tables and demo GIFs per component
26
+ - [Repository](https://github.com/DavidTrujillo123/orn-ui): source, tests, CI compatibility matrix
27
+
28
+ ## Components
29
+
30
+ - Atoms: Title, Subtitle, Body, Caption, Button, IconButton, Input, Checkbox, Badge, Card, Divider, Avatar, Image, Spinner, Skeleton, Transition, EmptyState, KeyValueRow, Fab, PressableScale
31
+ - Molecules: Stepper, OptionCard, InfoRow, FormActions, AvatarHeader, SegmentedControl, Steps, SymmetricGrid
32
+ - Organisms: Screen, Modal, BottomSheet, Select, List, SearchList, ReorderableList, Alert, AlertProvider, Toast, ToastProvider, DatePicker, DateField, Wizard, ThemeToggle, NavigationBar
33
+ - Theme: UIProvider, SafeAreaUIProvider, createTheme, createStyles, useTheme, useColors, useThemeMode, useIcons, useInsets, useLabels
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "orn-ui",
3
- "version": "0.2.5",
4
- "description": "Fast, tree-shakeable, atomic-design component library for React Native. Zero runtime dependencies.",
3
+ "version": "0.2.7",
4
+ "description": "React Native / Expo component library: 44 typed components, light/dark theming, zero runtime dependencies. Verified on Expo SDK 54-57. Agent-friendly: one component instead of hundreds of lines of hand-written UI.",
5
5
  "author": "David Trujillo <davot098@gmail.com>",
6
6
  "main": "lib/commonjs/index.js",
7
7
  "module": "lib/module/index.js",
@@ -17,6 +17,8 @@
17
17
  "lib",
18
18
  "bin",
19
19
  "registry",
20
+ "AGENTS.md",
21
+ "llms.txt",
20
22
  "!**/__tests__",
21
23
  "!**/*.test.*"
22
24
  ],
@@ -342,12 +344,14 @@
342
344
  "typecheck": "tsc --noEmit",
343
345
  "test": "jest",
344
346
  "test:coverage": "jest --coverage",
347
+ "compat": "node scripts/compat-matrix.mjs",
348
+ "compat:typecheck": "node scripts/compat-matrix.mjs --only typecheck",
345
349
  "prepare": "node scripts/gen-exports.mjs && node scripts/build-registry.mjs && bob build"
346
350
  },
347
351
  "peerDependencies": {
348
- "react": ">=18.0.0",
349
- "react-native": ">=0.73.0",
350
- "react-native-safe-area-context": ">=4.0.0"
352
+ "react": ">=19.1.0",
353
+ "react-native": ">=0.81.0",
354
+ "react-native-safe-area-context": ">=5.4.0"
351
355
  },
352
356
  "peerDependenciesMeta": {
353
357
  "react-native-safe-area-context": {
@@ -370,9 +374,23 @@
370
374
  },
371
375
  "keywords": [
372
376
  "react-native",
377
+ "expo",
378
+ "expo-sdk-54",
379
+ "expo-sdk-55",
380
+ "expo-sdk-56",
381
+ "expo-sdk-57",
373
382
  "ui",
374
383
  "components",
375
- "atomic-design"
384
+ "component-library",
385
+ "design-system",
386
+ "atomic-design",
387
+ "dark-mode",
388
+ "theming",
389
+ "typescript",
390
+ "zero-dependencies",
391
+ "ai",
392
+ "llm",
393
+ "agents"
376
394
  ],
377
395
  "license": "MIT",
378
396
  "repository": {
@@ -9,7 +9,7 @@
9
9
  "files": [
10
10
  {
11
11
  "path": "molecules/AvatarHeader.tsx",
12
- "content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport interface AvatarHeaderProps {\n /** Ícono a mostrar dentro del círculo. Si se omite, se usan `initials`. */\n iconName?: IconName;\n iconColor?: string;\n /** Texto corto (ej. 2 letras) mostrado si no se pasa `iconName`. */\n initials?: string;\n title: string;\n subtitle?: React.ReactNode;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { alignItems: 'center', marginBottom: theme.tokens.spacing.xl },\n avatar: {\n width: 80,\n height: 80,\n borderRadius: 40,\n backgroundColor: theme.colors.primarySoft,\n justifyContent: 'center',\n alignItems: 'center',\n marginBottom: theme.tokens.spacing.md,\n },\n initials: { fontSize: theme.tokens.fontSize.xxl, fontWeight: theme.tokens.fontWeight.bold, color: theme.colors.primaryText },\n title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, textAlign: 'center', color: theme.colors.text, marginBottom: theme.tokens.spacing.xs },\n subtitle: { fontSize: theme.tokens.fontSize.md, color: theme.colors.textLight },\n}));\n\n/**\n * AvatarHeader\n * Círculo 80x80 con ícono o iniciales + título/subtítulo centrado. Header\n * típico de modales de detalle (cliente, producto, etc).\n */\nexport const AvatarHeader = memo(({ iconName, iconColor, initials, title, subtitle, style }: AvatarHeaderProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n\n return (\n <View style={[styles.container, style]}>\n <View style={styles.avatar}>\n {iconName ? (\n <Icon name={iconName} size={40} color={iconColor || colors.primary} />\n ) : (\n <Text allowFontScaling={allowFontScaling} style={styles.initials}>\n {initials}\n </Text>\n )}\n </View>\n <Text allowFontScaling={allowFontScaling} style={styles.title}>\n {title}\n </Text>\n {typeof subtitle === 'string' ? (\n <Text allowFontScaling={allowFontScaling} style={styles.subtitle}>\n {subtitle}\n </Text>\n ) : (\n subtitle\n )}\n </View>\n );\n});\nAvatarHeader.displayName = 'AvatarHeader';\n"
12
+ "content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors } from '../theme/UIProvider';\nimport { Icon } from '../icons/Icon';\nimport type { IconName } from '../icons/types';\n\nexport interface AvatarHeaderProps {\n /** Icon to show inside the circle. If omitted, `initials` is used instead. */\n iconName?: IconName;\n iconColor?: string;\n /** Short text (e.g. 2 letters) shown when `iconName` isn't provided. */\n initials?: string;\n title: string;\n subtitle?: React.ReactNode;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { alignItems: 'center', marginBottom: theme.tokens.spacing.xl },\n avatar: {\n width: 80,\n height: 80,\n borderRadius: 40,\n backgroundColor: theme.colors.primarySoft,\n justifyContent: 'center',\n alignItems: 'center',\n marginBottom: theme.tokens.spacing.md,\n },\n initials: { fontSize: theme.tokens.fontSize.xxl, fontWeight: theme.tokens.fontWeight.bold, color: theme.colors.primaryText },\n title: { fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold, textAlign: 'center', color: theme.colors.text, marginBottom: theme.tokens.spacing.xs },\n subtitle: { fontSize: theme.tokens.fontSize.md, color: theme.colors.textLight },\n}));\n\n/**\n * AvatarHeader\n * Círculo 80x80 con ícono o iniciales + título/subtítulo centrado. Header\n * típico de modales de detalle (cliente, producto, etc).\n */\nexport const AvatarHeader = memo(({ iconName, iconColor, initials, title, subtitle, style }: AvatarHeaderProps) => {\n const colors = useColors();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n\n return (\n <View style={[styles.container, style]}>\n <View style={styles.avatar}>\n {iconName ? (\n <Icon name={iconName} size={40} color={iconColor || colors.primary} />\n ) : (\n <Text allowFontScaling={allowFontScaling} style={styles.initials}>\n {initials}\n </Text>\n )}\n </View>\n <Text allowFontScaling={allowFontScaling} style={styles.title}>\n {title}\n </Text>\n {typeof subtitle === 'string' ? (\n <Text allowFontScaling={allowFontScaling} style={styles.subtitle}>\n {subtitle}\n </Text>\n ) : (\n subtitle\n )}\n </View>\n );\n});\nAvatarHeader.displayName = 'AvatarHeader';\n"
13
13
  }
14
14
  ],
15
15
  "registryDependencies": [
@@ -9,7 +9,7 @@
9
9
  "files": [
10
10
  {
11
11
  "path": "atoms/Badge.tsx",
12
- "content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useTheme } from '../theme/UIProvider';\n\nexport type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral';\n\nexport interface BadgeProps {\n label: string;\n variant?: BadgeVariant;\n /** Override manual, gana sobre `variant`. */\n backgroundColor?: string;\n textColor?: string;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n badge: {\n paddingHorizontal: theme.tokens.spacing.md - 2,\n paddingVertical: theme.tokens.spacing.xs + 1,\n borderRadius: theme.tokens.radius.full,\n alignSelf: 'flex-start',\n },\n text: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.bold, letterSpacing: 0.3 },\n}));\n\n/**\n * Badge\n * Pill de estado. `variant` cubre los casos comunes (éxito/error/advertencia/\n * info/neutral); `backgroundColor`/`textColor` quedan para casos que no\n * encajan en el set semántico.\n */\nexport const Badge = memo(({ label, variant = 'neutral', backgroundColor, textColor, style }: BadgeProps) => {\n const theme = useTheme();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const { colors } = theme;\n\n const palette: Record<BadgeVariant, { bg: string; fg: string }> = {\n // fg es el rol `*Text`, no el acento pleno: el acento sobre su propio\n // tinte da ~2.2:1 en light — el label quedaba ilegible.\n success: { bg: colors.successSoft, fg: colors.successText },\n error: { bg: colors.errorSoft, fg: colors.errorText },\n warning: { bg: colors.warningSoft, fg: colors.warningText },\n info: { bg: colors.secondarySoft, fg: colors.secondaryText },\n neutral: { bg: colors.inputBackground, fg: colors.textLight },\n };\n\n return (\n <View style={[styles.badge, { backgroundColor: backgroundColor ?? palette[variant].bg }, style]}>\n <Text allowFontScaling={allowFontScaling} style={[styles.text, { color: textColor ?? palette[variant].fg }]}>\n {label}\n </Text>\n </View>\n );\n});\nBadge.displayName = 'Badge';\n"
12
+ "content": "import React, { memo } from 'react';\nimport { Text, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useTheme } from '../theme/UIProvider';\n\nexport type BadgeVariant = 'success' | 'error' | 'warning' | 'info' | 'neutral';\n\nexport interface BadgeProps {\n label: string;\n variant?: BadgeVariant;\n /** Manual override, takes precedence over `variant`. */\n backgroundColor?: string;\n textColor?: string;\n style?: StyleProp<ViewStyle>;\n}\n\nconst useStyles = createStyles((theme) => ({\n badge: {\n paddingHorizontal: theme.tokens.spacing.md - 2,\n paddingVertical: theme.tokens.spacing.xs + 1,\n borderRadius: theme.tokens.radius.full,\n alignSelf: 'flex-start',\n },\n text: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.bold, letterSpacing: 0.3 },\n}));\n\n/**\n * Badge\n * Pill de estado. `variant` cubre los casos comunes (éxito/error/advertencia/\n * info/neutral); `backgroundColor`/`textColor` quedan para casos que no\n * encajan en el set semántico.\n */\nexport const Badge = memo(({ label, variant = 'neutral', backgroundColor, textColor, style }: BadgeProps) => {\n const theme = useTheme();\n const styles = useStyles();\n const allowFontScaling = useAllowFontScaling();\n const { colors } = theme;\n\n const palette: Record<BadgeVariant, { bg: string; fg: string }> = {\n // fg es el rol `*Text`, no el acento pleno: el acento sobre su propio\n // tinte da ~2.2:1 en light — el label quedaba ilegible.\n success: { bg: colors.successSoft, fg: colors.successText },\n error: { bg: colors.errorSoft, fg: colors.errorText },\n warning: { bg: colors.warningSoft, fg: colors.warningText },\n info: { bg: colors.secondarySoft, fg: colors.secondaryText },\n neutral: { bg: colors.inputBackground, fg: colors.textLight },\n };\n\n return (\n <View style={[styles.badge, { backgroundColor: backgroundColor ?? palette[variant].bg }, style]}>\n <Text allowFontScaling={allowFontScaling} style={[styles.text, { color: textColor ?? palette[variant].fg }]}>\n {label}\n </Text>\n </View>\n );\n});\nBadge.displayName = 'Badge';\n"
13
13
  }
14
14
  ],
15
15
  "registryDependencies": [
@@ -9,7 +9,7 @@
9
9
  "files": [
10
10
  {
11
11
  "path": "organisms/BottomSheet.tsx",
12
- "content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n Dimensions,\n Keyboard,\n KeyboardAvoidingView,\n Modal,\n PanResponder,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useModalSafeAreaBoundary } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\nimport { Transition } from '../atoms/Transition';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\nconst DISMISS_THRESHOLD = 100;\n\nexport type FooterPlacement = 'fixed' | 'scroll' | 'hide-with-keyboard';\n\nexport interface BottomSheetProps {\n visible: boolean;\n onClose: () => void;\n children: React.ReactNode;\n /** Con título aparece el header, con su botón de cerrar. */\n title?: string;\n /** Acciones de la hoja. Dónde vive lo decide `footerPlacement`. */\n footer?: React.ReactNode;\n /**\n * 'fixed' lo ancla al pie de la hoja; 'scroll' lo deja al final del\n * contenido; 'hide-with-keyboard' lo esconde mientras se escribe, que en un\n * formulario largo le devuelve esa franja al campo enfocado. @default 'fixed'\n */\n footerPlacement?: FooterPlacement;\n /** El contenido scrollea cuando no entra. @default true */\n scrollable?: boolean;\n /** Arrastrar la hoja hacia abajo la cierra. @default true */\n draggable?: boolean;\n /** Fracción de la pantalla (0-1) o píxeles. @default 0.9 */\n maxHeight?: number;\n closeAccessibilityLabel?: string;\n containerStyle?: ViewStyle;\n contentStyle?: ViewStyle;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1, justifyContent: 'flex-end' },\n backdrop: { backgroundColor: theme.colors.overlay },\n sheet: {\n backgroundColor: theme.colors.surface,\n borderTopLeftRadius: theme.tokens.radius.xxl,\n borderTopRightRadius: theme.tokens.radius.xxl,\n ...theme.tokens.shadow.lg,\n },\n handleArea: { paddingVertical: theme.tokens.spacing.md, alignItems: 'center' },\n handle: { width: 40, height: 5, borderRadius: 3, backgroundColor: theme.colors.border },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingBottom: theme.tokens.spacing.md,\n },\n headerSpaced: { paddingTop: theme.tokens.spacing.xl },\n title: { flex: 1, fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold },\n content: { paddingHorizontal: theme.tokens.spacing.xl },\n scroll: { flexShrink: 1 },\n footer: {\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingTop: theme.tokens.spacing.md,\n borderTopWidth: 1,\n borderTopColor: theme.colors.border,\n },\n footerInline: { paddingTop: theme.tokens.spacing.xl },\n}));\n\nconst absoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };\n\n/**\n * BottomSheet\n * Hoja que entra desde abajo, con arrastre para cerrar (PanResponder, no un\n * gesture-handler externo: la librería es cero dependencias). Header, footer y\n * scroll son opcionales, así que cubre tanto la hoja desnuda como el formulario\n * completo.\n */\nexport const BottomSheet = memo(\n ({\n visible,\n onClose,\n children,\n title,\n footer,\n footerPlacement = 'fixed',\n scrollable = true,\n draggable = true,\n maxHeight = 0.9,\n closeAccessibilityLabel = 'Close',\n containerStyle,\n contentStyle,\n testID,\n }: BottomSheetProps) => {\n const styles = useStyles();\n const colors = useColors();\n const ModalSafeAreaBoundary = useModalSafeAreaBoundary();\n const [mounted, setMounted] = useState(visible);\n const [keyboardOpen, setKeyboardOpen] = useState(false);\n const progress = useRef(new Animated.Value(0)).current;\n // El PanResponder de más abajo se crea una sola vez; leer `onClose`\n // directo del closure daría siempre el de ese primer render. El ref\n // refleja la última prop sin importar cuándo se creó la closure.\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n\n useEffect(() => {\n const animation = visible\n ? Animated.timing(progress, { toValue: 1, duration: 250, useNativeDriver: true })\n : Animated.timing(progress, { toValue: 0, duration: 200, useNativeDriver: true });\n if (visible) setMounted(true);\n animation.start(({ finished }) => {\n if (!visible && finished) setMounted(false);\n });\n // Si el efecto vuelve a correr (o el componente se desmonta) antes de\n // que termine, se corta: si no, un `finished` tardío podría llamar\n // `setMounted` sobre un componente ya desmontado.\n return () => animation.stop();\n }, [visible, progress]);\n\n useEffect(() => {\n if (footerPlacement !== 'hide-with-keyboard') return;\n const [show, hide] =\n Platform.OS === 'ios' ? ['keyboardWillShow', 'keyboardWillHide'] : ['keyboardDidShow', 'keyboardDidHide'];\n const subs = [\n Keyboard.addListener(show as 'keyboardDidShow', () => setKeyboardOpen(true)),\n Keyboard.addListener(hide as 'keyboardDidHide', () => setKeyboardOpen(false)),\n ];\n return () => subs.forEach((sub) => sub.remove());\n }, [footerPlacement]);\n\n const panResponder = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 5 && Math.abs(gesture.dy) > Math.abs(gesture.dx),\n onPanResponderMove: (_, gesture) => {\n if (gesture.dy > 0) progress.setValue(1 - Math.min(gesture.dy, SCREEN_HEIGHT) / SCREEN_HEIGHT);\n },\n onPanResponderRelease: (_, gesture) => {\n if (gesture.dy > DISMISS_THRESHOLD) {\n onCloseRef.current();\n } else {\n Animated.timing(progress, { toValue: 1, duration: 150, useNativeDriver: true }).start();\n }\n },\n })\n ).current;\n\n if (!mounted) return null;\n\n // Porcentaje y no píxeles: se mide contra el contenedor, que con el teclado\n // abierto es más bajo, así que la hoja entra siempre entera.\n const resolvedMaxHeight: number | `${number}%` = maxHeight <= 1 ? `${maxHeight * 100}%` : maxHeight;\n const hasHeader = !!title;\n\n const scrolledFooter = !!footer && footerPlacement === 'scroll' && (\n <View style={styles.footerInline}>{footer}</View>\n );\n const pinnedFooter =\n !!footer && footerPlacement !== 'scroll' && !(footerPlacement === 'hide-with-keyboard' && keyboardOpen) ? (\n <View style={styles.footer}>{footer}</View>\n ) : null;\n\n const body = scrollable ? (\n <ScrollView\n style={styles.scroll}\n contentContainerStyle={[styles.content, contentStyle]}\n keyboardShouldPersistTaps=\"handled\"\n showsVerticalScrollIndicator={false}\n >\n {children}\n {scrolledFooter}\n </ScrollView>\n ) : (\n <View style={[styles.content, contentStyle]}>\n {children}\n {scrolledFooter}\n </View>\n );\n\n return (\n <Modal visible transparent animationType=\"none\" statusBarTranslucent onRequestClose={onClose}>\n {/* La hoja está anclada abajo, así que el teclado la tapa entera: hay\n que empujarla, no ajustar insets adentro del scroll. Una sola\n compensación, la del contenedor. */}\n <KeyboardAvoidingView\n style={styles.container}\n behavior={Platform.OS === 'ios' ? 'padding' : 'height'}\n >\n <Transition value={progress} preset=\"fade\" style={[absoluteFill, styles.backdrop]}>\n {/* Con header, el botón de cerrar ya expone la acción: repetir el\n label acá deja dos elementos con el mismo nombre. */}\n <Pressable\n style={absoluteFill}\n onPress={onClose}\n testID=\"bottom-sheet-backdrop\"\n accessibilityRole={hasHeader ? undefined : 'button'}\n accessibilityLabel={hasHeader ? undefined : closeAccessibilityLabel}\n />\n </Transition>\n\n {/* Insets medidos dentro de esta ventana nativa (el Modal nativo que\n usa BottomSheet por debajo), no los de la raíz de la app. */}\n <ModalSafeAreaBoundary>\n {(insets) => (\n <Transition\n value={progress}\n preset=\"slide-up\"\n distance={SCREEN_HEIGHT}\n testID={testID}\n style={[\n styles.sheet,\n { maxHeight: resolvedMaxHeight, paddingBottom: Math.max(insets.bottom, 20) },\n containerStyle,\n ]}\n >\n {draggable && (\n <View style={styles.handleArea} testID=\"bottom-sheet-handle\" {...panResponder.panHandlers}>\n <View style={styles.handle} />\n </View>\n )}\n\n {hasHeader && (\n <View style={[styles.header, !draggable && styles.headerSpaced]}>\n <Title style={styles.title} numberOfLines={1}>\n {title}\n </Title>\n <IconButton\n iconName=\"close\"\n size={24}\n color={colors.text}\n onPress={onClose}\n accessibilityLabel={closeAccessibilityLabel}\n />\n </View>\n )}\n\n {body}\n\n {pinnedFooter}\n </Transition>\n )}\n </ModalSafeAreaBoundary>\n </KeyboardAvoidingView>\n </Modal>\n );\n }\n);\nBottomSheet.displayName = 'BottomSheet';\n"
12
+ "content": "import React, { memo, useEffect, useRef, useState } from 'react';\nimport {\n Animated,\n Dimensions,\n Keyboard,\n KeyboardAvoidingView,\n Modal,\n PanResponder,\n Platform,\n Pressable,\n ScrollView,\n View,\n type ViewStyle,\n} from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useColors, useModalSafeAreaBoundary } from '../theme/UIProvider';\nimport { Title } from '../atoms/Text';\nimport { IconButton } from '../atoms/IconButton';\nimport { Transition } from '../atoms/Transition';\n\nconst { height: SCREEN_HEIGHT } = Dimensions.get('window');\nconst DISMISS_THRESHOLD = 100;\n\nexport type FooterPlacement = 'fixed' | 'scroll' | 'hide-with-keyboard';\n\nexport interface BottomSheetProps {\n visible: boolean;\n onClose: () => void;\n children: React.ReactNode;\n /** With a title, the header appears, with its close button. */\n title?: string;\n /** Sheet actions. Where they live is decided by `footerPlacement`. */\n footer?: React.ReactNode;\n /**\n * 'fixed' anchors it to the bottom of the sheet; 'scroll' leaves it at the\n * end of the content; 'hide-with-keyboard' hides it while typing, which in a\n * long form gives that strip back to the focused field. @default 'fixed'\n */\n footerPlacement?: FooterPlacement;\n /** The content scrolls when it doesn't fit. @default true */\n scrollable?: boolean;\n /** Dragging the sheet down closes it. @default true */\n draggable?: boolean;\n /** Fraction of the screen (0-1) or pixels. @default 0.9 */\n maxHeight?: number;\n closeAccessibilityLabel?: string;\n containerStyle?: ViewStyle;\n contentStyle?: ViewStyle;\n testID?: string;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { flex: 1, justifyContent: 'flex-end' },\n backdrop: { backgroundColor: theme.colors.overlay },\n sheet: {\n backgroundColor: theme.colors.surface,\n borderTopLeftRadius: theme.tokens.radius.xxl,\n borderTopRightRadius: theme.tokens.radius.xxl,\n ...theme.tokens.shadow.lg,\n },\n handleArea: { paddingVertical: theme.tokens.spacing.md, alignItems: 'center' },\n handle: { width: 40, height: 5, borderRadius: 3, backgroundColor: theme.colors.border },\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n gap: theme.tokens.spacing.md,\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingBottom: theme.tokens.spacing.md,\n },\n headerSpaced: { paddingTop: theme.tokens.spacing.xl },\n title: { flex: 1, fontSize: theme.tokens.fontSize.xl, fontWeight: theme.tokens.fontWeight.bold },\n content: { paddingHorizontal: theme.tokens.spacing.xl },\n scroll: { flexShrink: 1 },\n footer: {\n paddingHorizontal: theme.tokens.spacing.xl,\n paddingTop: theme.tokens.spacing.md,\n borderTopWidth: 1,\n borderTopColor: theme.colors.border,\n },\n footerInline: { paddingTop: theme.tokens.spacing.xl },\n}));\n\nconst absoluteFill: ViewStyle = { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 };\n\n/**\n * BottomSheet\n * Hoja que entra desde abajo, con arrastre para cerrar (PanResponder, no un\n * gesture-handler externo: la librería es cero dependencias). Header, footer y\n * scroll son opcionales, así que cubre tanto la hoja desnuda como el formulario\n * completo.\n */\nexport const BottomSheet = memo(\n ({\n visible,\n onClose,\n children,\n title,\n footer,\n footerPlacement = 'fixed',\n scrollable = true,\n draggable = true,\n maxHeight = 0.9,\n closeAccessibilityLabel = 'Close',\n containerStyle,\n contentStyle,\n testID,\n }: BottomSheetProps) => {\n const styles = useStyles();\n const colors = useColors();\n const ModalSafeAreaBoundary = useModalSafeAreaBoundary();\n const [mounted, setMounted] = useState(visible);\n const [keyboardOpen, setKeyboardOpen] = useState(false);\n const progress = useRef(new Animated.Value(0)).current;\n // El PanResponder de más abajo se crea una sola vez; leer `onClose`\n // directo del closure daría siempre el de ese primer render. El ref\n // refleja la última prop sin importar cuándo se creó la closure.\n const onCloseRef = useRef(onClose);\n onCloseRef.current = onClose;\n\n useEffect(() => {\n const animation = visible\n ? Animated.timing(progress, { toValue: 1, duration: 250, useNativeDriver: true })\n : Animated.timing(progress, { toValue: 0, duration: 200, useNativeDriver: true });\n if (visible) setMounted(true);\n animation.start(({ finished }) => {\n if (!visible && finished) setMounted(false);\n });\n // Si el efecto vuelve a correr (o el componente se desmonta) antes de\n // que termine, se corta: si no, un `finished` tardío podría llamar\n // `setMounted` sobre un componente ya desmontado.\n return () => animation.stop();\n }, [visible, progress]);\n\n useEffect(() => {\n if (footerPlacement !== 'hide-with-keyboard') return;\n const [show, hide] =\n Platform.OS === 'ios' ? ['keyboardWillShow', 'keyboardWillHide'] : ['keyboardDidShow', 'keyboardDidHide'];\n const subs = [\n Keyboard.addListener(show as 'keyboardDidShow', () => setKeyboardOpen(true)),\n Keyboard.addListener(hide as 'keyboardDidHide', () => setKeyboardOpen(false)),\n ];\n return () => subs.forEach((sub) => sub.remove());\n }, [footerPlacement]);\n\n const panResponder = useRef(\n PanResponder.create({\n onMoveShouldSetPanResponder: (_, gesture) => gesture.dy > 5 && Math.abs(gesture.dy) > Math.abs(gesture.dx),\n onPanResponderMove: (_, gesture) => {\n if (gesture.dy > 0) progress.setValue(1 - Math.min(gesture.dy, SCREEN_HEIGHT) / SCREEN_HEIGHT);\n },\n onPanResponderRelease: (_, gesture) => {\n if (gesture.dy > DISMISS_THRESHOLD) {\n onCloseRef.current();\n } else {\n Animated.timing(progress, { toValue: 1, duration: 150, useNativeDriver: true }).start();\n }\n },\n })\n ).current;\n\n if (!mounted) return null;\n\n // Porcentaje y no píxeles: se mide contra el contenedor, que con el teclado\n // abierto es más bajo, así que la hoja entra siempre entera.\n const resolvedMaxHeight: number | `${number}%` = maxHeight <= 1 ? `${maxHeight * 100}%` : maxHeight;\n const hasHeader = !!title;\n\n const scrolledFooter = !!footer && footerPlacement === 'scroll' && (\n <View style={styles.footerInline}>{footer}</View>\n );\n const pinnedFooter =\n !!footer && footerPlacement !== 'scroll' && !(footerPlacement === 'hide-with-keyboard' && keyboardOpen) ? (\n <View style={styles.footer}>{footer}</View>\n ) : null;\n\n const body = scrollable ? (\n <ScrollView\n style={styles.scroll}\n contentContainerStyle={[styles.content, contentStyle]}\n keyboardShouldPersistTaps=\"handled\"\n showsVerticalScrollIndicator={false}\n >\n {children}\n {scrolledFooter}\n </ScrollView>\n ) : (\n <View style={[styles.content, contentStyle]}>\n {children}\n {scrolledFooter}\n </View>\n );\n\n return (\n <Modal visible transparent animationType=\"none\" statusBarTranslucent onRequestClose={onClose}>\n {/* La hoja está anclada abajo, así que el teclado la tapa entera: hay\n que empujarla, no ajustar insets adentro del scroll. Una sola\n compensación, la del contenedor. */}\n <KeyboardAvoidingView\n style={styles.container}\n behavior={Platform.OS === 'ios' ? 'padding' : 'height'}\n >\n <Transition value={progress} preset=\"fade\" style={[absoluteFill, styles.backdrop]}>\n {/* Con header, el botón de cerrar ya expone la acción: repetir el\n label acá deja dos elementos con el mismo nombre. */}\n <Pressable\n style={absoluteFill}\n onPress={onClose}\n testID=\"bottom-sheet-backdrop\"\n accessibilityRole={hasHeader ? undefined : 'button'}\n accessibilityLabel={hasHeader ? undefined : closeAccessibilityLabel}\n />\n </Transition>\n\n {/* Insets medidos dentro de esta ventana nativa (el Modal nativo que\n usa BottomSheet por debajo), no los de la raíz de la app. */}\n <ModalSafeAreaBoundary>\n {(insets) => (\n <Transition\n value={progress}\n preset=\"slide-up\"\n distance={SCREEN_HEIGHT}\n testID={testID}\n style={[\n styles.sheet,\n { maxHeight: resolvedMaxHeight, paddingBottom: Math.max(insets.bottom, 20) },\n containerStyle,\n ]}\n >\n {draggable && (\n <View style={styles.handleArea} testID=\"bottom-sheet-handle\" {...panResponder.panHandlers}>\n <View style={styles.handle} />\n </View>\n )}\n\n {hasHeader && (\n <View style={[styles.header, !draggable && styles.headerSpaced]}>\n <Title style={styles.title} numberOfLines={1}>\n {title}\n </Title>\n <IconButton\n iconName=\"close\"\n size={24}\n color={colors.text}\n onPress={onClose}\n accessibilityLabel={closeAccessibilityLabel}\n />\n </View>\n )}\n\n {body}\n\n {pinnedFooter}\n </Transition>\n )}\n </ModalSafeAreaBoundary>\n </KeyboardAvoidingView>\n </Modal>\n );\n }\n);\nBottomSheet.displayName = 'BottomSheet';\n"
13
13
  }
14
14
  ],
15
15
  "registryDependencies": [
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "path": "theme/UIProvider.tsx",
24
- "content": "import React, { createContext, useContext, useMemo, useState, useCallback, useRef } from 'react';\nimport { Appearance, useColorScheme } from 'react-native';\nimport { defaultTheme } from './createTheme';\nimport { defaultLabels, type Labels } from './labels';\nimport { zeroInsets, type EdgeInsets } from './insets';\nimport type { Theme, ThemePair } from './types';\nimport { renderDefaultIcon } from '../icons/defaultIcons';\nimport type { IconRenderer } from '../icons/types';\n\nexport type ThemeMode = 'system' | 'light' | 'dark';\n\n/**\n * Un `<Modal>`/`<BottomSheet>` nativo abre su propia ventana (en Android\n * siempre; en iOS según `presentationStyle`), así que los insets medidos en\n * la raíz de la app pueden no valer ahí adentro. Este boundary es el punto de\n * inyección: el default sólo repite los insets ambientes (comportamiento de\n * siempre, cero deps), pero `SafeAreaUIProvider` lo reemplaza por uno que\n * monta un `<SafeAreaProvider>` nuevo y remide dentro de esa ventana.\n */\nexport interface ModalSafeAreaBoundaryProps {\n children: (insets: EdgeInsets) => React.ReactNode;\n}\nexport type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;\n\nfunction DefaultModalSafeAreaBoundary({ children }: ModalSafeAreaBoundaryProps) {\n const insets = useInsets();\n return <>{children(insets)}</>;\n}\n\ninterface UIContextValue {\n theme: Theme;\n mode: ThemeMode;\n setMode: (mode: ThemeMode) => void;\n icons: IconRenderer;\n insets: EdgeInsets;\n labels: Labels;\n allowFontScaling: boolean;\n ModalSafeAreaBoundary: ModalSafeAreaBoundaryComponent;\n}\n\nconst UIContext = createContext<UIContextValue | undefined>(undefined);\n\nexport interface UIProviderProps {\n children: React.ReactNode;\n /** Par light/dark. Por defecto el theme de marca de la librería (créalo con createTheme()). */\n theme?: ThemePair;\n /** Modo inicial (no controlado) si no se pasa `mode`. @default 'system' */\n defaultMode?: ThemeMode;\n /** Modo controlado. Si se pasa junto a onModeChange, el provider no gestiona estado propio. */\n mode?: ThemeMode;\n onModeChange?: (mode: ThemeMode) => void;\n /** Renderer de íconos. Por defecto usa los glifos zero-dep incluidos. */\n icons?: IconRenderer;\n /** Insets de safe area (de useSafeAreaInsets()). Por defecto {0,0,0,0}. */\n insets?: EdgeInsets;\n labels?: Partial<Labels>;\n /** @default false, para paridad con el resto de la librería (accesibilidad primero). */\n allowFontScaling?: boolean;\n /**\n * Fuente de insets que usan `Modal`/`BottomSheet` para su propia ventana\n * nativa. No la pases a mano — `SafeAreaUIProvider` la cablea sola.\n * @default repite los insets ambientes (comportamiento sin safe-area-context)\n */\n ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;\n}\n\n/**\n * UIProvider\n * Único punto de entrada de la librería: resuelve el theme activo (sistema u\n * override manual), e inyecta íconos, insets y textos por defecto a todo el\n * árbol. No persiste el modo — si el consumidor quiere recordar la preferencia\n * entre sesiones, controla `mode`/`onModeChange` y persiste donde prefiera.\n */\nexport function UIProvider({\n children,\n theme = defaultTheme,\n defaultMode = 'system',\n mode: controlledMode,\n onModeChange,\n icons = renderDefaultIcon,\n insets = zeroInsets,\n labels: labelOverrides,\n allowFontScaling = false,\n ModalSafeAreaBoundary = DefaultModalSafeAreaBoundary,\n}: UIProviderProps) {\n const systemScheme = useColorScheme();\n const [uncontrolledMode, setUncontrolledMode] = useState<ThemeMode>(defaultMode);\n const isControlled = controlledMode !== undefined;\n const mode = isControlled ? controlledMode : uncontrolledMode;\n\n const setMode = useCallback(\n (next: ThemeMode) => {\n if (!isControlled) setUncontrolledMode(next);\n onModeChange?.(next);\n },\n [isControlled, onModeChange]\n );\n\n // Por instancia, no por módulo: dos `<UIProvider>` en el mismo árbol (tests,\n // un modal con su propio provider) avisan cada uno si le faltan insets, en\n // vez de que el segundo se calle porque el primero ya avisó una vez.\n const warnedAboutInsets = useRef(false);\n\n // Olvidar `insets` no rompe nada visible al montar: el default de ceros\n // recién se nota cuando abrís un Modal `full` (se mete abajo del notch) o un\n // BottomSheet (queda pegado a la barra de gestos). El aviso llega antes.\n if (__DEV__ && insets === zeroInsets && !warnedAboutInsets.current) {\n warnedAboutInsets.current = true;\n console.warn(\n 'orn-ui: <UIProvider> sin `insets`, se usan {top:0,bottom:0,left:0,right:0}. ' +\n 'Los modales y el Screen quedan sin safe area. Pasá `insets={useSafeAreaInsets()}` ' +\n \"o usá <SafeAreaUIProvider> de 'orn-ui/safe-area', que lo cablea solo.\"\n );\n }\n\n const scheme: 'light' | 'dark' =\n mode === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : mode;\n\n const resolvedTheme = theme[scheme];\n\n const labels = useMemo(\n () => (labelOverrides ? { ...defaultLabels, ...labelOverrides } : defaultLabels),\n [labelOverrides]\n );\n\n const value = useMemo<UIContextValue>(\n () => ({\n theme: resolvedTheme,\n mode,\n setMode,\n icons,\n insets,\n labels,\n allowFontScaling,\n ModalSafeAreaBoundary,\n }),\n [resolvedTheme, mode, setMode, icons, insets, labels, allowFontScaling, ModalSafeAreaBoundary]\n );\n\n return <UIContext.Provider value={value}>{children}</UIContext.Provider>;\n}\n\nfunction useUIContext(): UIContextValue {\n const ctx = useContext(UIContext);\n if (!ctx) {\n throw new Error('orn-ui: this hook must be used within a <UIProvider>.');\n }\n return ctx;\n}\n\nexport function useTheme(): Theme {\n return useUIContext().theme;\n}\n\nexport function useColors(): Theme['colors'] {\n return useUIContext().theme.colors;\n}\n\nexport function useThemeMode() {\n const { mode, setMode } = useUIContext();\n return { mode, setMode };\n}\n\nexport function useIcons(): IconRenderer {\n return useUIContext().icons;\n}\n\nexport function useInsets(): EdgeInsets {\n return useUIContext().insets;\n}\n\nexport function useLabels(): Labels {\n return useUIContext().labels;\n}\n\nexport function useAllowFontScaling(): boolean {\n return useUIContext().allowFontScaling;\n}\n\n/** Interno: lo consumen `Modal`/`BottomSheet` para remedir insets dentro de su propia ventana nativa. */\nexport function useModalSafeAreaBoundary(): ModalSafeAreaBoundaryComponent {\n return useUIContext().ModalSafeAreaBoundary;\n}\n\n/** Re-exportado para que tests/consumidores puedan simular cambios de sistema sin RN real. */\nexport const __testUtils = { Appearance };\n"
24
+ "content": "import React, { createContext, useContext, useMemo, useState, useCallback, useRef } from 'react';\nimport { Appearance, useColorScheme } from 'react-native';\nimport { defaultTheme } from './createTheme';\nimport { defaultLabels, type Labels } from './labels';\nimport { zeroInsets, type EdgeInsets } from './insets';\nimport type { Theme, ThemePair } from './types';\nimport { renderDefaultIcon } from '../icons/defaultIcons';\nimport type { IconRenderer } from '../icons/types';\n\nexport type ThemeMode = 'system' | 'light' | 'dark';\n\n/**\n * Un `<Modal>`/`<BottomSheet>` nativo abre su propia ventana (en Android\n * siempre; en iOS según `presentationStyle`), así que los insets medidos en\n * la raíz de la app pueden no valer ahí adentro. Este boundary es el punto de\n * inyección: el default sólo repite los insets ambientes (comportamiento de\n * siempre, cero deps), pero `SafeAreaUIProvider` lo reemplaza por uno que\n * monta un `<SafeAreaProvider>` nuevo y remide dentro de esa ventana.\n */\nexport interface ModalSafeAreaBoundaryProps {\n children: (insets: EdgeInsets) => React.ReactNode;\n}\nexport type ModalSafeAreaBoundaryComponent = React.ComponentType<ModalSafeAreaBoundaryProps>;\n\nfunction DefaultModalSafeAreaBoundary({ children }: ModalSafeAreaBoundaryProps) {\n const insets = useInsets();\n return <>{children(insets)}</>;\n}\n\ninterface UIContextValue {\n theme: Theme;\n mode: ThemeMode;\n setMode: (mode: ThemeMode) => void;\n icons: IconRenderer;\n insets: EdgeInsets;\n labels: Labels;\n allowFontScaling: boolean;\n ModalSafeAreaBoundary: ModalSafeAreaBoundaryComponent;\n}\n\nconst UIContext = createContext<UIContextValue | undefined>(undefined);\n\nexport interface UIProviderProps {\n children: React.ReactNode;\n /** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */\n theme?: ThemePair;\n /** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */\n defaultMode?: ThemeMode;\n /** Controlled mode. If passed together with onModeChange, the provider doesn't manage its own state. */\n mode?: ThemeMode;\n onModeChange?: (mode: ThemeMode) => void;\n /** Icon renderer. Defaults to the included zero-dep glyphs. */\n icons?: IconRenderer;\n /** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */\n insets?: EdgeInsets;\n labels?: Partial<Labels>;\n /** @default false, for parity with the rest of the library (accessibility first). */\n allowFontScaling?: boolean;\n /**\n * Source of insets used by `Modal`/`BottomSheet` for their own native\n * window. Don't pass this by hand — `SafeAreaUIProvider` wires it up itself.\n * @default repeats the ambient insets (behavior without safe-area-context)\n */\n ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;\n}\n\n/**\n * UIProvider\n * Único punto de entrada de la librería: resuelve el theme activo (sistema u\n * override manual), e inyecta íconos, insets y textos por defecto a todo el\n * árbol. No persiste el modo — si el consumidor quiere recordar la preferencia\n * entre sesiones, controla `mode`/`onModeChange` y persiste donde prefiera.\n */\nexport function UIProvider({\n children,\n theme = defaultTheme,\n defaultMode = 'system',\n mode: controlledMode,\n onModeChange,\n icons = renderDefaultIcon,\n insets = zeroInsets,\n labels: labelOverrides,\n allowFontScaling = false,\n ModalSafeAreaBoundary = DefaultModalSafeAreaBoundary,\n}: UIProviderProps) {\n const systemScheme = useColorScheme();\n const [uncontrolledMode, setUncontrolledMode] = useState<ThemeMode>(defaultMode);\n const isControlled = controlledMode !== undefined;\n const mode = isControlled ? controlledMode : uncontrolledMode;\n\n const setMode = useCallback(\n (next: ThemeMode) => {\n if (!isControlled) setUncontrolledMode(next);\n onModeChange?.(next);\n },\n [isControlled, onModeChange]\n );\n\n // Por instancia, no por módulo: dos `<UIProvider>` en el mismo árbol (tests,\n // un modal con su propio provider) avisan cada uno si le faltan insets, en\n // vez de que el segundo se calle porque el primero ya avisó una vez.\n const warnedAboutInsets = useRef(false);\n\n // Olvidar `insets` no rompe nada visible al montar: el default de ceros\n // recién se nota cuando abrís un Modal `full` (se mete abajo del notch) o un\n // BottomSheet (queda pegado a la barra de gestos). El aviso llega antes.\n if (__DEV__ && insets === zeroInsets && !warnedAboutInsets.current) {\n warnedAboutInsets.current = true;\n console.warn(\n 'orn-ui: <UIProvider> sin `insets`, se usan {top:0,bottom:0,left:0,right:0}. ' +\n 'Los modales y el Screen quedan sin safe area. Pasá `insets={useSafeAreaInsets()}` ' +\n \"o usá <SafeAreaUIProvider> de 'orn-ui/safe-area', que lo cablea solo.\"\n );\n }\n\n const scheme: 'light' | 'dark' =\n mode === 'system' ? (systemScheme === 'dark' ? 'dark' : 'light') : mode;\n\n const resolvedTheme = theme[scheme];\n\n const labels = useMemo(\n () => (labelOverrides ? { ...defaultLabels, ...labelOverrides } : defaultLabels),\n [labelOverrides]\n );\n\n const value = useMemo<UIContextValue>(\n () => ({\n theme: resolvedTheme,\n mode,\n setMode,\n icons,\n insets,\n labels,\n allowFontScaling,\n ModalSafeAreaBoundary,\n }),\n [resolvedTheme, mode, setMode, icons, insets, labels, allowFontScaling, ModalSafeAreaBoundary]\n );\n\n return <UIContext.Provider value={value}>{children}</UIContext.Provider>;\n}\n\nfunction useUIContext(): UIContextValue {\n const ctx = useContext(UIContext);\n if (!ctx) {\n throw new Error('orn-ui: this hook must be used within a <UIProvider>.');\n }\n return ctx;\n}\n\nexport function useTheme(): Theme {\n return useUIContext().theme;\n}\n\nexport function useColors(): Theme['colors'] {\n return useUIContext().theme.colors;\n}\n\nexport function useThemeMode() {\n const { mode, setMode } = useUIContext();\n return { mode, setMode };\n}\n\nexport function useIcons(): IconRenderer {\n return useUIContext().icons;\n}\n\nexport function useInsets(): EdgeInsets {\n return useUIContext().insets;\n}\n\nexport function useLabels(): Labels {\n return useUIContext().labels;\n}\n\nexport function useAllowFontScaling(): boolean {\n return useUIContext().allowFontScaling;\n}\n\n/** Interno: lo consumen `Modal`/`BottomSheet` para remedir insets dentro de su propia ventana nativa. */\nexport function useModalSafeAreaBoundary(): ModalSafeAreaBoundaryComponent {\n return useUIContext().ModalSafeAreaBoundary;\n}\n\n/** Re-exportado para que tests/consumidores puedan simular cambios de sistema sin RN real. */\nexport const __testUtils = { Appearance };\n"
25
25
  },
26
26
  {
27
27
  "path": "theme/colors.ts",
@@ -9,7 +9,7 @@
9
9
  "files": [
10
10
  {
11
11
  "path": "organisms/DateField.tsx",
12
- "content": "import React, { memo, useCallback, useState } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Modal } from './Modal';\nimport { DatePicker, type DatePickerProps, type DateRange } from './DatePicker';\nimport { Button } from '../atoms/Button';\n\nexport interface DateFieldProps\n extends Pick<DatePickerProps, 'minDate' | 'maxDate' | 'monthNames' | 'weekdayNames' | 'firstDayOfWeek'> {\n label?: string;\n required?: boolean;\n /**\n * 'single' elige una fecha; 'range' elige un intervalo con dos toques.\n * @default 'single'\n */\n mode?: 'single' | 'range';\n /** Fecha elegida en mode=\"single\". */\n value?: Date;\n /** Se llama en mode=\"single\". */\n onChange?: (date: Date) => void;\n /** Rango elegido en mode=\"range\". */\n range?: DateRange;\n /** Se llama en mode=\"range\", tanto al fijar el inicio como al cerrar el rango. */\n onRangeChange?: (range: DateRange) => void;\n placeholder?: string;\n error?: string;\n disabled?: boolean;\n /** Formatea la fecha en el campo. @default \"Month D, YYYY\" */\n format?: (date: Date) => string;\n /** Título del modal del calendario. @default el `selectDate` de `useLabels()` */\n modalTitle?: string;\n /**\n * Si se pasa, el modal muestra un botón que borra la selección y cierra.\n * Sin esto no hay botón: el \"Clear\" anterior sólo cerraba, sin limpiar nada.\n */\n onClear?: () => void;\n /** Texto de ese botón. @default el `clear` de `useLabels()` */\n clearLabel?: string;\n containerStyle?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nfunction defaultFormat(date: Date, monthNames: string[]): string {\n return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { marginBottom: theme.tokens.spacing.lg, width: '100%', gap: theme.tokens.spacing.sm },\n label: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.text },\n required: { color: theme.colors.errorText },\n field: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: theme.colors.inputBackground,\n borderRadius: theme.tokens.radius.lg,\n borderWidth: 2,\n borderColor: theme.colors.border,\n minHeight: 50,\n paddingHorizontal: theme.tokens.spacing.lg,\n },\n fieldError: { borderColor: theme.colors.error },\n fieldText: { fontSize: theme.tokens.fontSize.lg, color: theme.colors.text },\n placeholder: { color: theme.colors.textLight },\n errorText: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.errorText },\n}));\n\n/**\n * DateField\n * Campo de fecha: se ve como un Input, abre un `DatePicker` en un Modal al\n * tocarlo. Sin picker nativo del sistema, así que se ve y se comporta igual\n * en iOS y Android.\n */\nexport const DateField = memo(\n ({\n label,\n required = false,\n mode = 'single',\n value,\n onChange,\n range,\n onRangeChange,\n placeholder = 'Select a date',\n error,\n disabled = false,\n format,\n modalTitle,\n onClear,\n clearLabel,\n containerStyle,\n monthNames,\n testID,\n ...pickerProps\n }: DateFieldProps) => {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const allowFontScaling = useAllowFontScaling();\n const [open, setOpen] = useState(false);\n\n const months = monthNames ?? labels.months;\n const isRange = mode === 'range';\n\n const formatDate = (date: Date) => (format ? format(date) : defaultFormat(date, months));\n\n // El calendario se cierra solo cuando ya no falta nada por elegir: en\n // rango, recién cuando el segundo toque cierra el intervalo.\n const handleChange = useCallback(\n (date: Date) => {\n onChange?.(date);\n setOpen(false);\n },\n [onChange]\n );\n\n const handleRangeChange = useCallback(\n (next: DateRange) => {\n onRangeChange?.(next);\n if (next.end) setOpen(false);\n },\n [onRangeChange]\n );\n\n const handleClear = useCallback(() => {\n onClear?.();\n setOpen(false);\n }, [onClear]);\n\n const hasValue = isRange ? !!range?.start : !!value;\n let display = placeholder;\n if (isRange && range?.start) {\n display = range.end ? `${formatDate(range.start)} — ${formatDate(range.end)}` : formatDate(range.start);\n } else if (!isRange && value) {\n display = formatDate(value);\n }\n\n return (\n <View style={[styles.container, containerStyle]} testID={testID}>\n {!!label && (\n <Text allowFontScaling={allowFontScaling} style={styles.label}>\n {label}\n {required && <Text style={styles.required}> *</Text>}\n </Text>\n )}\n\n <TouchableOpacity\n style={[styles.field, !!error && styles.fieldError, disabled && { opacity: 0.5 }]}\n onPress={() => setOpen(true)}\n disabled={disabled}\n activeOpacity={0.7}\n accessibilityRole=\"button\"\n accessibilityLabel={label ?? placeholder}\n accessibilityState={{ disabled, expanded: open }}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !hasValue && styles.placeholder]}>\n {display}\n </Text>\n <Text allowFontScaling={allowFontScaling} style={{ color: colors.textLight }}>\n {'▾'}\n </Text>\n </TouchableOpacity>\n\n {!!error && (\n <Text allowFontScaling={allowFontScaling} style={styles.errorText}>\n {error}\n </Text>\n )}\n\n <Modal\n visible={open}\n onClose={() => setOpen(false)}\n title={modalTitle ?? labels.selectDate}\n variant=\"overlay\"\n scrollable={false}\n footer={\n onClear ? <Button title={clearLabel ?? labels.clear} variant=\"ghost\" onPress={handleClear} /> : undefined\n }\n >\n <DatePicker\n mode={mode}\n value={value}\n range={range}\n monthNames={months}\n {...pickerProps}\n onChange={handleChange}\n onRangeChange={handleRangeChange}\n />\n </Modal>\n </View>\n );\n }\n);\nDateField.displayName = 'DateField';\n"
12
+ "content": "import React, { memo, useCallback, useState } from 'react';\nimport { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';\nimport { createStyles } from '../theme/createStyles';\nimport { useAllowFontScaling, useColors, useLabels } from '../theme/UIProvider';\nimport { Modal } from './Modal';\nimport { DatePicker, type DatePickerProps, type DateRange } from './DatePicker';\nimport { Button } from '../atoms/Button';\n\nexport interface DateFieldProps\n extends Pick<DatePickerProps, 'minDate' | 'maxDate' | 'monthNames' | 'weekdayNames' | 'firstDayOfWeek'> {\n label?: string;\n required?: boolean;\n /**\n * 'single' picks one date; 'range' picks an interval with two taps.\n * @default 'single'\n */\n mode?: 'single' | 'range';\n /** Selected date in mode=\"single\". */\n value?: Date;\n /** Called in mode=\"single\". */\n onChange?: (date: Date) => void;\n /** Selected range in mode=\"range\". */\n range?: DateRange;\n /** Called in mode=\"range\", both when setting the start and when closing the range. */\n onRangeChange?: (range: DateRange) => void;\n placeholder?: string;\n error?: string;\n disabled?: boolean;\n /** Formats the date in the field. @default \"Month D, YYYY\" */\n format?: (date: Date) => string;\n /** Title of the calendar modal. @default the `selectDate` from `useLabels()` */\n modalTitle?: string;\n /**\n * If provided, the modal shows a button that clears the selection and closes.\n * Without this there's no button: the previous \"Clear\" only closed, without clearing anything.\n */\n onClear?: () => void;\n /** Text for that button. @default the `clear` from `useLabels()` */\n clearLabel?: string;\n containerStyle?: StyleProp<ViewStyle>;\n testID?: string;\n}\n\nfunction defaultFormat(date: Date, monthNames: string[]): string {\n return `${monthNames[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;\n}\n\nconst useStyles = createStyles((theme) => ({\n container: { marginBottom: theme.tokens.spacing.lg, width: '100%', gap: theme.tokens.spacing.sm },\n label: { fontSize: theme.tokens.fontSize.md, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.text },\n required: { color: theme.colors.errorText },\n field: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: theme.colors.inputBackground,\n borderRadius: theme.tokens.radius.lg,\n borderWidth: 2,\n borderColor: theme.colors.border,\n minHeight: 50,\n paddingHorizontal: theme.tokens.spacing.lg,\n },\n fieldError: { borderColor: theme.colors.error },\n fieldText: { fontSize: theme.tokens.fontSize.lg, color: theme.colors.text },\n placeholder: { color: theme.colors.textLight },\n errorText: { fontSize: theme.tokens.fontSize.sm, color: theme.colors.errorText },\n}));\n\n/**\n * DateField\n * Campo de fecha: se ve como un Input, abre un `DatePicker` en un Modal al\n * tocarlo. Sin picker nativo del sistema, así que se ve y se comporta igual\n * en iOS y Android.\n */\nexport const DateField = memo(\n ({\n label,\n required = false,\n mode = 'single',\n value,\n onChange,\n range,\n onRangeChange,\n placeholder = 'Select a date',\n error,\n disabled = false,\n format,\n modalTitle,\n onClear,\n clearLabel,\n containerStyle,\n monthNames,\n testID,\n ...pickerProps\n }: DateFieldProps) => {\n const colors = useColors();\n const styles = useStyles();\n const labels = useLabels();\n const allowFontScaling = useAllowFontScaling();\n const [open, setOpen] = useState(false);\n\n const months = monthNames ?? labels.months;\n const isRange = mode === 'range';\n\n const formatDate = (date: Date) => (format ? format(date) : defaultFormat(date, months));\n\n // El calendario se cierra solo cuando ya no falta nada por elegir: en\n // rango, recién cuando el segundo toque cierra el intervalo.\n const handleChange = useCallback(\n (date: Date) => {\n onChange?.(date);\n setOpen(false);\n },\n [onChange]\n );\n\n const handleRangeChange = useCallback(\n (next: DateRange) => {\n onRangeChange?.(next);\n if (next.end) setOpen(false);\n },\n [onRangeChange]\n );\n\n const handleClear = useCallback(() => {\n onClear?.();\n setOpen(false);\n }, [onClear]);\n\n const hasValue = isRange ? !!range?.start : !!value;\n let display = placeholder;\n if (isRange && range?.start) {\n display = range.end ? `${formatDate(range.start)} — ${formatDate(range.end)}` : formatDate(range.start);\n } else if (!isRange && value) {\n display = formatDate(value);\n }\n\n return (\n <View style={[styles.container, containerStyle]} testID={testID}>\n {!!label && (\n <Text allowFontScaling={allowFontScaling} style={styles.label}>\n {label}\n {required && <Text style={styles.required}> *</Text>}\n </Text>\n )}\n\n <TouchableOpacity\n style={[styles.field, !!error && styles.fieldError, disabled && { opacity: 0.5 }]}\n onPress={() => setOpen(true)}\n disabled={disabled}\n activeOpacity={0.7}\n accessibilityRole=\"button\"\n accessibilityLabel={hasValue ? `${label ?? placeholder}, ${display}` : (label ?? placeholder)}\n accessibilityState={{ disabled, expanded: open }}\n >\n <Text allowFontScaling={allowFontScaling} style={[styles.fieldText, !hasValue && styles.placeholder]}>\n {display}\n </Text>\n <Text allowFontScaling={allowFontScaling} style={{ color: colors.textLight }}>\n {'▾'}\n </Text>\n </TouchableOpacity>\n\n {!!error && (\n <Text allowFontScaling={allowFontScaling} style={styles.errorText}>\n {error}\n </Text>\n )}\n\n <Modal\n visible={open}\n onClose={() => setOpen(false)}\n title={modalTitle ?? labels.selectDate}\n variant=\"overlay\"\n scrollable={false}\n footer={\n onClear ? <Button title={clearLabel ?? labels.clear} variant=\"ghost\" onPress={handleClear} /> : undefined\n }\n >\n <DatePicker\n mode={mode}\n value={value}\n range={range}\n monthNames={months}\n {...pickerProps}\n onChange={handleChange}\n onRangeChange={handleRangeChange}\n />\n </Modal>\n </View>\n );\n }\n);\nDateField.displayName = 'DateField';\n"
13
13
  }
14
14
  ],
15
15
  "registryDependencies": [