@sincpro/mobile-ui 0.5.0 → 0.6.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 (82) hide show
  1. package/dist/Actions/Actions.Button.d.ts +151 -0
  2. package/dist/Actions/Actions.Button.js +217 -0
  3. package/dist/Actions/Actions.FAB.d.ts +10 -0
  4. package/dist/Actions/Actions.FAB.js +17 -0
  5. package/dist/Actions/Actions.FilterButton.d.ts +10 -0
  6. package/dist/Actions/Actions.FilterButton.js +28 -0
  7. package/dist/Actions/Actions.IconButton.d.ts +67 -0
  8. package/dist/Actions/Actions.IconButton.js +38 -0
  9. package/dist/Actions/index.d.ts +22 -0
  10. package/dist/Actions/index.js +17 -0
  11. package/dist/Dialog/BottomSheet/BottomSheet.Root.js +1 -0
  12. package/dist/Dialog/Dialog.Sheet.js +6 -4
  13. package/dist/Display/Display.CircleButton.d.ts +15 -0
  14. package/dist/Display/Display.CircleButton.js +39 -0
  15. package/dist/Display/Display.MenuButton.d.ts +51 -8
  16. package/dist/Display/Display.MenuButton.js +79 -13
  17. package/dist/Display/Display.MenuCard.d.ts +15 -0
  18. package/dist/Display/Display.MenuCard.js +24 -0
  19. package/dist/Display/Display.MenuGrid.d.ts +19 -0
  20. package/dist/Display/Display.MenuGrid.js +7 -0
  21. package/dist/Display/Display.SwipeableRow.js +2 -1
  22. package/dist/Display/Display.Tooltip.d.ts +1 -1
  23. package/dist/Display/Display.Tooltip.js +16 -5
  24. package/dist/Display/index.d.ts +9 -0
  25. package/dist/Display/index.js +6 -0
  26. package/dist/Feedback/Feedback.Empty.js +2 -2
  27. package/dist/Feedback/Feedback.Error.js +2 -2
  28. package/dist/Feedback/Feedback.Progress.js +2 -2
  29. package/dist/Feedback/Feedback.Skeleton.js +2 -2
  30. package/dist/Feedback/Feedback.Splash.js +1 -0
  31. package/dist/Form/Form.Button.d.ts +3 -151
  32. package/dist/Form/Form.Button.js +1 -283
  33. package/dist/Form/Form.FAB.d.ts +3 -10
  34. package/dist/Form/Form.FAB.js +1 -17
  35. package/dist/Form/Form.FilterButton.d.ts +3 -12
  36. package/dist/Form/Form.FilterButton.js +1 -34
  37. package/dist/Form/Form.IconButton.d.ts +3 -67
  38. package/dist/Form/Form.IconButton.js +1 -64
  39. package/dist/Navigation/Navigation.AppBar.d.ts +56 -1
  40. package/dist/Navigation/Navigation.AppBar.js +43 -12
  41. package/dist/Navigation/Navigation.BottomNav.js +2 -1
  42. package/dist/Navigation/Navigation.Header.js +3 -2
  43. package/dist/Navigation/Navigation.Menu.js +6 -3
  44. package/dist/Navigation/Navigation.MenuSection.d.ts +19 -0
  45. package/dist/Navigation/Navigation.MenuSection.js +25 -0
  46. package/dist/Navigation/Navigation.SegmentedControl.d.ts +15 -1
  47. package/dist/Navigation/Navigation.SegmentedControl.js +22 -4
  48. package/dist/Navigation/Navigation.TabIcon.js +1 -2
  49. package/dist/Navigation/Navigation.TabItem.js +2 -2
  50. package/dist/Navigation/Navigation.Tabs.d.ts +8 -3
  51. package/dist/Navigation/Navigation.Tabs.js +19 -6
  52. package/dist/Navigation/index.d.ts +7 -6
  53. package/dist/Navigation/index.js +2 -0
  54. package/dist/index.d.ts +2 -0
  55. package/dist/index.js +2 -0
  56. package/dist/layouts/BottomInset.d.ts +16 -0
  57. package/dist/layouts/BottomInset.js +17 -0
  58. package/dist/layouts/TabNavigatorLayout.js +2 -2
  59. package/dist/primitives/BaseButton.js +3 -3
  60. package/dist/theme/default.js +1 -1
  61. package/dist/views/FormViewV2.d.ts +18 -6
  62. package/dist/views/FormViewV2.js +14 -11
  63. package/dist/views/ListViewV2.d.ts +12 -1
  64. package/dist/views/ListViewV2.js +16 -10
  65. package/dist/widgets/CircleButton.d.ts +3 -19
  66. package/dist/widgets/CircleButton.js +1 -41
  67. package/dist/widgets/JSONViewer.js +7 -7
  68. package/dist/widgets/MenuCard.d.ts +3 -13
  69. package/dist/widgets/MenuCard.js +1 -8
  70. package/dist/widgets/MenuGrid.d.ts +5 -17
  71. package/dist/widgets/MenuGrid.js +1 -6
  72. package/dist/widgets/MenuSection.d.ts +3 -0
  73. package/dist/widgets/MenuSection.js +1 -0
  74. package/dist/widgets/PromoBanner.d.ts +38 -0
  75. package/dist/widgets/PromoBanner.js +61 -0
  76. package/dist/widgets/PromoModal.d.ts +41 -0
  77. package/dist/widgets/PromoModal.js +75 -0
  78. package/dist/widgets/QuickActions.d.ts +26 -0
  79. package/dist/widgets/QuickActions.js +41 -0
  80. package/dist/widgets/index.d.ts +11 -1
  81. package/dist/widgets/index.js +4 -0
  82. package/package.json +2 -1
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
- import { Display } from "../Display";
2
+ import Icon from "../Display/Display.Icon";
3
3
  import { theme } from "../theme";
