orn-ui 0.2.6 → 0.2.8

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 (146) hide show
  1. package/AGENTS.md +372 -0
  2. package/README.md +46 -15
  3. package/lib/commonjs/atoms/Gradient.js +111 -0
  4. package/lib/commonjs/atoms/Gradient.js.map +1 -0
  5. package/lib/commonjs/atoms/index.js +7 -0
  6. package/lib/commonjs/atoms/index.js.map +1 -1
  7. package/lib/commonjs/organisms/Slides.js +406 -0
  8. package/lib/commonjs/organisms/Slides.js.map +1 -0
  9. package/lib/commonjs/organisms/index.js +7 -0
  10. package/lib/commonjs/organisms/index.js.map +1 -1
  11. package/lib/commonjs/organisms/useSlides.js +186 -0
  12. package/lib/commonjs/organisms/useSlides.js.map +1 -0
  13. package/lib/module/atoms/Gradient.js +106 -0
  14. package/lib/module/atoms/Gradient.js.map +1 -0
  15. package/lib/module/atoms/index.js +1 -0
  16. package/lib/module/atoms/index.js.map +1 -1
  17. package/lib/module/organisms/Slides.js +401 -0
  18. package/lib/module/organisms/Slides.js.map +1 -0
  19. package/lib/module/organisms/index.js +1 -0
  20. package/lib/module/organisms/index.js.map +1 -1
  21. package/lib/module/organisms/useSlides.js +182 -0
  22. package/lib/module/organisms/useSlides.js.map +1 -0
  23. package/lib/typescript/atoms/Badge.d.ts +1 -1
  24. package/lib/typescript/atoms/Badge.d.ts.map +1 -1
  25. package/lib/typescript/atoms/Gradient.d.ts +37 -0
  26. package/lib/typescript/atoms/Gradient.d.ts.map +1 -0
  27. package/lib/typescript/atoms/Image.d.ts +6 -6
  28. package/lib/typescript/atoms/Image.d.ts.map +1 -1
  29. package/lib/typescript/atoms/Input.d.ts +1 -1
  30. package/lib/typescript/atoms/Input.d.ts.map +1 -1
  31. package/lib/typescript/atoms/PressableScale.d.ts +1 -1
  32. package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
  33. package/lib/typescript/atoms/Skeleton.d.ts +5 -5
  34. package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
  35. package/lib/typescript/atoms/Spinner.d.ts +4 -4
  36. package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
  37. package/lib/typescript/atoms/Transition.d.ts +10 -10
  38. package/lib/typescript/atoms/Transition.d.ts.map +1 -1
  39. package/lib/typescript/atoms/index.d.ts +2 -0
  40. package/lib/typescript/atoms/index.d.ts.map +1 -1
  41. package/lib/typescript/molecules/AvatarHeader.d.ts +2 -2
  42. package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
  43. package/lib/typescript/molecules/FormActions.d.ts +1 -1
  44. package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
  45. package/lib/typescript/molecules/OptionCard.d.ts +2 -2
  46. package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
  47. package/lib/typescript/molecules/Stepper.d.ts +8 -8
  48. package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
  49. package/lib/typescript/molecules/Steps.d.ts +3 -3
  50. package/lib/typescript/molecules/Steps.d.ts.map +1 -1
  51. package/lib/typescript/molecules/SymmetricGrid.d.ts +4 -4
  52. package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
  53. package/lib/typescript/organisms/BottomSheet.d.ts +8 -8
  54. package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
  55. package/lib/typescript/organisms/DateField.d.ts +10 -10
  56. package/lib/typescript/organisms/DateField.d.ts.map +1 -1
  57. package/lib/typescript/organisms/DatePicker.d.ts +10 -10
  58. package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
  59. package/lib/typescript/organisms/List.d.ts +6 -6
  60. package/lib/typescript/organisms/List.d.ts.map +1 -1
  61. package/lib/typescript/organisms/NavigationBar.d.ts +2 -2
  62. package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
  63. package/lib/typescript/organisms/ReorderableList.d.ts +7 -7
  64. package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
  65. package/lib/typescript/organisms/Screen.d.ts +6 -6
  66. package/lib/typescript/organisms/SearchList.d.ts +8 -8
  67. package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
  68. package/lib/typescript/organisms/Select.d.ts +2 -2
  69. package/lib/typescript/organisms/Select.d.ts.map +1 -1
  70. package/lib/typescript/organisms/Slides.d.ts +69 -0
  71. package/lib/typescript/organisms/Slides.d.ts.map +1 -0
  72. package/lib/typescript/organisms/Toast.d.ts +2 -2
  73. package/lib/typescript/organisms/Toast.d.ts.map +1 -1
  74. package/lib/typescript/organisms/ToastProvider.d.ts +2 -2
  75. package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
  76. package/lib/typescript/organisms/Wizard.d.ts +7 -7
  77. package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
  78. package/lib/typescript/organisms/index.d.ts +2 -0
  79. package/lib/typescript/organisms/index.d.ts.map +1 -1
  80. package/lib/typescript/organisms/useSlides.d.ts +52 -0
  81. package/lib/typescript/organisms/useSlides.d.ts.map +1 -0
  82. package/lib/typescript/theme/UIProvider.d.ts +9 -9
  83. package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
  84. package/llms.txt +34 -0
  85. package/package.json +34 -6
  86. package/registry/avatar-header.json +1 -1
  87. package/registry/badge.json +1 -1
  88. package/registry/bottom-sheet.json +1 -1
  89. package/registry/core.json +1 -1
  90. package/registry/date-field.json +1 -1
  91. package/registry/date-picker.json +1 -1
  92. package/registry/form-actions.json +1 -1
  93. package/registry/gradient.json +18 -0
  94. package/registry/image.json +1 -1
  95. package/registry/index.json +52 -26
  96. package/registry/input.json +1 -1
  97. package/registry/list.json +1 -1
  98. package/registry/manifest.json +14 -0
  99. package/registry/navigation-bar.json +1 -1
  100. package/registry/option-card.json +1 -1
  101. package/registry/pressable-scale.json +1 -1
  102. package/registry/reorderable-list.json +1 -1
  103. package/registry/screen.json +1 -1
  104. package/registry/search-list.json +1 -1
  105. package/registry/select.json +1 -1
  106. package/registry/skeleton.json +1 -1
  107. package/registry/slides.json +24 -0
  108. package/registry/spinner.json +1 -1
  109. package/registry/stepper.json +1 -1
  110. package/registry/steps.json +1 -1
  111. package/registry/symmetric-grid.json +1 -1
  112. package/registry/toast-provider.json +1 -1
  113. package/registry/toast.json +1 -1
  114. package/registry/transition.json +1 -1
  115. package/registry/wizard.json +1 -1
  116. package/src/atoms/Badge.tsx +1 -1
  117. package/src/atoms/Gradient.tsx +99 -0
  118. package/src/atoms/Image.tsx +6 -6
  119. package/src/atoms/Input.tsx +1 -1
  120. package/src/atoms/PressableScale.tsx +1 -1
  121. package/src/atoms/Skeleton.tsx +5 -5
  122. package/src/atoms/Spinner.tsx +4 -4
  123. package/src/atoms/Transition.tsx +10 -10
  124. package/src/atoms/index.ts +2 -0
  125. package/src/molecules/AvatarHeader.tsx +2 -2
  126. package/src/molecules/FormActions.tsx +1 -1
  127. package/src/molecules/OptionCard.tsx +2 -2
  128. package/src/molecules/Stepper.tsx +8 -8
  129. package/src/molecules/Steps.tsx +3 -3
  130. package/src/molecules/SymmetricGrid.tsx +4 -4
  131. package/src/organisms/BottomSheet.tsx +8 -8
  132. package/src/organisms/DateField.tsx +10 -10
  133. package/src/organisms/DatePicker.tsx +10 -10
  134. package/src/organisms/List.tsx +6 -6
  135. package/src/organisms/NavigationBar.tsx +2 -2
  136. package/src/organisms/ReorderableList.tsx +7 -7
  137. package/src/organisms/Screen.tsx +6 -6
  138. package/src/organisms/SearchList.tsx +8 -8
  139. package/src/organisms/Select.tsx +2 -2
  140. package/src/organisms/Slides.tsx +413 -0
  141. package/src/organisms/Toast.tsx +2 -2
  142. package/src/organisms/ToastProvider.tsx +2 -2
  143. package/src/organisms/Wizard.tsx +7 -7
  144. package/src/organisms/index.ts +8 -0
  145. package/src/organisms/useSlides.ts +220 -0
  146. package/src/theme/UIProvider.tsx +9 -9
