@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
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // Design System - Compound Components
2
+ export { Actions } from "./Actions";
2
3
  export { Dialog } from "./Dialog";
3
4
  export { Display } from "./Display";
4
5
  export { Feedback } from "./Feedback";
@@ -13,6 +14,7 @@ export { getBrandingLogo, setBranding, } from "./branding";
13
14
  export { cn, tv } from "./theme/tw";
14
15
  export { fontFamilies, fontSizes, fontWeights, letterSpacing, lineHeights, typographyVariants, } from "./theme/typography";
15
16
  // Layouts
17
+ export { BottomInsetContext } from "./layouts/BottomInset";
16
18
  export { default as Container } from "./layouts/Container";
17
19
  export { default as GradientContainer } from "./layouts/GradientContainer";
18
20
  export { default as PlainLayout } from "./layouts/PlainLayout";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extra bottom inset for scroll views, provided by the host app when a floating
3
+ * element (e.g. position:absolute tab bar) sits above the native safe area.
4
+ *
5
+ * FormViewV2.Content and ListViewV2.Content add this value as extra
6
+ * `contentContainerStyle.paddingBottom` so content can scroll fully above the bar.
7
+ *
8
+ * Usage in the host app:
9
+ * <BottomInsetContext.Provider value={72}>
10
+ * <Tabs.Navigator ...>
11
+ *
12
+ * Default: 0 — no extra inset (backwards compatible).
13
+ * The native safe-area inset is already handled by Container (SafeAreaView edges=["bottom"]).
14
+ */
15
+ export declare const BottomInsetContext: import("react").Context<number>;
16
+ export declare const useBottomInset: () => number;
@@ -0,0 +1,17 @@
1
+ import { createContext, useContext } from "react";
2
+ /**
3
+ * Extra bottom inset for scroll views, provided by the host app when a floating
4
+ * element (e.g. position:absolute tab bar) sits above the native safe area.
5
+ *
6
+ * FormViewV2.Content and ListViewV2.Content add this value as extra
7
+ * `contentContainerStyle.paddingBottom` so content can scroll fully above the bar.
8
+ *
9
+ * Usage in the host app:
10
+ * <BottomInsetContext.Provider value={72}>
11
+ * <Tabs.Navigator ...>
12
+ *
13
+ * Default: 0 — no extra inset (backwards compatible).
14
+ * The native safe-area inset is already handled by Container (SafeAreaView edges=["bottom"]).
15
+ */
16
+ export const BottomInsetContext = createContext(0);
17
+ export const useBottomInset = () => useContext(BottomInsetContext);
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
2
- import { Display } from "../Display";
2
+ import DisplayIcon from "../Display/Display.Icon";
3
3
  import Container from "../layouts/Container";
4
4
  import { Typography } from "../Typography";
5
5
  import { Children, isValidElement } from "react";
6
6
  import { TouchableOpacity, View } from "react-native";
7
7
  import { useSafeAreaInsets } from "react-native-safe-area-context";
8
8
  function TabIcon({ focused, Icon }) {
9
- return (_jsxs(View, { className: "justify-center items-center w-10 h-10", children: [focused && _jsx(View, { className: "absolute w-10 h-10 rounded-full bg-primary" }), _jsx(Display.Icon, { color: focused ? "#FFFFFF" : "#6B7280", customIcon: Icon, size: 24, type: "custom" })] }));
9
+ return (_jsxs(View, { className: "justify-center items-center w-10 h-10", children: [focused && _jsx(View, { className: "absolute w-10 h-10 rounded-full bg-primary" }), _jsx(DisplayIcon, { color: focused ? "#FFFFFF" : "#6B7280", customIcon: Icon, size: 24, type: "custom" })] }));
10
10
  }
