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
|
@@ -3,6 +3,7 @@ import { View } from 'react-native';
|
|
|
3
3
|
import { AnimatedToast, Toast, type ToastVariant } from './Toast';
|
|
4
4
|
import { useInsets } from '../theme/UIProvider';
|
|
5
5
|
import { createStyles } from '../theme/createStyles';
|
|
6
|
+
import { createImperativeBridge } from './imperativeBridge';
|
|
6
7
|
|
|
7
8
|
export interface ToastOptions {
|
|
8
9
|
title: string;
|
|
@@ -26,6 +27,24 @@ interface ToastContextValue {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const ToastContext = createContext<ToastContextValue | undefined>(undefined);
|
|
30
|
+
const bridge = createImperativeBridge<ToastContextValue>('ToastProvider');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Muestra un toast desde fuera del árbol de React —un servicio, un
|
|
34
|
+
* interceptor— sin pasar por el hook. Devuelve el id, o '' si no hay
|
|
35
|
+
* <ToastProvider> montado.
|
|
36
|
+
*/
|
|
37
|
+
export function showToast(options: ToastOptions): string {
|
|
38
|
+
return bridge.get()?.show(options) ?? '';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function hideToast(id: string): void {
|
|
42
|
+
bridge.get()?.hide(id);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function hideAllToasts(): void {
|
|
46
|
+
bridge.get()?.hideAll();
|
|
47
|
+
}
|
|
29
48
|
|
|
30
49
|
const useStyles = createStyles((theme) => ({
|
|
31
50
|
host: { position: 'absolute', left: 0, right: 0, paddingHorizontal: theme.tokens.spacing.lg, gap: theme.tokens.spacing.sm },
|
|
@@ -122,6 +141,11 @@ export function ToastProvider({
|
|
|
122
141
|
|
|
123
142
|
const value = useMemo<ToastContextValue>(() => ({ show, hide, hideAll }), [show, hide, hideAll]);
|
|
124
143
|
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
bridge.set(value);
|
|
146
|
+
return () => bridge.set(null);
|
|
147
|
+
}, [value]);
|
|
148
|
+
|
|
125
149
|
const hostPosition =
|
|
126
150
|
position === 'top'
|
|
127
151
|
? { top: insets.top + 8 }
|
package/src/organisms/Wizard.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React, { useCallback, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import { ScrollView, View, type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import { createStyles } from '../theme/createStyles';
|
|
4
4
|
import { Steps, type StepItem } from '../molecules/Steps';
|
|
5
5
|
import { Button } from '../atoms/Button';
|
|
6
|
+
import { Transition } from '../atoms/Transition';
|
|
6
7
|
|
|
7
8
|
export interface WizardStep extends StepItem {
|
|
8
9
|
content: React.ReactNode;
|
|
@@ -31,6 +32,12 @@ export interface WizardProps {
|
|
|
31
32
|
* @default true
|
|
32
33
|
*/
|
|
33
34
|
scrollableContent?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* El contenido del paso entra deslizándose en el sentido de la navegación.
|
|
37
|
+
* Sólo opacity y transform, así que la animación corre en el hilo nativo.
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
animated?: boolean;
|
|
34
41
|
style?: StyleProp<ViewStyle>;
|
|
35
42
|
}
|
|
36
43
|
|
|
@@ -61,6 +68,7 @@ export function Wizard({
|
|
|
61
68
|
nextLabel = 'Next',
|
|
62
69
|
finishLabel = 'Finish',
|
|
63
70
|
scrollableContent = true,
|
|
71
|
+
animated = true,
|
|
64
72
|
style,
|
|
65
73
|
}: WizardProps) {
|
|
66
74
|
const styles = useStyles();
|
|
@@ -82,6 +90,15 @@ export function Wizard({
|
|
|
82
90
|
[steps]
|
|
83
91
|
);
|
|
84
92
|
|
|
93
|
+
// El ref todavía guarda el paso anterior durante el render que trae el nuevo.
|
|
94
|
+
const previous = useRef(current);
|
|
95
|
+
const forward = current >= previous.current;
|
|
96
|
+
const entered = useRef(false);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
previous.current = current;
|
|
99
|
+
entered.current = true;
|
|
100
|
+
}, [current]);
|
|
101
|
+
|
|
85
102
|
const activeStep = steps[current];
|
|
86
103
|
const isFirst = current === 0;
|
|
87
104
|
const isLast = current === steps.length - 1;
|
|
@@ -92,6 +109,22 @@ export function Wizard({
|
|
|
92
109
|
else goTo(current + 1);
|
|
93
110
|
};
|
|
94
111
|
|
|
112
|
+
// El paso saliente no se anima: mantener los dos montados a la vez duplicaría
|
|
113
|
+
// los formularios y su estado.
|
|
114
|
+
const renderContent = () =>
|
|
115
|
+
animated ? (
|
|
116
|
+
<Transition
|
|
117
|
+
key={current}
|
|
118
|
+
testID="wizard-step"
|
|
119
|
+
preset={['fade', forward ? 'slide-left' : 'slide-right']}
|
|
120
|
+
appear={entered.current}
|
|
121
|
+
>
|
|
122
|
+
{activeStep?.content}
|
|
123
|
+
</Transition>
|
|
124
|
+
) : (
|
|
125
|
+
activeStep?.content
|
|
126
|
+
);
|
|
127
|
+
|
|
95
128
|
return (
|
|
96
129
|
<View style={[styles.container, style]}>
|
|
97
130
|
<Steps
|
|
@@ -115,10 +148,10 @@ export function Wizard({
|
|
|
115
148
|
automaticallyAdjustKeyboardInsets
|
|
116
149
|
showsVerticalScrollIndicator={false}
|
|
117
150
|
>
|
|
118
|
-
{
|
|
151
|
+
{renderContent()}
|
|
119
152
|
</ScrollView>
|
|
120
153
|
) : (
|
|
121
|
-
<View style={styles.content}>{
|
|
154
|
+
<View style={styles.content}>{renderContent()}</View>
|
|
122
155
|
)}
|
|
123
156
|
|
|
124
157
|
<View style={styles.footer}>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Puente entre el proveedor montado y el código que no es un componente
|
|
3
|
+
* (servicios, capa de API, interceptores). El proveedor se registra al montar y
|
|
4
|
+
* se borra al desmontar, así que fuera del árbol nunca queda una referencia
|
|
5
|
+
* viva a un contexto muerto.
|
|
6
|
+
*/
|
|
7
|
+
export interface ImperativeBridge<T> {
|
|
8
|
+
set: (value: T | null) => void;
|
|
9
|
+
get: () => T | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createImperativeBridge<T>(providerName: string): ImperativeBridge<T> {
|
|
13
|
+
let current: T | null = null;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
set: (value) => {
|
|
17
|
+
current = value;
|
|
18
|
+
},
|
|
19
|
+
get: () => {
|
|
20
|
+
if (!current && __DEV__) {
|
|
21
|
+
console.warn(
|
|
22
|
+
`orn-ui: se llamó a la API imperativa sin un <${providerName}> montado. La llamada se ignoró.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return current;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
package/src/organisms/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { Modal } from './Modal';
|
|
2
2
|
export type { ModalProps, ModalVariant } from './Modal';
|
|
3
3
|
export { BottomSheet } from './BottomSheet';
|
|
4
|
-
export type { BottomSheetProps } from './BottomSheet';
|
|
4
|
+
export type { BottomSheetProps, FooterPlacement } from './BottomSheet';
|
|
5
5
|
export { Select } from './Select';
|
|
6
6
|
export type { SelectProps, SelectOption } from './Select';
|
|
7
7
|
export { Alert } from './Alert';
|
|
8
8
|
export type { AlertProps, AlertButton, AlertType } from './Alert';
|
|
9
|
-
export { AlertProvider, useAlert } from './AlertProvider';
|
|
9
|
+
export { AlertProvider, useAlert, showAlert, showConfirm, hideAlert } from './AlertProvider';
|
|
10
10
|
export type { AlertOptions, ConfirmOptions } from './AlertProvider';
|
|
11
11
|
export { Screen } from './Screen';
|
|
12
12
|
export type { ScreenProps, ScreenEdge } from './Screen';
|
|
@@ -16,7 +16,7 @@ export { SearchList } from './SearchList';
|
|
|
16
16
|
export type { SearchListProps } from './SearchList';
|
|
17
17
|
export { Toast } from './Toast';
|
|
18
18
|
export type { ToastProps, ToastVariant } from './Toast';
|
|
19
|
-
export { ToastProvider, useToast } from './ToastProvider';
|
|
19
|
+
export { ToastProvider, useToast, showToast, hideToast, hideAllToasts } from './ToastProvider';
|
|
20
20
|
export type { ToastProviderProps, ToastOptions } from './ToastProvider';
|
|
21
21
|
export { DatePicker, buildMonthGrid, DEFAULT_MONTH_NAMES, DEFAULT_WEEKDAY_NAMES } from './DatePicker';
|
|
22
22
|
export type { DatePickerProps, DateRange } from './DatePicker';
|
|
@@ -24,3 +24,7 @@ export { DateField } from './DateField';
|
|
|
24
24
|
export type { DateFieldProps } from './DateField';
|
|
25
25
|
export { Wizard } from './Wizard';
|
|
26
26
|
export type { WizardProps, WizardStep } from './Wizard';
|
|
27
|
+
export { ThemeToggle } from './ThemeToggle';
|
|
28
|
+
export type { ThemeToggleProps } from './ThemeToggle';
|
|
29
|
+
export { NavigationBar } from './NavigationBar';
|
|
30
|
+
export type { NavigationBarProps, NavigationBarItem } from './NavigationBar';
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilidades de color sin dependencias, usadas para derivar una familia de
|
|
3
|
+
* roles (relleno / encima / tenue / legible) a partir de un solo hex de marca.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Contraste mínimo AA para texto normal. */
|
|
7
|
+
export const AA_CONTRAST = 4.5;
|
|
8
|
+
|
|
9
|
+
export interface Rgb {
|
|
10
|
+
r: number;
|
|
11
|
+
g: number;
|
|
12
|
+
b: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Hsl {
|
|
16
|
+
h: number;
|
|
17
|
+
s: number;
|
|
18
|
+
l: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Roles que necesita un acento del theme, en un esquema concreto. */
|
|
22
|
+
export interface AccentRoles {
|
|
23
|
+
/** Relleno pleno: fondo de botón, círculo del paso actual. */
|
|
24
|
+
base: string;
|
|
25
|
+
/** Texto/ícono que va encima del relleno. */
|
|
26
|
+
on: string;
|
|
27
|
+
/** Fondo tenue: badges, pill del indicador activo. */
|
|
28
|
+
soft: string;
|
|
29
|
+
/** El acento legible sobre `surface`: labels, links. */
|
|
30
|
+
text: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const HEX = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
34
|
+
|
|
35
|
+
export function hexToRgb(hex: string): Rgb {
|
|
36
|
+
const match = HEX.exec(hex.trim());
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new Error(`orn-ui: se esperaba un color hex como "#7c3aed", se recibió "${hex}".`);
|
|
39
|
+
}
|
|
40
|
+
const digits = match[1]!;
|
|
41
|
+
const full = digits.length === 3 ? digits.replace(/./g, (c) => c + c) : digits;
|
|
42
|
+
return {
|
|
43
|
+
r: parseInt(full.slice(0, 2), 16),
|
|
44
|
+
g: parseInt(full.slice(2, 4), 16),
|
|
45
|
+
b: parseInt(full.slice(4, 6), 16),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const toHexPair = (value: number) => Math.round(clamp(value, 0, 255)).toString(16).padStart(2, '0');
|
|
50
|
+
|
|
51
|
+
export function rgbToHex({ r, g, b }: Rgb): string {
|
|
52
|
+
return `#${toHexPair(r)}${toHexPair(g)}${toHexPair(b)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function clamp(value: number, min: number, max: number) {
|
|
56
|
+
return Math.min(max, Math.max(min, value));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function rgbToHsl({ r, g, b }: Rgb): Hsl {
|
|
60
|
+
const rn = r / 255;
|
|
61
|
+
const gn = g / 255;
|
|
62
|
+
const bn = b / 255;
|
|
63
|
+
const max = Math.max(rn, gn, bn);
|
|
64
|
+
const min = Math.min(rn, gn, bn);
|
|
65
|
+
const delta = max - min;
|
|
66
|
+
const l = (max + min) / 2;
|
|
67
|
+
|
|
68
|
+
if (delta === 0) return { h: 0, s: 0, l };
|
|
69
|
+
|
|
70
|
+
const s = delta / (1 - Math.abs(2 * l - 1));
|
|
71
|
+
let h: number;
|
|
72
|
+
if (max === rn) h = ((gn - bn) / delta) % 6;
|
|
73
|
+
else if (max === gn) h = (bn - rn) / delta + 2;
|
|
74
|
+
else h = (rn - gn) / delta + 4;
|
|
75
|
+
|
|
76
|
+
h *= 60;
|
|
77
|
+
return { h: h < 0 ? h + 360 : h, s, l };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function hslToRgb({ h, s, l }: Hsl): Rgb {
|
|
81
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
82
|
+
const hp = (((h % 360) + 360) % 360) / 60;
|
|
83
|
+
const x = c * (1 - Math.abs((hp % 2) - 1));
|
|
84
|
+
const [r1, g1, b1] =
|
|
85
|
+
hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
|
|
86
|
+
const m = l - c / 2;
|
|
87
|
+
return { r: (r1 + m) * 255, g: (g1 + m) * 255, b: (b1 + m) * 255 };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function toHsl(hex: string): Hsl {
|
|
91
|
+
return rgbToHsl(hexToRgb(hex));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function toHex(hsl: Hsl): string {
|
|
95
|
+
return rgbToHex(hslToRgb({ ...hsl, s: clamp(hsl.s, 0, 1), l: clamp(hsl.l, 0, 1) }));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function channelLuminance(value: number) {
|
|
99
|
+
const c = value / 255;
|
|
100
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Luminancia relativa WCAG de un hex. */
|
|
104
|
+
export function luminance(hex: string): number {
|
|
105
|
+
const { r, g, b } = hexToRgb(hex);
|
|
106
|
+
return 0.2126 * channelLuminance(r) + 0.7152 * channelLuminance(g) + 0.0722 * channelLuminance(b);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Ratio de contraste WCAG entre dos hex: 1 (igual) a 21 (blanco sobre negro). */
|
|
110
|
+
export function contrast(a: string, b: string): number {
|
|
111
|
+
const la = luminance(a);
|
|
112
|
+
const lb = luminance(b);
|
|
113
|
+
return (Math.max(la, lb) + 0.05) / (Math.min(la, lb) + 0.05);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Blanco si alcanza el contraste AA —es lo que se espera encima de un relleno
|
|
118
|
+
* de color— y si no, el extremo que más contraste dé.
|
|
119
|
+
*/
|
|
120
|
+
function bestOnColor(background: string, light: string, dark: string): string {
|
|
121
|
+
if (contrast(background, light) >= AA_CONTRAST) return light;
|
|
122
|
+
return contrast(background, dark) >= contrast(background, light) ? dark : light;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Mueve la luminosidad del tono hasta alcanzar el contraste pedido contra
|
|
127
|
+
* `against`. Si ni el extremo llega —un amarillo puro sobre blanco, por
|
|
128
|
+
* ejemplo— devuelve el más contrastado que encontró en el camino.
|
|
129
|
+
*/
|
|
130
|
+
function reachContrast(hsl: Hsl, against: string, target: number, direction: 'darker' | 'lighter'): string {
|
|
131
|
+
const step = direction === 'darker' ? -0.02 : 0.02;
|
|
132
|
+
let best = toHex(hsl);
|
|
133
|
+
let bestRatio = contrast(best, against);
|
|
134
|
+
|
|
135
|
+
for (let l = hsl.l; l >= 0 && l <= 1; l += step) {
|
|
136
|
+
const candidate = toHex({ ...hsl, l });
|
|
137
|
+
const ratio = contrast(candidate, against);
|
|
138
|
+
if (ratio > bestRatio) {
|
|
139
|
+
best = candidate;
|
|
140
|
+
bestRatio = ratio;
|
|
141
|
+
}
|
|
142
|
+
if (ratio >= target) return candidate;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return best;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Deriva los cuatro roles de un acento a partir de un hex, para un esquema.
|
|
150
|
+
* `surface` es el fondo sobre el que tiene que leerse el rol `text`, así que
|
|
151
|
+
* cada esquema pasa el suyo.
|
|
152
|
+
*/
|
|
153
|
+
export function deriveAccent(hex: string, scheme: 'light' | 'dark', surface: string): AccentRoles {
|
|
154
|
+
const hsl = toHsl(hex);
|
|
155
|
+
|
|
156
|
+
if (scheme === 'light') {
|
|
157
|
+
const base = toHex(hsl);
|
|
158
|
+
return {
|
|
159
|
+
base,
|
|
160
|
+
on: bestOnColor(base, '#ffffff', '#1a1a1a'),
|
|
161
|
+
soft: toHex({ h: hsl.h, s: Math.min(hsl.s, 0.85), l: 0.94 }),
|
|
162
|
+
text: reachContrast({ ...hsl, l: Math.min(hsl.l, 0.38) }, surface, AA_CONTRAST, 'darker'),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// En oscuro el acento sube de luminosidad y lo que va encima baja: un
|
|
167
|
+
// #004cef sobre #1e1e1e no se distingue del fondo.
|
|
168
|
+
const base = toHex({ ...hsl, l: 0.72 });
|
|
169
|
+
return {
|
|
170
|
+
base,
|
|
171
|
+
on: bestOnColor(base, '#ffffff', toHex({ ...hsl, s: Math.min(hsl.s, 0.9), l: 0.16 })),
|
|
172
|
+
soft: toHex({ h: hsl.h, s: Math.min(hsl.s, 0.35), l: 0.18 }),
|
|
173
|
+
text: reachContrast({ ...hsl, l: Math.max(hsl.l, 0.74) }, surface, AA_CONTRAST, 'lighter'),
|
|
174
|
+
};
|
|
175
|
+
}
|
package/src/theme/createTheme.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { lightPalette, darkPalette } from './palettes';
|
|
1
|
+
import { lightPalette, darkPalette, type ThemeColors } from './palettes';
|
|
2
2
|
import { tokens as defaultTokens } from './tokens';
|
|
3
|
-
import
|
|
3
|
+
import { deriveAccent } from './colors';
|
|
4
|
+
import type { BrandAccent, Theme, ThemeOverrides, ThemePair } from './types';
|
|
4
5
|
|
|
5
6
|
function mergeDeep<T extends Record<string, any>>(base: T, patch?: Partial<T>): T {
|
|
6
7
|
if (!patch) return base;
|
|
@@ -23,22 +24,55 @@ function mergeDeep<T extends Record<string, any>>(base: T, patch?: Partial<T>):
|
|
|
23
24
|
return out as T;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const capitalize = <T extends string>(name: T) => (name.charAt(0).toUpperCase() + name.slice(1)) as Capitalize<T>;
|
|
28
|
+
|
|
29
|
+
/** Expande cada hex de `brand` a los cuatro roles del acento en un esquema. */
|
|
30
|
+
function brandColors(
|
|
31
|
+
brand: ThemeOverrides['brand'],
|
|
32
|
+
scheme: 'light' | 'dark',
|
|
33
|
+
palette: ThemeColors
|
|
34
|
+
): Partial<ThemeColors> {
|
|
35
|
+
if (!brand) return {};
|
|
36
|
+
const accents = typeof brand === 'string' ? { primary: brand } : brand;
|
|
37
|
+
const out: Partial<ThemeColors> = {};
|
|
38
|
+
|
|
39
|
+
for (const [name, hex] of Object.entries(accents) as [BrandAccent, string | undefined][]) {
|
|
40
|
+
if (!hex) continue;
|
|
41
|
+
const roles = deriveAccent(hex, scheme, palette.surface);
|
|
42
|
+
out[name] = roles.base;
|
|
43
|
+
out[`on${capitalize(name)}`] = roles.on;
|
|
44
|
+
out[`${name}Soft`] = roles.soft;
|
|
45
|
+
out[`${name}Text`] = roles.text;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
26
51
|
/**
|
|
27
52
|
* Construye el par de themes light/dark que consume <UIProvider theme={...}>.
|
|
28
53
|
* Nunca muta los defaults: cada llamada produce objetos nuevos, seguros de
|
|
29
54
|
* compartir entre providers distintos (tests, multi-brand en el mismo árbol).
|
|
55
|
+
*
|
|
56
|
+
* `brand` se aplica antes que `colors`, así que un rol escrito a mano siempre
|
|
57
|
+
* gana sobre el derivado.
|
|
30
58
|
*/
|
|
31
59
|
export function createTheme(overrides?: ThemeOverrides): ThemePair {
|
|
32
60
|
const tokens = mergeDeep(defaultTokens, overrides?.tokens as any);
|
|
33
61
|
|
|
34
62
|
const light: Theme = {
|
|
35
|
-
colors: mergeDeep(
|
|
63
|
+
colors: mergeDeep(
|
|
64
|
+
mergeDeep(lightPalette, brandColors(overrides?.brand, 'light', lightPalette)),
|
|
65
|
+
overrides?.colors?.light
|
|
66
|
+
),
|
|
36
67
|
tokens,
|
|
37
68
|
scheme: 'light',
|
|
38
69
|
};
|
|
39
70
|
|
|
40
71
|
const dark: Theme = {
|
|
41
|
-
colors: mergeDeep(
|
|
72
|
+
colors: mergeDeep(
|
|
73
|
+
mergeDeep(darkPalette, brandColors(overrides?.brand, 'dark', darkPalette)),
|
|
74
|
+
overrides?.colors?.dark
|
|
75
|
+
),
|
|
42
76
|
tokens,
|
|
43
77
|
scheme: 'dark',
|
|
44
78
|
};
|
package/src/theme/index.ts
CHANGED
|
@@ -3,7 +3,9 @@ export type { Tokens } from './tokens';
|
|
|
3
3
|
export { lightPalette, darkPalette } from './palettes';
|
|
4
4
|
export type { ThemeColors } from './palettes';
|
|
5
5
|
export { createTheme, defaultTheme } from './createTheme';
|
|
6
|
-
export type { Theme, ThemePair, ThemeOverrides, DeepPartial } from './types';
|
|
6
|
+
export type { Theme, ThemePair, ThemeOverrides, BrandAccent, DeepPartial } from './types';
|
|
7
|
+
export { deriveAccent, contrast, luminance, AA_CONTRAST } from './colors';
|
|
8
|
+
export type { AccentRoles } from './colors';
|
|
7
9
|
export { defaultLabels } from './labels';
|
|
8
10
|
export type { Labels } from './labels';
|
|
9
11
|
export { zeroInsets } from './insets';
|
package/src/theme/palettes.ts
CHANGED
|
@@ -81,8 +81,11 @@ export const lightPalette: ThemeColors = {
|
|
|
81
81
|
successSoft: '#e3f6e9',
|
|
82
82
|
successText: '#116a30',
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
// El rojo va con texto blanco, que es lo que la gente espera de un botón
|
|
85
|
+
// destructivo. #ff3b30 no llega a 4.5:1 con blanco y obligaba a texto casi
|
|
86
|
+
// negro sobre rojo; este tono sí, y sigue leyéndose como rojo de alerta.
|
|
87
|
+
error: '#dc2626',
|
|
88
|
+
onError: '#ffffff',
|
|
86
89
|
errorSoft: '#fdeaea',
|
|
87
90
|
errorText: '#c62222',
|
|
88
91
|
|
package/src/theme/types.ts
CHANGED
|
@@ -18,7 +18,17 @@ export type DeepPartial<T> = {
|
|
|
18
18
|
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/** Acentos que se pueden definir con un solo hex vía `brand`. */
|
|
22
|
+
export type BrandAccent = 'primary' | 'secondary' | 'success' | 'error' | 'warning';
|
|
23
|
+
|
|
21
24
|
export interface ThemeOverrides {
|
|
25
|
+
/**
|
|
26
|
+
* Un hex por acento; de cada uno se derivan sus cuatro roles (relleno, lo
|
|
27
|
+
* que va encima, el fondo tenue y la variante legible sobre `surface`) para
|
|
28
|
+
* light y para dark, con el contraste ya resuelto. Un string suelto define
|
|
29
|
+
* `primary`. Lo que se pase en `colors` gana sobre lo derivado.
|
|
30
|
+
*/
|
|
31
|
+
brand?: string | Partial<Record<BrandAccent, string>>;
|
|
22
32
|
colors?: {
|
|
23
33
|
light?: Partial<ThemeColors>;
|
|
24
34
|
dark?: Partial<ThemeColors>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_createStyles","_UIProvider","_jsxRuntime","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","MODES","DEFAULT_LABELS","system","light","dark","useStyles","createStyles","theme","track","flexDirection","backgroundColor","colors","inputBackground","borderRadius","tokens","radius","md","padding","segment","flex","paddingVertical","spacing","sm","alignItems","segmentActive","surface","shadow","label","fontSize","fontWeight","medium","color","textLight","labelActive","primaryText","semibold","ThemeToggle","exports","memo","style","labels","labelOverrides","mode","setMode","useThemeMode","useColors","styles","allowFontScaling","useAllowFontScaling","jsx","View","accessibilityRole","children","map","m","active","TouchableOpacity","onPress","accessibilityState","selected","accessibilityLabel","Text","displayName"],"sourceRoot":"../../../src","sources":["molecules/ThemeToggle.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,YAAA,GAAAD,OAAA;AACA,IAAAE,aAAA,GAAAF,OAAA;AACA,IAAAG,WAAA,GAAAH,OAAA;AAAmG,IAAAI,WAAA,GAAAJ,OAAA;AAAA,SAAAD,wBAAAM,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAR,uBAAA,YAAAA,CAAAM,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAOnG,MAAMkB,KAAkB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC;AACtD,MAAMC,cAAyC,GAAG;EAAEC,MAAM,EAAE,MAAM;EAAEC,KAAK,EAAE,OAAO;EAAEC,IAAI,EAAE;AAAO,CAAC;AAElG,MAAMC,SAAS,GAAG,IAAAC,0BAAY,EAAEC,KAAK,KAAM;EACzCC,KAAK,EAAE;IACLC,aAAa,EAAE,KAAK;IACpBC,eAAe,EAAEH,KAAK,CAACI,MAAM,CAACC,eAAe;IAC7CC,YAAY,EAAEN,KAAK,CAACO,MAAM,CAACC,MAAM,CAACC,EAAE;IACpCC,OAAO,EAAE;EACX,CAAC;EACDC,OAAO,EAAE;IACPC,IAAI,EAAE,CAAC;IACPC,eAAe,EAAEb,KAAK,CAACO,MAAM,CAACO,OAAO,CAACC,EAAE;IACxCT,YAAY,EAAEN,KAAK,CAACO,MAAM,CAACC,MAAM,CAACO,EAAE;IACpCC,UAAU,EAAE;EACd,CAAC;EACDC,aAAa,EAAE;IAAEd,eAAe,EAAEH,KAAK,CAACI,MAAM,CAACc,OAAO;IAAE,GAAGlB,KAAK,CAACO,MAAM,CAACY,MAAM,CAACJ;EAAG,CAAC;EACnFK,KAAK,EAAE;IAAEC,QAAQ,EAAErB,KAAK,CAACO,MAAM,CAACc,QAAQ,CAACN,EAAE;IAAEO,UAAU,EAAEtB,KAAK,CAACO,MAAM,CAACe,UAAU,CAACC,MAAM;IAAEC,KAAK,EAAExB,KAAK,CAACI,MAAM,CAACqB;EAAU,CAAC;EACxHC,WAAW,EAAE;IAAEF,KAAK,EAAExB,KAAK,CAACI,MAAM,CAACuB,WAAW;IAAEL,UAAU,EAAEtB,KAAK,CAACO,MAAM,CAACe,UAAU,CAACM;EAAS;AAC/F,CAAC,CAAC,CAAC;;AAEH;AACA;AACA;AACA;AACA;AACA;AACO,MAAMC,WAAW,GAAAC,OAAA,CAAAD,WAAA,gBAAG,IAAAE,WAAI,EAAC,CAAC;EAAEC,KAAK;EAAEC,MAAM,EAAEC;AAAiC,CAAC,KAAK;EACvF,MAAM;IAAEC,IAAI;IAAEC;EAAQ,CAAC,GAAG,IAAAC,wBAAY,EAAC,CAAC;EACxC,MAAMjC,MAAM,GAAG,IAAAkC,qBAAS,EAAC,CAAC;EAC1B,MAAMC,MAAM,GAAGzC,SAAS,CAAC,CAAC;EAC1B,MAAM0C,gBAAgB,GAAG,IAAAC,+BAAmB,EAAC,CAAC;EAC9C,MAAMR,MAAM,GAAG;IAAE,GAAGvC,cAAc;IAAE,GAAGwC;EAAe,CAAC;EAEvD,oBACE,IAAA7D,WAAA,CAAAqE,GAAA,EAACxE,YAAA,CAAAyE,IAAI;IAACX,KAAK,EAAE,CAACO,MAAM,CAACtC,KAAK,EAAE+B,KAAK,CAAE;IAACY,iBAAiB,EAAC,SAAS;IAAAC,QAAA,EAC5DpD,KAAK,CAACqD,GAAG,CAAEC,CAAC,IAAK;MAChB,MAAMC,MAAM,GAAGb,IAAI,KAAKY,CAAC;MACzB,oBACE,IAAA1E,WAAA,CAAAqE,GAAA,EAACxE,YAAA,CAAA+E,gBAAgB;QAEfjB,KAAK,EAAE,CAACO,MAAM,CAAC5B,OAAO,EAAEqC,MAAM,IAAIT,MAAM,CAACtB,aAAa,CAAE;QACxDiC,OAAO,EAAEA,CAAA,KAAMd,OAAO,CAACW,CAAC,CAAE;QAC1BH,iBAAiB,EAAC,KAAK;QACvBO,kBAAkB,EAAE;UAAEC,QAAQ,EAAEJ;QAAO,CAAE;QACzCK,kBAAkB,EAAEpB,MAAM,CAACc,CAAC,CAAE;QAAAF,QAAA,eAE9B,IAAAxE,WAAA,CAAAqE,GAAA,EAACxE,YAAA,CAAAoF,IAAI;UAACd,gBAAgB,EAAEA,gBAAiB;UAACR,KAAK,EAAE,CAACO,MAAM,CAACnB,KAAK,EAAE4B,MAAM,IAAIT,MAAM,CAACb,WAAW,CAAE;UAAAmB,QAAA,EAC3FZ,MAAM,CAACc,CAAC;QAAC,CACN;MAAC,GATFA,CAUW,CAAC;IAEvB,CAAC;EAAC,CACE,CAAC;AAEX,CAAC,CAAC;AACFlB,WAAW,CAAC0B,WAAW,GAAG,aAAa","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["React","memo","Text","TouchableOpacity","View","createStyles","useAllowFontScaling","useColors","useThemeMode","jsx","_jsx","MODES","DEFAULT_LABELS","system","light","dark","useStyles","theme","track","flexDirection","backgroundColor","colors","inputBackground","borderRadius","tokens","radius","md","padding","segment","flex","paddingVertical","spacing","sm","alignItems","segmentActive","surface","shadow","label","fontSize","fontWeight","medium","color","textLight","labelActive","primaryText","semibold","ThemeToggle","style","labels","labelOverrides","mode","setMode","styles","allowFontScaling","accessibilityRole","children","map","m","active","onPress","accessibilityState","selected","accessibilityLabel","displayName"],"sourceRoot":"../../../src","sources":["molecules/ThemeToggle.tsx"],"mappings":";;AAAA,OAAOA,KAAK,IAAIC,IAAI,QAAQ,OAAO;AACnC,SAASC,IAAI,EAAEC,gBAAgB,EAAEC,IAAI,QAAwC,cAAc;AAC3F,SAASC,YAAY,QAAQ,uBAAuB;AACpD,SAASC,mBAAmB,EAAEC,SAAS,EAAEC,YAAY,QAAwB,qBAAqB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAOnG,MAAMC,KAAkB,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC;AACtD,MAAMC,cAAyC,GAAG;EAAEC,MAAM,EAAE,MAAM;EAAEC,KAAK,EAAE,OAAO;EAAEC,IAAI,EAAE;AAAO,CAAC;AAElG,MAAMC,SAAS,GAAGX,YAAY,CAAEY,KAAK,KAAM;EACzCC,KAAK,EAAE;IACLC,aAAa,EAAE,KAAK;IACpBC,eAAe,EAAEH,KAAK,CAACI,MAAM,CAACC,eAAe;IAC7CC,YAAY,EAAEN,KAAK,CAACO,MAAM,CAACC,MAAM,CAACC,EAAE;IACpCC,OAAO,EAAE;EACX,CAAC;EACDC,OAAO,EAAE;IACPC,IAAI,EAAE,CAAC;IACPC,eAAe,EAAEb,KAAK,CAACO,MAAM,CAACO,OAAO,CAACC,EAAE;IACxCT,YAAY,EAAEN,KAAK,CAACO,MAAM,CAACC,MAAM,CAACO,EAAE;IACpCC,UAAU,EAAE;EACd,CAAC;EACDC,aAAa,EAAE;IAAEd,eAAe,EAAEH,KAAK,CAACI,MAAM,CAACc,OAAO;IAAE,GAAGlB,KAAK,CAACO,MAAM,CAACY,MAAM,CAACJ;EAAG,CAAC;EACnFK,KAAK,EAAE;IAAEC,QAAQ,EAAErB,KAAK,CAACO,MAAM,CAACc,QAAQ,CAACN,EAAE;IAAEO,UAAU,EAAEtB,KAAK,CAACO,MAAM,CAACe,UAAU,CAACC,MAAM;IAAEC,KAAK,EAAExB,KAAK,CAACI,MAAM,CAACqB;EAAU,CAAC;EACxHC,WAAW,EAAE;IAAEF,KAAK,EAAExB,KAAK,CAACI,MAAM,CAACuB,WAAW;IAAEL,UAAU,EAAEtB,KAAK,CAACO,MAAM,CAACe,UAAU,CAACM;EAAS;AAC/F,CAAC,CAAC,CAAC;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,WAAW,gBAAG7C,IAAI,CAAC,CAAC;EAAE8C,KAAK;EAAEC,MAAM,EAAEC;AAAiC,CAAC,KAAK;EACvF,MAAM;IAAEC,IAAI;IAAEC;EAAQ,CAAC,GAAG3C,YAAY,CAAC,CAAC;EACxC,MAAMa,MAAM,GAAGd,SAAS,CAAC,CAAC;EAC1B,MAAM6C,MAAM,GAAGpC,SAAS,CAAC,CAAC;EAC1B,MAAMqC,gBAAgB,GAAG/C,mBAAmB,CAAC,CAAC;EAC9C,MAAM0C,MAAM,GAAG;IAAE,GAAGpC,cAAc;IAAE,GAAGqC;EAAe,CAAC;EAEvD,oBACEvC,IAAA,CAACN,IAAI;IAAC2C,KAAK,EAAE,CAACK,MAAM,CAAClC,KAAK,EAAE6B,KAAK,CAAE;IAACO,iBAAiB,EAAC,SAAS;IAAAC,QAAA,EAC5D5C,KAAK,CAAC6C,GAAG,CAAEC,CAAC,IAAK;MAChB,MAAMC,MAAM,GAAGR,IAAI,KAAKO,CAAC;MACzB,oBACE/C,IAAA,CAACP,gBAAgB;QAEf4C,KAAK,EAAE,CAACK,MAAM,CAACxB,OAAO,EAAE8B,MAAM,IAAIN,MAAM,CAAClB,aAAa,CAAE;QACxDyB,OAAO,EAAEA,CAAA,KAAMR,OAAO,CAACM,CAAC,CAAE;QAC1BH,iBAAiB,EAAC,KAAK;QACvBM,kBAAkB,EAAE;UAAEC,QAAQ,EAAEH;QAAO,CAAE;QACzCI,kBAAkB,EAAEd,MAAM,CAACS,CAAC,CAAE;QAAAF,QAAA,eAE9B7C,IAAA,CAACR,IAAI;UAACmD,gBAAgB,EAAEA,gBAAiB;UAACN,KAAK,EAAE,CAACK,MAAM,CAACf,KAAK,EAAEqB,MAAM,IAAIN,MAAM,CAACT,WAAW,CAAE;UAAAY,QAAA,EAC3FP,MAAM,CAACS,CAAC;QAAC,CACN;MAAC,GATFA,CAUW,CAAC;IAEvB,CAAC;EAAC,CACE,CAAC;AAEX,CAAC,CAAC;AACFX,WAAW,CAACiB,WAAW,GAAG,aAAa","ignoreList":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../../src/molecules/ThemeToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAe,MAAM,OAAO,CAAC;AACpC,OAAO,EAAgC,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5F,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;CAC7C;AAuBD;;;;;GAKG;AACH,eAAO,MAAM,WAAW,gEAA4C,gBAAgB,uBA4BlF,CAAC"}
|
|
@@ -1,67 +0,0 @@
|
|
|
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, useColors, useThemeMode, type ThemeMode } from '../theme/UIProvider';
|
|
5
|
-
|
|
6
|
-
export interface ThemeToggleProps {
|
|
7
|
-
style?: StyleProp<ViewStyle>;
|
|
8
|
-
labels?: Partial<Record<ThemeMode, string>>;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const MODES: ThemeMode[] = ['system', 'light', 'dark'];
|
|
12
|
-
const DEFAULT_LABELS: Record<ThemeMode, string> = { system: 'Auto', light: 'Light', dark: 'Dark' };
|
|
13
|
-
|
|
14
|
-
const useStyles = createStyles((theme) => ({
|
|
15
|
-
track: {
|
|
16
|
-
flexDirection: 'row',
|
|
17
|
-
backgroundColor: theme.colors.inputBackground,
|
|
18
|
-
borderRadius: theme.tokens.radius.md,
|
|
19
|
-
padding: 3,
|
|
20
|
-
},
|
|
21
|
-
segment: {
|
|
22
|
-
flex: 1,
|
|
23
|
-
paddingVertical: theme.tokens.spacing.sm,
|
|
24
|
-
borderRadius: theme.tokens.radius.sm,
|
|
25
|
-
alignItems: 'center',
|
|
26
|
-
},
|
|
27
|
-
segmentActive: { backgroundColor: theme.colors.surface, ...theme.tokens.shadow.sm },
|
|
28
|
-
label: { fontSize: theme.tokens.fontSize.sm, fontWeight: theme.tokens.fontWeight.medium, color: theme.colors.textLight },
|
|
29
|
-
labelActive: { color: theme.colors.primaryText, fontWeight: theme.tokens.fontWeight.semibold },
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* ThemeToggle
|
|
34
|
-
* Segmented control de 3 estados (system/light/dark) conectado a
|
|
35
|
-
* UIProvider.setMode. Sin persistencia — el consumidor la resuelve vía
|
|
36
|
-
* onModeChange en <UIProvider>.
|
|
37
|
-
*/
|
|
38
|
-
export const ThemeToggle = memo(({ style, labels: labelOverrides }: ThemeToggleProps) => {
|
|
39
|
-
const { mode, setMode } = useThemeMode();
|
|
40
|
-
const colors = useColors();
|
|
41
|
-
const styles = useStyles();
|
|
42
|
-
const allowFontScaling = useAllowFontScaling();
|
|
43
|
-
const labels = { ...DEFAULT_LABELS, ...labelOverrides };
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<View style={[styles.track, style]} accessibilityRole="tablist">
|
|
47
|
-
{MODES.map((m) => {
|
|
48
|
-
const active = mode === m;
|
|
49
|
-
return (
|
|
50
|
-
<TouchableOpacity
|
|
51
|
-
key={m}
|
|
52
|
-
style={[styles.segment, active && styles.segmentActive]}
|
|
53
|
-
onPress={() => setMode(m)}
|
|
54
|
-
accessibilityRole="tab"
|
|
55
|
-
accessibilityState={{ selected: active }}
|
|
56
|
-
accessibilityLabel={labels[m]}
|
|
57
|
-
>
|
|
58
|
-
<Text allowFontScaling={allowFontScaling} style={[styles.label, active && styles.labelActive]}>
|
|
59
|
-
{labels[m]}
|
|
60
|
-
</Text>
|
|
61
|
-
</TouchableOpacity>
|
|
62
|
-
);
|
|
63
|
-
})}
|
|
64
|
-
</View>
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
ThemeToggle.displayName = 'ThemeToggle';
|