@@ -0,0 +1,220 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import type { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView } from 'react-native';
3
+ import { useReduceMotion } from '../atoms/Transition';
4
+
5
+ export type SlidesOrientation = 'horizontal' | 'vertical';
6
+
7
+ export interface UseSlidesOptions {
8
+ total: number;
9
+ orientation: SlidesOrientation;
10
+ loop: boolean;
11
+ autoPlay: boolean;
12
+ interval: number;
13
+ /** Índice controlado; sin él manda el estado interno. */
14
+ index?: number;
15
+ defaultIndex: number;
16
+ onIndexChange?: (index: number) => void;
17
+ }
18
+
19
+ /** Una página del scroll. Los clones del modo infinito repiten el `real` de un vecino. */
20
+ export interface SlidesPage {
21
+ real: number;
22
+ clone: 'head' | 'tail' | null;
23
+ }
24
+
25
+ export interface UseSlidesResult {
26
+ active: number;
27
+ pages: SlidesPage[];
28
+ isHorizontal: boolean;
29
+ isLoop: boolean;
30
+ size: { width: number; height: number };
31
+ /** Largo de una página sobre el eje del scroll. En 0 todavía no se midió. */
32
+ pageSize: number;
33
+ scrollRef: React.RefObject<ScrollView | null>;
34
+ autoPlayPaused: boolean;
35
+ /** El sistema pidió reducir movimiento: no hay autoPlay ni transiciones. */
36
+ motionReduced: boolean;
37
+ goTo: (index: number) => void;
38
+ toggleAutoPlay: () => void;
39
+ onLayout: (event: LayoutChangeEvent) => void;
40
+ onMomentumScrollEnd: (event: NativeSyntheticEvent<NativeScrollEvent>) => void;
41
+ onScrollBeginDrag: () => void;
42
+ onScrollEndDrag: () => void;
43
+ }
44
+
45
+ export function clampIndex(value: number, total: number): number {
46
+ if (total === 0) return 0;
47
+ if (value < 0) return 0;
48
+ if (value > total - 1) return total - 1;
49
+ return value;
50
+ }
51
+
52
+ /**
53
+ * useSlides
54
+ * Todo lo que en `Slides` no se ve: qué página está encuadrada, cuándo mover el
55
+ * scroll, cómo se mapean los clones del modo infinito a índices reales, y el
56
+ * reloj del avance automático.
57
+ *
58
+ * Trabaja sólo con índices, nunca con los ítems: así no necesita el genérico
59
+ * `T` del componente, y el UI resuelve `pages[i].real` contra su propio `data`.
60
+ */
61
+ export function useSlides({
62
+ total,
63
+ orientation,
64
+ loop,
65
+ autoPlay,
66
+ interval,
67
+ index,
68
+ defaultIndex,
69
+ onIndexChange,
70
+ }: UseSlidesOptions): UseSlidesResult {
71
+ const motionReduced = useReduceMotion();
72
+ const isHorizontal = orientation === 'horizontal';
73
+ // Un solo elemento no tiene a dónde ciclar: quedaría deslizable entre tres
74
+ // copias de lo mismo.
75
+ const isLoop = loop && total > 1;
76
+
77
+ const [uncontrolled, setUncontrolled] = useState(() => clampIndex(defaultIndex, total));
78
+ const active = clampIndex(index ?? uncontrolled, total);
79
+ const activeRef = useRef(active);
80
+ activeRef.current = active;
81
+
82
+ const [autoPlayPaused, setAutoPlayPaused] = useState(false);
83
+ const [size, setSize] = useState({ width: 0, height: 0 });
84
+ const pageSize = isHorizontal ? size.width : size.height;
85
+
86
+ const scrollRef = useRef<ScrollView>(null);
87
+ /** Página realmente visible (índice dentro de `pages`, clones incluidos). */
88
+ const offsetPageRef = useRef(isLoop ? 1 : 0);
89
+ const didInitRef = useRef(false);
90
+ const lastPageSizeRef = useRef(0);
91
+ const draggingRef = useRef(false);
92
+
93
+ const pages = useMemo<SlidesPage[]>(() => {
94
+ const real = Array.from({ length: total }, (_, i) => ({ real: i, clone: null }));
95
+ if (!isLoop) return real;
96
+ return [{ real: total - 1, clone: 'head' }, ...real, { real: 0, clone: 'tail' }];
97
+ }, [isLoop, total]);
98
+
99
+ const scrollToPage = useCallback(
100
+ (page: number, animated: boolean) => {
101
+ const offset = page * pageSize;
102
+ scrollRef.current?.scrollTo({
103
+ x: isHorizontal ? offset : 0,
104
+ y: isHorizontal ? 0 : offset,
105
+ animated,
106
+ });
107
+ },
108
+ [isHorizontal, pageSize]
109
+ );
110
+
111
+ const goTo = useCallback(
112
+ (next: number) => {
113
+ if (next === activeRef.current) return;
114
+ activeRef.current = next;
115
+ if (index == null) setUncontrolled(next);
116
+ onIndexChange?.(next);
117
+ },
118
+ [index, onIndexChange]
119
+ );
120
+
121
+ // Sólo actúa cuando el índice y la página visible discrepan: un cambio que
122
+ // vino del propio gesto no dispara un segundo scroll encima del que frenó.
123
+ useEffect(() => {
124
+ if (pageSize === 0 || total === 0) return;
125
+ const target = isLoop ? active + 1 : active;
126
+ const resized = lastPageSizeRef.current !== pageSize;
127
+ if (offsetPageRef.current === target && didInitRef.current && !resized) return;
128
+ // Ni el primer posicionamiento ni el reacomodo por rotación son un cambio
129
+ // de slide: animarlos sería un deslizamiento que nadie pidió.
130
+ const animated = didInitRef.current && !resized;
131
+ didInitRef.current = true;
132
+ lastPageSizeRef.current = pageSize;
133
+ offsetPageRef.current = target;
134
+ scrollToPage(target, animated);
135
+ }, [active, isLoop, pageSize, scrollToPage, total]);
136
+
137
+ const onLayout = useCallback((event: LayoutChangeEvent) => {
138
+ const { width, height } = event.nativeEvent.layout;
139
+ setSize((prev) => (prev.width === width && prev.height === height ? prev : { width, height }));
140
+ }, []);
141
+
142
+ const onMomentumScrollEnd = useCallback(
143
+ (event: NativeSyntheticEvent<NativeScrollEvent>) => {
144
+ draggingRef.current = false;
145
+ if (pageSize === 0) return;
146
+ const { contentOffset } = event.nativeEvent;
147
+ const page = Math.round((isHorizontal ? contentOffset.x : contentOffset.y) / pageSize);
148
+
149
+ if (isLoop) {
150
+ if (page === 0) {
151
+ offsetPageRef.current = total;
152
+ scrollToPage(total, false);
153
+ goTo(total - 1);
154
+ return;
155
+ }
156
+ if (page === total + 1) {
157
+ offsetPageRef.current = 1;
158
+ scrollToPage(1, false);
159
+ goTo(0);
160
+ return;
161
+ }
162
+ offsetPageRef.current = page;
163
+ goTo(page - 1);
164
+ return;
165
+ }
166
+
167
+ offsetPageRef.current = page;
168
+ goTo(clampIndex(page, total));
169
+ },
170
+ [goTo, isHorizontal, isLoop, pageSize, scrollToPage, total]
171
+ );
172
+
173
+ const onScrollBeginDrag = useCallback(() => {
174
+ draggingRef.current = true;
175
+ }, []);
176
+
177
+ const onScrollEndDrag = useCallback(() => {
178
+ // Un arrastre corto puede soltarse sin inercia y no emitir
179
+ // momentumScrollEnd: sin esto el autoPlay quedaría pausado para siempre.
180
+ draggingRef.current = false;
181
+ }, []);
182
+
183
+ const toggleAutoPlay = useCallback(() => setAutoPlayPaused((paused) => !paused), []);
184
+
185
+ useEffect(() => {
186
+ if (!autoPlay || autoPlayPaused || motionReduced || total < 2 || pageSize === 0) return;
187
+ const timer = setInterval(() => {
188
+ if (draggingRef.current) return;
189
+ const from = activeRef.current;
190
+ if (from < total - 1) {
191
+ goTo(from + 1);
192
+ return;
193
+ }
194
+ if (!isLoop) return;
195
+ // Última en modo infinito: avanza al clon de la primera en vez de
196
+ // rebobinar; el salto al original lo cierra momentumScrollEnd.
197
+ offsetPageRef.current = total + 1;
198
+ scrollToPage(total + 1, true);
199
+ }, interval);
200
+ return () => clearInterval(timer);
201
+ }, [autoPlay, autoPlayPaused, goTo, interval, isLoop, motionReduced, pageSize, scrollToPage, total]);
202
+
203
+ return {
204
+ active,
205
+ pages,
206
+ isHorizontal,
207
+ isLoop,
208
+ size,
209
+ pageSize,
210
+ scrollRef,
211
+ autoPlayPaused,
212
+ motionReduced,
213
+ goTo,
214
+ toggleAutoPlay,
215
+ onLayout,
216
+ onMomentumScrollEnd,
217
+ onScrollBeginDrag,
218
+ onScrollEndDrag,
219
+ };
220
+ }
@@ -42,24 +42,24 @@ const UIContext = createContext<UIContextValue | undefined>(undefined);
42
42
 
