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.
- package/AGENTS.md +372 -0
- package/README.md +46 -15
- package/lib/commonjs/atoms/Gradient.js +111 -0
- package/lib/commonjs/atoms/Gradient.js.map +1 -0
- package/lib/commonjs/atoms/index.js +7 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/organisms/Slides.js +406 -0
- package/lib/commonjs/organisms/Slides.js.map +1 -0
- package/lib/commonjs/organisms/index.js +7 -0
- package/lib/commonjs/organisms/index.js.map +1 -1
- package/lib/commonjs/organisms/useSlides.js +186 -0
- package/lib/commonjs/organisms/useSlides.js.map +1 -0
- package/lib/module/atoms/Gradient.js +106 -0
- package/lib/module/atoms/Gradient.js.map +1 -0
- package/lib/module/atoms/index.js +1 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/organisms/Slides.js +401 -0
- package/lib/module/organisms/Slides.js.map +1 -0
- package/lib/module/organisms/index.js +1 -0
- package/lib/module/organisms/index.js.map +1 -1
- package/lib/module/organisms/useSlides.js +182 -0
- package/lib/module/organisms/useSlides.js.map +1 -0
- package/lib/typescript/atoms/Badge.d.ts +1 -1
- package/lib/typescript/atoms/Badge.d.ts.map +1 -1
- package/lib/typescript/atoms/Gradient.d.ts +37 -0
- package/lib/typescript/atoms/Gradient.d.ts.map +1 -0
- package/lib/typescript/atoms/Image.d.ts +6 -6
- package/lib/typescript/atoms/Image.d.ts.map +1 -1
- package/lib/typescript/atoms/Input.d.ts +1 -1
- package/lib/typescript/atoms/Input.d.ts.map +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts +1 -1
- package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
- package/lib/typescript/atoms/Skeleton.d.ts +5 -5
- package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
- package/lib/typescript/atoms/Spinner.d.ts +4 -4
- package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
- package/lib/typescript/atoms/Transition.d.ts +10 -10
- package/lib/typescript/atoms/Transition.d.ts.map +1 -1
- package/lib/typescript/atoms/index.d.ts +2 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/molecules/AvatarHeader.d.ts +2 -2
- package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
- package/lib/typescript/molecules/FormActions.d.ts +1 -1
- package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
- package/lib/typescript/molecules/OptionCard.d.ts +2 -2
- package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
- package/lib/typescript/molecules/Stepper.d.ts +8 -8
- package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
- package/lib/typescript/molecules/Steps.d.ts +3 -3
- package/lib/typescript/molecules/Steps.d.ts.map +1 -1
- package/lib/typescript/molecules/SymmetricGrid.d.ts +4 -4
- package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
- package/lib/typescript/organisms/BottomSheet.d.ts +8 -8
- package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
- package/lib/typescript/organisms/DateField.d.ts +10 -10
- package/lib/typescript/organisms/DateField.d.ts.map +1 -1
- package/lib/typescript/organisms/DatePicker.d.ts +10 -10
- package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
- package/lib/typescript/organisms/List.d.ts +6 -6
- package/lib/typescript/organisms/List.d.ts.map +1 -1
- package/lib/typescript/organisms/NavigationBar.d.ts +2 -2
- package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
- package/lib/typescript/organisms/ReorderableList.d.ts +7 -7
- package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
- package/lib/typescript/organisms/Screen.d.ts +6 -6
- package/lib/typescript/organisms/SearchList.d.ts +8 -8
- package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
- package/lib/typescript/organisms/Select.d.ts +2 -2
- package/lib/typescript/organisms/Select.d.ts.map +1 -1
- package/lib/typescript/organisms/Slides.d.ts +69 -0
- package/lib/typescript/organisms/Slides.d.ts.map +1 -0
- package/lib/typescript/organisms/Toast.d.ts +2 -2
- package/lib/typescript/organisms/Toast.d.ts.map +1 -1
- package/lib/typescript/organisms/ToastProvider.d.ts +2 -2
- package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
- package/lib/typescript/organisms/Wizard.d.ts +7 -7
- package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
- package/lib/typescript/organisms/index.d.ts +2 -0
- package/lib/typescript/organisms/index.d.ts.map +1 -1
- package/lib/typescript/organisms/useSlides.d.ts +52 -0
- package/lib/typescript/organisms/useSlides.d.ts.map +1 -0
- package/lib/typescript/theme/UIProvider.d.ts +9 -9
- package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
- package/llms.txt +34 -0
- package/package.json +34 -6
- package/registry/avatar-header.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/bottom-sheet.json +1 -1
- package/registry/core.json +1 -1
- package/registry/date-field.json +1 -1
- package/registry/date-picker.json +1 -1
- package/registry/form-actions.json +1 -1
- package/registry/gradient.json +18 -0
- package/registry/image.json +1 -1
- package/registry/index.json +52 -26
- package/registry/input.json +1 -1
- package/registry/list.json +1 -1
- package/registry/manifest.json +14 -0
- package/registry/navigation-bar.json +1 -1
- package/registry/option-card.json +1 -1
- package/registry/pressable-scale.json +1 -1
- package/registry/reorderable-list.json +1 -1
- package/registry/screen.json +1 -1
- package/registry/search-list.json +1 -1
- package/registry/select.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/slides.json +24 -0
- package/registry/spinner.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/steps.json +1 -1
- package/registry/symmetric-grid.json +1 -1
- package/registry/toast-provider.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/transition.json +1 -1
- package/registry/wizard.json +1 -1
- package/src/atoms/Badge.tsx +1 -1
- package/src/atoms/Gradient.tsx +99 -0
- package/src/atoms/Image.tsx +6 -6
- package/src/atoms/Input.tsx +1 -1
- package/src/atoms/PressableScale.tsx +1 -1
- package/src/atoms/Skeleton.tsx +5 -5
- package/src/atoms/Spinner.tsx +4 -4
- package/src/atoms/Transition.tsx +10 -10
- package/src/atoms/index.ts +2 -0
- package/src/molecules/AvatarHeader.tsx +2 -2
- package/src/molecules/FormActions.tsx +1 -1
- package/src/molecules/OptionCard.tsx +2 -2
- package/src/molecules/Stepper.tsx +8 -8
- package/src/molecules/Steps.tsx +3 -3
- package/src/molecules/SymmetricGrid.tsx +4 -4
- package/src/organisms/BottomSheet.tsx +8 -8
- package/src/organisms/DateField.tsx +10 -10
- package/src/organisms/DatePicker.tsx +10 -10
- package/src/organisms/List.tsx +6 -6
- package/src/organisms/NavigationBar.tsx +2 -2
- package/src/organisms/ReorderableList.tsx +7 -7
- package/src/organisms/Screen.tsx +6 -6
- package/src/organisms/SearchList.tsx +8 -8
- package/src/organisms/Select.tsx +2 -2
- package/src/organisms/Slides.tsx +413 -0
- package/src/organisms/Toast.tsx +2 -2
- package/src/organisms/ToastProvider.tsx +2 -2
- package/src/organisms/Wizard.tsx +7 -7
- package/src/organisms/index.ts +8 -0
- package/src/organisms/useSlides.ts +220 -0
- 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
|
+
}
|
package/src/theme/UIProvider.tsx
CHANGED
|
@@ -42,24 +42,24 @@ const UIContext = createContext<UIContextValue | undefined>(undefined);
|
|
|
42
42
|
|
|
43
43
|
export interface UIProviderProps {
|
|
44
44
|
children: React.ReactNode;
|
|
45
|
-
/**
|
|
45
|
+
/** Light/dark pair. Defaults to the library's brand theme (create one with createTheme()). */
|
|
46
46
|
theme?: ThemePair;
|
|
47
|
-
/**
|
|
47
|
+
/** Initial mode (uncontrolled) if `mode` isn't passed. @default 'system' */
|
|
48
48
|
defaultMode?: ThemeMode;
|
|
49
|
-
/**
|
|
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
|
-
/**
|
|
52
|
+
/** Icon renderer. Defaults to the included zero-dep glyphs. */
|
|
53
53
|
icons?: IconRenderer;
|
|
54
|
-
/**
|
|
54
|
+
/** Safe area insets (from useSafeAreaInsets()). Defaults to {0,0,0,0}. */
|
|
55
55
|
insets?: EdgeInsets;
|
|
56
56
|
labels?: Partial<Labels>;
|
|
57
|
-
/** @default false,
|
|
57
|
+
/** @default false, for parity with the rest of the library (accessibility first). */
|
|
58
58
|
allowFontScaling?: boolean;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* @default
|
|
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
|
}
|