@sincpro/mobile-ui 1.0.1 → 2.0.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.
Files changed (168) hide show
  1. package/README.md +701 -231
  2. package/dist/Actions/Actions.Button.d.ts +15 -12
  3. package/dist/Actions/Actions.Button.js +25 -14
  4. package/dist/Actions/Actions.Dock.d.ts +20 -0
  5. package/dist/Actions/Actions.Dock.js +89 -0
  6. package/dist/Actions/Actions.FAB.d.ts +2 -1
  7. package/dist/Actions/Actions.FAB.js +5 -3
  8. package/dist/Actions/Actions.FilterButton.d.ts +10 -2
  9. package/dist/Actions/Actions.FilterButton.js +21 -6
  10. package/dist/Actions/Actions.IconButton.d.ts +4 -2
  11. package/dist/Actions/Actions.IconButton.js +7 -3
  12. package/dist/Actions/index.d.ts +6 -2
  13. package/dist/Actions/index.js +4 -1
  14. package/dist/Dialog/BottomSheet/BottomSheet.Root.js +22 -34
  15. package/dist/Dialog/Dialog.ActionSheet.d.ts +2 -1
  16. package/dist/Dialog/Dialog.ActionSheet.js +17 -21
  17. package/dist/Dialog/Dialog.Confirmation.js +8 -6
  18. package/dist/Dialog/Dialog.EditValue.js +3 -1
  19. package/dist/Dialog/Dialog.NativeSheet.d.ts +10 -0
  20. package/dist/Dialog/Dialog.NativeSheet.js +21 -0
  21. package/dist/Dialog/Dialog.Sheet.d.ts +5 -1
  22. package/dist/Dialog/Dialog.Sheet.js +30 -31
  23. package/dist/Display/Display.Accordion.js +7 -3
  24. package/dist/Display/Display.AmountSummary.d.ts +28 -0
  25. package/dist/Display/Display.AmountSummary.js +25 -0
  26. package/dist/Display/Display.Carousel.d.ts +27 -4
  27. package/dist/Display/Display.Carousel.js +112 -12
  28. package/dist/Display/Display.CopyableText.d.ts +9 -2
  29. package/dist/Display/Display.CopyableText.js +75 -7
  30. package/dist/Display/Display.ExpoImage.d.ts +8 -0
  31. package/dist/Display/Display.ExpoImage.js +15 -0
  32. package/dist/Display/Display.Icon.js +2 -1
  33. package/dist/Display/Display.Image.d.ts +10 -6
  34. package/dist/Display/Display.Image.js +39 -6
  35. package/dist/Display/Display.KeyValue.d.ts +11 -4
  36. package/dist/Display/Display.KeyValue.js +6 -4
  37. package/dist/Display/Display.ListGroup.d.ts +23 -0
  38. package/dist/Display/Display.ListGroup.js +17 -0
  39. package/dist/Display/Display.ListItem.d.ts +15 -2
  40. package/dist/Display/Display.ListItem.js +25 -8
  41. package/dist/Display/Display.Logo.d.ts +2 -2
  42. package/dist/Display/Display.Logo.js +3 -2
  43. package/dist/Display/Display.MediaCard.d.ts +2 -2
  44. package/dist/Display/Display.MediaEmbed.d.ts +2 -2
  45. package/dist/Display/Display.ProductCard.d.ts +2 -2
  46. package/dist/Display/Display.SectionHeader.d.ts +11 -1
  47. package/dist/Display/Display.SectionHeader.js +11 -2
  48. package/dist/Display/Display.Story.d.ts +3 -3
  49. package/dist/Display/Display.Story.js +5 -4
  50. package/dist/Display/Display.StoryTray.d.ts +4 -4
  51. package/dist/Display/Display.StoryTray.js +3 -2
  52. package/dist/Display/Display.StoryViewer.d.ts +3 -3
  53. package/dist/Display/Display.StoryViewer.js +6 -3
  54. package/dist/Display/Display.SwipeableRow.d.ts +3 -1
  55. package/dist/Display/Display.SwipeableRow.js +3 -3
  56. package/dist/Display/index.d.ts +13 -0
  57. package/dist/Display/index.js +6 -0
  58. package/dist/Feedback/Feedback.Banner.js +1 -1
  59. package/dist/Feedback/Feedback.DomainSplashScreen.d.ts +13 -4
  60. package/dist/Feedback/Feedback.DomainSplashScreen.js +68 -53
  61. package/dist/Feedback/Feedback.EmptyState.d.ts +2 -2
  62. package/dist/Feedback/Feedback.EmptyState.js +3 -2
  63. package/dist/Feedback/Feedback.Error.d.ts +2 -1
  64. package/dist/Feedback/Feedback.Error.js +4 -3
  65. package/dist/Feedback/Feedback.Loading.d.ts +6 -2
  66. package/dist/Feedback/Feedback.Loading.js +2 -2
  67. package/dist/Feedback/Feedback.Progress.d.ts +6 -3
  68. package/dist/Feedback/Feedback.Progress.js +29 -19
  69. package/dist/Feedback/Feedback.ProgressCircle.d.ts +3 -2
  70. package/dist/Feedback/Feedback.ProgressCircle.js +10 -7
  71. package/dist/Feedback/Feedback.Skeleton.d.ts +4 -4
  72. package/dist/Feedback/Feedback.Skeleton.js +15 -3
  73. package/dist/Feedback/Feedback.Spinner.d.ts +3 -3
  74. package/dist/Feedback/Feedback.Spinner.js +40 -9
  75. package/dist/Feedback/Feedback.Splash.d.ts +2 -2
  76. package/dist/Feedback/Feedback.Splash.js +5 -3
  77. package/dist/Feedback/Toast.context.d.ts +64 -3
  78. package/dist/Feedback/Toast.context.js +112 -31
  79. package/dist/Feedback/index.d.ts +15 -1
  80. package/dist/Feedback/index.js +4 -1
  81. package/dist/Form/Form.Checkbox.d.ts +3 -1
  82. package/dist/Form/Form.Checkbox.js +2 -2
  83. package/dist/Form/Form.DatePicker.js +3 -2
  84. package/dist/Form/Form.Dropdown.d.ts +15 -3
  85. package/dist/Form/Form.Dropdown.js +21 -6
  86. package/dist/Form/Form.ErrorSummary.d.ts +23 -0
  87. package/dist/Form/Form.ErrorSummary.js +53 -0
  88. package/dist/Form/Form.FilterButton.d.ts +1 -1
  89. package/dist/Form/Form.Input.d.ts +4 -3
  90. package/dist/Form/Form.Input.js +2 -2
  91. package/dist/Form/Form.KeyboardForm.d.ts +11 -0
  92. package/dist/Form/Form.KeyboardForm.js +9 -0
  93. package/dist/Form/Form.KeyboardToolbar.d.ts +21 -0
  94. package/dist/Form/Form.KeyboardToolbar.js +70 -0
  95. package/dist/Form/Form.Select.d.ts +2 -1
  96. package/dist/Form/Form.Select.js +26 -26
  97. package/dist/Form/index.d.ts +9 -0
  98. package/dist/Form/index.js +6 -0
  99. package/dist/Navigation/Navigation.AppBar.js +8 -6
  100. package/dist/Navigation/Navigation.BottomNav.js +10 -5
  101. package/dist/Navigation/Navigation.Header.d.ts +2 -2
  102. package/dist/Navigation/Navigation.Menu.js +10 -3
  103. package/dist/Navigation/Navigation.RowItem.d.ts +1 -1
  104. package/dist/Navigation/Navigation.RowItem.js +2 -2
  105. package/dist/Navigation/Navigation.Search.d.ts +41 -0
  106. package/dist/Navigation/Navigation.Search.js +47 -0
  107. package/dist/Navigation/Navigation.SearchBar.d.ts +5 -1
  108. package/dist/Navigation/Navigation.SearchBar.js +25 -7
  109. package/dist/Navigation/Navigation.SearchSuggestions.d.ts +14 -0
  110. package/dist/Navigation/Navigation.SearchSuggestions.js +7 -0
  111. package/dist/Navigation/Navigation.SegmentedControl.d.ts +11 -1
  112. package/dist/Navigation/Navigation.SegmentedControl.js +29 -15
  113. package/dist/Navigation/index.d.ts +13 -0
  114. package/dist/Navigation/index.js +4 -0
  115. package/dist/Typography/Typography.Heading.js +1 -1
  116. package/dist/Typography/Typography.SelectableText.d.ts +10 -0
  117. package/dist/Typography/Typography.SelectableText.js +16 -0
  118. package/dist/Typography/Typography.Text.js +4 -20
  119. package/dist/Typography/index.d.ts +3 -0
  120. package/dist/Typography/index.js +3 -0
  121. package/dist/branding.d.ts +3 -2
  122. package/dist/layouts/AuthContainer.d.ts +4 -1
  123. package/dist/layouts/AuthContainer.js +3 -3
  124. package/dist/primitives/BaseButton.d.ts +15 -12
  125. package/dist/primitives/BaseButton.js +21 -9
  126. package/dist/theme/typography.d.ts +16 -1
  127. package/dist/theme/typography.js +19 -0
  128. package/dist/utils/criteria.d.ts +56 -0
  129. package/dist/utils/criteria.js +99 -0
  130. package/dist/utils/image.d.ts +3 -0
  131. package/dist/utils/image.js +13 -0
  132. package/dist/utils/keys.d.ts +1 -0
  133. package/dist/utils/keys.js +7 -0
  134. package/dist/utils/progress.d.ts +1 -0
  135. package/dist/utils/progress.js +5 -0
  136. package/dist/utils/reorder.d.ts +4 -0
  137. package/dist/utils/reorder.js +29 -0
  138. package/dist/utils/useReducedMotion.d.ts +1 -0
  139. package/dist/utils/useReducedMotion.js +43 -0
  140. package/dist/utils/useSheetTranslation.d.ts +5 -0
  141. package/dist/utils/useSheetTranslation.js +23 -0
  142. package/dist/views/AppSplashView.d.ts +3 -2
  143. package/dist/views/AppSplashView.js +3 -2
  144. package/dist/views/AuthFormView.d.ts +6 -3
  145. package/dist/views/AuthFormView.js +2 -2
  146. package/dist/views/FormViewV2.d.ts +9 -4
  147. package/dist/views/FormViewV2.js +35 -16
  148. package/dist/views/ListViewV2.context.d.ts +3 -1
  149. package/dist/views/ListViewV2.context.js +41 -21
  150. package/dist/views/ListViewV2.d.ts +18 -7
  151. package/dist/views/ListViewV2.js +69 -35
  152. package/dist/views/types/IListView.js +6 -5
  153. package/dist/widgets/CollectionOptions.d.ts +15 -0
  154. package/dist/widgets/CollectionOptions.js +129 -0
  155. package/dist/widgets/FilterBar.d.ts +21 -0
  156. package/dist/widgets/FilterBar.js +14 -0
  157. package/dist/widgets/FilterBuilder.d.ts +10 -0
  158. package/dist/widgets/FilterBuilder.js +115 -0
  159. package/dist/widgets/HomeHeader.d.ts +2 -2
  160. package/dist/widgets/PromoModal.js +3 -1
  161. package/dist/widgets/QueryChoice.d.ts +10 -0
  162. package/dist/widgets/QueryChoice.js +13 -0
  163. package/dist/widgets/ReorderableList.d.ts +51 -0
  164. package/dist/widgets/ReorderableList.js +138 -0
  165. package/dist/widgets/ScreenHeader.d.ts +2 -2
  166. package/dist/widgets/index.d.ts +4 -0
  167. package/dist/widgets/index.js +3 -0
  168. package/package.json +18 -1