4
4
  import { tv } from "../theme/tw";
5
5
  import { Typography } from "../Typography";
@@ -222,9 +222,9 @@ export const BaseButtonIcon = ({ icon, onPress, variant = "primary", size = "med
222
222
  return (_jsx(LinearGradient, { colors: gradientColors, end: { x: 1, y: 1 }, start: { x: 0, y: 0 }, style: {
223
223
  borderRadius: 8,
224
224
  opacity: disabled ? 0.7 : 1,
225
- }, children: _jsx(TouchableOpacity, { activeOpacity: 0.8, disabled: disabled || loading, onPress: onPress, children: _jsx(View, { className: buttonIcon({ size, disabled }), style: { backgroundColor: "transparent" }, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Display.Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }) }) }));
225
+ }, children: _jsx(TouchableOpacity, { activeOpacity: 0.8, disabled: disabled || loading, onPress: onPress, children: _jsx(View, { className: buttonIcon({ size, disabled }), style: { backgroundColor: "transparent" }, children: loading ? (_jsx(ActivityIndicator, { color: loadingColorValue, size: "small" })) : (_jsx(Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }) }) }));
226
226
  }
227
- 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(Display.Icon, { color: iconColorValue, name: icon, size: iconSizeValue })) }));
227
+ 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 })) }));
228
228
  };
229
229
  // ============================================================================
230
230
  // COMPOUND COMPONENT
