@sincpro/mobile-ui 0.4.1 → 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 +63 -4
  40. package/dist/Navigation/Navigation.AppBar.js +70 -18
  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 +8 -7
  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 +99 -9
  62. package/dist/views/FormViewV2.js +79 -19
  63. package/dist/views/ListViewV2.d.ts +58 -5
  64. package/dist/views/ListViewV2.js +58 -11
  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,9 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
2
2
  import { Display } from "../Display";
3
+ import SearchHistory from "../Display/Display.SearchHistory";
3
4
  import { ErrorBoundary, Feedback } from "../Feedback";
5
+ import { useBottomInset } from "../layouts/BottomInset";
4
6
  import Container from "../layouts/Container";
5
7
  import { Navigation } from "../Navigation";
6
- import { theme } from "../theme";
8
+ import { SegmentedControl, } from "../Navigation/Navigation.SegmentedControl";
9
+ import { useTheme } from "../theme";
7
10
  import { cn, tv } from "../theme/tw";
8
11
  import { Typography } from "../Typography";
9
12
  import { ListViewProvider, useListView } from "../views/ListViewV2.context";
@@ -12,17 +15,15 @@ import { useCallback, useEffect, useMemo, useState } from "react";
12
15
  import { FlatList, RefreshControl, ScrollView, TouchableOpacity, View, } from "react-native";
13
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, }) {
14
17
  const Wrapper = withContainer ? Container : View;
15
- // The header (AppBar) / parent layout owns the TOP safe-area; the Container only reserves
16
- // the remaining edges to avoid stacking the top inset (double padding).
17
18
  const wrapperProps = withContainer
18
19
  ? { edges: ["bottom", "left", "right"] }
19
20
  : { className: "flex-1 bg-bg-page" };
20
21
  // Propagate contentWithMargin to ContentList via context
21
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 }) }) }));
22
23
  }
23
- function Header({ children, }) {
24
+ function Header({ variant = "large", tone, actions, children, background, topSpacing, }) {
24
25
  const { name, description, onBack } = useListView();
25
- return (_jsx(Navigation.AppBar, { onBack: onBack, safeArea: false, subheader: children, subtitle: description ? description : `Seleccionar ${name}`, title: name }));
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 }));
26
27
  }
27
28
  function HeaderSearch({ searchQuery = "" }) {
28
29
  const { name, onSearch } = useListView();
@@ -38,14 +39,32 @@ function HeaderSearch({ searchQuery = "" }) {
38
39
  if (onSearch && debouncedQuery !== undefined) {
39
40
  onSearch(debouncedQuery);
40
41
  }
41
- }, [debouncedQuery]);
42
+ }, [debouncedQuery, onSearch]);
42
43
  if (!onSearch) {
43
44
  return null;
44
45
  }
45
46
  return (_jsx(View, { className: "pb-2", children: _jsx(Navigation.SearchBar, { onChangeText: setQuery, placeholder: `Buscar ${name}`, value: query }) }));
46
47
  }
47
48
  function HeaderActions({ children }) {
48
- return (_jsx(View, { className: "flex-row px-4 my-3 items-center justify-between flex-1 gap-2", children: children }));
49
+ return (_jsx(View, { className: "flex-row px-4 my-2 items-center justify-between flex-1 gap-2", children: children }));
50
+ }
51
+ /** Secondary action bar docked below the AppBar — use for Save/Cancel, filter chips, etc. */
52
+ function HeaderActionsBar({ children }) {
53
+ return (_jsx(View, { className: "bg-bg-card px-4 pt-1 pb-3 border-b border-border-light flex-row gap-2 items-center", children: children }));
54
+ }
55
+ /**
56
+ * Tab-like segmented switch for the header subheader (e.g. "Activos" / "Archivados").
57
+ * Use as a child of `<ListViewV2.Header>` — it lands in the AppBar subheader slot.
58
+ */
59
+ function HeaderSegmented(props) {
60
+ return _jsx(SegmentedControl, { ...props });
61
+ }
62
+ /**
63
+ * Recent-searches list. Render as a SIBLING of the Header (like Filters/ActionsBar) —
64
+ * typically while the search query is empty. Delegates to `Display.SearchHistory`.
65
+ */
66
+ function HeaderSearchHistory(props) {
67
+ return _jsx(SearchHistory, { ...props });
49
68
  }
