orn-ui 0.1.1 → 0.2.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 +9 -2
- package/lib/commonjs/atoms/PressableScale.js +9 -10
- package/lib/commonjs/atoms/PressableScale.js.map +1 -1
- package/lib/commonjs/atoms/Skeleton.js +96 -0
- package/lib/commonjs/atoms/Skeleton.js.map +1 -0
- package/lib/commonjs/atoms/Transition.js +171 -0
- package/lib/commonjs/atoms/Transition.js.map +1 -0
- package/lib/commonjs/atoms/index.js +20 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/molecules/OptionCard.js +55 -22
- package/lib/commonjs/molecules/OptionCard.js.map +1 -1
- package/lib/commonjs/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/commonjs/molecules/SegmentedControl.js.map +1 -0
- package/lib/commonjs/molecules/Stepper.js +124 -47
- package/lib/commonjs/molecules/Stepper.js.map +1 -1
- package/lib/commonjs/molecules/index.js +9 -3
- package/lib/commonjs/molecules/index.js.map +1 -1
- package/lib/commonjs/organisms/AlertProvider.js +26 -0
- package/lib/commonjs/organisms/AlertProvider.js.map +1 -1
- package/lib/commonjs/organisms/BottomSheet.js +118 -39
- package/lib/commonjs/organisms/BottomSheet.js.map +1 -1
- package/lib/commonjs/organisms/List.js +66 -44
- package/lib/commonjs/organisms/List.js.map +1 -1
- package/lib/commonjs/organisms/Modal.js +42 -87
- package/lib/commonjs/organisms/Modal.js.map +1 -1
- package/lib/commonjs/organisms/NavigationBar.js +205 -0
- package/lib/commonjs/organisms/NavigationBar.js.map +1 -0
- package/lib/commonjs/organisms/Screen.js +9 -2
- package/lib/commonjs/organisms/Screen.js.map +1 -1
- package/lib/commonjs/organisms/SearchList.js +97 -28
- package/lib/commonjs/organisms/SearchList.js.map +1 -1
- package/lib/commonjs/organisms/Select.js +222 -50
- package/lib/commonjs/organisms/Select.js.map +1 -1
- package/lib/commonjs/organisms/ThemeToggle.js +52 -0
- package/lib/commonjs/organisms/ThemeToggle.js.map +1 -0
- package/lib/commonjs/organisms/Toast.js +8 -22
- package/lib/commonjs/organisms/Toast.js.map +1 -1
- package/lib/commonjs/organisms/ToastProvider.js +24 -0
- package/lib/commonjs/organisms/ToastProvider.js.map +1 -1
- package/lib/commonjs/organisms/Wizard.js +22 -2
- package/lib/commonjs/organisms/Wizard.js.map +1 -1
- package/lib/commonjs/organisms/imperativeBridge.js +28 -0
- package/lib/commonjs/organisms/imperativeBridge.js.map +1 -0
- package/lib/commonjs/organisms/index.js +50 -0
- package/lib/commonjs/organisms/index.js.map +1 -1
- package/lib/commonjs/theme/colors.js +207 -0
- package/lib/commonjs/theme/colors.js.map +1 -0
- package/lib/commonjs/theme/createTheme.js +25 -2
- package/lib/commonjs/theme/createTheme.js.map +1 -1
- package/lib/commonjs/theme/index.js +25 -0
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/palettes.js +5 -2
- package/lib/commonjs/theme/palettes.js.map +1 -1
- package/lib/module/atoms/PressableScale.js +9 -10
- package/lib/module/atoms/PressableScale.js.map +1 -1
- package/lib/module/atoms/Skeleton.js +91 -0
- package/lib/module/atoms/Skeleton.js.map +1 -0
- package/lib/module/atoms/Transition.js +165 -0
- package/lib/module/atoms/Transition.js.map +1 -0
- package/lib/module/atoms/index.js +2 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/molecules/OptionCard.js +56 -23
- package/lib/module/molecules/OptionCard.js.map +1 -1
- package/lib/module/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
- package/lib/module/molecules/SegmentedControl.js.map +1 -0
- package/lib/module/molecules/Stepper.js +125 -49
- package/lib/module/molecules/Stepper.js.map +1 -1
- package/lib/module/molecules/index.js +2 -2
- package/lib/module/molecules/index.js.map +1 -1
- package/lib/module/organisms/AlertProvider.js +24 -1
- package/lib/module/organisms/AlertProvider.js.map +1 -1
- package/lib/module/organisms/BottomSheet.js +120 -41
- package/lib/module/organisms/BottomSheet.js.map +1 -1
- package/lib/module/organisms/List.js +68 -46
- package/lib/module/organisms/List.js.map +1 -1
- package/lib/module/organisms/Modal.js +44 -89
- package/lib/module/organisms/Modal.js.map +1 -1
- package/lib/module/organisms/NavigationBar.js +200 -0
- package/lib/module/organisms/NavigationBar.js.map +1 -0
- package/lib/module/organisms/Screen.js +9 -2
- package/lib/module/organisms/Screen.js.map +1 -1
- package/lib/module/organisms/SearchList.js +98 -29
- package/lib/module/organisms/SearchList.js.map +1 -1
- package/lib/module/organisms/Select.js +224 -52
- package/lib/module/organisms/Select.js.map +1 -1
- package/lib/module/organisms/ThemeToggle.js +47 -0
- package/lib/module/organisms/ThemeToggle.js.map +1 -0
- package/lib/module/organisms/Toast.js +10 -24
- package/lib/module/organisms/Toast.js.map +1 -1
- package/lib/module/organisms/ToastProvider.js +21 -0
- package/lib/module/organisms/ToastProvider.js.map +1 -1
- package/lib/module/organisms/Wizard.js +23 -3
- package/lib/module/organisms/Wizard.js.map +1 -1
- package/lib/module/organisms/imperativeBridge.js +24 -0
- package/lib/module/organisms/imperativeBridge.js.map +1 -0
- package/lib/module/organisms/index.js +4 -2
- package/lib/module/organisms/index.js.map +1 -1
- package/lib/module/theme/colors.js +194 -0
- package/lib/module/theme/colors.js.map +1 -0
- package/lib/module/theme/createTheme.js +25 -2
- package/lib/module/theme/createTheme.js.map +1 -1
- package/lib/module/theme/index.js +1 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/palettes.js +5 -2
- package/lib/module/theme/palettes.js.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +3 -2
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +29 -0
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -0
- package/lib/typescript/atoms/Transition.d.ts +51 -0
- package/lib/typescript/atoms/Transition.d.ts.map +1 -0
- package/lib/typescript/atoms/index.d.ts +4 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +11 -6
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/SegmentedControl.d.ts +24 -0
- package/lib/typescript/molecules/SegmentedControl.d.ts.map +1 -0
- package/lib/typescript/molecules/Stepper.d.ts +35 -3
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/index.d.ts +4 -4
- package/lib/typescript/molecules/index.d.ts.map +1 -1
- package/lib/typescript/organisms/AlertProvider.d.ts +9 -0
- package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +26 -4
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +9 -2
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/Modal.d.ts +3 -5
- package/lib/typescript/organisms/Modal.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +32 -0
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -0
- package/lib/typescript/organisms/Screen.d.ts +2 -1
- package/lib/typescript/organisms/Screen.d.ts.map +1 -1
- package/lib/typescript/organisms/SearchList.d.ts +21 -5
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +19 -4
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/{molecules → organisms}/ThemeToggle.d.ts +4 -4
- package/lib/typescript/organisms/ThemeToggle.d.ts.map +1 -0
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +8 -0
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +7 -1
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/organisms/imperativeBridge.d.ts +12 -0
- package/lib/typescript/organisms/imperativeBridge.d.ts.map +1 -0
- package/lib/typescript/organisms/index.d.ts +7 -3
- package/lib/typescript/organisms/index.d.ts.map +1 -1
- package/lib/typescript/theme/colors.d.ts +44 -0
- package/lib/typescript/theme/colors.d.ts.map +1 -0
- package/lib/typescript/theme/createTheme.d.ts +3 -0
- package/lib/typescript/theme/createTheme.d.ts.map +1 -1
- package/lib/typescript/theme/index.d.ts +3 -1
- package/lib/typescript/theme/index.d.ts.map +1 -1
- package/lib/typescript/theme/palettes.d.ts.map +1 -1
- package/lib/typescript/theme/types.d.ts +9 -0
- package/lib/typescript/theme/types.d.ts.map +1 -1
- package/package.json +67 -6
- package/registry/alert-provider.json +9 -2
- package/registry/bottom-sheet.json +6 -2
- package/registry/core.json +8 -4
- package/registry/date-field.json +1 -0
- package/registry/index.json +96 -27
- package/registry/list.json +2 -1
- package/registry/manifest.json +44 -6
- package/registry/modal.json +3 -2
- package/registry/navigation-bar.json +18 -0
- package/registry/option-card.json +3 -2
- package/registry/pressable-scale.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +2 -1
- package/registry/segmented-control.json +18 -0
- package/registry/select.json +1 -1
- package/registry/skeleton.json +18 -0
- package/registry/stepper.json +3 -2
- package/registry/theme-toggle.json +5 -4
- package/registry/toast-provider.json +11 -3
- package/registry/toast.json +3 -2
- package/registry/transition.json +19 -0
- package/registry/wizard.json +3 -2
- package/src/atoms/PressableScale.tsx +16 -6
- package/src/atoms/Skeleton.tsx +114 -0
- package/src/atoms/Transition.tsx +189 -0
- package/src/atoms/index.ts +4 -0
- package/src/molecules/OptionCard.tsx +62 -25
- package/src/molecules/SegmentedControl.tsx +84 -0
- package/src/molecules/Stepper.tsx +147 -32
- package/src/molecules/index.ts +4 -4
- package/src/organisms/AlertProvider.tsx +26 -1
- package/src/organisms/BottomSheet.tsx +214 -62
- package/src/organisms/List.tsx +73 -41
- package/src/organisms/Modal.tsx +49 -61
- package/src/organisms/NavigationBar.tsx +223 -0
- package/src/organisms/Screen.tsx +11 -3
- package/src/organisms/SearchList.tsx +132 -36
- package/src/organisms/Select.tsx +223 -45
- package/src/organisms/ThemeToggle.tsx +30 -0
- package/src/organisms/Toast.tsx +8 -21
- package/src/organisms/ToastProvider.tsx +24 -0
- package/src/organisms/Wizard.tsx +36 -3
- package/src/organisms/imperativeBridge.ts +28 -0
- package/src/organisms/index.ts +7 -3
- package/src/theme/colors.ts +175 -0
- package/src/theme/createTheme.ts +38 -4
- package/src/theme/index.ts +3 -1
- package/src/theme/palettes.ts +5 -2
- package/src/theme/types.ts +10 -0
- package/lib/commonjs/molecules/ThemeToggle.js.map +0 -1
- package/lib/module/molecules/ThemeToggle.js.map +0 -1
- package/lib/typescript/molecules/ThemeToggle.d.ts.map +0 -1
- package/src/molecules/ThemeToggle.tsx +0 -67
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { AccessibilityInfo, Animated, Easing, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { useTheme } from '../theme/UIProvider';
|
|
4
|
+
|
|
5
|
+
export type TransitionPreset =
|
|
6
|
+
| 'fade'
|
|
7
|
+
| 'scale'
|
|
8
|
+
| 'pop'
|
|
9
|
+
| 'slide-up'
|
|
10
|
+
| 'slide-down'
|
|
11
|
+
| 'slide-left'
|
|
12
|
+
| 'slide-right';
|
|
13
|
+
|
|
14
|
+
export interface TransitionProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
/** Entra en true, sale (y desmonta) en false. @default true */
|
|
17
|
+
visible?: boolean;
|
|
18
|
+
/** Uno o varios: 'fade scale' combina opacidad y escala. @default 'fade' */
|
|
19
|
+
preset?: TransitionPreset | TransitionPreset[];
|
|
20
|
+
/** ms del timing. @default tokens.duration.base */
|
|
21
|
+
duration?: number;
|
|
22
|
+
/** ms antes de arrancar. Un índice por delay escalona una lista. @default 0 */
|
|
23
|
+
delay?: number;
|
|
24
|
+
/** Píxeles que recorren los presets de slide. @default 24 */
|
|
25
|
+
distance?: number;
|
|
26
|
+
/** Spring en vez de timing (implícito en 'pop'). */
|
|
27
|
+
spring?: boolean;
|
|
28
|
+
/** Anima también el primer render. En false arranca ya presente. @default true */
|
|
29
|
+
appear?: boolean;
|
|
30
|
+
/** Deja el nodo montado al salir, sólo lo esconde. @default false */
|
|
31
|
+
keepMounted?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Progreso 0→1 manejado desde afuera. Con esto el Transition sólo interpola:
|
|
34
|
+
* quien lo pasa decide cuándo y cómo avanza (un PanResponder, por ejemplo).
|
|
35
|
+
*/
|
|
36
|
+
value?: Animated.Value;
|
|
37
|
+
onEntered?: () => void;
|
|
38
|
+
onExited?: () => void;
|
|
39
|
+
style?: StyleProp<ViewStyle>;
|
|
40
|
+
testID?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const SCALE_FROM: Record<'scale' | 'pop', number> = { scale: 0.92, pop: 0.8 };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sigue el ajuste de "reducir movimiento" del sistema: con él activo las
|
|
47
|
+
* animaciones saltan al estado final en vez de desactivarse, así que los
|
|
48
|
+
* callbacks y el desmontaje siguen ocurriendo igual.
|
|
49
|
+
*/
|
|
50
|
+
export function useReduceMotion(): boolean {
|
|
51
|
+
const [reduceMotion, setReduceMotion] = useState(false);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let alive = true;
|
|
55
|
+
// Sólo se actualiza el estado si el ajuste está activo: el valor inicial ya
|
|
56
|
+
// es `false` y un setState de false a false costaría un render por montaje.
|
|
57
|
+
AccessibilityInfo.isReduceMotionEnabled().then((enabled) => {
|
|
58
|
+
if (alive && enabled) setReduceMotion(true);
|
|
59
|
+
});
|
|
60
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduceMotion);
|
|
61
|
+
return () => {
|
|
62
|
+
alive = false;
|
|
63
|
+
sub.remove();
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
return reduceMotion;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Transition
|
|
72
|
+
* Envuelve cualquier árbol y lo anima al entrar y al salir. Todos los presets
|
|
73
|
+
* se reducen a `opacity` y `transform` sobre un único Animated.Value, que es
|
|
74
|
+
* lo que `useNativeDriver` sabe mandar al hilo nativo: la animación corre ahí
|
|
75
|
+
* y el hilo de JS no participa de ningún frame — por eso no hay presets de
|
|
76
|
+
* alto, ancho o color, que obligarían a volver a JS en cada frame.
|
|
77
|
+
*
|
|
78
|
+
* El nodo se mantiene montado hasta que termina la animación de salida, y el
|
|
79
|
+
* comportamiento es idéntico en iOS y Android: no hay ninguna rama por
|
|
80
|
+
* plataforma.
|
|
81
|
+
*/
|
|
82
|
+
export const Transition = memo(
|
|
83
|
+
({
|
|
84
|
+
children,
|
|
85
|
+
visible = true,
|
|
86
|
+
preset = 'fade',
|
|
87
|
+
duration,
|
|
88
|
+
delay = 0,
|
|
89
|
+
distance = 24,
|
|
90
|
+
spring,
|
|
91
|
+
appear = true,
|
|
92
|
+
keepMounted = false,
|
|
93
|
+
value,
|
|
94
|
+
onEntered,
|
|
95
|
+
onExited,
|
|
96
|
+
style,
|
|
97
|
+
testID,
|
|
98
|
+
}: TransitionProps) => {
|
|
99
|
+
const theme = useTheme();
|
|
100
|
+
const reduceMotion = useReduceMotion();
|
|
101
|
+
const internal = useRef(new Animated.Value(visible && !appear ? 1 : 0)).current;
|
|
102
|
+
const progress = value ?? internal;
|
|
103
|
+
const driven = value === undefined;
|
|
104
|
+
|
|
105
|
+
const [mounted, setMounted] = useState(visible || keepMounted);
|
|
106
|
+
|
|
107
|
+
// Los callbacks viven en un ref: pasados inline cambian de identidad en
|
|
108
|
+
// cada render y, como dependencias del efecto, reiniciarían la animación.
|
|
109
|
+
const callbacks = useRef({ onEntered, onExited });
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
callbacks.current = { onEntered, onExited };
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const presetKey = Array.isArray(preset) ? preset.join(' ') : preset;
|
|
115
|
+
const presets = useMemo(() => presetKey.split(' ') as TransitionPreset[], [presetKey]);
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (visible) setMounted(true);
|
|
119
|
+
}, [visible]);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (!driven) return;
|
|
123
|
+
const toValue = visible ? 1 : 0;
|
|
124
|
+
const useSpring = spring ?? presets.includes('pop');
|
|
125
|
+
|
|
126
|
+
const animation = reduceMotion
|
|
127
|
+
? Animated.timing(progress, { toValue, duration: 0, delay, useNativeDriver: true })
|
|
128
|
+
: useSpring
|
|
129
|
+
? Animated.spring(progress, { toValue, delay, friction: 9, tension: 90, useNativeDriver: true })
|
|
130
|
+
: Animated.timing(progress, {
|
|
131
|
+
toValue,
|
|
132
|
+
delay,
|
|
133
|
+
duration: duration ?? theme.tokens.duration.base,
|
|
134
|
+
easing: visible ? Easing.out(Easing.cubic) : Easing.in(Easing.cubic),
|
|
135
|
+
useNativeDriver: true,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
animation.start(({ finished }) => {
|
|
139
|
+
if (!finished) return;
|
|
140
|
+
if (visible) {
|
|
141
|
+
callbacks.current.onEntered?.();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
callbacks.current.onExited?.();
|
|
145
|
+
if (!keepMounted) setMounted(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
return () => animation.stop();
|
|
149
|
+
}, [visible, driven, reduceMotion, spring, delay, duration, keepMounted, presets, progress, theme]);
|
|
150
|
+
|
|
151
|
+
const animatedStyle = useMemo(() => {
|
|
152
|
+
type Interpolated = Animated.AnimatedInterpolation<number>;
|
|
153
|
+
const transform: Array<{ scale: Interpolated } | { translateX: Interpolated } | { translateY: Interpolated }> = [];
|
|
154
|
+
let opacity: Animated.Value | undefined;
|
|
155
|
+
|
|
156
|
+
for (const item of presets) {
|
|
157
|
+
if (item === 'fade') {
|
|
158
|
+
opacity = progress;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (item === 'scale' || item === 'pop') {
|
|
162
|
+
transform.push({
|
|
163
|
+
scale: progress.interpolate({ inputRange: [0, 1], outputRange: [SCALE_FROM[item], 1] }),
|
|
164
|
+
});
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const vertical = item === 'slide-up' || item === 'slide-down';
|
|
168
|
+
const from = item === 'slide-up' || item === 'slide-left' ? distance : -distance;
|
|
169
|
+
const offset = progress.interpolate({ inputRange: [0, 1], outputRange: [from, 0] });
|
|
170
|
+
transform.push(vertical ? { translateY: offset } : { translateX: offset });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { opacity, transform };
|
|
174
|
+
}, [presets, progress, distance]);
|
|
175
|
+
|
|
176
|
+
if (!mounted) return null;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<Animated.View
|
|
180
|
+
testID={testID}
|
|
181
|
+
pointerEvents={visible ? undefined : 'none'}
|
|
182
|
+
style={[style, animatedStyle]}
|
|
183
|
+
>
|
|
184
|
+
{children}
|
|
185
|
+
</Animated.View>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
Transition.displayName = 'Transition';
|
package/src/atoms/index.ts
CHANGED
|
@@ -20,6 +20,10 @@ export { Image, prefetchImage } from './Image';
|
|
|
20
20
|
export type { ImageProps } from './Image';
|
|
21
21
|
export { Spinner } from './Spinner';
|
|
22
22
|
export type { SpinnerProps, SpinnerVariant } from './Spinner';
|
|
23
|
+
export { Skeleton } from './Skeleton';
|
|
24
|
+
export type { SkeletonProps, SkeletonVariant } from './Skeleton';
|
|
25
|
+
export { Transition, useReduceMotion } from './Transition';
|
|
26
|
+
export type { TransitionProps, TransitionPreset } from './Transition';
|
|
23
27
|
export { EmptyState } from './EmptyState';
|
|
24
28
|
export type { EmptyStateProps } from './EmptyState';
|
|
25
29
|
export { KeyValueRow } from './KeyValueRow';
|
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
import React, { memo } from 'react';
|
|
2
|
-
import { Text,
|
|
2
|
+
import { Text, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import { createStyles } from '../theme/createStyles';
|
|
4
4
|
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
5
5
|
import { Icon } from '../icons/Icon';
|
|
6
|
+
import { PressableScale } from '../atoms/PressableScale';
|
|
6
7
|
import type { IconName } from '../icons/types';
|
|
7
8
|
|
|
8
9
|
export interface OptionCardProps {
|
|
9
10
|
label: string;
|
|
10
|
-
|
|
11
|
+
/** Línea secundaria: el detalle que decide la elección ("llega en 3 días"). */
|
|
12
|
+
description?: string;
|
|
13
|
+
iconName?: IconName;
|
|
11
14
|
isSelected: boolean;
|
|
12
15
|
onPress: () => void;
|
|
13
|
-
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
/** 'vertical': ícono arriba, texto abajo centrado. 'horizontal': en fila. @default 'horizontal' */
|
|
14
18
|
layout?: 'vertical' | 'horizontal';
|
|
15
19
|
style?: StyleProp<ViewStyle>;
|
|
20
|
+
testID?: string;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
const useStyles = createStyles((theme) => ({
|
|
19
24
|
card: {
|
|
25
|
+
// Las tarjetas se reparten la fila que las contenga, sean dos o cinco.
|
|
26
|
+
flex: 1,
|
|
20
27
|
alignItems: 'center',
|
|
21
28
|
justifyContent: 'center',
|
|
22
29
|
backgroundColor: theme.colors.surface,
|
|
@@ -25,8 +32,8 @@ const useStyles = createStyles((theme) => ({
|
|
|
25
32
|
borderColor: theme.colors.border,
|
|
26
33
|
...theme.tokens.shadow.sm,
|
|
27
34
|
},
|
|
28
|
-
cardHorizontal: {
|
|
29
|
-
cardVertical: {
|
|
35
|
+
cardHorizontal: { flexDirection: 'row', padding: theme.tokens.spacing.md - 2, gap: theme.tokens.spacing.sm },
|
|
36
|
+
cardVertical: { padding: theme.tokens.spacing.md - 2, gap: 6, minHeight: 100 },
|
|
30
37
|
cardSelected: {
|
|
31
38
|
borderColor: theme.colors.primary,
|
|
32
39
|
backgroundColor: theme.colors.primarySoft,
|
|
@@ -34,48 +41,78 @@ const useStyles = createStyles((theme) => ({
|
|
|
34
41
|
elevation: 0,
|
|
35
42
|
shadowOpacity: 0,
|
|
36
43
|
},
|
|
44
|
+
cardDisabled: { opacity: 0.5 },
|
|
45
|
+
text: { flexShrink: 1 },
|
|
46
|
+
textVertical: { alignItems: 'center' },
|
|
37
47
|
label: { color: theme.colors.textLight, fontWeight: theme.tokens.fontWeight.medium },
|
|
38
48
|
labelVertical: { fontSize: theme.tokens.fontSize.xs, textAlign: 'center' },
|
|
39
49
|
labelSelected: { color: theme.colors.primaryText },
|
|
50
|
+
description: { fontSize: theme.tokens.fontSize.xs, color: theme.colors.textLight, marginTop: 2 },
|
|
51
|
+
descriptionVertical: { textAlign: 'center' },
|
|
40
52
|
}));
|
|
41
53
|
|
|
42
54
|
/**
|
|
43
55
|
* OptionCard
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* de
|
|
56
|
+
* Opción excluyente con ícono y label que resalta cuando `isSelected`. Es un
|
|
57
|
+
* radio con cara de tarjeta, así que se anuncia como tal: envolver el grupo en
|
|
58
|
+
* una View con `accessibilityRole="radiogroup"` para que el lector de pantalla
|
|
59
|
+
* lea "1 de 3".
|
|
47
60
|
*/
|
|
48
61
|
export const OptionCard = memo(
|
|
49
|
-
({
|
|
62
|
+
({
|
|
63
|
+
label,
|
|
64
|
+
description,
|
|
65
|
+
iconName,
|
|
66
|
+
isSelected,
|
|
67
|
+
onPress,
|
|
68
|
+
disabled = false,
|
|
69
|
+
layout = 'horizontal',
|
|
70
|
+
style,
|
|
71
|
+
testID,
|
|
72
|
+
}: OptionCardProps) => {
|
|
50
73
|
const colors = useColors();
|
|
51
74
|
const styles = useStyles();
|
|
52
75
|
const allowFontScaling = useAllowFontScaling();
|
|
76
|
+
const isVertical = layout === 'vertical';
|
|
53
77
|
|
|
78
|
+
// Feedback por transform y no por opacity: con elevation, animar la opacity
|
|
79
|
+
// dispara un bug de Android que redibuja la sombra como un borde oscuro.
|
|
54
80
|
return (
|
|
55
|
-
<
|
|
56
|
-
// activeOpacity=1: esta tarjeta tiene elevation (sombra Android); animar su
|
|
57
|
-
// propia opacity en el press dispara un bug de Android que redibuja la
|
|
58
|
-
// sombra como un borde oscuro pegado al borde del componente.
|
|
59
|
-
activeOpacity={1}
|
|
81
|
+
<PressableScale
|
|
60
82
|
style={[
|
|
61
83
|
styles.card,
|
|
62
|
-
|
|
84
|
+
isVertical ? styles.cardVertical : styles.cardHorizontal,
|
|
63
85
|
isSelected && styles.cardSelected,
|
|
86
|
+
disabled && styles.cardDisabled,
|
|
64
87
|
style,
|
|
65
88
|
]}
|
|
66
89
|
onPress={onPress}
|
|
67
|
-
|
|
68
|
-
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
accessibilityRole="radio"
|
|
92
|
+
accessibilityState={{ selected: isSelected, checked: isSelected, disabled }}
|
|
69
93
|
accessibilityLabel={label}
|
|
94
|
+
accessibilityHint={description}
|
|
95
|
+
testID={testID}
|
|
70
96
|
>
|
|
71
|
-
<Icon name={iconName} size={24} color={isSelected ? colors.primary : colors.textLight} />
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
{!!iconName && <Icon name={iconName} size={24} color={isSelected ? colors.primary : colors.textLight} />}
|
|
98
|
+
|
|
99
|
+
<View style={[styles.text, isVertical && styles.textVertical]}>
|
|
100
|
+
<Text
|
|
101
|
+
allowFontScaling={allowFontScaling}
|
|
102
|
+
style={[styles.label, isVertical && styles.labelVertical, isSelected && styles.labelSelected]}
|
|
103
|
+
>
|
|
104
|
+
{label}
|
|
105
|
+
</Text>
|
|
106
|
+
{!!description && (
|
|
107
|
+
<Text
|
|
108
|
+
allowFontScaling={allowFontScaling}
|
|
109
|
+
style={[styles.description, isVertical && styles.descriptionVertical]}
|
|
110
|
+
>
|
|
111
|
+
{description}
|
|
112
|
+
</Text>
|
|
113
|
+
)}
|
|
114
|
+
</View>
|
|
115
|
+
</PressableScale>
|
|
79
116
|
);
|
|
80
117
|
}
|
|
81
118
|
);
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Text, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
|
+
import { createStyles } from '../theme/createStyles';
|
|
4
|
+
import { useAllowFontScaling } from '../theme/UIProvider';
|
|
5
|
+
|
|
6
|
+
export interface SegmentedControlOption<T extends string = string> {
|
|
7
|
+
value: T;
|
|
8
|
+
label: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SegmentedControlProps<T extends string = string> {
|
|
13
|
+
options: SegmentedControlOption<T>[];
|
|
14
|
+
value: T;
|
|
15
|
+
onChange: (value: T) => void;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
testID?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const useStyles = createStyles((theme) => ({
|
|
22
|
+
track: {
|
|
23
|
+
flexDirection: 'row',
|
|
24
|
+
backgroundColor: theme.colors.inputBackground,
|
|
25
|
+
borderRadius: theme.tokens.radius.md,
|
|
26
|
+
padding: 3,
|
|
27
|
+
},
|
|
28
|
+
segment: {
|
|
29
|
+
flex: 1,
|
|
30
|
+
paddingVertical: theme.tokens.spacing.sm,
|
|
31
|
+
borderRadius: theme.tokens.radius.sm,
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
},
|
|
34
|
+
segmentActive: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },
|
|
35
|
+
segmentDisabled: { opacity: 0.5 },
|
|
36
|
+
label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.textLight },
|
|
37
|
+
labelActive: { color: theme.colors.primaryText, fontWeight: theme.tokens.fontWeight.semibold },
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* SegmentedControl
|
|
42
|
+
* Elige una opción entre pocas, todas visibles. No conoce ningún estado
|
|
43
|
+
* global: recibe `value` y avisa por `onChange`.
|
|
44
|
+
*/
|
|
45
|
+
function SegmentedControlInner<T extends string>({
|
|
46
|
+
options,
|
|
47
|
+
value,
|
|
48
|
+
onChange,
|
|
49
|
+
disabled = false,
|
|
50
|
+
style,
|
|
51
|
+
testID,
|
|
52
|
+
}: SegmentedControlProps<T>) {
|
|
53
|
+
const styles = useStyles();
|
|
54
|
+
const allowFontScaling = useAllowFontScaling();
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View style={[styles.track, style]} accessibilityRole="radiogroup" testID={testID}>
|
|
58
|
+
{options.map((option) => {
|
|
59
|
+
const active = option.value === value;
|
|
60
|
+
const isDisabled = disabled || !!option.disabled;
|
|
61
|
+
return (
|
|
62
|
+
<TouchableOpacity
|
|
63
|
+
key={option.value}
|
|
64
|
+
style={[styles.segment, active && styles.segmentActive, isDisabled && styles.segmentDisabled]}
|
|
65
|
+
onPress={() => onChange(option.value)}
|
|
66
|
+
disabled={isDisabled}
|
|
67
|
+
accessibilityRole="radio"
|
|
68
|
+
accessibilityState={{ selected: active, checked: active, disabled: isDisabled }}
|
|
69
|
+
accessibilityLabel={option.label}
|
|
70
|
+
testID={testID && `${testID}-${option.value}`}
|
|
71
|
+
>
|
|
72
|
+
<Text allowFontScaling={allowFontScaling} style={[styles.label, active && styles.labelActive]}>
|
|
73
|
+
{option.label}
|
|
74
|
+
</Text>
|
|
75
|
+
</TouchableOpacity>
|
|
76
|
+
);
|
|
77
|
+
})}
|
|
78
|
+
</View>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// memo() pierde los genéricos al envolver: el cast los devuelve para que
|
|
83
|
+
// `value` y `onChange` sigan atados al union de `options`.
|
|
84
|
+
export const SegmentedControl = memo(SegmentedControlInner) as typeof SegmentedControlInner;
|
|
@@ -1,29 +1,89 @@
|
|
|
1
|
-
import React, { memo } from 'react';
|
|
1
|
+
import React, { memo, useState } from 'react';
|
|
2
2
|
import { TextInput, TouchableOpacity, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import { createStyles } from '../theme/createStyles';
|
|
4
|
-
import { useAllowFontScaling, useColors } from '../theme/UIProvider';
|
|
4
|
+
import { useAllowFontScaling, useColors, useTheme } from '../theme/UIProvider';
|
|
5
5
|
import { Icon } from '../icons/Icon';
|
|
6
6
|
|
|
7
|
+
export type StepperSize = 'sm' | 'md' | 'lg';
|
|
8
|
+
|
|
7
9
|
export interface StepperProps {
|
|
8
10
|
value: string;
|
|
9
11
|
onChangeText: (text: string) => void;
|
|
10
12
|
onIncrement: () => void;
|
|
11
13
|
onDecrement: () => void;
|
|
12
|
-
|
|
14
|
+
/** Escala del control: alto, botones, ícono y tipografía. @default 'md' */
|
|
15
|
+
size?: StepperSize;
|
|
16
|
+
/** Ocupa todo el ancho disponible y el input se lleva el sobrante. @default false */
|
|
17
|
+
block?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* En false el valor sólo se cambia con los botones: el teclado no se abre y
|
|
20
|
+
* el campo pierde el fondo que lo anuncia como editable. @default true
|
|
21
|
+
*/
|
|
22
|
+
editable?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Mínimo permitido. Deshabilita "-" al alcanzarlo y acota lo tipeado al
|
|
25
|
+
* salir del campo. Los negativos se aceptan sólo si `min` es negativo.
|
|
26
|
+
*/
|
|
27
|
+
min?: number;
|
|
28
|
+
/** Máximo permitido. Deshabilita "+" al alcanzarlo y acota lo tipeado. */
|
|
29
|
+
max?: number;
|
|
30
|
+
/** Acepta un separador decimal, normalizando la coma a punto. @default false */
|
|
31
|
+
allowDecimals?: boolean;
|
|
13
32
|
style?: StyleProp<ViewStyle>;
|
|
14
33
|
incrementAccessibilityLabel?: string;
|
|
15
34
|
decrementAccessibilityLabel?: string;
|
|
35
|
+
testID?: string;
|
|
16
36
|
}
|
|
17
37
|
|
|
38
|
+
const SIZES: Record<StepperSize, { height: number; icon: number; inputWidth: number }> = {
|
|
39
|
+
sm: { height: 36, icon: 18, inputWidth: 48 },
|
|
40
|
+
md: { height: 44, icon: 20, inputWidth: 56 },
|
|
41
|
+
lg: { height: 52, icon: 22, inputWidth: 64 },
|
|
42
|
+
};
|
|
43
|
+
|
|
18
44
|
const useStyles = createStyles((theme) => ({
|
|
19
|
-
control: {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
control: {
|
|
46
|
+
flexDirection: 'row',
|
|
47
|
+
alignItems: 'center',
|
|
48
|
+
alignSelf: 'flex-start',
|
|
49
|
+
borderWidth: 1,
|
|
50
|
+
borderColor: theme.colors.border,
|
|
51
|
+
borderRadius: theme.tokens.radius.md,
|
|
52
|
+
},
|
|
53
|
+
controlBlock: { alignSelf: 'stretch' },
|
|
54
|
+
btn: { height: '100%', justifyContent: 'center', alignItems: 'center' },
|
|
55
|
+
input: {
|
|
56
|
+
textAlign: 'center',
|
|
57
|
+
height: '100%',
|
|
58
|
+
color: theme.colors.text,
|
|
59
|
+
borderLeftWidth: 1,
|
|
60
|
+
borderRightWidth: 1,
|
|
61
|
+
borderColor: theme.colors.border,
|
|
62
|
+
},
|
|
63
|
+
// El fondo distingue el campo de un valor de sólo lectura: es la única
|
|
64
|
+
// señal de que ahí se puede tipear antes de tocarlo.
|
|
65
|
+
editable: { backgroundColor: theme.colors.inputBackground },
|
|
24
66
|
}));
|
|
25
67
|
|
|
26
|
-
/**
|
|
68
|
+
/** Deja sólo lo que puede formar parte del número permitido por el rango. */
|
|
69
|
+
export function sanitizeNumeric(text: string, { allowDecimals = false, allowNegative = false } = {}): string {
|
|
70
|
+
const normalized = allowDecimals ? text.replace(',', '.') : text;
|
|
71
|
+
const negative = allowNegative && normalized.trimStart().startsWith('-');
|
|
72
|
+
const digits = normalized.replace(allowDecimals ? /[^0-9.]/g : /[^0-9]/g, '');
|
|
73
|
+
const [whole = '', ...decimals] = digits.split('.');
|
|
74
|
+
const body = decimals.length > 0 ? `${whole}.${decimals.join('')}` : whole;
|
|
75
|
+
return negative ? `-${body}` : body;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Stepper
|
|
80
|
+
* Botón "-" + input numérico editable + botón "+". `size` controla sólo la
|
|
81
|
+
* escala y `block` sólo el ancho, así que los dos ejes se combinan libremente.
|
|
82
|
+
*
|
|
83
|
+
* El componente no hace la aritmética de los botones —eso vive en
|
|
84
|
+
* `onIncrement`/`onDecrement`—, pero sí limpia lo que se tipea y avisa cuándo
|
|
85
|
+
* el valor llegó a un extremo del rango deshabilitando el botón que lo pasaría.
|
|
86
|
+
*/
|
|
27
87
|
export const Stepper = memo(
|
|
28
88
|
({
|
|
29
89
|
value,
|
|
@@ -31,44 +91,99 @@ export const Stepper = memo(
|
|
|
31
91
|
onIncrement,
|
|
32
92
|
onDecrement,
|
|
33
93
|
size = 'md',
|
|
94
|
+
block = false,
|
|
95
|
+
editable = true,
|
|
96
|
+
min,
|
|
97
|
+
max,
|
|
98
|
+
allowDecimals = false,
|
|
34
99
|
style,
|
|
35
100
|
incrementAccessibilityLabel = 'Increment',
|
|
36
101
|
decrementAccessibilityLabel = 'Decrement',
|
|
102
|
+
testID,
|
|
37
103
|
}: StepperProps) => {
|
|
104
|
+
const theme = useTheme();
|
|
38
105
|
const colors = useColors();
|
|
39
106
|
const styles = useStyles();
|
|
40
107
|
const allowFontScaling = useAllowFontScaling();
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
108
|
+
const [focused, setFocused] = useState(false);
|
|
109
|
+
|
|
110
|
+
const { height, icon, inputWidth } = SIZES[size];
|
|
111
|
+
const fontSize =
|
|
112
|
+
size === 'sm' ? theme.tokens.fontSize.md : size === 'md' ? theme.tokens.fontSize.lg : theme.tokens.fontSize.xl;
|
|
113
|
+
|
|
114
|
+
const allowNegative = min !== undefined && min < 0;
|
|
115
|
+
const numeric = Number(value);
|
|
116
|
+
const isNumber = value.trim() !== '' && Number.isFinite(numeric);
|
|
117
|
+
const atMin = isNumber && min !== undefined && numeric <= min;
|
|
118
|
+
const atMax = isNumber && max !== undefined && numeric >= max;
|
|
119
|
+
|
|
120
|
+
const handleChangeText = (text: string) => {
|
|
121
|
+
onChangeText(sanitizeNumeric(text, { allowDecimals, allowNegative }));
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// El recorte al rango espera al blur: hacerlo mientras se tipea impide
|
|
125
|
+
// llegar a "12" cuando el mínimo es 5, porque el "1" ya se corregiría.
|
|
126
|
+
const handleBlur = () => {
|
|
127
|
+
setFocused(false);
|
|
128
|
+
if (!isNumber) return;
|
|
129
|
+
const clamped = Math.min(max ?? numeric, Math.max(min ?? numeric, numeric));
|
|
130
|
+
if (clamped !== numeric) onChangeText(String(clamped));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const stepButton = (
|
|
134
|
+
direction: 'decrement' | 'increment',
|
|
135
|
+
onPress: () => void,
|
|
136
|
+
disabled: boolean,
|
|
137
|
+
label: string
|
|
138
|
+
) => (
|
|
139
|
+
<TouchableOpacity
|
|
140
|
+
onPress={onPress}
|
|
141
|
+
disabled={disabled}
|
|
142
|
+
style={[styles.btn, { width: height }]}
|
|
143
|
+
accessibilityRole="button"
|
|
144
|
+
accessibilityLabel={label}
|
|
145
|
+
accessibilityState={{ disabled }}
|
|
146
|
+
testID={testID && `${testID}-${direction}`}
|
|
147
|
+
>
|
|
148
|
+
<Icon
|
|
149
|
+
name={direction === 'increment' ? 'plus' : 'minus'}
|
|
150
|
+
size={icon}
|
|
151
|
+
color={disabled ? colors.textLight : colors.primary}
|
|
152
|
+
/>
|
|
153
|
+
</TouchableOpacity>
|
|
154
|
+
);
|
|
44
155
|
|
|
45
156
|
return (
|
|
46
|
-
<View
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
157
|
+
<View
|
|
158
|
+
testID={testID}
|
|
159
|
+
style={[
|
|
160
|
+
styles.control,
|
|
161
|
+
{ height },
|
|
162
|
+
block && styles.controlBlock,
|
|
163
|
+
focused && { borderColor: colors.primary },
|
|
164
|
+
style,
|
|
165
|
+
]}
|
|
166
|
+
>
|
|
167
|
+
{stepButton('decrement', onDecrement, atMin, decrementAccessibilityLabel)}
|
|
55
168
|
<TextInput
|
|
56
169
|
allowFontScaling={allowFontScaling}
|
|
57
|
-
style={[
|
|
170
|
+
style={[
|
|
171
|
+
styles.input,
|
|
172
|
+
{ fontSize },
|
|
173
|
+
editable && styles.editable,
|
|
174
|
+
focused && { borderColor: colors.primary },
|
|
175
|
+
block ? { flex: 1 } : { width: inputWidth },
|
|
176
|
+
]}
|
|
58
177
|
value={value}
|
|
59
|
-
onChangeText={
|
|
60
|
-
|
|
178
|
+
onChangeText={handleChangeText}
|
|
179
|
+
onFocus={() => setFocused(true)}
|
|
180
|
+
onBlur={handleBlur}
|
|
181
|
+
editable={editable}
|
|
182
|
+
keyboardType={allowDecimals ? 'decimal-pad' : 'number-pad'}
|
|
61
183
|
selectTextOnFocus
|
|
62
184
|
accessibilityLabel="Quantity"
|
|
63
185
|
/>
|
|
64
|
-
|
|
65
|
-
onPress={onIncrement}
|
|
66
|
-
style={[styles.btn, { width: btnSize }, isSmall && styles.btnSm]}
|
|
67
|
-
accessibilityRole="button"
|
|
68
|
-
accessibilityLabel={incrementAccessibilityLabel}
|
|
69
|
-
>
|
|
70
|
-
<Icon name="plus" size={iconSize} color={colors.primary} />
|
|
71
|
-
</TouchableOpacity>
|
|
186
|
+
{stepButton('increment', onIncrement, atMax, incrementAccessibilityLabel)}
|
|
72
187
|
</View>
|
|
73
188
|
);
|
|
74
189
|
}
|
package/src/molecules/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { Stepper } from './Stepper';
|
|
2
|
-
export type { StepperProps } from './Stepper';
|
|
1
|
+
export { Stepper, sanitizeNumeric } from './Stepper';
|
|
2
|
+
export type { StepperProps, StepperSize } from './Stepper';
|
|
3
3
|
export { OptionCard } from './OptionCard';
|
|
4
4
|
export type { OptionCardProps } from './OptionCard';
|
|
5
5
|
export { InfoRow } from './InfoRow';
|
|
@@ -8,7 +8,7 @@ export { FormActions } from './FormActions';
|
|
|
8
8
|
export type { FormActionsProps } from './FormActions';
|
|
9
9
|
export { AvatarHeader } from './AvatarHeader';
|
|
10
10
|
export type { AvatarHeaderProps } from './AvatarHeader';
|
|
11
|
-
export {
|
|
12
|
-
export type {
|
|
11
|
+
export { SegmentedControl } from './SegmentedControl';
|
|
12
|
+
export type { SegmentedControlProps, SegmentedControlOption } from './SegmentedControl';
|
|
13
13
|
export { Steps } from './Steps';
|
|
14
14
|
export type { StepsProps, StepItem, StepStatus } from './Steps';
|