@sincpro/mobile-ui 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/assets/brand-mesh.png +0 -0
- package/assets/fonts/FFL.txt +57 -0
- package/assets/fonts/Satoshi-Black.otf +0 -0
- package/assets/fonts/Satoshi-Bold.otf +0 -0
- package/assets/fonts/Satoshi-Medium.otf +0 -0
- package/assets/fonts/Satoshi-Regular.otf +0 -0
- package/dist/Actions/Actions.Button.d.ts +2 -1
- package/dist/Actions/Actions.Button.js +31 -37
- package/dist/Actions/Actions.Dock.d.ts +6 -6
- package/dist/Actions/Actions.Dock.js +14 -72
- package/dist/Actions/Actions.FAB.d.ts +2 -1
- package/dist/Actions/Actions.FilterButton.js +1 -1
- package/dist/Actions/Actions.IconButton.d.ts +3 -4
- package/dist/Actions/Actions.IconButton.js +2 -2
- package/dist/Dialog/Confirmation.context.d.ts +5 -14
- package/dist/Dialog/Confirmation.context.js +11 -51
- package/dist/Dialog/Dialog.ActionSheet.d.ts +8 -7
- package/dist/Dialog/Dialog.ActionSheet.js +45 -19
- package/dist/Dialog/Dialog.Confirmation.d.ts +14 -11
- package/dist/Dialog/Dialog.Confirmation.js +38 -14
- package/dist/Dialog/Dialog.EditValue.d.ts +21 -16
- package/dist/Dialog/Dialog.EditValue.js +73 -14
- package/dist/Dialog/Dialog.Sheet.d.ts +8 -24
- package/dist/Dialog/Dialog.Sheet.js +15 -60
- package/dist/Dialog/index.d.ts +14 -14
- package/dist/Dialog/index.js +5 -3
- package/dist/Dialog/requestConfirmation.d.ts +3 -0
- package/dist/Dialog/requestConfirmation.js +13 -0
- package/dist/Display/Display.Accordion.js +1 -1
- package/dist/Display/Display.Avatar.d.ts +6 -6
- package/dist/Display/Display.Avatar.js +2 -2
- package/dist/Display/Display.Badge.js +4 -4
- package/dist/Display/Display.Card.js +5 -2
- package/dist/Display/Display.Carousel.js +1 -1
- package/dist/Display/Display.CircleButton.d.ts +3 -8
- package/dist/Display/Display.CircleButton.js +2 -2
- package/dist/Display/Display.CopyableText.js +1 -5
- package/dist/Display/Display.Date.d.ts +3 -2
- package/dist/Display/Display.Date.js +3 -3
- package/dist/Display/Display.GradientSurface.d.ts +52 -9
- package/dist/Display/Display.GradientSurface.js +9 -14
- package/dist/Display/Display.Icon.d.ts +7 -11
- package/dist/Display/Display.Icon.js +15 -30
- package/dist/Display/Display.Image.d.ts +2 -1
- package/dist/Display/Display.Image.js +1 -1
- package/dist/Display/Display.KeyValue.js +1 -1
- package/dist/Display/Display.ListItem.js +1 -1
- package/dist/Display/Display.MenuButton.js +4 -4
- package/dist/Display/Display.MenuCard.d.ts +3 -8
- package/dist/Display/Display.MenuCard.js +2 -2
- package/dist/Display/Display.MenuGrid.d.ts +2 -7
- package/dist/Display/Display.MenuGrid.js +1 -1
- package/dist/Display/Display.Monetary.d.ts +2 -6
- package/dist/Display/Display.Monetary.js +2 -2
- package/dist/Display/Display.PriceTag.js +1 -1
- package/dist/Display/Display.Rating.js +1 -1
- package/dist/Display/Display.SearchHistory.d.ts +2 -1
- package/dist/Display/Display.SearchHistory.js +1 -1
- package/dist/Display/Display.StatCard.d.ts +2 -1
- package/dist/Display/Display.StatCard.js +1 -1
- package/dist/Display/Display.StoryViewer.js +10 -6
- package/dist/Display/Display.SwipeableRow.d.ts +2 -1
- package/dist/Display/Display.Timeline.js +1 -1
- package/dist/Display/icon-map.d.ts +344 -0
- package/dist/Display/icon-map.js +86 -0
- package/dist/Display/index.d.ts +2 -0
- package/dist/Display/index.js +1 -0
- package/dist/Feedback/Feedback.Banner.js +13 -12
- package/dist/Feedback/Feedback.Empty.js +1 -1
- package/dist/Feedback/Feedback.EmptyState.d.ts +2 -1
- package/dist/Feedback/Feedback.EmptyState.js +1 -1
- package/dist/Feedback/Feedback.Error.js +1 -1
- package/dist/Feedback/Feedback.PermissionCard.d.ts +2 -1
- package/dist/Feedback/Feedback.PermissionCard.js +2 -2
- package/dist/Feedback/Toast.context.js +3 -3
- package/dist/Form/Form.ControlledDropdown.d.ts +5 -11
- package/dist/Form/Form.ControlledDropdown.js +5 -2
- package/dist/Form/Form.DatePicker.d.ts +5 -9
- package/dist/Form/Form.DatePicker.js +116 -21
- package/dist/Form/Form.Dropdown.d.ts +11 -16
- package/dist/Form/Form.Dropdown.js +54 -90
- package/dist/Form/Form.FractionalQuantityInput.js +4 -11
- package/dist/Form/Form.Input.d.ts +3 -3
- package/dist/Form/Form.KeyboardForm.d.ts +2 -2
- package/dist/Form/Form.KeyboardForm.js +2 -2
- package/dist/Form/Form.KeyboardToolbar.d.ts +2 -1
- package/dist/Form/Form.KeyboardToolbar.js +3 -7
- package/dist/Form/Form.PasswordInput.js +1 -1
- package/dist/Form/Form.QuantitySelector.js +1 -1
- package/dist/Form/Form.Select.d.ts +12 -8
- package/dist/Form/Form.Select.js +64 -31
- package/dist/Form/Form.Slider.d.ts +4 -7
- package/dist/Form/Form.Slider.js +11 -10
- package/dist/Form/Form.Switch.js +2 -1
- package/dist/Form/index.d.ts +6 -14
- package/dist/Form/index.js +0 -11
- package/dist/Navigation/Navigation.AppBar.d.ts +2 -1
- package/dist/Navigation/Navigation.AppBar.js +2 -2
- package/dist/Navigation/Navigation.BottomNav.d.ts +4 -8
- package/dist/Navigation/Navigation.BottomNav.js +2 -2
- package/dist/Navigation/Navigation.CollapsingHeader.js +3 -2
- package/dist/Navigation/Navigation.Menu.d.ts +16 -11
- package/dist/Navigation/Navigation.Menu.js +97 -42
- package/dist/Navigation/Navigation.Search.js +2 -2
- package/dist/Navigation/Navigation.SearchBar.js +3 -4
- package/dist/Navigation/Navigation.SegmentedControl.d.ts +7 -25
- package/dist/Navigation/Navigation.SegmentedControl.js +18 -39
- package/dist/Navigation/Navigation.Steps.js +1 -1
- package/dist/Navigation/Navigation.Tabs.d.ts +2 -1
- package/dist/Navigation/index.d.ts +0 -6
- package/dist/Navigation/index.js +0 -8
- package/dist/Typography/Typography.SelectableText.js +3 -7
- package/dist/Typography/Typography.Text.d.ts +1 -6
- package/dist/Typography/Typography.Text.js +3 -6
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/layouts/AuthContainer.d.ts +4 -2
- package/dist/layouts/AuthContainer.js +3 -4
- package/dist/layouts/Container.d.ts +0 -4
- package/dist/layouts/Container.js +3 -4
- package/dist/layouts/ScrollContainer.d.ts +7 -3
- package/dist/layouts/ScrollContainer.js +5 -4
- package/dist/primitives/BaseButton.d.ts +2 -1
- package/dist/theme/build.js +0 -4
- package/dist/theme/css-vars.js +0 -4
- package/dist/theme/dark.js +2 -6
- package/dist/theme/default.js +1 -5
- package/dist/theme/index.d.ts +1 -1
- package/dist/theme/index.js +1 -1
- package/dist/theme/types.d.ts +0 -11
- package/dist/theme/typography.d.ts +8 -7
- package/dist/theme/typography.js +25 -4
- package/dist/utils/timezone.d.ts +0 -5
- package/dist/views/AuthFormView.d.ts +3 -1
- package/dist/views/AuthFormView.js +2 -2
- package/dist/views/FormViewV2.d.ts +6 -13
- package/dist/views/FormViewV2.js +29 -25
- package/dist/views/ListViewV2.context.d.ts +5 -5
- package/dist/views/ListViewV2.d.ts +23 -18
- package/dist/views/ListViewV2.js +45 -48
- package/dist/views/types/IListView.d.ts +5 -4
- package/dist/views/types/IListView.js +18 -15
- package/dist/widgets/CollectionOptions.d.ts +2 -2
- package/dist/widgets/CollectionOptions.js +92 -65
- package/dist/widgets/FilterBar.js +1 -1
- package/dist/widgets/PromoModal.js +10 -4
- package/dist/widgets/QuickActions.d.ts +2 -7
- package/dist/widgets/QuickActions.js +1 -1
- package/dist/widgets/ReorderableList.js +3 -3
- package/dist/widgets/TimeZoneSelectorModal.d.ts +1 -6
- package/dist/widgets/TimeZoneSelectorModal.js +17 -12
- package/dist/widgets/index.d.ts +0 -11
- package/dist/widgets/index.js +0 -7
- package/package.json +36 -38
- package/tailwind.preset.js +0 -4
- package/dist/Dialog/BottomSheet/BottomSheet.Root.d.ts +0 -324
- package/dist/Dialog/BottomSheet/BottomSheet.Root.js +0 -373
- package/dist/Dialog/BottomSheet/index.d.ts +0 -1
- package/dist/Dialog/BottomSheet/index.js +0 -1
- package/dist/Dialog/Dialog.NativeSheet.d.ts +0 -10
- package/dist/Dialog/Dialog.NativeSheet.js +0 -21
- package/dist/Form/Form.Button.d.ts +0 -3
- package/dist/Form/Form.Button.js +0 -1
- package/dist/Form/Form.FAB.d.ts +0 -3
- package/dist/Form/Form.FAB.js +0 -1
- package/dist/Form/Form.FilterButton.d.ts +0 -3
- package/dist/Form/Form.FilterButton.js +0 -1
- package/dist/Form/Form.IconButton.d.ts +0 -3
- package/dist/Form/Form.IconButton.js +0 -1
- package/dist/Form/Form.SearchBar.d.ts +0 -13
- package/dist/Form/Form.SearchBar.js +0 -26
- package/dist/Navigation/Navigation.Header.d.ts +0 -16
- package/dist/Navigation/Navigation.Header.js +0 -17
- package/dist/Navigation/Navigation.HeaderToolbar.d.ts +0 -15
- package/dist/Navigation/Navigation.HeaderToolbar.js +0 -18
- package/dist/Navigation/Navigation.TabIcon.d.ts +0 -18
- package/dist/Navigation/Navigation.TabIcon.js +0 -12
- package/dist/Navigation/Navigation.TabItem.d.ts +0 -19
- package/dist/Navigation/Navigation.TabItem.js +0 -13
- package/dist/icons/OdooIcon.d.ts +0 -11
- package/dist/icons/OdooIcon.js +0 -11
- package/dist/icons/PrinterIcon.d.ts +0 -9
- package/dist/icons/PrinterIcon.js +0 -9
- package/dist/layouts/TabNavigatorLayout.d.ts +0 -26
- package/dist/layouts/TabNavigatorLayout.js +0 -60
- package/dist/utils/icon.d.ts +0 -1
- package/dist/utils/icon.js +0 -1
- package/dist/widgets/CircleButton.d.ts +0 -3
- package/dist/widgets/CircleButton.js +0 -1
- package/dist/widgets/GeoPermissionCard.d.ts +0 -13
- package/dist/widgets/GeoPermissionCard.js +0 -24
- package/dist/widgets/HomeHeader.d.ts +0 -15
- package/dist/widgets/HomeHeader.js +0 -13
- package/dist/widgets/MenuCard.d.ts +0 -3
- package/dist/widgets/MenuCard.js +0 -1
- package/dist/widgets/MenuGrid.d.ts +0 -5
- package/dist/widgets/MenuGrid.js +0 -1
- package/dist/widgets/MenuSection.d.ts +0 -3
- package/dist/widgets/MenuSection.js +0 -1
- package/dist/widgets/ScreenHeader.d.ts +0 -25
- package/dist/widgets/ScreenHeader.js +0 -32
- package/dist/widgets/TimeZoneSelector.d.ts +0 -12
- package/dist/widgets/TimeZoneSelector.js +0 -24
package/dist/Form/Form.Select.js
CHANGED
|
@@ -1,41 +1,74 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
+
import { Host, Picker } from "@expo/ui";
|
|
3
|
+
import { Actions } from "../Actions/index.js";
|
|
4
|
+
import Sheet from "../Dialog/Dialog.Sheet.js";
|
|
2
5
|
import Icon from "../Display/Display.Icon.js";
|
|
3
6
|
import Pressable from "../primitives/Pressable.js";
|
|
4
|
-
import {
|
|
7
|
+
import { relativeLuminance, useTheme } from "../theme/index.js";
|
|
5
8
|
import { cn } from "../theme/tw.js";
|
|
6
9
|
import { Typography } from "../Typography/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
import { Animated, Modal, ScrollView, View } from "react-native";
|
|
11
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
|
-
function Select({ options, value, onChange, label, placeholder = "Seleccionar…", title, disabled = false, className, testID, showCloseButton = true, }) {
|
|
10
|
+
import { useRef, useState } from "react";
|
|
11
|
+
import { ActivityIndicator, Platform, View } from "react-native";
|
|
12
|
+
function SelectOptions({ options, value, blocked, onSelect, }) {
|
|
13
13
|
const theme = useTheme();
|
|
14
|
-
|
|
14
|
+
if (!options.length) {
|
|
15
|
+
return _jsx(Typography.Text, { children: "No hay opciones disponibles." });
|
|
16
|
+
}
|
|
17
|
+
return (_jsx(View, { className: "gap-2", children: options.map((option, index) => {
|
|
18
|
+
const selected = option.value === value;
|
|
19
|
+
return (_jsxs(Pressable, { accessibilityLabel: option.label, accessibilityRole: "radio", accessibilityState: { disabled: blocked, selected }, className: cn("flex-row items-center gap-3 rounded-lg border px-3", selected ? "border-primary bg-primary/10" : "border-border-default"), disabled: blocked, onPress: () => onSelect(option.value), style: { minHeight: 48 }, children: [_jsx(Typography.Text, { className: "flex-1 text-text-primary", children: option.label }), selected ? _jsx(Icon, { color: theme.primary, name: "check", size: 18 }) : null] }, `${index}:${String(option.value)}`));
|
|
20
|
+
}) }));
|
|
21
|
+
}
|
|
22
|
+
function Select({ options, value, onChange, onBlur, label, placeholder = "Seleccionar…", title, confirmLabel = "Aplicar", disabled = false, readOnly = false, loading = false, error, className, testID, }) {
|
|
23
|
+
const theme = useTheme();
|
|
24
|
+
const appearance = relativeLuminance(theme.bg.card) < 0.179 ? "dark" : "light";
|
|
25
|
+
const saving = useRef(false);
|
|
15
26
|
const [open, setOpen] = useState(false);
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
27
|
+
const [draftValue, setDraftValue] = useState(null);
|
|
28
|
+
const [pending, setPending] = useState(false);
|
|
29
|
+
const [failure, setFailure] = useState();
|
|
30
|
+
const selected = options.find((option) => option.value === value);
|
|
31
|
+
const draftIndex = options.findIndex((option) => option.value === draftValue);
|
|
32
|
+
const blocked = disabled || readOnly || loading || pending;
|
|
33
|
+
const android = Platform.OS === "android";
|
|
34
|
+
const close = () => {
|
|
35
|
+
if (saving.current)
|
|
36
|
+
return;
|
|
37
|
+
setOpen(false);
|
|
38
|
+
onBlur?.();
|
|
39
|
+
};
|
|
40
|
+
const commit = async (next) => {
|
|
41
|
+
if (next === null || blocked || saving.current)
|
|
42
|
+
return;
|
|
43
|
+
saving.current = true;
|
|
44
|
+
setPending(true);
|
|
45
|
+
setFailure(undefined);
|
|
46
|
+
try {
|
|
47
|
+
await onChange(next);
|
|
48
|
+
setOpen(false);
|
|
49
|
+
onBlur?.();
|
|
50
|
+
}
|
|
51
|
+
catch (cause) {
|
|
52
|
+
setFailure(cause instanceof Error && cause.message.trim()
|
|
53
|
+
? cause.message
|
|
54
|
+
: "No se pudo aplicar la selección. Vuelve a intentarlo.");
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
saving.current = false;
|
|
58
|
+
setPending(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label ? (_jsx(Typography.Text, { className: "text-text-primary", semibold: true, variant: "bodySmall", children: label })) : null, _jsxs(Pressable, { accessibilityHint: "Abre el selector de opciones", accessibilityLabel: label
|
|
19
62
|
? `${label}: ${selected?.label ?? placeholder}`
|
|
20
|
-
: (selected?.label ?? placeholder), accessibilityRole: "button", accessibilityState: { disabled, expanded: open }, className: "flex-row items-center
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
minHeight: 48,
|
|
31
|
-
justifyContent: "center",
|
|
32
|
-
alignItems: "center",
|
|
33
|
-
}, children: _jsx(Typography.Text, { className: "text-text-accent", variant: "bodySmall", children: "Cerrar" }) })) : null] })) : null, _jsx(ScrollView, { children: options.map((option) => {
|
|
34
|
-
const isSelected = option.value === value;
|
|
35
|
-
return (_jsxs(Pressable, { accessibilityRole: "menuitem", accessibilityState: { selected: isSelected }, className: "flex-row items-center justify-between px-4 py-3 border-b border-border-light", onPress: () => {
|
|
36
|
-
onChange(option.value);
|
|
37
|
-
setOpen(false);
|
|
38
|
-
}, style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1 text-sm", isSelected ? "text-text-accent font-semibold" : "text-text-primary"), children: option.label }), isSelected ? (_jsx(Icon, { color: theme.text.accent, name: "checkmark", size: 18 })) : null] }, String(option.value)));
|
|
39
|
-
}) })] })] }) })] }));
|
|
63
|
+
: (selected?.label ?? placeholder), accessibilityRole: "button", accessibilityState: { disabled: blocked, expanded: open, busy: loading || pending }, className: "flex-row items-center gap-3 rounded-lg border border-input bg-bg-card px-3", disabled: blocked, onPress: () => {
|
|
64
|
+
setDraftValue(selected?.value ?? null);
|
|
65
|
+
setFailure(undefined);
|
|
66
|
+
setOpen(true);
|
|
67
|
+
}, style: { minHeight: 48, minWidth: 48 }, children: [_jsx(Typography.Text, { className: cn("flex-1", selected ? "text-text-primary" : "text-text-tertiary"), style: { paddingVertical: 12 }, children: selected?.label ?? placeholder }), loading || pending ? (_jsx(ActivityIndicator, { color: theme.primary })) : (_jsx(Icon, { color: theme.icon.secondary, name: "expand", size: 18 }))] }), error ? (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", className: "text-danger", children: error })) : null, _jsxs(Sheet, { dismissible: !pending, footer: android ? undefined : (_jsx(Actions.Button, { disabled: blocked || draftIndex < 0, loading: pending, onPress: () => void commit(draftValue), title: confirmLabel })), onClose: close, title: title ?? label ?? "Seleccionar", visible: open, children: [open && android ? (_jsx(SelectOptions, { blocked: blocked, onSelect: (next) => void commit(next), options: options, value: value })) : null, open && !android ? (_jsx(Host, { colorScheme: appearance, matchContents: { vertical: true }, seedColor: theme.primary, style: { minHeight: 48 }, children: _jsxs(Picker, { appearance: "wheel", enabled: !blocked && options.length > 0, onValueChange: (index) => {
|
|
68
|
+
if (blocked || saving.current)
|
|
69
|
+
return;
|
|
70
|
+
setDraftValue(options.find((_, itemIndex) => String(itemIndex) === index)?.value ?? null);
|
|
71
|
+
setFailure(undefined);
|
|
72
|
+
}, selectedValue: draftIndex < 0 ? "" : String(draftIndex), testID: testID ? `${testID}-picker` : undefined, children: [_jsx(Picker.Item, { label: placeholder, value: "" }), options.map((option, index) => (_jsx(Picker.Item, { label: option.label, value: String(index) }, String(index))))] }) })) : null, open && !android && !options.length ? (_jsx(Typography.Text, { children: "No hay opciones disponibles." })) : null, draftValue !== null && draftIndex < 0 ? (_jsx(Typography.Text, { accessibilityRole: "alert", className: "text-danger", children: "La opci\u00F3n ya no est\u00E1 disponible. Selecciona otra." })) : null, failure ? (_jsx(Typography.Text, { accessibilityLiveRegion: "polite", accessibilityRole: "alert", className: "pt-3 text-danger", selectable: true, children: failure })) : null] })] }));
|
|
40
73
|
}
|
|
41
74
|
export default Select;
|
|
@@ -7,14 +7,11 @@ export interface SliderProps {
|
|
|
7
7
|
label?: string;
|
|
8
8
|
formatValue?: (value: number) => string;
|
|
9
9
|
disabled?: boolean;
|
|
10
|
+
readOnly?: boolean;
|
|
11
|
+
accessibilityLabel?: string;
|
|
12
|
+
accessibilityHint?: string;
|
|
10
13
|
className?: string;
|
|
11
14
|
testID?: string;
|
|
12
15
|
}
|
|
13
|
-
|
|
14
|
-
* **Tokenized** slider (track in `primary`) on `@react-native-community/slider` — clean,
|
|
15
|
-
* on-brand, consistent across iOS/Android. (The `@expo/ui` Slider was evaluated but its
|
|
16
|
-
* Material style with ticks ignores the tokens → not a case where native wins.)
|
|
17
|
-
* Subpath `Form/Slider`; **optional** peer dep.
|
|
18
|
-
*/
|
|
19
|
-
declare function Slider({ value, onChange, min, max, step, label, formatValue, disabled, className, testID, }: SliderProps): import("react").JSX.Element;
|
|
16
|
+
declare function Slider({ value, onChange, min, max, step, label, formatValue, disabled, readOnly, accessibilityLabel, accessibilityHint, className, testID, }: SliderProps): import("react").JSX.Element;
|
|
20
17
|
export default Slider;
|
package/dist/Form/Form.Slider.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import NativeSlider from "@expo/ui/community/slider";
|
|
3
|
+
import { useTheme } from "../theme/index.js";
|
|
4
4
|
import { cn } from "../theme/tw.js";
|
|
5
5
|
import { Typography } from "../Typography/index.js";
|
|
6
6
|
import { View } from "react-native";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
function Slider({ value, onChange, min = 0, max = 100, step = 1, label, formatValue, disabled = false, readOnly = false, accessibilityLabel, accessibilityHint, className, testID, }) {
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
const blocked = disabled || readOnly;
|
|
10
|
+
return (_jsxs(View, { className: cn("gap-1", className), testID: testID, children: [label || formatValue ? (_jsxs(View, { className: "flex-row items-center justify-between", children: [label ? (_jsx(Typography.Text, { className: "font-medium text-text-primary", variant: "bodySmall", children: label })) : null, formatValue ? (_jsx(Typography.Text, { className: "text-text-secondary", variant: "bodySmall", children: formatValue(value) })) : null] })) : null, _jsx(NativeSlider, { accessibilityHint: accessibilityHint, accessibilityLabel: accessibilityLabel ?? label ?? "Valor", disabled: blocked, maximumTrackTintColor: theme.container.primary, maximumValue: max, minimumTrackTintColor: theme.primary, minimumValue: min, onValueChange: (next) => {
|
|
11
|
+
if (blocked)
|
|
12
|
+
return;
|
|
13
|
+
const snapped = step > 0 ? min + Math.round((next - min) / step) * step : next;
|
|
14
|
+
onChange(Math.min(max, Math.max(min, snapped)));
|
|
15
|
+
}, step: step, style: { minHeight: 48 }, testID: testID ? `${testID}-slider` : undefined, thumbTintColor: theme.primary, value: value })] }));
|
|
15
16
|
}
|
|
16
17
|
export default Slider;
|
package/dist/Form/Form.Switch.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useTheme } from "../theme/index.js";
|
|
3
3
|
import { cn } from "../theme/tw.js";
|
|
4
4
|
import { Typography } from "../Typography/index.js";
|
|
5
5
|
import { Switch as RNSwitch, View } from "react-native";
|
|
6
6
|
function Switch({ value, onValueChange, label, disabled = false, className, testID, }) {
|
|
7
|
+
const theme = useTheme();
|
|
7
8
|
return (_jsxs(View, { className: cn("flex-row items-center justify-between gap-3", className), children: [label ? (_jsx(Typography.Text, { className: cn("flex-1 text-text-primary", disabled && "text-text-disabled"), children: label })) : null, _jsx(RNSwitch, { accessibilityLabel: label, accessibilityRole: "switch", accessibilityState: { checked: value, disabled }, disabled: disabled, ios_backgroundColor: theme.bg.muted, onValueChange: onValueChange, testID: testID, thumbColor: theme.bg.card, trackColor: { false: theme.bg.muted, true: theme.primary }, value: value })] }));
|
|
8
9
|
}
|
|
9
10
|
export default Switch;
|
package/dist/Form/index.d.ts
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import Checkbox from "./Form.Checkbox.js";
|
|
2
2
|
import ControlledCheckbox from "./Form.ControlledCheckbox.js";
|
|
3
|
-
import ControlledDropdown
|
|
3
|
+
import ControlledDropdown from "./Form.ControlledDropdown.js";
|
|
4
4
|
import ControlledInput from "./Form.ControlledInput.js";
|
|
5
5
|
import ControlledMonetaryInput from "./Form.ControlledMonetaryInput.js";
|
|
6
6
|
import ControlledRadioButton from "./Form.ControlledRadioButton.js";
|
|
7
7
|
import DatePicker from "./Form.DatePicker.js";
|
|
8
8
|
import Dropdown from "./Form.Dropdown.js";
|
|
9
9
|
import ErrorSummary from "./Form.ErrorSummary.js";
|
|
10
|
-
import FAB from "./Form.FAB.js";
|
|
11
|
-
import { FilterButtonGroup } from "./Form.FilterButton.js";
|
|
12
10
|
import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
|
|
13
|
-
import IconButton from "./Form.IconButton.js";
|
|
14
11
|
import Input from "./Form.Input.js";
|
|
15
12
|
import KeyboardForm from "./Form.KeyboardForm.js";
|
|
16
13
|
import KeyboardToolbar from "./Form.KeyboardToolbar.js";
|
|
@@ -18,7 +15,6 @@ import MonetaryInput from "./Form.MonetaryInput.js";
|
|
|
18
15
|
import PinInput from "./Form.PinInput.js";
|
|
19
16
|
import QuantitySelector from "./Form.QuantitySelector.js";
|
|
20
17
|
import RadioButton from "./Form.RadioButton.js";
|
|
21
|
-
import SearchBar from "./Form.SearchBar.js";
|
|
22
18
|
import Select from "./Form.Select.js";
|
|
23
19
|
import Slider from "./Form.Slider.js";
|
|
24
20
|
import Switch from "./Form.Switch.js";
|
|
@@ -29,10 +25,6 @@ export declare const Form: {
|
|
|
29
25
|
KeyboardForm: typeof KeyboardForm;
|
|
30
26
|
KeyboardToolbar: typeof KeyboardToolbar;
|
|
31
27
|
ErrorSummary: typeof ErrorSummary;
|
|
32
|
-
Button: import("react").FC<import("./Form.Button.js").ButtonProps> & {
|
|
33
|
-
Icon: import("react").FC<import("./Form.Button.js").ButtonIconProps>;
|
|
34
|
-
};
|
|
35
|
-
IconButton: typeof IconButton;
|
|
36
28
|
PasswordInput: import("react").FC<import("./Form.PasswordInput.js").PasswordFieldProps>;
|
|
37
29
|
QuantitySelector: typeof QuantitySelector;
|
|
38
30
|
FractionalQuantityInput: typeof FractionalQuantityInput;
|
|
@@ -43,8 +35,6 @@ export declare const Form: {
|
|
|
43
35
|
PinInput: typeof PinInput;
|
|
44
36
|
Slider: typeof Slider;
|
|
45
37
|
DatePicker: typeof DatePicker;
|
|
46
|
-
FAB: typeof FAB;
|
|
47
|
-
SearchBar: typeof SearchBar;
|
|
48
38
|
Dropdown: typeof Dropdown;
|
|
49
39
|
MonetaryInput: typeof MonetaryInput;
|
|
50
40
|
ControlledInput: typeof ControlledInput;
|
|
@@ -52,11 +42,13 @@ export declare const Form: {
|
|
|
52
42
|
ControlledCheckbox: typeof ControlledCheckbox;
|
|
53
43
|
ControlledDropdown: typeof ControlledDropdown;
|
|
54
44
|
ControlledMonetaryInput: typeof ControlledMonetaryInput;
|
|
55
|
-
FilterButton: import("react").FC<import("./Form.FilterButton.js").FilterButtonProps>;
|
|
56
|
-
FilterButtonGroup: typeof FilterButtonGroup;
|
|
57
45
|
};
|
|
58
|
-
export {
|
|
46
|
+
export type { ControlledDropdownProps } from "./Form.ControlledDropdown.js";
|
|
47
|
+
export type { DatePickerProps } from "./Form.DatePicker.js";
|
|
48
|
+
export type { DropdownButtonProps, DropdownOption, } from "./Form.Dropdown.js";
|
|
59
49
|
export type { ErrorSummaryItem, ErrorSummaryProps, } from "./Form.ErrorSummary.js";
|
|
60
50
|
export type { KeyboardFormProps } from "./Form.KeyboardForm.js";
|
|
61
51
|
export type { KeyboardToolbarAction, KeyboardToolbarProps, } from "./Form.KeyboardToolbar.js";
|
|
52
|
+
export type { SelectOption, SelectProps } from "./Form.Select.js";
|
|
53
|
+
export type { SliderProps } from "./Form.Slider.js";
|
|
62
54
|
export default Form;
|
package/dist/Form/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import ButtonWithIcon from "./Form.Button.js";
|
|
2
1
|
import Checkbox from "./Form.Checkbox.js";
|
|
3
2
|
import ControlledCheckbox from "./Form.ControlledCheckbox.js";
|
|
4
3
|
import ControlledDropdown from "./Form.ControlledDropdown.js";
|
|
@@ -8,10 +7,7 @@ import ControlledRadioButton from "./Form.ControlledRadioButton.js";
|
|
|
8
7
|
import DatePicker from "./Form.DatePicker.js";
|
|
9
8
|
import Dropdown from "./Form.Dropdown.js";
|
|
10
9
|
import ErrorSummary from "./Form.ErrorSummary.js";
|
|
11
|
-
import FAB from "./Form.FAB.js";
|
|
12
|
-
import { FilterButton, FilterButtonGroup } from "./Form.FilterButton.js";
|
|
13
10
|
import FractionalQuantityInput from "./Form.FractionalQuantityInput.js";
|
|
14
|
-
import IconButton from "./Form.IconButton.js";
|
|
15
11
|
import Input from "./Form.Input.js";
|
|
16
12
|
import InputField from "./Form.InputField.js";
|
|
17
13
|
import KeyboardForm from "./Form.KeyboardForm.js";
|
|
@@ -21,7 +17,6 @@ import PasswordInput from "./Form.PasswordInput.js";
|
|
|
21
17
|
import PinInput from "./Form.PinInput.js";
|
|
22
18
|
import QuantitySelector from "./Form.QuantitySelector.js";
|
|
23
19
|
import RadioButton from "./Form.RadioButton.js";
|
|
24
|
-
import SearchBar from "./Form.SearchBar.js";
|
|
25
20
|
import Select from "./Form.Select.js";
|
|
26
21
|
import Slider from "./Form.Slider.js";
|
|
27
22
|
import Switch from "./Form.Switch.js";
|
|
@@ -33,8 +28,6 @@ export const Form = {
|
|
|
33
28
|
KeyboardForm,
|
|
34
29
|
KeyboardToolbar,
|
|
35
30
|
ErrorSummary,
|
|
36
|
-
Button: ButtonWithIcon,
|
|
37
|
-
IconButton,
|
|
38
31
|
PasswordInput,
|
|
39
32
|
QuantitySelector,
|
|
40
33
|
FractionalQuantityInput,
|
|
@@ -45,8 +38,6 @@ export const Form = {
|
|
|
45
38
|
PinInput,
|
|
46
39
|
Slider,
|
|
47
40
|
DatePicker,
|
|
48
|
-
FAB,
|
|
49
|
-
SearchBar,
|
|
50
41
|
Dropdown,
|
|
51
42
|
MonetaryInput,
|
|
52
43
|
ControlledInput,
|
|
@@ -54,7 +45,5 @@ export const Form = {
|
|
|
54
45
|
ControlledCheckbox,
|
|
55
46
|
ControlledDropdown,
|
|
56
47
|
ControlledMonetaryInput,
|
|
57
|
-
FilterButton,
|
|
58
|
-
FilterButtonGroup,
|
|
59
48
|
};
|
|
60
49
|
export default Form;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { IconName } from "../Display/index.js";
|
|
1
2
|
import { type GradientPreset } from "../Display/Display.GradientSurface.js";
|
|
2
3
|
import { type PatternKind } from "../Display/Display.Pattern.js";
|
|
3
4
|
import type { LinearGradientPoint } from "expo-linear-gradient";
|
|
4
5
|
import type { ReactNode } from "react";
|
|
5
6
|
export interface AppBarActionProps {
|
|
6
7
|
/** Ionicon name. Optional when `label` is set (text-only CTA). */
|
|
7
|
-
icon?:
|
|
8
|
+
icon?: IconName;
|
|
8
9
|
/** Text label → renders a pill CTA ("Editar", "Nuevo", "Guardar") instead of an icon circle. */
|
|
9
10
|
label?: string;
|
|
10
11
|
onPress: () => void;
|
|
@@ -50,7 +50,7 @@ function AppBarAction({ icon, label, onPress, badge, accessibilityLabel, tone =
|
|
|
50
50
|
borderRadius: 12,
|
|
51
51
|
backgroundColor: bg,
|
|
52
52
|
...(showBorder ? { borderWidth: 1, borderColor: theme.border.light } : null),
|
|
53
|
-
}, testID: testID, children: [_jsx(Icon, { color: iconColor, name: icon ?? "
|
|
53
|
+
}, testID: testID, children: [_jsx(Icon, { color: iconColor, name: icon ?? "circle", size: bare ? 24 : 20 }), badge != null ? (_jsx(View, { className: "absolute bg-danger rounded-full min-w-[16px] h-4 px-1 items-center justify-center", style: { top: -2, right: -2, borderWidth: 2, borderColor: theme.bg.card }, children: _jsx(Typography.Text, { style: { color: theme.text.inverse, fontSize: 9, lineHeight: 11 }, children: badge }) })) : null] }));
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
56
|
* Flat, modern top bar (merge of Material 3 + iOS 26): tokenized surface, left-aligned
|
|
@@ -108,7 +108,7 @@ function AppBar({ title, subtitle, onBack, leading, actions, subheader, variant
|
|
|
108
108
|
? { borderBottomLeftRadius: 24, borderBottomRightRadius: 24, overflow: "hidden" }
|
|
109
109
|
: null),
|
|
110
110
|
...(showShadow ? theme.shadow.md : null),
|
|
111
|
-
}, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "
|
|
111
|
+
}, testID: testID, children: [hasBackground ? (_jsx(GradientSurface, { colors: background.colors, end: background.end, padding: "none", preset: background.surface ?? "warm", radius: "none", start: background.start, style: StyleSheet.absoluteFill })) : gradient ? (_jsx(GradientSurface, { colors: theme.gradient.accent, padding: "none", radius: "none", style: StyleSheet.absoluteFill })) : null, background?.pattern ? (_jsx(Pattern, { color: background.patternColor ?? "#FFFFFF", kind: background.pattern, opacity: background.patternOpacity ?? 0.18 })) : null, showTopBar ? (_jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[56px]", children: [_jsxs(View, { className: "flex-row items-center gap-2", style: { minWidth: 38 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading] }), _jsxs(View, { className: cn("flex-1", centered && "items-center"), children: [title ? (_jsx(Typography.Text, { accessibilityRole: "header", className: colored ? "" : "text-text-primary", semibold: true, style: titleStyle, variant: "subtitle", children: title })) : null, subtitle ? (_jsx(Typography.Text, { className: colored ? "" : "text-text-secondary", style: subtitleStyle, variant: "caption", children: subtitle })) : null] }), _jsx(View, { className: "flex-row items-center justify-end gap-2", style: { minWidth: 38 }, children: actions })] })) : null, large && title ? (_jsxs(View, { className: cn("flex-row px-4 pb-3", largeHasNav ? "items-center gap-2" : ""), style: { paddingTop: topSpacing != null ? topSpacing : 8 }, children: [onBack ? (_jsx(AppBarAction, { accessibilityLabel: "Volver", bare: backVariant === "plain", icon: "back", onDark: dark || gradient || hasBackground, onPress: onBack })) : null, leading, accentBar && !largeHasNav ? (_jsx(View, { style: {
|
|
112
112
|
width: 4,
|
|
113
113
|
marginRight: 10,
|
|
114
114
|
marginVertical: 2,
|
|
@@ -1,17 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { ComponentType } from "react";
|
|
1
|
+
import type { IconName } from "../Display/index.js";
|
|
3
2
|
export interface BottomNavItem {
|
|
4
3
|
key: string;
|
|
5
4
|
label?: string;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
activeIcon?: string;
|
|
9
|
-
/** Brand/custom SVG icon component. Takes precedence over `icon` (for non-font brand marks). */
|
|
10
|
-
customIcon?: ComponentType<CustomIconProps>;
|
|
5
|
+
icon?: IconName;
|
|
6
|
+
activeIcon?: IconName;
|
|
11
7
|
badge?: string | number;
|
|
12
8
|
}
|
|
13
9
|
export interface BottomNavCenterAction {
|
|
14
|
-
icon:
|
|
10
|
+
icon: IconName;
|
|
15
11
|
onPress: () => void;
|
|
16
12
|
accessibilityLabel?: string;
|
|
17
13
|
}
|
|
@@ -44,8 +44,8 @@ function BottomNav({ items, value, onChange, showLabels = true, centerAction, sa
|
|
|
44
44
|
...(bordered ? { borderTopWidth: 1, borderTopColor: theme.border.light } : null),
|
|
45
45
|
};
|
|
46
46
|
const renderIconNode = (item, active, color) => {
|
|
47
|
-
const
|
|
48
|
-
return (_jsxs(View, { children: [
|
|
47
|
+
const name = active ? (item.activeIcon ?? item.icon) : item.icon;
|
|
48
|
+
return (_jsxs(View, { children: [name ? _jsx(Icon, { color: color, name: name, size: 24 }) : null, item.badge != null ? (_jsx(View, { className: "absolute bg-danger rounded-full min-w-[16px] h-4 px-1 items-center justify-center", style: { top: -4, right: -10, borderWidth: 2, borderColor: surfaceColor }, children: _jsx(Typography.Text, { style: { color: theme.text.inverse, fontSize: 9, lineHeight: 11 }, children: item.badge }) })) : null] }));
|
|
49
49
|
};
|
|
50
50
|
const renderItem = (item) => {
|
|
51
51
|
const active = item.key === value;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
|
|
2
2
|
import AppBar from "./Navigation.AppBar.js";
|
|
3
|
-
import {
|
|
3
|
+
import { useTheme } from "../theme/index.js";
|
|
4
4
|
import { Typography } from "../Typography/index.js";
|
|
5
5
|
import { Animated, View } from "react-native";
|
|
6
6
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -21,6 +21,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
function CollapsingHeader({ title, subtitle, scrollY, onBack, actions, largeHeight = subtitle ? 72 : 56, safeArea = true, testID, }) {
|
|
24
|
+
const theme = useTheme();
|
|
24
25
|
const insets = useSafeAreaInsets();
|
|
25
26
|
const largeOpacity = scrollY.interpolate({
|
|
26
27
|
inputRange: [0, largeHeight * 0.6],
|
|
@@ -42,6 +43,6 @@ function CollapsingHeader({ title, subtitle, scrollY, onBack, actions, largeHeig
|
|
|
42
43
|
outputRange: [0, 1],
|
|
43
44
|
extrapolate: "clamp",
|
|
44
45
|
});
|
|
45
|
-
return (_jsxs(View, { style: { backgroundColor: theme.bg.card }, testID: testID, children: [_jsx(View, { style: { paddingTop: safeArea ? insets.top : 0 }, children: _jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[52px]", children: [_jsx(View, { style: { minWidth: 38 }, children: onBack ? (_jsx(AppBar.Action, { accessibilityLabel: "Volver", icon: "
|
|
46
|
+
return (_jsxs(View, { style: { backgroundColor: theme.bg.card }, testID: testID, children: [_jsx(View, { style: { paddingTop: safeArea ? insets.top : 0 }, children: _jsxs(View, { className: "flex-row items-center px-3 gap-2 min-h-[52px]", children: [_jsx(View, { style: { minWidth: 38 }, children: onBack ? (_jsx(AppBar.Action, { accessibilityLabel: "Volver", icon: "back", onPress: onBack })) : null }), _jsx(Animated.View, { className: "flex-1", style: { opacity: compactTitleOpacity }, children: _jsx(Typography.Text, { className: "text-text-primary", numberOfLines: 1, semibold: true, variant: "subtitle", children: title }) }), _jsx(View, { className: "flex-row items-center justify-end gap-2", style: { minWidth: 38 }, children: actions })] }) }), _jsx(Animated.View, { style: { height: largeContainerHeight, overflow: "hidden" }, children: _jsxs(Animated.View, { className: "px-4 pb-2", style: { opacity: largeOpacity, transform: [{ translateY: largeTranslate }] }, children: [_jsx(Typography.Text, { className: "text-text-primary", variant: "h2", children: title }), subtitle ? (_jsx(Typography.Text, { className: "text-text-secondary", variant: "bodySmall", children: subtitle })) : null] }) }), _jsx(View, { className: "border-b border-border-light" })] }));
|
|
46
47
|
}
|
|
47
48
|
export default CollapsingHeader;
|
|
@@ -1,21 +1,26 @@
|
|
|
1
|
+
import { type IconName } from "../Display/icon-map.js";
|
|
1
2
|
import { type ReactNode } from "react";
|
|
2
|
-
|
|
3
|
+
import { type StyleProp, type ViewStyle } from "react-native";
|
|
4
|
+
export type MenuItem = {
|
|
3
5
|
label: string;
|
|
4
|
-
icon?:
|
|
6
|
+
icon?: IconName;
|
|
5
7
|
destructive?: boolean;
|
|
6
8
|
disabled?: boolean;
|
|
7
|
-
onPress: () => void
|
|
8
|
-
}
|
|
9
|
-
export
|
|
9
|
+
onPress: () => void | Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
export type MenuSeparator = {
|
|
10
12
|
separator: true;
|
|
11
|
-
}
|
|
13
|
+
};
|
|
12
14
|
export type MenuEntry = MenuItem | MenuSeparator;
|
|
13
|
-
export
|
|
14
|
-
items: MenuEntry[];
|
|
15
|
+
export type MenuProps = {
|
|
16
|
+
items: readonly MenuEntry[];
|
|
15
17
|
trigger?: ReactNode;
|
|
16
|
-
|
|
18
|
+
title?: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
accessibilityLabel?: string;
|
|
21
|
+
style?: StyleProp<ViewStyle>;
|
|
17
22
|
testID?: string;
|
|
18
|
-
}
|
|
19
|
-
declare function Menu({ items, trigger,
|
|
23
|
+
};
|
|
24
|
+
declare function Menu({ items, trigger, title, disabled, accessibilityLabel, style, testID, }: MenuProps): import("react").JSX.Element;
|
|
20
25
|
export default Menu;
|
|
21
26
|
export { Menu };
|
|
@@ -1,52 +1,107 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "nativewind/jsx-runtime";
|
|
2
|
+
import { MenuView } from "@expo/ui/community/menu";
|
|
2
3
|
import Icon from "../Display/Display.Icon.js";
|
|
3
|
-
import
|
|
4
|
-
import { useTheme } from "../theme/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
import { Dimensions, Modal, Pressable as RNPressable, View, } from "react-native";
|
|
10
|
-
function Menu({ items, trigger, menuWidth = 220, testID }) {
|
|
4
|
+
import { ICON_MAP } from "../Display/icon-map.js";
|
|
5
|
+
import { relativeLuminance, useTheme } from "../theme/index.js";
|
|
6
|
+
import { unstable_getMaterialSymbolSourceAsync } from "expo-symbols";
|
|
7
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { ActivityIndicator, Alert, Platform, View, } from "react-native";
|
|
9
|
+
function Menu({ items, trigger, title, disabled = false, accessibilityLabel = "Más opciones", style, testID, }) {
|
|
11
10
|
const theme = useTheme();
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
const running = useRef(false);
|
|
12
|
+
const [pending, setPending] = useState(false);
|
|
13
|
+
const blocked = disabled || pending || !items.some((entry) => !("separator" in entry));
|
|
14
|
+
const [sources, setSources] = useState(() => new Map());
|
|
15
|
+
const requests = useMemo(() => items.flatMap((entry) => "separator" in entry || !entry.icon
|
|
16
|
+
? []
|
|
17
|
+
: [
|
|
18
|
+
{
|
|
19
|
+
icon: entry.icon,
|
|
20
|
+
color: entry.destructive ? theme.danger : theme.icon.secondary,
|
|
21
|
+
},
|
|
22
|
+
]), [items, theme.danger, theme.icon.secondary]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (Platform.OS !== "android" || !requests.length)
|
|
25
|
+
return;
|
|
26
|
+
let active = true;
|
|
27
|
+
void Promise.all(requests.map(async ({ icon, color }) => ({
|
|
28
|
+
key: `${icon}:${color}`,
|
|
29
|
+
source: await unstable_getMaterialSymbolSourceAsync(ICON_MAP[icon].md, 24, color),
|
|
30
|
+
}))).then((results) => {
|
|
31
|
+
if (!active)
|
|
32
|
+
return;
|
|
33
|
+
const next = new Map();
|
|
34
|
+
for (const { key, source } of results) {
|
|
35
|
+
if (source)
|
|
36
|
+
next.set(key, source);
|
|
37
|
+
}
|
|
38
|
+
setSources(next);
|
|
39
|
+
}, (cause) => {
|
|
40
|
+
console.error("[Navigation.Menu] Could not render native icons", cause);
|
|
18
41
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
return () => {
|
|
43
|
+
active = false;
|
|
44
|
+
};
|
|
45
|
+
}, [requests]);
|
|
46
|
+
const groups = [[]];
|
|
47
|
+
items.forEach((entry, index) => {
|
|
48
|
+
if ("separator" in entry) {
|
|
49
|
+
if (groups[groups.length - 1].length)
|
|
50
|
+
groups.push([]);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const color = entry.disabled
|
|
54
|
+
? theme.text.disabled
|
|
55
|
+
: entry.destructive
|
|
56
|
+
? theme.danger
|
|
57
|
+
: theme.text.primary;
|
|
58
|
+
const imageColor = entry.destructive ? theme.danger : theme.icon.secondary;
|
|
59
|
+
groups[groups.length - 1].push({
|
|
60
|
+
id: String(index),
|
|
61
|
+
title: entry.label,
|
|
62
|
+
titleColor: color,
|
|
63
|
+
image: entry.icon
|
|
64
|
+
? Platform.OS === "ios"
|
|
65
|
+
? ICON_MAP[entry.icon].sf
|
|
66
|
+
: sources.get(`${entry.icon}:${imageColor}`)
|
|
67
|
+
: undefined,
|
|
68
|
+
imageColor,
|
|
69
|
+
attributes: { disabled: blocked || entry.disabled, destructive: entry.destructive },
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const sections = groups.filter((group) => group.length > 0);
|
|
73
|
+
const actions = sections.length > 1
|
|
74
|
+
? sections.map((subactions, index) => ({
|
|
75
|
+
id: `section-${index}`,
|
|
76
|
+
title: "",
|
|
77
|
+
displayInline: true,
|
|
78
|
+
subactions,
|
|
79
|
+
}))
|
|
80
|
+
: (sections[0] ?? []);
|
|
81
|
+
return (_jsx(View, { pointerEvents: blocked ? "none" : "auto", style: style, testID: testID, children: _jsx(MenuView, { actions: actions, colorScheme: relativeLuminance(theme.bg.card) < 0.179 ? "dark" : "light", onPressAction: ({ nativeEvent }) => {
|
|
82
|
+
const entry = items.find((_, index) => String(index) === nativeEvent.event);
|
|
83
|
+
if (!entry || "separator" in entry || blocked || entry.disabled || running.current)
|
|
84
|
+
return;
|
|
85
|
+
running.current = true;
|
|
86
|
+
setPending(true);
|
|
87
|
+
void Promise.resolve()
|
|
88
|
+
.then(() => entry.onPress())
|
|
89
|
+
.catch((cause) => {
|
|
90
|
+
Alert.alert("No se pudo completar la acción", cause instanceof Error && cause.message.trim()
|
|
91
|
+
? cause.message
|
|
92
|
+
: "Vuelve a intentarlo.");
|
|
93
|
+
})
|
|
94
|
+
.finally(() => {
|
|
95
|
+
running.current = false;
|
|
96
|
+
setPending(false);
|
|
97
|
+
});
|
|
98
|
+
}, style: { minWidth: 48, minHeight: 48 }, testID: testID ? `${testID}-native-menu` : undefined, title: title, children: _jsx(View, { accessibilityLabel: accessibilityLabel, accessibilityRole: "button", accessibilityState: { disabled: blocked, busy: pending }, accessible: true, pointerEvents: "none", style: {
|
|
27
99
|
minWidth: 48,
|
|
28
100
|
minHeight: 48,
|
|
29
101
|
alignItems: "center",
|
|
30
102
|
justifyContent: "center",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
top,
|
|
34
|
-
right,
|
|
35
|
-
width: menuWidth,
|
|
36
|
-
backgroundColor: theme.bg.popover,
|
|
37
|
-
borderWidth: 1,
|
|
38
|
-
borderColor: theme.border.light,
|
|
39
|
-
...theme.shadow.lg,
|
|
40
|
-
}, children: items.map((entry, index) => {
|
|
41
|
-
if ("separator" in entry && entry.separator) {
|
|
42
|
-
return _jsx(View, { className: "my-1 h-px bg-border-default" }, `sep-${index}`);
|
|
43
|
-
}
|
|
44
|
-
const item = entry;
|
|
45
|
-
return (_jsxs(Pressable, { accessibilityLabel: item.label, accessibilityRole: "menuitem", accessibilityState: { disabled: item.disabled }, className: "flex-row items-center gap-3 px-4 py-3", disabled: item.disabled, onPress: () => {
|
|
46
|
-
close();
|
|
47
|
-
item.onPress();
|
|
48
|
-
}, style: { minHeight: 48 }, children: [item.icon ? (_jsx(Icon, { color: item.destructive ? theme.danger : theme.icon.secondary, name: item.icon, size: 18 })) : null, _jsx(Typography.Text, { className: cn("flex-1 text-sm", item.destructive ? "text-danger" : "text-text-primary", item.disabled && "text-text-disabled"), children: item.label })] }, item.label));
|
|
49
|
-
}) }) }) })] }));
|
|
103
|
+
opacity: blocked ? 0.5 : 1,
|
|
104
|
+
}, children: pending ? (_jsx(ActivityIndicator, { color: theme.primary })) : ((trigger ?? _jsx(Icon, { color: theme.icon.secondary, name: "more-vertical", size: 22 }))) }) }) }));
|
|
50
105
|
}
|
|
51
106
|
export default Menu;
|
|
52
107
|
export { Menu };
|
|
@@ -25,10 +25,10 @@ function FilterSheet({ children, activeCount = 0, title = "Filtros", triggerLabe
|
|
|
25
25
|
const [visible, setVisible] = useState(false);
|
|
26
26
|
const theme = useTheme();
|
|
27
27
|
const countLabel = activeCount > 0 ? `, ${activeCount} activos` : "";
|
|
28
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Pressable, { accessibilityLabel: `${triggerLabel}${countLabel}`, accessibilityRole: "button", className: "min-h-11 flex-row items-center justify-center gap-2 self-start rounded-full bg-bg-muted px-4", onPress: () => setVisible(true), style: { minHeight: 44 }, testID: testID ? `${testID}-trigger` : undefined, children: [_jsx(Icon, { color: theme.icon.secondary, name: "
|
|
28
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Pressable, { accessibilityLabel: `${triggerLabel}${countLabel}`, accessibilityRole: "button", className: "min-h-11 flex-row items-center justify-center gap-2 self-start rounded-full bg-bg-muted px-4", onPress: () => setVisible(true), style: { minHeight: 44 }, testID: testID ? `${testID}-trigger` : undefined, children: [_jsx(Icon, { color: theme.icon.secondary, name: "filter", size: 18 }), _jsx(Typography.Text, { className: "text-text-primary", variant: "buttonSmall", children: triggerLabel }), activeCount > 0 ? (_jsx(Typography.Text, { accessibilityLabel: `${activeCount} filtros activos`, className: "text-text-accent", variant: "caption", children: activeCount })) : null] }), _jsx(Sheet, { footer: _jsxs(View, { className: "flex-row gap-3", children: [onClear ? (_jsx(View, { className: "flex-1", children: _jsx(Button, { onPress: onClear, title: clearLabel, variant: "secondary" }) })) : null, _jsx(View, { className: "flex-1", children: _jsx(Button, { onPress: () => {
|
|
29
29
|
onApply();
|
|
30
30
|
setVisible(false);
|
|
31
|
-
}, title: applyLabel }) })] }), onClose: () => setVisible(false),
|
|
31
|
+
}, title: applyLabel }) })] }), onClose: () => setVisible(false), testID: testID ? `${testID}-sheet` : undefined, title: title, visible: visible, children: children })] }));
|
|
32
32
|
}
|
|
33
33
|
function History(props) {
|
|
34
34
|
return _jsx(SearchHistory, { ...props });
|