11
11
  function TabNavigator({ items, currentPath, onTabPress, }) {
12
12
  const insets = useSafeAreaInsets();
@@ -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,4 +1,5 @@
1
- import { EVariantScreenHeader } from "../widgets/ScreenHeader";
1
+ import type { AppBarBackground } from "../Navigation/Navigation.AppBar";
2
+ import { type SegmentedControlProps } from "../Navigation/Navigation.SegmentedControl";
2
3
  import React, { ReactNode } from "react";
3
4
  import { type ImageSourcePropType } from "react-native";
4
5
  interface FormViewProps<T> {
@@ -17,46 +18,135 @@ interface FormViewProps<T> {
17
18
  children: ReactNode;
18
19
  }
19
20
  declare function FormViewRoot<T>({ name, description, item, isLoading, isEmpty, hasError, errorMessage, readonly, onRefresh, onRetry, onBack, withContainer, children, }: FormViewProps<T>): React.JSX.Element;
20
- declare function Header({ children, }: {
21
- /** @deprecated The header is now flat (Navigation.AppBar); `variant`/`logoSource` are ignored. */
22
- variant?: EVariantScreenHeader;
23
- logoSource?: ImageSourcePropType;
21
+ interface HeaderProps {
22
+ /** AppBar display variant. `large` = big title (list/index screens). `center` = modal/detail. */
23
+ variant?: "default" | "large" | "center";
24
+ /** Surface tone. `dark` = always-dark hero. `gradient` = brand accent gradient. */
25
+ tone?: "default" | "dark" | "gradient";
26
+ /** Actions rendered in the AppBar action slot (AppBar.Action nodes). */
27
+ actions?: ReactNode;
28
+ /** Subheader content rendered below the title bar (SearchBar, SegmentedControl, etc.). */
24
29
  children?: ReactNode;
25
- }): React.JSX.Element;
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;
40
+ /** @deprecated Pass `variant` prop instead. */
41
+ logoSource?: ImageSourcePropType;
42
+ }
43
+ declare function Header({ variant, tone, actions, children, topSpacing, background, }: HeaderProps): React.JSX.Element;
26
44
  declare function HeaderActions({ children }: {
27
45
  children: ReactNode;
28
46
  }): React.JSX.Element;
47
+ /** Secondary action bar docked below the AppBar — use for Save/Cancel, filter chips, etc. */
29
48
  declare function HeaderActionsBar({ children }: {
30
49
  children: ReactNode;
31
50
  }): React.JSX.Element;
51
+ /**
52
+ * Tab-like segmented switch for the header subheader (e.g. "Detalles" / "Historial").
53
+ * Use as a child of `<FormViewV2.Header>` — it lands in the AppBar subheader slot.
54
+ */
55
+ declare function HeaderSegmented<T extends string>(props: SegmentedControlProps<T>): React.JSX.Element;
32
56
  declare function Content({ withMargin, children, }: {
33
57
  withMargin?: boolean;
34
58
  children: ReactNode;
35
59
  }): React.JSX.Element;
60
+ /**
61
+ * @deprecated Content already handles isEmpty/!item by rendering Feedback.Empty internally.
62
+ * Use `<FormViewV2.Content>` directly — this wrapper is redundant.
63
+ */
36
64
  declare function Form({ children }: {
37
65
  children: ReactNode;
38
66
  }): React.JSX.Element;
67
+ /** Layout wrapper for a set of Group cards. Content already handles isEmpty/loading states. */
39
68
  declare function Groups({ children }: {
40
69
  children: ReactNode;
41
- }): React.JSX.Element | null;
42
- declare function Group({ children, withMargin, elevation, }: {
70
+ }): React.JSX.Element;
71
+ /**
72
+ * Card-bordered input group for form fields. Use inside `Content.Groups`.
73
+ *
74
+ * Separation — by DEFAULT each Group is its own inset card (`withMargin=true`:
75
+ * margin 16/4, iOS "grouped" style), so several Groups read as separate sections.
76
+ * If you DON'T want separation, put all fields in a SINGLE Group (one continuous
77
+ * card), or pass `withMargin={false}` to render flush/edge-to-edge — e.g. when
78
+ * embedding inside your own layout.
79
+ *
80
+ * `title` renders a SectionHeader label above the card.
81
+ */
82
+ declare function Group({ children, withMargin, elevation, padding, variant, title, }: {
43
83
  children: ReactNode;
84
+ /** `true` (default) = inset card with margins; `false` = flush, no margin. */
44
85
  withMargin?: boolean;
45
- elevation?: "sm" | "md" | "lg";
86
+ /**
87
+ * Frame style. `card` (default) wraps fields in a Display.Card. `plain` drops the
88
+ * frame so the content sits directly on the page — useful when the children already
89
+ * carry their own surface (Form.Input, Display.Card, custom rows) and a wrapping card
90
+ * would just double up.
91
+ */
92
+ variant?: "card" | "plain";
93
+ /**
94
+ * [card only] Shadow. `none` (default) = flat — the inputs already carry their own card
95
+ * surface, so a group shadow only adds a "floating" double-shadow. Opt into `sm`/`md`
96
+ * for a raised group when it sits directly on the page.
97
+ */
98
+ elevation?: "none" | "sm" | "md" | "lg";
99
+ /**
100
+ * Inner padding (Display.Card scale). `md` (default) aligns the title with the labels.
101
+ * Applies to BOTH variants, so `plain` keeps the exact same content position as `card`.
102
+ */
103
+ padding?: "none" | "sm" | "md" | "lg";
104
+ /** Label rendered above the group (SectionHeader style). */
105
+ title?: string;
106
+ }): React.JSX.Element;
107
+ /**
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.
111
+ */
112
+ declare function Section({ title, footer, children, }: {
113
+ title?: string;
114
+ /** Hint text rendered below the card (e.g. "Toca para cambiar la configuración"). */
115
+ footer?: string;
116
+ children: ReactNode;
46
117
  }): React.JSX.Element;
47
118
  declare function Footer({ children }: {
48
119
  children: ReactNode;
49
120
  }): React.JSX.Element | null;
121
+ /**
122
+ * Screen container for a single record (detail / create / edit / settings).
123
+ * Owns safe-area, scroll (KeyboardAware), refresh, and the loading/empty/error states.
124
+ *
125
+ * API map:
126
+ * FormViewV2.Root — provider + container; pass `item`, `isLoading`, `hasError`, `onBack`…
127
+ * FormViewV2.Header — AppBar (variant: default | large | center · tone: default | dark | gradient)
128
+ * .Header child → subheader slot: <Header.Segmented> or any node (SearchBar, etc.)
129
+ * .Header.Segmented — tab-like switch in the subheader (Detalles / Historial)
130
+ * .Header.ActionsBar — SIBLING bar below the header (Guardar / Cancelar)
131
+ * .Header.Actions — inline action row (legacy helper)
132
+ * FormViewV2.Content — scroll body; renders Feedback for loading/empty/error
133
+ * .Content.Groups — layout wrapper for Group cards
134
+ * .Content.Group — bordered card of inputs (optional `title`)
135
+ * .Content.Section — SectionHeader + Card (settings/menu rows)
136
+ * FormViewV2.Footer — sticky bottom bar (primary CTA)
137
+ */
50
138
  export declare const FormViewV2: {
51
139
  Root: typeof FormViewRoot;
52
140
  Header: typeof Header & {
53
141
  Actions: typeof HeaderActions;
54
142
  ActionsBar: typeof HeaderActionsBar;
143
+ Segmented: typeof HeaderSegmented;
55
144
  };
56
145
  Content: typeof Content & {
57
146
  Form: typeof Form;
58
147
  Groups: typeof Groups;
59
148
  Group: typeof Group;
149
+ Section: typeof Section;
60
150
  };
61
151
  Footer: typeof Footer;
62
152
  };
@@ -1,33 +1,45 @@
1
- import { jsx as _jsx, Fragment as _Fragment } from "nativewind/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import { Display } from "../Display";
2
3
  import { ErrorBoundary, Feedback } from "../Feedback";
4
+ import { useBottomInset } from "../layouts/BottomInset";
3
5
  import Container from "../layouts/Container";
4
6
  import { AppBar } from "../Navigation/Navigation.AppBar";
5
- import { theme } from "../theme";
7
+ import { MenuSection } from "../Navigation/Navigation.MenuSection";
8
+ import { SegmentedControl, } from "../Navigation/Navigation.SegmentedControl";
9
+ import { useTheme } from "../theme";
6
10
  import { FormViewProvider, useFormView } from "../views/FormViewV2.context";
7
11
  import React from "react";
8
12
  import { RefreshControl, View } from "react-native";
9
13
  import { KeyboardAwareScrollView } from "react-native-keyboard-controller";
10
14
  function FormViewRoot({ name, description, item, isLoading = false, isEmpty = false, hasError = false, errorMessage, readonly = false, onRefresh, onRetry, onBack, withContainer = true, children, }) {
11
15
  const Wrapper = withContainer ? Container : View;
12
- // The header (AppBar) / parent layout owns the TOP safe-area; the inner Container only
13
- // reserves the remaining edges to avoid stacking the top inset (double padding).
14
16
  const wrapperProps = withContainer
15
17
  ? { edges: ["bottom", "left", "right"] }
16
18
  : { style: { flex: 1 } };
17
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 }) }) }));
18
20
  }