@@ -4,7 +4,7 @@ export const DEFAULT_THEME = {
4
4
  secondary: "#64748B",
5
5
  accent: "#F59E0B",
6
6
  bg: {
7
- page: "#F9FAFB",
7
+ page: "#F0F1F3",
8
8
  card: "#FFFFFF",
9
9
  popover: "#FFFFFF",
10
10
  muted: "#F3F4F6",
@@ -1,3 +1,4 @@
1
+ import type { AppBarBackground } from "../Navigation/Navigation.AppBar";
1
2
  import { type SegmentedControlProps } from "../Navigation/Navigation.SegmentedControl";
2
3
  import React, { ReactNode } from "react";
3
4
  import { type ImageSourcePropType } from "react-native";
@@ -26,10 +27,20 @@ interface HeaderProps {
26
27
  actions?: ReactNode;
27
28
  /** Subheader content rendered below the title bar (SearchBar, SegmentedControl, etc.). */
28
29
  children?: ReactNode;
30
+ /**
31
+ * [large variant] Extra top padding (px) above the title.
32
+ * Use ~16 on root screens (no back button) for more breathing room.
33
+ */
34
+ topSpacing?: number;
35
+ /**
36
+ * Gradient + texture surface. Automatically activates white text/icons — no need to set `tone="dark"`.
37
+ * See `AppBarBackground` for the full shape.
38
+ */
39
+ background?: AppBarBackground;
29
40
  /** @deprecated Pass `variant` prop instead. */
30
41
  logoSource?: ImageSourcePropType;
31
42
  }
32
- declare function Header({ variant, tone, actions, children }: HeaderProps): React.JSX.Element;
43
+ declare function Header({ variant, tone, actions, children, topSpacing, background, }: HeaderProps): React.JSX.Element;
33
44
  declare function HeaderActions({ children }: {
34
45
  children: ReactNode;
35
46
  }): React.JSX.Element;
@@ -94,14 +105,15 @@ declare function Group({ children, withMargin, elevation, padding, variant, titl
94
105
  title?: string;
95
106
  }): React.JSX.Element;
96
107
  /**
97
- * Settings-style section: SectionHeader label + Card(padding="none") wrapper.
98
- * Use inside FormViewV2.Content for preference/config screens where rows are
99
- * Display.ListItem or Display.MenuButton (not form inputs).
108
+ * Settings-style section: titled card grouping Display.MenuButton rows.
109
+ * Delegates to the standalone `MenuSection` widget — single source of truth
110
+ * for this pattern whether used inside a FormView or on a standalone screen.
100
111
  */
101
- declare function Section({ title, children, withMargin, }: {
112
+ declare function Section({ title, footer, children, }: {
102
113
  title?: string;
114
+ /** Hint text rendered below the card (e.g. "Toca para cambiar la configuración"). */
115
+ footer?: string;
103
116
  children: ReactNode;
104
- withMargin?: boolean;
105
117
  }): React.JSX.Element;
106
118
  declare function Footer({ children }: {
107
119
  children: ReactNode;
@@ -1,26 +1,26 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { Display } from "../Display";
3
3
  import { ErrorBoundary, Feedback } from "../Feedback";
4
+ import { useBottomInset } from "../layouts/BottomInset";
4
5
  import Container from "../layouts/Container";
5
6
  import { AppBar } from "../Navigation/Navigation.AppBar";
7
+ import { MenuSection } from "../Navigation/Navigation.MenuSection";
6
8
  import { SegmentedControl, } from "../Navigation/Navigation.SegmentedControl";
7
- import { theme } from "../theme";
9
+ import { useTheme } from "../theme";
8
10
  import { FormViewProvider, useFormView } from "../views/FormViewV2.context";
9
11
  import React from "react";
10
12
  import { RefreshControl, View } from "react-native";
11
13
  import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
12
14
  function FormViewRoot({ name, description, item, isLoading = false, isEmpty = false, hasError = false, errorMessage, readonly = false, onRefresh, onRetry, onBack, withContainer = true, children, }) {
13
15
  const Wrapper = withContainer ? Container : View;
14
- // The header (AppBar) / parent layout owns the TOP safe-area; the inner Container only
15
- // reserves the remaining edges to avoid stacking the top inset (double padding).
16
16
  const wrapperProps = withContainer
17
17
  ? { edges: ["bottom", "left", "right"] }
18
18
  : { style: { flex: 1 } };
19
19
  return (_jsx(FormViewProvider, { description: description, errorMessage: errorMessage, hasError: hasError, isEmpty: isEmpty, isLoading: isLoading, item: item, name: name, onBack: onBack, onRefresh: onRefresh, onRetry: onRetry, readonly: readonly, children: _jsx(ErrorBoundary, { children: _jsx(Wrapper, { ...wrapperProps, children: children }) }) }));
20
20
  }
21
- function Header({ variant = "default", tone, actions, children }) {
21
+ function Header({ variant = "default", tone, actions, children, topSpacing, background, }) {
22
22
  const { name, description, onBack } = useFormView();
23
- return (_jsx(AppBar, { actions: actions, onBack: onBack, safeArea: false, subheader: children, subtitle: description ?? undefined, title: name, tone: tone, variant: variant }));
23
+ return (_jsx(AppBar, { actions: actions, background: background, onBack: onBack, safeArea: false, subheader: children, subtitle: description ?? undefined, title: name, tone: tone, topSpacing: topSpacing, variant: variant }));
24
24
  }
25
25
  function HeaderActions({ children }) {
26
26
  return (_jsx(View, { className: "flex-row px-4 my-2 items-center justify-between flex-1 gap-2", children: children }));
@@ -38,6 +38,8 @@ function HeaderSegmented(props) {
38
38
  }
39
39
  function Content({ withMargin = false, children, }) {
40
40
  const { name, isLoading, isEmpty, hasError, errorMessage, item, onRefresh, onRetry, onBack, showRefreshing, setShowRefreshing, } = useFormView();
41
+ const bottomInset = useBottomInset();
42
+ const theme = useTheme();
41
43
  const handleRefresh = React.useCallback(async () => {
42
44
  setShowRefreshing(true);
43
45
  if (onRefresh) {
@@ -50,7 +52,8 @@ function Content({ withMargin = false, children, }) {
50
52
  flexGrow: 1,
51
53
  justifyContent: isCenteredContent ? "center" : "flex-start",
52
54
  alignItems: isCenteredContent ? "center" : "stretch",
53
- }, keyboardShouldPersistTaps: "handled", refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), style: { flex: 1 }, children: isCenteredContent ? (_jsx(_Fragment, { children: isLoading ? (_jsx(Feedback.Loading, { message: `Cargando ${name}...` })) : hasError ? (_jsx(Feedback.Error, { message: errorMessage, onBack: onBack, onRetry: onRetry })) : (_jsx(Feedback.Empty, {})) })) : (_jsx(View, { style: withMargin ? { paddingHorizontal: 16 } : undefined, children: children })) }));
55
+ paddingBottom: bottomInset,
56
+ }, keyboardShouldPersistTaps: "handled", refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), style: { flex: 1, backgroundColor: theme.bg.page }, children: isCenteredContent ? (_jsx(_Fragment, { children: isLoading ? (_jsx(Feedback.Loading, { message: `Cargando ${name}...` })) : hasError ? (_jsx(Feedback.Error, { message: errorMessage, onBack: onBack, onRetry: onRetry })) : (_jsx(Feedback.Empty, {})) })) : (_jsx(View, { style: withMargin ? { paddingHorizontal: 16 } : undefined, children: children })) }));
54
57
  }
55
58
  /**
56
59
  * @deprecated Content already handles isEmpty/!item by rendering Feedback.Empty internally.
@@ -91,12 +94,12 @@ function Group({ children, withMargin = true, elevation = "none", padding = "md"
91
94
  return (_jsxs(_Fragment, { children: [header, body] }));
92
95
  }
93
96
  /**
94
- * Settings-style section: SectionHeader label + Card(padding="none") wrapper.
95
- * Use inside FormViewV2.Content for preference/config screens where rows are
96
- * Display.ListItem or Display.MenuButton (not form inputs).
97
+ * Settings-style section: titled card grouping Display.MenuButton rows.
98
+ * Delegates to the standalone `MenuSection` widget — single source of truth
99
+ * for this pattern whether used inside a FormView or on a standalone screen.
97
100
  */
98
- function Section({ title, children, withMargin = true, }) {
99
- return (_jsxs(View, { style: withMargin ? { marginHorizontal: 16 } : undefined, children: [title ? _jsx(Display.SectionHeader, { title: title }) : null, _jsx(Display.Card, { padding: "none", children: children })] }));
101
+ function Section({ title, footer, children, }) {
102
+ return (_jsx(MenuSection, { footer: footer, title: title, children: children }));
100
103
  }
101
104
  function Footer({ children }) {
102
105
  const { isEmpty, isLoading } = useFormView();
@@ -1,4 +1,5 @@
1
1
  import { type SearchHistoryProps } from "../Display/Display.SearchHistory";
2
+ import type { AppBarBackground } from "../Navigation/Navigation.AppBar";
2
3
  import { type SegmentedControlProps } from "../Navigation/Navigation.SegmentedControl";
3
4
  import React, { ReactNode } from "react";
4
5
  import { StyleProp, ViewStyle } from "react-native";
@@ -34,8 +35,18 @@ interface HeaderProps {
34
35
  actions?: ReactNode;
35
36
  /** Subheader content rendered below the title bar (SearchBar, SegmentedControl, etc.). */
36
37
  children?: ReactNode;
38
+ /**
39
+ * Gradient + texture surface. Automatically activates white text/icons.
40
+ * See `AppBarBackground` for the full shape.
41
+ */
42
+ background?: AppBarBackground;
43
+ /**
44
+ * [large variant] Extra top padding (px) above the large title.
45
+ * Use ~16 on root screens (no back button) for breathing room.
46
+ */
47
+ topSpacing?: number;
37
48
  }
38
- declare function Header({ variant, tone, actions, children }: HeaderProps): React.JSX.Element;
49
+ declare function Header({ variant, tone, actions, children, background, topSpacing, }: HeaderProps): React.JSX.Element;
39
50
  declare function HeaderSearch({ searchQuery }: {
40
51
  searchQuery?: string;
41
52
  }): React.JSX.Element | null;
@@ -2,10 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/js
2
2
  import { Display } from "../Display";
3
3
  import SearchHistory from "../Display/Display.SearchHistory";
4
4
  import { ErrorBoundary, Feedback } from "../Feedback";
5
+ import { useBottomInset } from "../layouts/BottomInset";
5
6
  import Container from "../layouts/Container";
6
7
  import { Navigation } from "../Navigation";
7
8
  import { SegmentedControl, } from "../Navigation/Navigation.SegmentedControl";
8
- import { theme } from "../theme";
9
+ import { useTheme } from "../theme";
9
10
  import { cn, tv } from "../theme/tw";
10
11
  import { Typography } from "../Typography";
11
12
  import { ListViewProvider, useListView } from "../views/ListViewV2.context";
@@ -14,17 +15,15 @@ import { useCallback, useEffect, useMemo, useState } from "react";
14
15
  import { FlatList, RefreshControl, ScrollView, TouchableOpacity, View, } from "react-native";
15
16
  function ListViewRoot({ name, description, items, isLoading = false, hasError = false, errorMessage, readonly = false, onRefresh, onRetry, onPressItem, onDeleteItem, onSearch, onBack, onLoadMore, loadingMore = false, backgroundColor, withContainer = true, children, }) {
16
17
  const Wrapper = withContainer ? Container : View;
17
- // The header (AppBar) / parent layout owns the TOP safe-area; the Container only reserves
18
- // the remaining edges to avoid stacking the top inset (double padding).
19
18
  const wrapperProps = withContainer
20
19
  ? { edges: ["bottom", "left", "right"] }
21
20
  : { className: "flex-1 bg-bg-page" };
22
21
  // Propagate contentWithMargin to ContentList via context
23
22
  return (_jsx(ListViewProvider, { description: description, errorMessage: errorMessage, hasError: hasError, isLoading: isLoading, items: items, loadingMore: loadingMore, name: name, onBack: onBack, onDeleteItem: onDeleteItem, onLoadMore: onLoadMore, onPressItem: onPressItem, onRefresh: onRefresh, onRetry: onRetry, onSearch: onSearch, readonly: readonly, children: _jsx(ErrorBoundary, { children: _jsx(Wrapper, { ...wrapperProps, children: children }) }) }));
24
23
  }
25
- function Header({ variant = "large", tone, actions, children }) {
24
+ function Header({ variant = "large", tone, actions, children, background, topSpacing, }) {
26
25
  const { name, description, onBack } = useListView();
27
- return (_jsx(Navigation.AppBar, { actions: actions, onBack: onBack, safeArea: false, subheader: children, subtitle: description ?? undefined, title: name, tone: tone, variant: variant }));
26
+ return (_jsx(Navigation.AppBar, { actions: actions, background: background, onBack: onBack, safeArea: false, subheader: children, subtitle: description ?? undefined, title: name, tone: tone, topSpacing: topSpacing, variant: variant }));
28
27
  }
29
28
  function HeaderSearch({ searchQuery = "" }) {
30
29
  const { name, onSearch } = useListView();
@@ -40,7 +39,7 @@ function HeaderSearch({ searchQuery = "" }) {
40
39
  if (onSearch && debouncedQuery !== undefined) {
41
40
  onSearch(debouncedQuery);
42
41
  }
43
- }, [debouncedQuery]);
42
+ }, [debouncedQuery, onSearch]);
44
43
  if (!onSearch) {
45
44
  return null;
46
45
  }
@@ -98,6 +97,8 @@ function FilterChips({ children }) {
98
97
  }
99
98
  function Content({ children, withMargin = true, }) {
100
99
  const { name, items, isLoading, hasError, errorMessage, onRefresh, onRetry, onBack, showRefreshing, setShowRefreshing, } = useListView();
100
+ const bottomInset = useBottomInset();
101
+ const theme = useTheme();
101
102
  const handleRefresh = useCallback(async () => {
102
103
  setShowRefreshing(true);
103
104
  if (onRefresh) {
@@ -113,19 +114,23 @@ function Content({ children, withMargin = true, }) {
113
114
  flexGrow: 1,
114
115
  justifyContent: "center",
115
116
  alignItems: "center",
116
- }, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), children: _jsx(Feedback.Error, { message: errorMessage, onBack: onBack, onRetry: onRetry }) }));
117
+ paddingBottom: bottomInset,
118
+ }, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), style: { backgroundColor: theme.bg.page }, children: _jsx(Feedback.Error, { message: errorMessage, onBack: onBack, onRetry: onRetry }) }));
117
119
  }