@@ -8,7 +8,7 @@ import { ActivityIndicator, TouchableOpacity, View } from "react-native";
8
8
  // STYLE VARIANTS
9
9
  // ============================================================================
10
10
  const button = tv({
11
- base: "rounded-md items-center justify-center flex-row min-h-[44px]",
11
+ base: "rounded-md items-center justify-center flex-row min-h-[48px]",
12
12
  variants: {
13
13
  variant: {
14
14
  primary: "bg-accent",
@@ -18,12 +18,12 @@ const button = tv({
18
18
  outlineDanger: "bg-transparent border border-danger",
19
19
  outlineBlack: "bg-transparent border border-border-strong",
20
20
  flat: "bg-text-primary/10",
21
- link: "bg-transparent min-h-0",
21
+ link: "bg-transparent",
22
22
  transparent: "bg-transparent border border-border-light",
23
23
  },
24
24
  size: {
25
- small: "py-1.5 px-3 min-h-[36px]",
26
- medium: "py-2.5 px-4 min-h-[44px]",
25
+ small: "py-1.5 px-3 min-h-[48px]",
26
+ medium: "py-2.5 px-4 min-h-[48px]",
27
27
  large: "py-3 px-5 min-h-[48px]",
28
28
  },
29
29
  disabled: {
@@ -192,7 +192,7 @@ function getIconSize(size) {
192
192
  function getTextVariant(size) {
193
193
  return textVariantMap[size] ?? "button";
194
194
  }
195
- export const BaseButton = ({ title, onPress, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, }) => {
195
+ export const BaseButton = ({ title, onPress, variant = "primary", size = "medium", loading = false, disabled = false, className, icon, testID, accessibilityLabel, accessibilityState, ...accessibilityProps }) => {
196
196
  const textColorClass = textColor({
197
197
  variant: variant ?? "primary",
198
198
  disabled,
@@ -200,15 +200,27 @@ export const BaseButton = ({ title, onPress, variant = "primary", size = "medium
200
200
  const loadingColorValue = getLoadingColor(variant ?? "primary");
201
201
  const textVariantValue = getTextVariant(size ?? "medium");
202
202
  if (variant === "link") {
203
- return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: onPress, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { className: textColorClass, variant: textVariantValue, children: title })) }));
203
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: className, disabled: disabled || loading, onPress: onPress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
204
+ ...accessibilityState,
205
+ disabled: disabled || loading,
206
+ busy: loading,
207
+ }, style: { minHeight: 48, minWidth: 48, justifyContent: "center" }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, style: { flexShrink: 1 }, variant: textVariantValue, children: title })) }));
204
208
  }
205
- return (_jsxs(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: onPress, children: [icon && !loading && _jsx(View, { className: "mr-2", children: icon }), loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { className: textColorClass, semibold: variant === "primary", variant: textVariantValue, children: title }))] }));
209
+ return (_jsxs(TouchableOpacity, { activeOpacity: 0.8, className: button({ variant, size, disabled, className }), disabled: disabled || loading, onPress: onPress, ...accessibilityProps, accessibilityLabel: accessibilityLabel ?? title, accessibilityRole: "button", accessibilityState: {
210
+ ...accessibilityState,
211
+ disabled: disabled || loading,
212
+ busy: loading,
213
+ }, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: [icon && !loading && _jsx(View, { className: "mr-2", children: icon }), loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Typography.Text, { allowFontScaling: true, className: textColorClass, semibold: variant === "primary", style: { flexShrink: 1 }, variant: textVariantValue, children: title }))] }));
206
214
  };