19
- function Header({ children, }) {
21
+ function Header({ variant = "default", tone, actions, children, topSpacing, background, }) {
20
22
  const { name, description, onBack } = useFormView();
21
- return (_jsx(AppBar, { onBack: onBack, safeArea: false, subheader: children, subtitle: description ? description : `Seleccionar ${name}`, title: name }));
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 }));
22
24
  }
23
25
  function HeaderActions({ children }) {
24
- return (_jsx(View, { className: "flex-row px-4 my-4 items-center justify-between flex-1 gap-2", children: children }));
26
+ return (_jsx(View, { className: "flex-row px-4 my-2 items-center justify-between flex-1 gap-2", children: children }));
25
27
  }
28
+ /** Secondary action bar docked below the AppBar — use for Save/Cancel, filter chips, etc. */
26
29
  function HeaderActionsBar({ children }) {
27
- return (_jsx(View, { className: "bg-bg-card px-4 py-3 border-b border-border-default flex-row gap-2", children: children }));
30
+ 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 }));
31
+ }
32
+ /**
33
+ * Tab-like segmented switch for the header subheader (e.g. "Detalles" / "Historial").
34
+ * Use as a child of `<FormViewV2.Header>` — it lands in the AppBar subheader slot.
35
+ */
36
+ function HeaderSegmented(props) {
37
+ return _jsx(SegmentedControl, { ...props });
28
38
  }
