@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.
- package/dist/Actions/Actions.Button.d.ts +151 -0
- package/dist/Actions/Actions.Button.js +217 -0
- package/dist/Actions/Actions.FAB.d.ts +10 -0
- package/dist/Actions/Actions.FAB.js +17 -0
- package/dist/Actions/Actions.FilterButton.d.ts +10 -0
- package/dist/Actions/Actions.FilterButton.js +28 -0
- package/dist/Actions/Actions.IconButton.d.ts +67 -0
- package/dist/Actions/Actions.IconButton.js +38 -0
- package/dist/Actions/index.d.ts +22 -0
- package/dist/Actions/index.js +17 -0
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +1 -0
- package/dist/Dialog/Dialog.Sheet.js +6 -4
- package/dist/Display/Display.CircleButton.d.ts +15 -0
- package/dist/Display/Display.CircleButton.js +39 -0
- package/dist/Display/Display.MenuButton.d.ts +51 -8
- package/dist/Display/Display.MenuButton.js +79 -13
- package/dist/Display/Display.MenuCard.d.ts +15 -0
- package/dist/Display/Display.MenuCard.js +24 -0
- package/dist/Display/Display.MenuGrid.d.ts +19 -0
- package/dist/Display/Display.MenuGrid.js +7 -0
- package/dist/Display/Display.SwipeableRow.js +2 -1
- package/dist/Display/Display.Tooltip.d.ts +1 -1
- package/dist/Display/Display.Tooltip.js +16 -5
- package/dist/Display/index.d.ts +9 -0
- package/dist/Display/index.js +6 -0
- package/dist/Feedback/Feedback.Empty.js +2 -2
- package/dist/Feedback/Feedback.Error.js +2 -2
- package/dist/Feedback/Feedback.Progress.js +2 -2
- package/dist/Feedback/Feedback.Skeleton.js +2 -2
- package/dist/Feedback/Feedback.Splash.js +1 -0
- package/dist/Form/Form.Button.d.ts +3 -151
- package/dist/Form/Form.Button.js +1 -283
- package/dist/Form/Form.FAB.d.ts +3 -10
- package/dist/Form/Form.FAB.js +1 -17
- package/dist/Form/Form.FilterButton.d.ts +3 -12
- package/dist/Form/Form.FilterButton.js +1 -34
- package/dist/Form/Form.IconButton.d.ts +3 -67
- package/dist/Form/Form.IconButton.js +1 -64
- package/dist/Navigation/Navigation.AppBar.d.ts +56 -1
- package/dist/Navigation/Navigation.AppBar.js +43 -12
- package/dist/Navigation/Navigation.BottomNav.js +2 -1
- package/dist/Navigation/Navigation.Header.js +3 -2
- package/dist/Navigation/Navigation.Menu.js +6 -3
- package/dist/Navigation/Navigation.MenuSection.d.ts +19 -0
- package/dist/Navigation/Navigation.MenuSection.js +25 -0
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +15 -1
- package/dist/Navigation/Navigation.SegmentedControl.js +22 -4
- package/dist/Navigation/Navigation.TabIcon.js +1 -2
- package/dist/Navigation/Navigation.TabItem.js +2 -2
- package/dist/Navigation/Navigation.Tabs.d.ts +8 -3
- package/dist/Navigation/Navigation.Tabs.js +19 -6
- package/dist/Navigation/index.d.ts +7 -6
- package/dist/Navigation/index.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/layouts/BottomInset.d.ts +16 -0
- package/dist/layouts/BottomInset.js +17 -0
- package/dist/layouts/TabNavigatorLayout.js +2 -2
- package/dist/primitives/BaseButton.js +3 -3
- package/dist/theme/default.js +1 -1
- package/dist/views/FormViewV2.d.ts +18 -6
- package/dist/views/FormViewV2.js +14 -11
- package/dist/views/ListViewV2.d.ts +12 -1
- package/dist/views/ListViewV2.js +16 -10
- package/dist/widgets/CircleButton.d.ts +3 -19
- package/dist/widgets/CircleButton.js +1 -41
- package/dist/widgets/JSONViewer.js +7 -7
- package/dist/widgets/MenuCard.d.ts +3 -13
- package/dist/widgets/MenuCard.js +1 -8
- package/dist/widgets/MenuGrid.d.ts +5 -17
- package/dist/widgets/MenuGrid.js +1 -6
- package/dist/widgets/MenuSection.d.ts +3 -0
- package/dist/widgets/MenuSection.js +1 -0
- package/dist/widgets/PromoBanner.d.ts +38 -0
- package/dist/widgets/PromoBanner.js +61 -0
- package/dist/widgets/PromoModal.d.ts +41 -0
- package/dist/widgets/PromoModal.js +75 -0
- package/dist/widgets/QuickActions.d.ts +26 -0
- package/dist/widgets/QuickActions.js +41 -0
- package/dist/widgets/index.d.ts +11 -1
- package/dist/widgets/index.js +4 -0
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
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(
|
|
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(
|
|
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
|
package/dist/theme/default.js
CHANGED
|
@@ -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:
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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,
|
|
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;
|
package/dist/views/FormViewV2.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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:
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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,
|
|
99
|
-
return (
|
|
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;
|
package/dist/views/ListViewV2.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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";
|
package/dist/widgets/MenuCard.js
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
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";
|
package/dist/widgets/MenuGrid.js
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
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 @@
|
|
|
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;
|