207
- export const BaseButtonIcon = ({ icon, onPress, variant = "primary", size = "medium", loading = false, disabled = false, className, }) => {
215
+ export const BaseButtonIcon = ({ icon, onPress, variant = "primary", size = "medium", loading = false, disabled = false, className, testID, accessibilityState, ...accessibilityProps }) => {
208
216
  const iconColorValue = getIconColor(variant ?? "primary", disabled);
209
217
  const loadingColorValue = getLoadingColor(variant ?? "primary");
210
218
  const iconSizeValue = getIconSize(size ?? "medium");
211
- return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: onPress, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
219
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.8, className: buttonIcon({ variant, size, disabled, className }), disabled: disabled || loading, onPress: onPress, ...accessibilityProps, accessibilityRole: "button", accessibilityState: {
220
+ ...accessibilityState,
221
+ disabled: disabled || loading,
222
+ busy: loading,
223
+ }, style: { minHeight: 48, minWidth: 48 }, testID: testID, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
212
224
  };
213
225
  // ============================================================================
214
226
  // COMPOUND COMPONENT
@@ -10,6 +10,7 @@ export declare const fontFamilies: Record<FontRole, string>;
10
10
  export declare function configureFonts(overrides: Partial<Record<FontRole, string>>): void;
11
11
  /** Reset all font roles to the defaults (IBM Plex Sans + IBM Plex Mono). Useful in tests and stories. */
12
12
  export declare function resetFonts(): void;
13
+ export declare function resolveTextFontFamily(variant: keyof typeof typographyVariants, bold?: boolean, semibold?: boolean): string;
13
14
  export declare const fontWeights: {
14
15
  readonly light: "300";
15
16
  readonly regular: "400";
@@ -103,6 +104,13 @@ export declare const typographyVariants: {
103
104
  readonly lineHeight: number;
104
105
  readonly letterSpacing: 0;
105
106
  };
107
+ readonly bodyReadable: {
108
+ readonly fontSize: 17;
109
+ readonly fontFamily: string;
110
+ readonly fontWeight: "400";
111
+ readonly lineHeight: number;
112
+ readonly letterSpacing: 0;
113
+ };
106
114
  readonly bodyLarge: {
107
115
  readonly fontSize: 16;
108
116
  readonly fontFamily: string;
@@ -314,6 +322,13 @@ export declare const Typography: {
314
322
  readonly lineHeight: number;
315
323
  readonly letterSpacing: 0;
316
324
  };
325
+ readonly bodyReadable: {
326
+ readonly fontSize: 17;
327
+ readonly fontFamily: string;
328
+ readonly fontWeight: "400";
329
+ readonly lineHeight: number;
330
+ readonly letterSpacing: 0;
331
+ };
317
332
  readonly bodyLarge: {
318
333
  readonly fontSize: 16;
319
334
  readonly fontFamily: string;
@@ -421,7 +436,7 @@ export declare const Typography: {
421
436
  readonly letterSpacing: 0;
422
437
  };
423
438
  };
424
- readonly variantFontRole: Record<"label" | "button" | "title" | "display" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "bodyLarge" | "body" | "bodySmall" | "labelSmall" | "data" | "dataLarge" | "overline" | "caption" | "captionSmall" | "buttonSmall" | "buttonLarge" | "subtitle", FontRole>;
439
+ readonly variantFontRole: Record<"display" | "label" | "button" | "title" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "bodyReadable" | "bodyLarge" | "body" | "bodySmall" | "labelSmall" | "data" | "dataLarge" | "overline" | "caption" | "captionSmall" | "buttonSmall" | "buttonLarge" | "subtitle", FontRole>;
425
440
  readonly createTextStyle: (variant: keyof typeof typographyVariants, overrides?: Partial<TextStyle>) => TextStyle;
426
441
  readonly createCustomTextStyle: (fontSize: keyof typeof fontSizes, fontFamily: keyof typeof fontFamilies, options?: {
427
442
  lineHeight?: keyof typeof lineHeights;
@@ -26,6 +26,17 @@ export function configureFonts(overrides) {
26
26
  export function resetFonts() {
27
27
  Object.assign(fontFamilies, DEFAULT_FONT_FAMILIES);
28
28
  }
29
+ export function resolveTextFontFamily(variant, bold = false, semibold = false) {
30
+ const variantFamily = fontFamilies[variantFontRole[variant]] ?? typographyVariants[variant].fontFamily;
31
+ const isBrandedFace = !variantFamily.startsWith("Inter_") && !variantFamily.startsWith("IBMPlexSans_");
32
+ return isBrandedFace
33
+ ? variantFamily
34
+ : bold
35
+ ? fontFamilies.extraBold
36
+ : semibold
37
+ ? fontFamilies.semiBold
38
+ : variantFamily;
39
+ }
29
40
  export const fontWeights = {
30
41
  light: "300",
31
42
  regular: "400",
@@ -126,6 +137,13 @@ export const typographyVariants = {
126
137
  letterSpacing: letterSpacing.normal,
127
138
  },
128
139
  // Body
140
+ bodyReadable: {
141
+ fontSize: 17,
142
+ fontFamily: fontFamilies.regular,
143
+ fontWeight: fontWeights.regular,
144
+ lineHeight: 17 * lineHeights.normal,
145
+ letterSpacing: letterSpacing.normal,
146
+ },
129
147
  bodyLarge: {
130
148
  fontSize: fontSizes.lg,
131
149
  fontFamily: fontFamilies.regular,
@@ -251,6 +269,7 @@ export const variantFontRole = {
251
269
  h4: "medium",
252
270
  h5: "medium",
253
271
  h6: "medium",
272
+ bodyReadable: "regular",
254
273
  bodyLarge: "regular",
255
274
  body: "regular",
256
275
  bodySmall: "regular",
@@ -0,0 +1,56 @@
1
+ export type CriteriaValue = string | number | boolean;
2
+ export interface CriteriaOperator {
3
+ id: string;
4
+ label: string;
5
+ arity: "none" | "one" | "range";
6
+ }
7
+ export interface CriteriaField {
8
+ id: string;
9
+ label: string;
10
+ type: "text" | "number" | "date" | "choice";
11
+ operators: readonly CriteriaOperator[];
12
+ options?: readonly {
13
+ label: string;
14
+ value: CriteriaValue;
15
+ }[];
16
+ }
17
+ export interface CriteriaCondition {
18
+ kind: "condition";
19
+ field: string;
20
+ operator: string;
21
+ values: readonly CriteriaValue[];
22
+ }
23
+ export interface CriteriaGroup {
24
+ kind: "group";
25
+ join: "all" | "any" | "none";
26
+ children: readonly CriteriaNode[];
27
+ }
28
+ export type CriteriaNode = CriteriaCondition | CriteriaGroup;
29
+ export interface CollectionSort {
30
+ field: string;
31
+ direction: "asc" | "desc";
32
+ }
33
+ export interface CollectionQuery {
34
+ search: string;
35
+ filter: CriteriaGroup;
36
+ sort: readonly CollectionSort[];
37
+ groupBy: readonly string[];
38
+ }
39
+ export interface CollectionField {
40
+ id: string;
41
+ label: string;
42
+ ascLabel?: string;
43
+ descLabel?: string;
44
+ }
45
+ export interface SavedCollectionQuery {
46
+ id: string;
47
+ label: string;
48
+ query: CollectionQuery;
49
+ }
50
+ export declare function parseCriteriaDate(value: CriteriaValue | undefined): Date | null;
51
+ export declare function criteriaAt(root: CriteriaNode, path: readonly number[]): CriteriaNode;
52
+ export declare function replaceCriteria(root: CriteriaGroup, path: readonly number[], replacement: CriteriaNode | null): CriteriaGroup;
53
+ export declare function describeCriteria(node: CriteriaNode, fields: readonly CriteriaField[]): string;
54
+ export declare function validateCondition(condition: CriteriaCondition, fields: readonly CriteriaField[]): string | undefined;
55
+ export declare function validateCriteria(group: CriteriaGroup, fields: readonly CriteriaField[]): string | undefined;
56
+ export declare function moveCollectionEntry<T>(items: readonly T[], index: number, direction: -1 | 1): T[];
@@ -0,0 +1,99 @@
1
+ export function parseCriteriaDate(value) {
2
+ if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
3
+ return null;
4
+ const date = new Date(`${value}T00:00:00Z`);
5
+ if (!Number.isFinite(date.getTime()) || date.toISOString().slice(0, 10) !== value)
6
+ return null;
7
+ return new Date(`${value}T12:00:00`);
8
+ }
9
+ export function criteriaAt(root, path) {
10
+ let node = root;
11
+ for (const index of path) {
12
+ if (node.kind !== "group" || !node.children[index])
13
+ throw new Error("Invalid criteria path");
14
+ node = node.children[index];
15
+ }
16
+ return node;
17
+ }
18
+ export function replaceCriteria(root, path, replacement) {
19
+ if (path.length === 0) {
20
+ if (replacement?.kind !== "group")
21
+ throw new Error("Criteria root must remain a group");
22
+ return replacement;
23
+ }
24
+ const [index, ...rest] = path;
25
+ const child = root.children[index];
26
+ if (!child)
27
+ throw new Error("Invalid criteria path");
28
+ if (rest.length && child.kind !== "group")
29
+ throw new Error("Invalid criteria path");
30
+ const next = rest.length && child.kind === "group"
31
+ ? replaceCriteria(child, rest, replacement)
32
+ : replacement;
33
+ return {
34
+ ...root,
35
+ children: root.children.flatMap((item, position) => position !== index ? [item] : next ? [next] : []),
36
+ };
37
+ }
38
+ export function describeCriteria(node, fields) {
39
+ if (node.kind === "group") {
40
+ if (!node.children.length)
41
+ return "Sin condiciones";
42
+ const join = { all: "Todas", any: "Cualquiera", none: "Ninguna" }[node.join];
43
+ return `${join}: (${node.children.map((child) => describeCriteria(child, fields)).join("; ")})`;
44
+ }
45
+ const field = fields.find((item) => item.id === node.field);
46
+ const operator = field?.operators.find((item) => item.id === node.operator);
47
+ const values = node.values.map((value) => field?.options?.find((option) => option.value === value)?.label ?? String(value));
48
+ return `${field?.label ?? node.field} ${operator?.label ?? node.operator}${values.length ? ` ${values.join(" — ")}` : ""}`;
49
+ }
50
+ export function validateCondition(condition, fields) {
51
+ const field = fields.find((item) => item.id === condition.field);
52
+ if (!field)
53
+ return "El campo ya no está disponible.";
54
+ const operator = field.operators.find((item) => item.id === condition.operator);
55
+ if (!operator)
56
+ return "El operador no está disponible para este campo.";
57
+ const count = { none: 0, one: 1, range: 2 }[operator.arity];
58
+ if (condition.values.length !== count)
59
+ return "Completa los valores de la condición.";
60
+ for (const value of condition.values) {
61
+ if (field.type === "number" && (typeof value !== "number" || !Number.isFinite(value)))
62
+ return "Introduce un número válido.";
63
+ if (field.type === "text" && (typeof value !== "string" || !value.trim()))
64
+ return "Introduce un texto.";
65
+ if (field.type === "date" && !parseCriteriaDate(value))
66
+ return "Introduce una fecha válida (AAAA-MM-DD).";
67
+ if (field.type === "choice" && !field.options?.some((option) => option.value === value))
68
+ return "Selecciona un valor disponible.";
69
+ }
70
+ if (operator.arity === "range" &&
71
+ field.type === "number" &&
72
+ Number(condition.values[0]) > Number(condition.values[1]))
73
+ return "El inicio no puede superar el final.";
74
+ if (operator.arity === "range" &&
75
+ field.type === "date" &&
76
+ String(condition.values[0]) > String(condition.values[1]))
77
+ return "El inicio no puede superar el final.";
78
+ return undefined;
79
+ }
80
+ export function validateCriteria(group, fields) {
81
+ for (const child of group.children) {
82
+ if (child.kind === "group" && !child.children.length)
83
+ return "Completa o elimina el grupo vacío.";
84
+ const error = child.kind === "group"
85
+ ? validateCriteria(child, fields)
86
+ : validateCondition(child, fields);
87
+ if (error)
88
+ return error;
89
+ }
90
+ return undefined;
91
+ }
92
+ export function moveCollectionEntry(items, index, direction) {
93
+ const target = index + direction;
94
+ if (index < 0 || index >= items.length || target < 0 || target >= items.length)
95
+ throw new Error("Invalid collection reorder");
96
+ const next = [...items];
97
+ [next[index], next[target]] = [next[target], next[index]];
98
+ return next;
99
+ }
@@ -0,0 +1,3 @@
1
+ import type { ImageProps } from "expo-image";
2
+ export type ImageSource = NonNullable<ImageProps["source"]>;
3
+ export declare function imageSourceKey(source: ImageProps["source"]): string | undefined;
@@ -0,0 +1,13 @@
1
+ const sharedSourceKeys = new WeakMap();
2
+ let nextSharedSourceKey = 0;
3
+ export function imageSourceKey(source) {
4
+ if (source && typeof source === "object" && "nativeRefType" in source) {
5
+ let key = sharedSourceKeys.get(source);
6
+ if (key === undefined) {
7
+ key = nextSharedSourceKey++;
8
+ sharedSourceKeys.set(source, key);
9
+ }
10
+ return `shared-image-${key}`;
11
+ }
12
+ return JSON.stringify(source);
13
+ }
@@ -0,0 +1 @@
1
+ export declare function uniqueKeys<T>(items: readonly T[], keyExtractor: (item: T, index: number) => string, component: string): string[];
@@ -0,0 +1,7 @@
1
+ export function uniqueKeys(items, keyExtractor, component) {
2
+ const keys = items.map(keyExtractor);
3
+ if (keys.some((key) => typeof key !== "string" || !key.trim()) ||
4
+ new Set(keys).size !== keys.length)
5
+ throw new Error(`${component} requires unique, non-empty string keys.`);
6
+ return keys;
7
+ }
@@ -0,0 +1 @@
1
+ export declare function normalizeProgress(value: number): number;
@@ -0,0 +1,5 @@
1
+ export function normalizeProgress(value) {
2
+ if (!Number.isFinite(value))
3
+ throw new RangeError("Progress value must be a finite number");
4
+ return Math.max(0, Math.min(1, value));
5
+ }
@@ -0,0 +1,4 @@
1
+ export declare function reorderKeys<T>(items: readonly T[], keyExtractor: (item: T) => string): string[];
2
+ export declare function sameOrder(left: readonly string[], right: readonly string[]): boolean;
3
+ export declare function moveReorderItem<T>(items: readonly T[], fromIndex: number, toIndex: number): T[];
4
+ export declare function applyReorder<T>(items: readonly T[], keyExtractor: (item: T) => string, order: readonly string[]): T[];
@@ -0,0 +1,29 @@
1
+ import { uniqueKeys } from "./keys.js";
2
+ export function reorderKeys(items, keyExtractor) {
3
+ return uniqueKeys(items, keyExtractor, "ReorderableList");
4
+ }
5
+ export function sameOrder(left, right) {
6
+ return left.length === right.length && left.every((key, index) => key === right[index]);
7
+ }
8
+ export function moveReorderItem(items, fromIndex, toIndex) {
9
+ if (!Number.isInteger(fromIndex) ||
10
+ !Number.isInteger(toIndex) ||
11
+ fromIndex < 0 ||
12
+ fromIndex >= items.length ||
13
+ toIndex < 0 ||
14
+ toIndex >= items.length)
15
+ throw new Error("ReorderableList received an invalid move.");
16
+ const next = [...items];
17
+ const [item] = next.splice(fromIndex, 1);
18
+ next.splice(toIndex, 0, item);
19
+ return next;
20
+ }
21
+ export function applyReorder(items, keyExtractor, order) {
22
+ const keys = reorderKeys(items, keyExtractor);
23
+ const indices = new Map(keys.map((key, index) => [key, index]));
24
+ if (order.length !== keys.length ||
25
+ new Set(order).size !== order.length ||
26
+ order.some((key) => !indices.has(key)))
27
+ throw new Error("ReorderableList received an order that does not match the current items.");
28
+ return order.map((key) => items[indices.get(key)]);
29
+ }
@@ -0,0 +1 @@
1
+ export declare function useReducedMotion(): boolean;
@@ -0,0 +1,43 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import { AccessibilityInfo } from "react-native";
3
+ const listeners = new Set();
4
+ let reducedMotion = true;
5
+ let generation = 0;
6
+ let subscription;
7
+ function publish(enabled) {
8
+ if (reducedMotion === enabled)
9
+ return;
10
+ reducedMotion = enabled;
11
+ for (const listener of [...listeners])
12
+ listener();
13
+ }
14
+ function subscribe(listener) {
15
+ listeners.add(listener);
16
+ if (listeners.size === 1) {
17
+ const request = ++generation;
18
+ let receivedChange = false;
19
+ subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", (enabled) => {
20
+ receivedChange = true;
21
+ publish(enabled);
22
+ });
23
+ AccessibilityInfo.isReduceMotionEnabled().then((enabled) => {
24
+ if (request === generation && !receivedChange)
25
+ publish(enabled);
26
+ }, (error) => {
27
+ if (request === generation)
28
+ console.warn("[useReducedMotion] Could not read accessibility preference", error);
29
+ });
30
+ }
31
+ return () => {
32
+ listeners.delete(listener);
33
+ if (listeners.size === 0) {
34
+ generation += 1;
35
+ subscription?.remove();
36
+ subscription = undefined;
37
+ reducedMotion = true;
38
+ }
39
+ };
40
+ }
41
+ export function useReducedMotion() {
42
+ return useSyncExternalStore(subscribe, () => reducedMotion, () => true);
43
+ }
@@ -0,0 +1,5 @@
1
+ import { Animated } from "react-native";
2
+ export declare function useSheetTranslation(visible: boolean, hiddenOffset?: number): {
3
+ translateY: Animated.Value;
4
+ reducedMotion: boolean;
5
+ };
@@ -0,0 +1,23 @@
1
+ import { useReducedMotion } from "./useReducedMotion.js";
2
+ import { useEffect, useState } from "react";
3
+ import { Animated } from "react-native";
4
+ export function useSheetTranslation(visible, hiddenOffset = 800) {
5
+ const reducedMotion = useReducedMotion();
6
+ const [translateY] = useState(() => new Animated.Value(visible ? 0 : hiddenOffset));
7
+ useEffect(() => {
8
+ translateY.stopAnimation();
9
+ if (visible && !reducedMotion) {
10
+ const animation = Animated.spring(translateY, {
11
+ toValue: 0,
12
+ useNativeDriver: true,
13
+ damping: 20,
14
+ stiffness: 300,
15
+ });
16
+ animation.start();
17
+ return () => animation.stop();
18
+ }
19
+ translateY.setValue(visible ? 0 : hiddenOffset);
20
+ return () => translateY.stopAnimation();
21
+ }, [visible, hiddenOffset, reducedMotion, translateY]);
22
+ return { translateY, reducedMotion };
23
+ }
@@ -1,12 +1,13 @@
1
1
  import { type SpinnerVariant } from "../Feedback/Feedback.Spinner.js";
2
2
  import type { AppBarBackground } from "../Navigation/Navigation.AppBar.js";
3
+ import type { ImageSource } from "../utils/image.js";
3
4
  import type { ReactNode } from "react";
4
- import { type ImageSourcePropType, type ViewStyle } from "react-native";
5
+ import { type ViewStyle } from "react-native";
5
6
  export interface AppSplashViewProps {
6
7
  /** Gradient + pattern background. Defaults to `night-green` preset. */
7
8
  background?: AppBarBackground;
8
9
  /** App logo shown centered above the loading indicator. */
9
- logo?: ImageSourcePropType;
10
+ logo?: ImageSource;
10
11
  /** Built-in loading animation variant. Ignored when `loader` is provided. Defaults to `"dots"`. */
11
12
  loadingVariant?: SpinnerVariant;
12
13
  /** Fully custom loading element. Takes precedence over `loadingVariant`. */
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import ExpoImage from "../Display/Display.ExpoImage.js";
2
3
  import GradientSurface from "../Display/Display.GradientSurface.js";
3
4
  import { Pattern } from "../Display/Display.Pattern.js";
4
5
  import Spinner from "../Feedback/Feedback.Spinner.js";
5
- import { Image, View } from "react-native";
6
+ import { View } from "react-native";
6
7
  export function AppSplashView({ background, logo, loadingVariant = "dots", loader, style, }) {
7
- return (_jsxs(GradientSurface, { className: "flex-1", colors: background?.colors, end: background?.end, padding: "none", preset: background?.surface ?? "night-green", radius: "none", start: background?.start, style: { flex: 1, ...style }, children: [background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, _jsxs(View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: [logo ? (_jsx(Image, { source: logo, style: { width: 140, height: 140, resizeMode: "contain", marginBottom: 40 } })) : null, loader ?? (_jsx(Spinner, { color: "rgba(255,255,255,0.9)", size: "large", variant: loadingVariant }))] })] }));
8
+ return (_jsxs(GradientSurface, { className: "flex-1", colors: background?.colors, end: background?.end, padding: "none", preset: background?.surface ?? "night-green", radius: "none", start: background?.start, style: { flex: 1, ...style }, children: [background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, _jsxs(View, { style: { flex: 1, alignItems: "center", justifyContent: "center" }, children: [logo ? (_jsx(ExpoImage, { contentFit: "contain", source: logo, style: { width: 140, height: 140, marginBottom: 40 } })) : null, loader ?? (_jsx(Spinner, { color: "rgba(255,255,255,0.9)", size: "large", variant: loadingVariant }))] })] }));
8
9
  }
9
10
  export default AppSplashView;
@@ -1,13 +1,16 @@
1
+ import type { KeyboardFormProps } from "../Form/Form.KeyboardForm.js";
1
2
  import type { AppBarBackground } from "../Navigation/Navigation.AppBar.js";
3
+ import type { ImageSource } from "../utils/image.js";
2
4
  import { ReactNode } from "react";
3
- import { type ImageSourcePropType } from "react-native";
4
5
  interface AuthFormProps {
5
6
  title?: string;
6
7
  description?: string;
7
- logoSource?: ImageSourcePropType;
8
+ logoSource?: ImageSource;
8
9
  onBack?: () => void;
9
10
  FormNode: ReactNode;
10
11
  background?: AppBarBackground;
12
+ keyboardToolbar?: boolean;
13
+ keyboardFormProps?: Omit<KeyboardFormProps, "children" | "toolbar">;
11
14
  }
12
- declare function AuthFormView({ title, description, logoSource, onBack, FormNode, background, }: AuthFormProps): import("react").JSX.Element;
15
+ declare function AuthFormView({ title, description, logoSource, onBack, FormNode, background, keyboardToolbar, keyboardFormProps, }: AuthFormProps): import("react").JSX.Element;
13
16
  export default AuthFormView;
@@ -5,7 +5,7 @@ import { Typography } from "../Typography/index.js";
5
5
  import { View } from "react-native";
6
6
  const Logo = Display.Logo;
7
7
  const Text = Typography.Text;
8
- function AuthFormView({ title, description, logoSource, onBack, FormNode, background, }) {
8
+ function AuthFormView({ title, description, logoSource, onBack, FormNode, background, keyboardToolbar, keyboardFormProps, }) {
9
9
  const renderTitle = () => {
10
10
  if (!title)
11
11
  return null;
@@ -16,6 +16,6 @@ function AuthFormView({ title, description, logoSource, onBack, FormNode, backgr
16
16
  return null;
17
17
  return _jsx(Text, { variant: "body", children: description });
18
18
  };
19
- return (_jsx(AuthContainer, { background: background, header: _jsx(Logo, { size: "large", source: logoSource }), onBackPress: onBack, children: _jsxs(View, { className: "m-4", children: [_jsxs(View, { className: "mb-5", children: [renderTitle(), renderDescription()] }), FormNode] }) }));
19
+ return (_jsx(AuthContainer, { background: background, header: _jsx(Logo, { size: "large", source: logoSource }), keyboardFormProps: keyboardFormProps, keyboardToolbar: keyboardToolbar, onBackPress: onBack, children: _jsxs(View, { className: "m-4", children: [_jsxs(View, { className: "mb-5", children: [renderTitle(), renderDescription()] }), FormNode] }) }));
20
20
  }
21
21
  export default AuthFormView;
@@ -1,7 +1,8 @@
1
+ import { type KeyboardFormProps } from "../Form/Form.KeyboardForm.js";
1
2
  import type { AppBarBackground } from "../Navigation/Navigation.AppBar.js";
2
3
  import { type SegmentedControlProps } from "../Navigation/Navigation.SegmentedControl.js";
4
+ import type { ImageSource } from "../utils/image.js";
3
5
  import React, { ReactNode } from "react";
4
- import { type ImageSourcePropType } from "react-native";
5
6
  interface FormViewProps<T> {
6
7
  name: string;
7
8
  description?: string;
@@ -12,12 +13,14 @@ interface FormViewProps<T> {
12
13
  errorMessage?: string;
13
14
  readonly?: boolean;
14
15
  onRefresh?: () => Promise<void>;
16
+ onRefreshError?: (error: unknown) => void;
15
17
  onRetry?: () => void;
16
18
  onBack?: () => void;
17
19
  withContainer?: boolean;
18
20
  children: ReactNode;
19
21
  }
20
- declare function FormViewRoot<T>({ name, description, item, isLoading, isEmpty, hasError, errorMessage, readonly, onRefresh, onRetry, onBack, withContainer, children, }: FormViewProps<T>): React.JSX.Element;
22
+ export declare function refreshFormData(onRefresh: (() => Promise<void>) | undefined, onRefreshError: ((error: unknown) => void) | undefined, setShowRefreshing: (value: boolean) => void): Promise<void>;
23
+ declare function FormViewRoot<T>({ name, description, item, isLoading, isEmpty, hasError, errorMessage, readonly, onRefresh, onRefreshError, onRetry, onBack, withContainer, children, }: FormViewProps<T>): React.JSX.Element;
21
24
  interface HeaderProps {
22
25
  /** AppBar display variant. `large` = big title (list/index screens). `center` = modal/detail. */
23
26
  variant?: "default" | "large" | "center";
@@ -38,7 +41,7 @@ interface HeaderProps {
38
41
  */
39
42
  background?: AppBarBackground;
40
43
  /** @deprecated Pass `variant` prop instead. */
41
- logoSource?: ImageSourcePropType;
44
+ logoSource?: ImageSource;
42
45
  }
43
46
  declare function Header({ variant, tone, actions, children, topSpacing, background, }: HeaderProps): React.JSX.Element;
44
47
  declare function HeaderActions({ children }: {
@@ -53,8 +56,10 @@ declare function HeaderActionsBar({ children }: {
53
56
  * Use as a child of `<FormViewV2.Header>` — it lands in the AppBar subheader slot.
54
57
  */
55
58
  declare function HeaderSegmented<T extends string>(props: SegmentedControlProps<T>): React.JSX.Element;
56
- declare function Content({ withMargin, children, }: {
59
+ declare function Content({ withMargin, keyboardToolbar, keyboardFormProps, children, }: {
57
60
  withMargin?: boolean;
61
+ keyboardToolbar?: boolean;
62
+ keyboardFormProps?: Omit<KeyboardFormProps, "children" | "toolbar">;
58
63
  children: ReactNode;
59
64
  }): React.JSX.Element;
60
65
  /**