29
39
  function Content({ withMargin = false, children, }) {
30
40
  const { name, isLoading, isEmpty, hasError, errorMessage, item, onRefresh, onRetry, onBack, showRefreshing, setShowRefreshing, } = useFormView();
41
+ const bottomInset = useBottomInset();
42
+ const theme = useTheme();
31
43
  const handleRefresh = React.useCallback(async () => {
32
44
  setShowRefreshing(true);
33
45
  if (onRefresh) {
@@ -40,8 +52,13 @@ function Content({ withMargin = false, children, }) {
40
52
  flexGrow: 1,
41
53
  justifyContent: isCenteredContent ? "center" : "flex-start",
42
54
  alignItems: isCenteredContent ? "center" : "stretch",
43
- }, 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 })) }));
44
57
  }
58
+ /**
59
+ * @deprecated Content already handles isEmpty/!item by rendering Feedback.Empty internally.
60
+ * Use `<FormViewV2.Content>` directly — this wrapper is redundant.
61
+ */
45
62
  function Form({ children }) {
46
63
  const { isEmpty, item } = useFormView();
47
64
  if (isEmpty || !item) {
@@ -49,37 +66,80 @@ function Form({ children }) {
49
66
  }
50
67
  return _jsx(_Fragment, { children: children });
51
68
  }
69
+ /** Layout wrapper for a set of Group cards. Content already handles isEmpty/loading states. */
52
70
  function Groups({ children }) {
53
- const { isEmpty } = useFormView();
54
- if (isEmpty) {
55
- return null;
56
- }
57
71
  return _jsx(_Fragment, { children: children });
58
72
  }
59
- function Group({ children, withMargin = true, elevation = "md", }) {
60
- const groupClass = withMargin
61
- ? "bg-bg-card mx-4 my-1 p-1 rounded-lg border border-border-light"
62
- : "bg-bg-card p-1 rounded-lg border border-border-light";
63
- return (_jsx(View, { className: groupClass, style: theme.shadow[elevation], children: children }));
73
+ /**
74
+ * Card-bordered input group for form fields. Use inside `Content.Groups`.
75
+ *
76
+ * Separation — by DEFAULT each Group is its own inset card (`withMargin=true`:
77
+ * margin 16/4, iOS "grouped" style), so several Groups read as separate sections.
78
+ * If you DON'T want separation, put all fields in a SINGLE Group (one continuous
79
+ * card), or pass `withMargin={false}` to render flush/edge-to-edge — e.g. when
80
+ * embedding inside your own layout.
81
+ *
82
+ * `title` renders a SectionHeader label above the card.
83
+ */
84
+ function Group({ children, withMargin = true, elevation = "none", padding = "md", variant = "card", title, }) {
85
+ // `plain` mirrors the card's inner padding so the content lands in the SAME position
86
+ // as `card` — only the frame (border/bg/shadow) is dropped.
87
+ const paddingClass = { none: "p-0", sm: "p-3", md: "p-4", lg: "p-6" }[padding];
88
+ const body = variant === "plain" ? (_jsx(View, { className: paddingClass, children: children })) : (_jsx(Display.Card, { elevation: elevation, padding: padding, children: children }));
89
+ // Title alignment is identical in both variants (same inset as the card's content).
90
+ const header = title ? _jsx(Display.SectionHeader, { title: title }) : null;
91
+ if (withMargin) {
92
+ return (_jsxs(View, { style: { marginHorizontal: 16, marginVertical: 6 }, children: [header, body] }));
93
+ }
94
+ return (_jsxs(_Fragment, { children: [header, body] }));
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.
100
+ */
101
+ function Section({ title, footer, children, }) {
102
+ return (_jsx(MenuSection, { footer: footer, title: title, children: children }));
64
103
  }
65
104
  function Footer({ children }) {
66
105
  const { isEmpty, isLoading } = useFormView();
67
106
  if (isEmpty || isLoading) {
68
107
  return null;
69
108
  }
70
- return (_jsx(View, { className: "bg-bg-card px-4 py-3 border-t border-border-default", style: theme.shadow.sm, children: children }));
109
+ // Sticky bottom bar (lives outside the scroll). No shadow — a top hairline integrates it
110
+ // with the form instead of reading as a detached floating card.
111
+ return (_jsx(View, { className: "bg-bg-card px-4 py-3 border-t border-border-light", children: children }));
71
112
  }
72
113
  // Nested Header components
73
114
  const HeaderWithSubComponents = Object.assign(Header, {
74
115
  Actions: HeaderActions,
75
116
  ActionsBar: HeaderActionsBar,
117
+ Segmented: HeaderSegmented,
76
118
  });
77
119
  // Nested Content components
78
120
  const ContentWithSubComponents = Object.assign(Content, {
79
121
  Form,
80
122
  Groups,
81
123
  Group,
124
+ Section,
82
125
  });
126
+ /**
127
+ * Screen container for a single record (detail / create / edit / settings).
128
+ * Owns safe-area, scroll (KeyboardAware), refresh, and the loading/empty/error states.
129
+ *
130
+ * API map:
131
+ * FormViewV2.Root — provider + container; pass `item`, `isLoading`, `hasError`, `onBack`…
132
+ * FormViewV2.Header — AppBar (variant: default | large | center · tone: default | dark | gradient)
133
+ * .Header child → subheader slot: <Header.Segmented> or any node (SearchBar, etc.)
134
+ * .Header.Segmented — tab-like switch in the subheader (Detalles / Historial)
135
+ * .Header.ActionsBar — SIBLING bar below the header (Guardar / Cancelar)
136
+ * .Header.Actions — inline action row (legacy helper)
137
+ * FormViewV2.Content — scroll body; renders Feedback for loading/empty/error
138
+ * .Content.Groups — layout wrapper for Group cards
139
+ * .Content.Group — bordered card of inputs (optional `title`)
140
+ * .Content.Section — SectionHeader + Card (settings/menu rows)
141
+ * FormViewV2.Footer — sticky bottom bar (primary CTA)
142
+ */
83
143
  export const FormViewV2 = {
84
144
  Root: FormViewRoot,
85
145
  Header: HeaderWithSubComponents,
@@ -1,4 +1,6 @@
1
- import { EVariantScreenHeader } from "../widgets/ScreenHeader";
1
+ import { type SearchHistoryProps } from "../Display/Display.SearchHistory";
2
+ import type { AppBarBackground } from "../Navigation/Navigation.AppBar";
3
+ import { type SegmentedControlProps } from "../Navigation/Navigation.SegmentedControl";
2
4
  import React, { ReactNode } from "react";
3
5
  import { StyleProp, ViewStyle } from "react-native";
4
6
  interface ListViewProps<T> {
@@ -24,17 +26,47 @@ interface ListViewProps<T> {
24
26
  children: ReactNode;
25
27
  }
26
28
  declare function ListViewRoot<T>({ name, description, items, isLoading, hasError, errorMessage, readonly, onRefresh, onRetry, onPressItem, onDeleteItem, onSearch, onBack, onLoadMore, loadingMore, backgroundColor, withContainer, children, }: ListViewProps<T>): React.JSX.Element;
27
- declare function Header({ children, }: {
28
- /** @deprecated The header is now flat (Navigation.AppBar); `variant` is ignored. */
29
- variant?: EVariantScreenHeader;
29
+ interface HeaderProps {
30
+ /** AppBar display variant. `large` = big title (list/index). `center` = modal/detail. */
31
+ variant?: "default" | "large" | "center";
32
+ /** Surface tone. `dark` = always-dark hero. `gradient` = brand accent gradient. */
33
+ tone?: "default" | "dark" | "gradient";
34
+ /** Actions rendered in the AppBar action slot (AppBar.Action nodes). */
35
+ actions?: ReactNode;
36
+ /** Subheader content rendered below the title bar (SearchBar, SegmentedControl, etc.). */
30
37
  children?: ReactNode;
31
- }): React.JSX.Element;
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;
48
+ }
49
+ declare function Header({ variant, tone, actions, children, background, topSpacing, }: HeaderProps): React.JSX.Element;
32
50
  declare function HeaderSearch({ searchQuery }: {
33
51
  searchQuery?: string;
34
52
  }): React.JSX.Element | null;
35
53
  declare function HeaderActions({ children }: {
36
54
  children: ReactNode;
37
55
  }): React.JSX.Element;
56
+ /** Secondary action bar docked below the AppBar — use for Save/Cancel, filter chips, etc. */
57
+ declare function HeaderActionsBar({ children }: {
58
+ children: ReactNode;
59
+ }): React.JSX.Element;
60
+ /**
61
+ * Tab-like segmented switch for the header subheader (e.g. "Activos" / "Archivados").
62
+ * Use as a child of `<ListViewV2.Header>` — it lands in the AppBar subheader slot.
63
+ */
64
+ declare function HeaderSegmented<T extends string>(props: SegmentedControlProps<T>): React.JSX.Element;
65
+ /**
66
+ * Recent-searches list. Render as a SIBLING of the Header (like Filters/ActionsBar) —
67
+ * typically while the search query is empty. Delegates to `Display.SearchHistory`.
68
+ */
69
+ declare function HeaderSearchHistory(props: SearchHistoryProps): React.JSX.Element;
38
70
  declare function HeaderFilters({ children }: {
39
71
  children: ReactNode;
40
72
  }): React.JSX.Element;
@@ -108,11 +140,32 @@ interface RowActionButtonProps {
108
140
  onPress?: () => void;
109
141
  }
110
142
  declare function RowActionButton({ icon, onPress }: RowActionButtonProps): React.JSX.Element;
143
+ /**
144
+ * Screen container for a collection (list / index / search results).
145
+ * Owns safe-area, the FlatList (pagination + pull-to-refresh), record count, and the
146
+ * loading/empty/error states.
147
+ *
148
+ * API map:
149
+ * ListViewV2.Root — provider + container; pass `items`, `onSearch`, `onLoadMore`, `onBack`…
150
+ * ListViewV2.Header — AppBar (variant: default | large | center · tone: default | dark | gradient)
151
+ * .Header.Search — debounced SearchBar in the subheader (wires `onSearch`)
152
+ * .Header.SearchHistory — recent-searches list (SIBLING; show while query is empty)
153
+ * .Header.Segmented — tab-like switch in the subheader (Activos / Archivados)
154
+ * .Header.Filters(.Chips/.Chip) — horizontal filter pills (SIBLING below the header)
155
+ * .Header.ActionsBar — SIBLING bar below the header (bulk select, etc.)
156
+ * ListViewV2.Content — FlatList body; `children` is an optional custom row renderer
157
+ * .Content.Row(.Avatar/.Content/.Title/.Subtitle/.Footer/.Actions/.ActionButton)
158
+ * ListViewV2.Footer — pinned footer (hidden when empty)
159
+ * ListViewV2.FloatingButton — absolute FAB overlay
160
+ */
111
161
  export declare const ListViewV2: {
112
162
  Root: typeof ListViewRoot;
113
163
  Header: typeof Header & {
114
164
  Search: typeof HeaderSearch;
165
+ SearchHistory: typeof HeaderSearchHistory;
166
+ Segmented: typeof HeaderSegmented;
115
167
  Actions: typeof HeaderActions;
168
+ ActionsBar: typeof HeaderActionsBar;
116
169
  Filters: typeof HeaderFilters & {
117
170
  Chip: typeof FilterChip;
118
171
  Chips: typeof FilterChips;