50
69
  function HeaderFilters({ children }) {
51
70
  return _jsx(View, { className: "px-4 py-2 items-center", children: children });
@@ -78,6 +97,8 @@ function FilterChips({ children }) {
78
97
  }
79
98
  function Content({ children, withMargin = true, }) {
80
99
  const { name, items, isLoading, hasError, errorMessage, onRefresh, onRetry, onBack, showRefreshing, setShowRefreshing, } = useListView();
100
+ const bottomInset = useBottomInset();
101
+ const theme = useTheme();
81
102
  const handleRefresh = useCallback(async () => {
82
103
  setShowRefreshing(true);
83
104
  if (onRefresh) {
@@ -93,19 +114,23 @@ function Content({ children, withMargin = true, }) {
93
114
  flexGrow: 1,
94
115
  justifyContent: "center",
95
116
  alignItems: "center",
96
- }, 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 }) }));
97
119
  }
98
120
  if (items.length === 0) {
99
121
  return (_jsx(ScrollView, { contentContainerStyle: {
100
122
  flexGrow: 1,
101
123
  justifyContent: "center",
102
124
  alignItems: "center",
103
- }, 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, {}) }));
104
127
  }
105
- 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 })] }));
106
129
  }
107
130
  function ContentList({ children, contentWithMargin, }) {
108
131
  const { items, onRefresh, showRefreshing, setShowRefreshing, onPressItem, onDeleteItem, readonly, onLoadMore, loadingMore, } = useListView();
132
+ const bottomInset = useBottomInset();
133
+ const theme = useTheme();
109
134
  const rowItems = useMemo(() => toRowItems(items), [items]);
110
135
  const handleRefresh = useCallback(async () => {
111
136
  setShowRefreshing(true);
@@ -127,7 +152,8 @@ function ContentList({ children, contentWithMargin, }) {
127
152
  flex: 1,
128
153
  marginTop: 2,
129
154
  paddingHorizontal: contentWithMargin ? 4 : 0,
130
- }, 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 }) }));
131
157
  }