43
43
  export interface UIProviderProps {
44
44
  children: React.ReactNode;
45
- /** Par light/dark. Por defecto el theme de marca de la librería (créalo con createTheme()). */
45
+ /** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */
46
46
  theme?: ThemePair;
47
- /** Modo inicial (no controlado) si no se pasa `mode`. @default 'system' */
47
+ /** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */
48
48
  defaultMode?: ThemeMode;
49
- /** Modo controlado. Si se pasa junto a onModeChange, el provider no gestiona estado propio. */
49
+ /** Controlled mode. If passed together with onModeChange, the provider doesn't manage its own state. */
50
50
  mode?: ThemeMode;
51
51
  onModeChange?: (mode: ThemeMode) => void;
52
- /** Renderer de íconos. Por defecto usa los glifos zero-dep incluidos. */
52
+ /** Icon renderer. Defaults to the included zero-dep glyphs. */
53
53
  icons?: IconRenderer;
54
- /** Insets de safe area (de useSafeAreaInsets()). Por defecto {0,0,0,0}. */
54
+ /** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */
55
55
  insets?: EdgeInsets;
56
56
  labels?: Partial<Labels>;
57
- /** @default false, para paridad con el resto de la librería (accesibilidad primero). */
57
+ /** @default false, for parity with the rest of the library (accessibility first). */
58
58
  allowFontScaling?: boolean;
59
59
  /**
60
- * Fuente de insets que usan `Modal`/`BottomSheet` para su propia ventana
61
- * nativa. No la pases a mano — `SafeAreaUIProvider` la cablea sola.
62
- * @default repite los insets ambientes (comportamiento sin safe-area-context)
60
+ * Source of insets used by `Modal`/`BottomSheet` for their own native
61
+ * window. Don't pass this by hand — `SafeAreaUIProvider` wires it up itself.
62
+ * @default repeats the ambient insets (behavior without safe-area-context)
63
63
  */
64
64
  ModalSafeAreaBoundary?: ModalSafeAreaBoundaryComponent;
65
65
  }