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.
Files changed (211) hide show
  1. package/README.md +9 -2
  2. package/lib/commonjs/atoms/PressableScale.js +9 -10
  3. package/lib/commonjs/atoms/PressableScale.js.map +1 -1
  4. package/lib/commonjs/atoms/Skeleton.js +96 -0
  5. package/lib/commonjs/atoms/Skeleton.js.map +1 -0
  6. package/lib/commonjs/atoms/Transition.js +171 -0
  7. package/lib/commonjs/atoms/Transition.js.map +1 -0
  8. package/lib/commonjs/atoms/index.js +20 -0
  9. package/lib/commonjs/atoms/index.js.map +1 -1
  10. package/lib/commonjs/molecules/OptionCard.js +55 -22
  11. package/lib/commonjs/molecules/OptionCard.js.map +1 -1
  12. package/lib/commonjs/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
  13. package/lib/commonjs/molecules/SegmentedControl.js.map +1 -0
  14. package/lib/commonjs/molecules/Stepper.js +124 -47
  15. package/lib/commonjs/molecules/Stepper.js.map +1 -1
  16. package/lib/commonjs/molecules/index.js +9 -3
  17. package/lib/commonjs/molecules/index.js.map +1 -1
  18. package/lib/commonjs/organisms/AlertProvider.js +26 -0
  19. package/lib/commonjs/organisms/AlertProvider.js.map +1 -1
  20. package/lib/commonjs/organisms/BottomSheet.js +118 -39
  21. package/lib/commonjs/organisms/BottomSheet.js.map +1 -1
  22. package/lib/commonjs/organisms/List.js +66 -44
  23. package/lib/commonjs/organisms/List.js.map +1 -1
  24. package/lib/commonjs/organisms/Modal.js +42 -87
  25. package/lib/commonjs/organisms/Modal.js.map +1 -1
  26. package/lib/commonjs/organisms/NavigationBar.js +205 -0
  27. package/lib/commonjs/organisms/NavigationBar.js.map +1 -0
  28. package/lib/commonjs/organisms/Screen.js +9 -2
  29. package/lib/commonjs/organisms/Screen.js.map +1 -1
  30. package/lib/commonjs/organisms/SearchList.js +97 -28
  31. package/lib/commonjs/organisms/SearchList.js.map +1 -1
  32. package/lib/commonjs/organisms/Select.js +222 -50
  33. package/lib/commonjs/organisms/Select.js.map +1 -1
  34. package/lib/commonjs/organisms/ThemeToggle.js +52 -0
  35. package/lib/commonjs/organisms/ThemeToggle.js.map +1 -0
  36. package/lib/commonjs/organisms/Toast.js +8 -22
  37. package/lib/commonjs/organisms/Toast.js.map +1 -1
  38. package/lib/commonjs/organisms/ToastProvider.js +24 -0
  39. package/lib/commonjs/organisms/ToastProvider.js.map +1 -1
  40. package/lib/commonjs/organisms/Wizard.js +22 -2
  41. package/lib/commonjs/organisms/Wizard.js.map +1 -1
  42. package/lib/commonjs/organisms/imperativeBridge.js +28 -0
  43. package/lib/commonjs/organisms/imperativeBridge.js.map +1 -0
  44. package/lib/commonjs/organisms/index.js +50 -0
  45. package/lib/commonjs/organisms/index.js.map +1 -1
  46. package/lib/commonjs/theme/colors.js +207 -0
  47. package/lib/commonjs/theme/colors.js.map +1 -0
  48. package/lib/commonjs/theme/createTheme.js +25 -2
  49. package/lib/commonjs/theme/createTheme.js.map +1 -1
  50. package/lib/commonjs/theme/index.js +25 -0
  51. package/lib/commonjs/theme/index.js.map +1 -1
  52. package/lib/commonjs/theme/palettes.js +5 -2
  53. package/lib/commonjs/theme/palettes.js.map +1 -1
  54. package/lib/module/atoms/PressableScale.js +9 -10
  55. package/lib/module/atoms/PressableScale.js.map +1 -1
  56. package/lib/module/atoms/Skeleton.js +91 -0
  57. package/lib/module/atoms/Skeleton.js.map +1 -0
  58. package/lib/module/atoms/Transition.js +165 -0
  59. package/lib/module/atoms/Transition.js.map +1 -0
  60. package/lib/module/atoms/index.js +2 -0
  61. package/lib/module/atoms/index.js.map +1 -1
  62. package/lib/module/molecules/OptionCard.js +56 -23
  63. package/lib/module/molecules/OptionCard.js.map +1 -1
  64. package/lib/module/molecules/{ThemeToggle.js → SegmentedControl.js} +36 -36
  65. package/lib/module/molecules/SegmentedControl.js.map +1 -0
  66. package/lib/module/molecules/Stepper.js +125 -49
  67. package/lib/module/molecules/Stepper.js.map +1 -1
  68. package/lib/module/molecules/index.js +2 -2
  69. package/lib/module/molecules/index.js.map +1 -1
  70. package/lib/module/organisms/AlertProvider.js +24 -1
  71. package/lib/module/organisms/AlertProvider.js.map +1 -1
  72. package/lib/module/organisms/BottomSheet.js +120 -41
  73. package/lib/module/organisms/BottomSheet.js.map +1 -1
  74. package/lib/module/organisms/List.js +68 -46
  75. package/lib/module/organisms/List.js.map +1 -1
  76. package/lib/module/organisms/Modal.js +44 -89
  77. package/lib/module/organisms/Modal.js.map +1 -1
  78. package/lib/module/organisms/NavigationBar.js +200 -0
  79. package/lib/module/organisms/NavigationBar.js.map +1 -0
  80. package/lib/module/organisms/Screen.js +9 -2
  81. package/lib/module/organisms/Screen.js.map +1 -1
  82. package/lib/module/organisms/SearchList.js +98 -29
  83. package/lib/module/organisms/SearchList.js.map +1 -1
  84. package/lib/module/organisms/Select.js +224 -52
  85. package/lib/module/organisms/Select.js.map +1 -1
  86. package/lib/module/organisms/ThemeToggle.js +47 -0
  87. package/lib/module/organisms/ThemeToggle.js.map +1 -0
  88. package/lib/module/organisms/Toast.js +10 -24
  89. package/lib/module/organisms/Toast.js.map +1 -1
  90. package/lib/module/organisms/ToastProvider.js +21 -0
  91. package/lib/module/organisms/ToastProvider.js.map +1 -1
  92. package/lib/module/organisms/Wizard.js +23 -3
  93. package/lib/module/organisms/Wizard.js.map +1 -1
  94. package/lib/module/organisms/imperativeBridge.js +24 -0
  95. package/lib/module/organisms/imperativeBridge.js.map +1 -0
  96. package/lib/module/organisms/index.js +4 -2
  97. package/lib/module/organisms/index.js.map +1 -1
  98. package/lib/module/theme/colors.js +194 -0
  99. package/lib/module/theme/colors.js.map +1 -0
  100. package/lib/module/theme/createTheme.js +25 -2
  101. package/lib/module/theme/createTheme.js.map +1 -1
  102. package/lib/module/theme/index.js +1 -0
  103. package/lib/module/theme/index.js.map +1 -1
  104. package/lib/module/theme/palettes.js +5 -2
  105. package/lib/module/theme/palettes.js.map +1 -1
  106. package/lib/typescript/atoms/PressableScale.d.ts +3 -2
  107. package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
  108. package/lib/typescript/atoms/Skeleton.d.ts +29 -0
  109. package/lib/typescript/atoms/Skeleton.d.ts.map +1 -0
  110. package/lib/typescript/atoms/Transition.d.ts +51 -0
  111. package/lib/typescript/atoms/Transition.d.ts.map +1 -0
  112. package/lib/typescript/atoms/index.d.ts +4 -0
  113. package/lib/typescript/atoms/index.d.ts.map +1 -1
  114. package/lib/typescript/molecules/OptionCard.d.ts +11 -6
  115. package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
  116. package/lib/typescript/molecules/SegmentedControl.d.ts +24 -0
  117. package/lib/typescript/molecules/SegmentedControl.d.ts.map +1 -0
  118. package/lib/typescript/molecules/Stepper.d.ts +35 -3
  119. package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
  120. package/lib/typescript/molecules/index.d.ts +4 -4
  121. package/lib/typescript/molecules/index.d.ts.map +1 -1
  122. package/lib/typescript/organisms/AlertProvider.d.ts +9 -0
  123. package/lib/typescript/organisms/AlertProvider.d.ts.map +1 -1
  124. package/lib/typescript/organisms/BottomSheet.d.ts +26 -4
  125. package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
  126. package/lib/typescript/organisms/List.d.ts +9 -2
  127. package/lib/typescript/organisms/List.d.ts.map +1 -1
  128. package/lib/typescript/organisms/Modal.d.ts +3 -5
  129. package/lib/typescript/organisms/Modal.d.ts.map +1 -1
  130. package/lib/typescript/organisms/NavigationBar.d.ts +32 -0
  131. package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -0
  132. package/lib/typescript/organisms/Screen.d.ts +2 -1
  133. package/lib/typescript/organisms/Screen.d.ts.map +1 -1
  134. package/lib/typescript/organisms/SearchList.d.ts +21 -5
  135. package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
  136. package/lib/typescript/organisms/Select.d.ts +19 -4
  137. package/lib/typescript/organisms/Select.d.ts.map +1 -1
  138. package/lib/typescript/{molecules → organisms}/ThemeToggle.d.ts +4 -4
  139. package/lib/typescript/organisms/ThemeToggle.d.ts.map +1 -0
  140. package/lib/typescript/organisms/Toast.d.ts.map +1 -1
  141. package/lib/typescript/organisms/ToastProvider.d.ts +8 -0
  142. package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
  143. package/lib/typescript/organisms/Wizard.d.ts +7 -1
  144. package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
  145. package/lib/typescript/organisms/imperativeBridge.d.ts +12 -0
  146. package/lib/typescript/organisms/imperativeBridge.d.ts.map +1 -0
  147. package/lib/typescript/organisms/index.d.ts +7 -3
  148. package/lib/typescript/organisms/index.d.ts.map +1 -1
  149. package/lib/typescript/theme/colors.d.ts +44 -0
  150. package/lib/typescript/theme/colors.d.ts.map +1 -0
  151. package/lib/typescript/theme/createTheme.d.ts +3 -0
  152. package/lib/typescript/theme/createTheme.d.ts.map +1 -1
  153. package/lib/typescript/theme/index.d.ts +3 -1
  154. package/lib/typescript/theme/index.d.ts.map +1 -1
  155. package/lib/typescript/theme/palettes.d.ts.map +1 -1
  156. package/lib/typescript/theme/types.d.ts +9 -0
  157. package/lib/typescript/theme/types.d.ts.map +1 -1
  158. package/package.json +67 -6
  159. package/registry/alert-provider.json +9 -2
  160. package/registry/bottom-sheet.json +6 -2
  161. package/registry/core.json +8 -4
  162. package/registry/date-field.json +1 -0
  163. package/registry/index.json +96 -27
  164. package/registry/list.json +2 -1
  165. package/registry/manifest.json +44 -6
  166. package/registry/modal.json +3 -2
  167. package/registry/navigation-bar.json +18 -0
  168. package/registry/option-card.json +3 -2
  169. package/registry/pressable-scale.json +1 -1
  170. package/registry/screen.json +1 -1
  171. package/registry/search-list.json +2 -1
  172. package/registry/segmented-control.json +18 -0
  173. package/registry/select.json +1 -1
  174. package/registry/skeleton.json +18 -0
  175. package/registry/stepper.json +3 -2
  176. package/registry/theme-toggle.json +5 -4
  177. package/registry/toast-provider.json +11 -3
  178. package/registry/toast.json +3 -2
  179. package/registry/transition.json +19 -0
  180. package/registry/wizard.json +3 -2
  181. package/src/atoms/PressableScale.tsx +16 -6
  182. package/src/atoms/Skeleton.tsx +114 -0
  183. package/src/atoms/Transition.tsx +189 -0
  184. package/src/atoms/index.ts +4 -0
  185. package/src/molecules/OptionCard.tsx +62 -25
  186. package/src/molecules/SegmentedControl.tsx +84 -0
  187. package/src/molecules/Stepper.tsx +147 -32
  188. package/src/molecules/index.ts +4 -4
  189. package/src/organisms/AlertProvider.tsx +26 -1
  190. package/src/organisms/BottomSheet.tsx +214 -62
  191. package/src/organisms/List.tsx +73 -41
  192. package/src/organisms/Modal.tsx +49 -61
  193. package/src/organisms/NavigationBar.tsx +223 -0
  194. package/src/organisms/Screen.tsx +11 -3
  195. package/src/organisms/SearchList.tsx +132 -36
  196. package/src/organisms/Select.tsx +223 -45
  197. package/src/organisms/ThemeToggle.tsx +30 -0
  198. package/src/organisms/Toast.tsx +8 -21
  199. package/src/organisms/ToastProvider.tsx +24 -0
  200. package/src/organisms/Wizard.tsx +36 -3
  201. package/src/organisms/imperativeBridge.ts +28 -0
  202. package/src/organisms/index.ts +7 -3
  203. package/src/theme/colors.ts +175 -0
  204. package/src/theme/createTheme.ts +38 -4
  205. package/src/theme/index.ts +3 -1
  206. package/src/theme/palettes.ts +5 -2
  207. package/src/theme/types.ts +10 -0
  208. package/lib/commonjs/molecules/ThemeToggle.js.map +0 -1
  209. package/lib/module/molecules/ThemeToggle.js.map +0 -1
  210. package/lib/typescript/molecules/ThemeToggle.d.ts.map +0 -1
  211. 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 }
@@ -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
- {activeStep?.content}
151
+ {renderContent()}
119
152
  </ScrollView>
120
153
  ) : (
121
- <View style={styles.content}>{activeStep?.content}</View>
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
+ }
@@ -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
+ }
@@ -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 type { Theme, ThemeOverrides, ThemePair } from './types';
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(lightPalette, overrides?.colors?.light),
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(darkPalette, overrides?.colors?.dark),
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
  };
@@ -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';
@@ -81,8 +81,11 @@ export const lightPalette: ThemeColors = {
81
81
  successSoft: '#e3f6e9',
82
82
  successText: '#116a30',
83
83
 
84
- error: '#ff3b30',
85
- onError: '#3d0300',
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
 
@@ -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":"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","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":"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","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';