132
158
  function Footer({ children }) {
133
159
  const { items } = useListView();
@@ -178,7 +204,10 @@ const FiltersWithSubComponents = Object.assign(HeaderFilters, {
178
204
  });
179
205
  const HeaderWithSubComponents = Object.assign(Header, {
180
206
  Search: HeaderSearch,
207
+ SearchHistory: HeaderSearchHistory,
208
+ Segmented: HeaderSegmented,
181
209
  Actions: HeaderActions,
210
+ ActionsBar: HeaderActionsBar,
182
211
  Filters: FiltersWithSubComponents,
183
212
  });
184
213
  const RowWithSubComponents = Object.assign(RowRoot, {
@@ -194,6 +223,24 @@ const ContentWithSubComponents = Object.assign(Content, {
194
223
  List: ContentList,
195
224
  Row: RowWithSubComponents,
196
225
  });
226
+ /**
227
+ * Screen container for a collection (list / index / search results).
228
+ * Owns safe-area, the FlatList (pagination + pull-to-refresh), record count, and the
229
+ * loading/empty/error states.
230
+ *
231
+ * API map:
232
+ * ListViewV2.Root — provider + container; pass `items`, `onSearch`, `onLoadMore`, `onBack`…
233
+ * ListViewV2.Header — AppBar (variant: default | large | center · tone: default | dark | gradient)
234
+ * .Header.Search — debounced SearchBar in the subheader (wires `onSearch`)
235
+ * .Header.SearchHistory — recent-searches list (SIBLING; show while query is empty)
236
+ * .Header.Segmented — tab-like switch in the subheader (Activos / Archivados)
237
+ * .Header.Filters(.Chips/.Chip) — horizontal filter pills (SIBLING below the header)
238
+ * .Header.ActionsBar — SIBLING bar below the header (bulk select, etc.)
239
+ * ListViewV2.Content — FlatList body; `children` is an optional custom row renderer
240
+ * .Content.Row(.Avatar/.Content/.Title/.Subtitle/.Footer/.Actions/.ActionButton)
241
+ * ListViewV2.Footer — pinned footer (hidden when empty)
242
+ * ListViewV2.FloatingButton — absolute FAB overlay
243
+ */
197
244
  export const ListViewV2 = {
198
245
  Root: ListViewRoot,
199
246
  Header: HeaderWithSubComponents,
@@ -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;
@@ -0,0 +1,75 @@
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 { Modal, TouchableOpacity, TouchableWithoutFeedback, View } from "react-native";
6
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
7
+ /**
8
+ * Bottom-anchored modal for news, promotions, or announcements.
9
+ * Tapping outside (backdrop) also dismisses it.
10
+ *
11
+ * Usage:
12
+ * <PromoModal
13
+ * visible={showPromo}
14
+ * onDismiss={() => setShowPromo(false)}
15
+ * title="Nueva función disponible"
16
+ * body="Ahora puedes filtrar rutas por color directamente desde el panel principal."
17
+ * badge="Novedad"
18
+ * headerGradient={["#022B14", "#065E2B"]}
19
+ * icon={BellIcon}
20
+ * ctaLabel="Explorar"
21
+ * onPressCta={handleExplore}
22
+ * />
23
+ */
24
+ export function PromoModal({ visible, onDismiss, title, body, icon: Icon, headerGradient, ctaLabel, onPressCta, dismissLabel = "Ahora no", badge, accentColor, }) {
25
+ const theme = useTheme();
26
+ const insets = useSafeAreaInsets();
27
+ const accent = accentColor ?? theme.accent;
28
+ return (_jsxs(Modal, { animationType: "fade", onRequestClose: onDismiss, statusBarTranslucent: true, transparent: true, visible: visible, children: [_jsx(TouchableWithoutFeedback, { onPress: onDismiss, children: _jsx(View, { style: {
29
+ flex: 1,
30
+ backgroundColor: "rgba(0,0,0,0.55)",
31
+ alignItems: "center",
32
+ justifyContent: "center",
33
+ paddingHorizontal: 24,
34
+ paddingVertical: insets.top + 16,
35
+ } }) }), _jsx(View, { pointerEvents: "box-none", style: {
36
+ position: "absolute",
37
+ top: 0,
38
+ left: 0,
39
+ right: 0,
40
+ bottom: 0,
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ paddingHorizontal: 24,
44
+ }, children: _jsxs(View, { style: {
45
+ backgroundColor: theme.bg.card,
46
+ borderRadius: 28,
47
+ borderWidth: 1,
48
+ borderColor: theme.border.default,
49
+ width: "100%",
50
+ maxWidth: 400,
51
+ overflow: "hidden",
52
+ }, children: [headerGradient || Icon ? (_jsxs(View, { style: { height: 120, justifyContent: "center", alignItems: "center" }, children: [headerGradient ? (_jsx(GradientSurface, { colors: headerGradient, padding: "none", radius: "none", style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 } })) : null, Icon ? (_jsx(View, { style: {
53
+ width: 64,
54
+ height: 64,
55
+ borderRadius: 20,
56
+ backgroundColor: "rgba(255,255,255,0.18)",
57
+ alignItems: "center",
58
+ justifyContent: "center",
59
+ }, children: _jsx(Icon, { color: "#FFFFFF", size: 32 }) })) : null] })) : null, _jsxs(View, { style: { paddingHorizontal: 24, paddingTop: 12, paddingBottom: 8 }, children: [badge ? (_jsx(View, { style: {
60
+ alignSelf: "flex-start",
61
+ backgroundColor: accent,
62
+ borderRadius: 999,
63
+ paddingHorizontal: 12,
64
+ paddingVertical: 4,
65
+ marginBottom: 12,
66
+ }, children: _jsx(Typography.Text, { semibold: true, style: { color: theme.text.onAccent, fontSize: 11 }, variant: "caption", children: badge }) })) : null, _jsx(Typography.Text, { semibold: true, style: { color: theme.text.primary }, variant: "h3", children: title }), body ? (_jsx(Typography.Text, { style: { color: theme.text.secondary, marginTop: 8, lineHeight: 22 }, variant: "body", children: body })) : null] }), _jsxs(View, { style: { paddingHorizontal: 24, paddingTop: 16, gap: 10 }, children: [ctaLabel ? (_jsx(TouchableOpacity, { activeOpacity: 0.8, onPress: () => {
67
+ onPressCta?.();
68
+ onDismiss();
69
+ }, style: {
70
+ backgroundColor: accent,
71
+ borderRadius: 14,
72
+ paddingVertical: 15,
73
+ alignItems: "center",
74
+ }, children: _jsx(Typography.Text, { semibold: true, style: { color: theme.text.onAccent }, variant: "body", children: ctaLabel }) })) : null, _jsx(TouchableOpacity, { activeOpacity: 0.7, onPress: onDismiss, style: { alignItems: "center", paddingVertical: 12 }, children: _jsx(Typography.Text, { style: { color: theme.text.secondary }, variant: "body", children: dismissLabel }) })] })] }) })] }));
75
+ }
@@ -0,0 +1,26 @@
1
+ import { ComponentType } from "react";
2
+ export interface QuickActionItem {
3
+ id: string;
4
+ label: string;
5
+ /** Ionicon name — use this OR `customIcon`. */
6
+ icon?: string;
7
+ customIcon?: ComponentType<{
8
+ size?: number;
9
+ color?: string;
10
+ }>;
11
+ badge?: string | number;
12
+ onPress?: () => void;
13
+ /** Circle background color override. Defaults to `theme.bg.muted`. */
14
+ color?: string;
15
+ }
16
+ export interface QuickActionsProps {
17
+ items: QuickActionItem[];
18
+ /** Section heading rendered above the row. */
19
+ title?: string;
20
+ }
21
+ /**
22
+ * Horizontal scrollable row of small icon+label shortcuts ("Acciones Rápidas").
23
+ * Each item renders a 48 px icon circle + optional badge + label below.
24
+ * Scroll is horizontal and indicator-free for a clean look.
25
+ */
26
+ export declare function QuickActions({ items, title }: QuickActionsProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Icon from "../Display/Display.Icon";
3
+ import { useTheme } from "../theme";
4
+ import { Typography } from "../Typography";
5
+ import { ScrollView, TouchableOpacity, View } from "react-native";
6
+ /**
7
+ * Horizontal scrollable row of small icon+label shortcuts ("Acciones Rápidas").
8
+ * Each item renders a 48 px icon circle + optional badge + label below.
9
+ * Scroll is horizontal and indicator-free for a clean look.
10
+ */
11
+ export function QuickActions({ items, title }) {
12
+ const theme = useTheme();
13
+ 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: 12, gap: 4 }, horizontal: true, showsHorizontalScrollIndicator: false, children: items.map((item) => (_jsxs(TouchableOpacity, { activeOpacity: 0.7, onPress: item.onPress, style: { alignItems: "center", width: 72, paddingVertical: 4 }, children: [_jsx(View, { style: {
14
+ width: 52,
15
+ height: 52,
16
+ borderRadius: 999,
17
+ backgroundColor: item.color ?? theme.bg.muted,
18
+ alignItems: "center",
19
+ justifyContent: "center",
20
+ borderWidth: 1,
21
+ borderColor: theme.border.default,
22
+ }, children: _jsx(Icon, { color: theme.text.primary, customIcon: item.customIcon, name: item.icon, size: 24, type: item.customIcon ? "custom" : "ionicons" }) }), item.badge != null ? (_jsx(View, { style: {
23
+ position: "absolute",
24
+ top: 2,
25
+ right: 4,
26
+ minWidth: 18,
27
+ height: 18,
28
+ borderRadius: 999,
29
+ backgroundColor: theme.danger,
30
+ alignItems: "center",
31
+ justifyContent: "center",
32
+ paddingHorizontal: 4,
33
+ borderWidth: 2,
34
+ borderColor: theme.bg.card,
35
+ }, children: _jsx(Typography.Text, { style: { color: "#FFFFFF", fontSize: 9, lineHeight: 11 }, children: item.badge }) })) : null, _jsx(Typography.Text, { numberOfLines: 2, style: {
36
+ color: theme.text.primary,
37
+ marginTop: 6,
38
+ textAlign: "center",
39
+ fontSize: 11,
40
+ }, variant: "caption", children: item.label })] }, item.id))) })] }));
41
+ }
@@ -2,6 +2,16 @@ export { CircleButton } from "../widgets/CircleButton";
2
2
  export { default as GeoPermissionCard } from "../widgets/GeoPermissionCard";
3
3
  export { HomeHeader } from "../widgets/HomeHeader";
4
4
  export { MenuCard } from "../widgets/MenuCard";
5
- export type { MenuItem } from "../widgets/MenuGrid";
5
+ export type { MenuGridItem, MenuGridProps } from "../widgets/MenuGrid";
6
+ /** @deprecated Renamed to `MenuGridItem`. */
7
+ export type { MenuGridItem as MenuItem } from "../widgets/MenuGrid";
6
8
  export { MenuGrid } from "../widgets/MenuGrid";
9
+ export type { MenuSectionProps } from "../widgets/MenuSection";
10
+ export { MenuSection } from "../widgets/MenuSection";
11
+ export type { PromoBannerItem, PromoBannerProps, } from "../widgets/PromoBanner";
12
+ export { PromoBanner } from "../widgets/PromoBanner";
13
+ export type { PromoModalProps } from "../widgets/PromoModal";
14
+ export { PromoModal } from "../widgets/PromoModal";
15
+ export type { QuickActionItem, QuickActionsProps, } from "../widgets/QuickActions";
16
+ export { QuickActions } from "../widgets/QuickActions";
7
17
  export { TimeZoneSelector } from "../widgets/TimeZoneSelector";