118
120
  if (items.length === 0) {
119
121
  return (_jsx(ScrollView, { contentContainerStyle: {
120
122
  flexGrow: 1,
121
123
  justifyContent: "center",
122
124
  alignItems: "center",
123
- }, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), children: _jsx(Feedback.Empty, {}) }));
125
+ paddingBottom: bottomInset,
126
+ }, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), style: { backgroundColor: theme.bg.page }, children: _jsx(Feedback.Empty, {}) }));
124
127
  }
125
- return (_jsxs(View, { style: { flex: 1 }, children: [_jsx(Display.CountRecords, { count: items.length || 0, name: name }), _jsx(ContentList, { contentWithMargin: withMargin, children: children })] }));
128
+ return (_jsxs(View, { style: { flex: 1, backgroundColor: theme.bg.page }, children: [_jsx(Display.CountRecords, { count: items.length || 0, name: name }), _jsx(ContentList, { contentWithMargin: withMargin, children: children })] }));
126
129
  }
127
130
  function ContentList({ children, contentWithMargin, }) {
128
131
  const { items, onRefresh, showRefreshing, setShowRefreshing, onPressItem, onDeleteItem, readonly, onLoadMore, loadingMore, } = useListView();
132
+ const bottomInset = useBottomInset();
133
+ const theme = useTheme();
129
134
  const rowItems = useMemo(() => toRowItems(items), [items]);
130
135
  const handleRefresh = useCallback(async () => {
131
136
  setShowRefreshing(true);
@@ -147,7 +152,8 @@ function ContentList({ children, contentWithMargin, }) {
147
152
  flex: 1,
148
153
  marginTop: 2,
149
154
  paddingHorizontal: contentWithMargin ? 4 : 0,
150
- }, children: _jsx(FlatList, { data: rowItems, keyExtractor: (item) => String(item.index), ListFooterComponent: loadingMore ? (_jsx(View, { className: "py-4", children: _jsx(Feedback.Spinner, { size: "small" }) })) : null, onEndReached: onLoadMore ? () => onLoadMore() : undefined, onEndReachedThreshold: 0.5, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), renderItem: renderItem, scrollEnabled: true, showsVerticalScrollIndicator: false }) }));
155
+ backgroundColor: theme.bg.page,
156
+ }, children: _jsx(FlatList, { contentContainerStyle: bottomInset ? { paddingBottom: bottomInset } : undefined, data: rowItems, keyExtractor: (item) => String(item.index), ListFooterComponent: loadingMore ? (_jsx(View, { className: "py-4", children: _jsx(Feedback.Spinner, { size: "small" }) })) : null, onEndReached: onLoadMore ? () => onLoadMore() : undefined, onEndReachedThreshold: 0.5, refreshControl: _jsx(RefreshControl, { colors: [theme.primary], onRefresh: handleRefresh, refreshing: showRefreshing }), renderItem: renderItem, scrollEnabled: true, showsVerticalScrollIndicator: false }) }));
151
157
  }
152
158
  function Footer({ children }) {
153
159
  const { items } = useListView();
@@ -1,19 +1,3 @@
1
- import { IconType } from "../utils/icon";
2
- import React, { ComponentType } from "react";
3
- interface CircleButtonProps {
4
- iconType?: IconType;
5
- size?: "sm" | "md" | "lg";
6
- color?: string;
7
- variant?: "primary" | "secondary" | "accent" | "success" | "danger" | "warning";
8
- className?: string;
9
- customIcon?: ComponentType<{
10
- size?: number;
11
- color?: string;
12
- }>;
13
- }
14
- /**
15
- * @deprecated Duplicates the icon-button. Use `Form.IconButton` (general) or
16
- * `Navigation.AppBar.Action` (headers). Kept for backwards compatibility.
17
- */
18
- export declare function CircleButton({ iconType, size, color, variant, className, customIcon, }: CircleButtonProps): React.JSX.Element;
19
- export {};
1
+ /** @deprecated Import from `@sincpro/mobile-ui/Display` → `Display.CircleButton` instead. */
2
+ export type { CircleButtonProps } from "../Display/Display.CircleButton";
3
+ export { CircleButton } from "../Display/Display.CircleButton";
@@ -1,41 +1 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
2
- import { Display } from "../Display";
3
- import { theme } from "../theme";
4
- import { cn } from "../theme/tw";
5
- import { View } from "react-native";
6
- import { tv } from "tailwind-variants";
7
- const circleButtonVariants = tv({
8
- base: "rounded-full items-center justify-center shadow-sm",
9
- variants: {
10
- size: {
11
- sm: "w-12 h-12", // 48px
12
- md: "w-15 h-15", // 60px
13
- lg: "w-20 h-20", // 80px
14
- },
15
- variant: {
16
- primary: "bg-primary",
17
- secondary: "bg-secondary",
18
- accent: "bg-accent",
19
- success: "bg-success",
20
- danger: "bg-danger",
21
- warning: "bg-warning",
22
- },
23
- },
24
- defaultVariants: {
25
- size: "md",
26
- variant: "accent",
27
- },
28
- });
29
- const SIZE_MAP = {
30
- sm: { container: 48, icon: 24 },
31
- md: { container: 60, icon: 30 },
32
- lg: { container: 80, icon: 40 },
33
- };
34
- /**
35
- * @deprecated Duplicates the icon-button. Use `Form.IconButton` (general) or
36
- * `Navigation.AppBar.Action` (headers). Kept for backwards compatibility.
37
- */
38
- export function CircleButton({ iconType = "ionicons", size = "md", color = theme.text.inverse, variant = "accent", className, customIcon, }) {
39
- const iconSize = SIZE_MAP[size].icon;
40
- return (_jsx(View, { className: cn(circleButtonVariants({ size, variant }), className), children: _jsx(Display.Icon, { color: color, customIcon: customIcon, size: iconSize, type: iconType }) }));
41
- }
1
+ export { CircleButton } from "../Display/Display.CircleButton";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { ThemedActivityIndicator } from "../theme";
3
3
  import { fontFamilies } from "../theme/typography";
4
- import { useMemo, useState } from "react";
4
+ import { useEffect, useMemo, useState } from "react";
5
5
  import { Text, View } from "react-native";
6
6
  const parseDeepJson = (value) => {
7
7
  if (typeof value === "string") {
@@ -116,19 +116,19 @@ const JsonPreview = ({ selectedJson }) => {
116
116
  }, [selectedJson]);
117
117
  // Guard 4: colorize with processing control
118
118
  const coloredLines = useMemo(() => {
119
- setIsProcessing(true);
120
119
  try {
121
120
  return parseJsonToColoredLines(formattedJson);
122
121
  }
123
122
  catch (error) {
124
123
  console.warn("[JSONViewer] Error coloring JSON:", error);
125
- // Fallback a texto plano si falla el coloreado
126
124
  return [{ text: formattedJson, color: "#333333" }];
127
125
  }
128
- finally {
129
- // Small delay so the loading state is visible if needed
130
- setTimeout(() => setIsProcessing(false), 50);
131
- }
126
+ }, [formattedJson]);
127
+ useEffect(() => {
128
+ // eslint-disable-next-line react-hooks/set-state-in-effect
129
+ setIsProcessing(true);
130
+ const t = setTimeout(() => setIsProcessing(false), 50);
131
+ return () => clearTimeout(t);
132
132
  }, [formattedJson]);
133
133
  // Guard 5: show loading while processing large JSON
134
134
  if (isProcessing && formattedJson.length > 50_000) {
@@ -1,13 +1,3 @@
1
- import { IconType } from "../utils/icon";
2
- import { ComponentType } from "react";
3
- interface MenuCardProps {
4
- title: string;
5
- iconType?: IconType;
6
- onPress?: () => void;
7
- customIcon?: ComponentType<{
8
- size?: number;
9
- color?: string;
10
- }>;
11
- }
12
- export declare function MenuCard({ title, iconType, onPress, customIcon, }: MenuCardProps): import("react").JSX.Element;
13
- export {};
1
+ /** @deprecated Import from `@sincpro/mobile-ui/Display` → `Display.MenuCard` instead. */
2
+ export type { MenuCardProps } from "../Display/Display.MenuCard";
3
+ export { MenuCard } from "../Display/Display.MenuCard";
@@ -1,8 +1 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
- import { theme } from "../theme/useTheme";
3
- import { Typography } from "../Typography";
4
- import { CircleButton } from "../widgets/CircleButton";
5
- import { TouchableOpacity, View } from "react-native";
6
- export function MenuCard({ title, iconType = "ionicons", onPress, customIcon, }) {
7
- return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: "bg-bg-card rounded-2xl p-4 m-2 w-[45%] shadow-sm", onPress: onPress, style: theme.shadow["sm"], children: _jsxs(View, { className: "items-center justify-center", children: [_jsx(CircleButton, { customIcon: customIcon, iconType: iconType }), _jsx(Typography.Text, { className: "text-text-primary mt-3 text-center", variant: "label", children: title })] }) }));
8
- }
1
+ export { MenuCard } from "../Display/Display.MenuCard";
@@ -1,17 +1,5 @@
1
- import { IconType } from "../utils/icon";
2
- import React, { ComponentType } from "react";
3
- export interface MenuItem {
4
- id: string;
5
- title: string;
6
- iconType?: IconType;
7
- onPress?: () => void;
8
- customIcon?: ComponentType<{
9
- size?: number;
10
- color?: string;
11
- }>;
12
- }
13
- interface MenuGridProps {
14
- readonly items: MenuItem[];
15
- }
16
- export declare const MenuGrid: React.FC<MenuGridProps>;
17
- export {};
1
+ /** @deprecated Import from `@sincpro/mobile-ui/Display` → `Display.MenuGrid` instead. */
2
+ export type { MenuGridItem, MenuGridProps, } from "../Display/Display.MenuGrid";
3
+ export { MenuGrid } from "../Display/Display.MenuGrid";
4
+ /** @deprecated Renamed to `MenuGridItem`. */
5
+ export type { MenuGridItem as MenuItem } from "../Display/Display.MenuGrid";
@@ -1,6 +1 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
2
- import { MenuCard } from "../widgets/MenuCard";
3
- import { View } from "react-native";
4
- export const MenuGrid = ({ items }) => {
5
- return (_jsx(View, { className: "flex-row flex-wrap justify-start p-2 bg-bg-page", children: items.map((item) => (_jsx(MenuCard, { customIcon: item.customIcon, iconType: item.iconType, onPress: item.onPress, title: item.title }, item.id))) }));
6
- };
1
+ export { MenuGrid } from "../Display/Display.MenuGrid";
@@ -0,0 +1,3 @@
1
+ /** @deprecated Import from `@sincpro/mobile-ui/Navigation` → `Navigation.MenuSection` instead. */
2
+ export type { MenuSectionProps } from "../Navigation/Navigation.MenuSection";
3
+ export { MenuSection } from "../Navigation/Navigation.MenuSection";
@@ -0,0 +1 @@
1
+ export { MenuSection } from "../Navigation/Navigation.MenuSection";
@@ -0,0 +1,38 @@
1
+ import { ComponentType } from "react";
2
+ export interface PromoBannerItem {
3
+ id: string;
4
+ title: string;
5
+ subtitle?: string;
6
+ /** Left accent icon. */
7
+ icon?: ComponentType<{
8
+ size?: number;
9
+ color?: string;
10
+ }>;
11
+ /** Gradient color stops for the card surface. */
12
+ gradient?: readonly [string, string, ...string[]];
13
+ /** Accent color for the CTA button and badge. Defaults to `theme.accent`. */
14
+ accentColor?: string;
15
+ ctaLabel?: string;
16
+ onPress?: () => void;
17
+ /** Small badge text (e.g. "Nuevo", "Promo"). */
18
+ badge?: string;
19
+ }
20
+ export interface PromoBannerProps {
21
+ items: PromoBannerItem[];
22
+ /** Section heading above the banners row. */
23
+ title?: string;
24
+ }
25
+ /**
26
+ * Horizontal scrollable row of promotional/announcement cards.
27
+ * Each card supports gradient surfaces, icon, badge, title, subtitle, and a CTA button.
28
+ *
29
+ * Usage:
30
+ * <PromoBanner
31
+ * title="Destacados"
32
+ * items={[
33
+ * { id: "1", title: "Nueva ruta", subtitle: "Servicio express", badge: "Nuevo",
34
+ * gradient: ["#0D2B1F", "#00C357"], ctaLabel: "Ver más", onPress: ... },
35
+ * ]}
36
+ * />
37
+ */
38
+ export declare function PromoBanner({ items, title }: PromoBannerProps): import("react").JSX.Element;
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import GradientSurface from "../Display/Display.GradientSurface";
3
+ import { useTheme } from "../theme";
4
+ import { Typography } from "../Typography";
5
+ import { ScrollView, TouchableOpacity, View } from "react-native";
6
+ function BannerCard({ item }) {
7
+ const theme = useTheme();
8
+ const accent = item.accentColor ?? theme.accent;
9
+ const hasGradient = !!item.gradient;
10
+ const Icon = item.icon;
11
+ const textColor = hasGradient ? "#FFFFFF" : theme.text.primary;
12
+ const subtextColor = hasGradient ? "rgba(255,255,255,0.75)" : theme.text.secondary;
13
+ return (_jsxs(TouchableOpacity, { activeOpacity: 0.85, onPress: item.onPress, style: {
14
+ width: 280,
15
+ borderRadius: 16,
16
+ overflow: "hidden",
17
+ marginRight: 12,
18
+ backgroundColor: hasGradient ? "transparent" : theme.bg.card,
19
+ borderWidth: hasGradient ? 0 : 1,
20
+ borderColor: theme.border.default,
21
+ ...theme.shadow.md,
22
+ }, children: [hasGradient ? (_jsx(GradientSurface, { colors: item.gradient, padding: "none", radius: "none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } })) : null, _jsxs(View, { style: { padding: 16 }, children: [item.badge ? (_jsx(View, { style: {
23
+ alignSelf: "flex-start",
24
+ backgroundColor: accent,
25
+ borderRadius: 999,
26
+ paddingHorizontal: 10,
27
+ paddingVertical: 3,
28
+ marginBottom: 10,
29
+ }, children: _jsx(Typography.Text, { semibold: true, style: { color: "#FFFFFF", fontSize: 11 }, variant: "caption", children: item.badge }) })) : null, _jsxs(View, { style: { flexDirection: "row", alignItems: "center", gap: 12 }, children: [Icon ? (_jsx(View, { style: {
30
+ width: 48,
31
+ height: 48,
32
+ borderRadius: 12,
33
+ backgroundColor: hasGradient ? "rgba(255,255,255,0.15)" : theme.bg.muted,
34
+ alignItems: "center",
35
+ justifyContent: "center",
36
+ }, children: _jsx(Icon, { color: hasGradient ? "#FFFFFF" : theme.icon.primary, size: 24 }) })) : null, _jsxs(View, { style: { flex: 1 }, children: [_jsx(Typography.Text, { semibold: true, style: { color: textColor }, variant: "subtitle", children: item.title }), item.subtitle ? (_jsx(Typography.Text, { style: { color: subtextColor, marginTop: 2 }, variant: "bodySmall", children: item.subtitle })) : null] })] }), item.ctaLabel ? (_jsx(View, { style: {
37
+ marginTop: 14,
38
+ alignSelf: "flex-start",
39
+ backgroundColor: hasGradient ? "rgba(255,255,255,0.2)" : accent,
40
+ borderRadius: 999,
41
+ paddingHorizontal: 16,
42
+ paddingVertical: 7,
43
+ }, children: _jsx(Typography.Text, { semibold: true, style: { color: hasGradient ? "#FFFFFF" : theme.text.onAccent, fontSize: 13 }, variant: "bodySmall", children: item.ctaLabel }) })) : null] })] }));
44
+ }
45
+ /**
46
+ * Horizontal scrollable row of promotional/announcement cards.
47
+ * Each card supports gradient surfaces, icon, badge, title, subtitle, and a CTA button.
48
+ *
49
+ * Usage:
50
+ * <PromoBanner
51
+ * title="Destacados"
52
+ * items={[
53
+ * { id: "1", title: "Nueva ruta", subtitle: "Servicio express", badge: "Nuevo",
54
+ * gradient: ["#0D2B1F", "#00C357"], ctaLabel: "Ver más", onPress: ... },
55
+ * ]}
56
+ * />
57
+ */
58
+ export function PromoBanner({ items, title }) {
59
+ const theme = useTheme();
60
+ return (_jsxs(View, { children: [title ? (_jsx(Typography.Text, { semibold: true, style: { color: theme.text.secondary, marginBottom: 10, paddingHorizontal: 16 }, variant: "caption", children: title.toUpperCase() })) : null, _jsx(ScrollView, { contentContainerStyle: { paddingHorizontal: 16, paddingVertical: 2 }, horizontal: true, showsHorizontalScrollIndicator: false, children: items.map((item) => (_jsx(BannerCard, { item: item }, item.id))) })] }));
61
+ }
@@ -0,0 +1,41 @@
1
+ import { ComponentType } from "react";
2
+ export interface PromoModalProps {
3
+ visible: boolean;
4
+ onDismiss: () => void;
5
+ title: string;
6
+ body?: string;
7
+ /** Optional icon rendered at the top of the card. */
8
+ icon?: ComponentType<{
9
+ size?: number;
10
+ color?: string;
11
+ }>;
12
+ /** Gradient for the icon header area (top strip). */
13
+ headerGradient?: readonly [string, string, ...string[]];
14
+ /** Primary CTA label. */
15
+ ctaLabel?: string;
16
+ onPressCta?: () => void;
17
+ /** Secondary link/dismiss label. Defaults to "Ahora no". */
18
+ dismissLabel?: string;
19
+ /** Badge chip text above the title (e.g. "Novedad", "Oferta"). */
20
+ badge?: string;
21
+ /** Accent color for the CTA button and badge. */
22
+ accentColor?: string;
23
+ }
24
+ /**
25
+ * Bottom-anchored modal for news, promotions, or announcements.
26
+ * Tapping outside (backdrop) also dismisses it.
27
+ *
28
+ * Usage:
29
+ * <PromoModal
30
+ * visible={showPromo}
31
+ * onDismiss={() => setShowPromo(false)}
32
+ * title="Nueva función disponible"
33
+ * body="Ahora puedes filtrar rutas por color directamente desde el panel principal."
34
+ * badge="Novedad"
35
+ * headerGradient={["#022B14", "#065E2B"]}
36
+ * icon={BellIcon}
37
+ * ctaLabel="Explorar"
38
+ * onPressCta={handleExplore}
39
+ * />
40
+ */
41
+ export declare function PromoModal({ visible, onDismiss, title, body, icon: Icon, headerGradient, ctaLabel, onPressCta, dismissLabel, badge, accentColor, }: PromoModalProps): import("react").JSX.Element;