react-native-molecules 0.5.0-beta.22 → 0.5.0-beta.23
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/components/Button/Button.tsx +3 -1
- package/components/DatePicker/utils.ts +2 -0
- package/components/DatePickerInline/MonthPicker.tsx +24 -40
- package/components/DatePickerInline/YearPicker.tsx +44 -79
- package/components/List/List.tsx +154 -386
- package/components/List/context.tsx +2 -4
- package/components/List/index.ts +0 -1
- package/components/List/types.ts +77 -109
- package/components/List/utils.ts +4 -37
- package/components/Menu/Menu.tsx +13 -30
- package/components/Menu/index.tsx +0 -2
- package/components/Popover/Popover.tsx +7 -10
- package/components/Popover/PopoverRoot.tsx +6 -20
- package/components/Popover/common.ts +4 -0
- package/components/Popover/index.ts +2 -8
- package/components/Popover/usePlatformMeasure.ts +4 -2
- package/components/Select/Select.tsx +211 -47
- package/components/Select/context.tsx +27 -2
- package/components/Select/types.ts +41 -25
- package/components/Select/utils.ts +7 -0
- package/components/TouchableRipple/TouchableRipple.tsx +76 -152
- package/package.json +3 -2
|
@@ -5,12 +5,12 @@ import {
|
|
|
5
5
|
Pressable,
|
|
6
6
|
type PressableProps,
|
|
7
7
|
type StyleProp,
|
|
8
|
-
View,
|
|
9
8
|
type ViewStyle,
|
|
10
9
|
} from 'react-native';
|
|
11
10
|
import { StyleSheet } from 'react-native-unistyles';
|
|
12
11
|
|
|
13
12
|
import { useTheme } from '../../hooks/useTheme';
|
|
13
|
+
import { noop } from '../../utils/lodash';
|
|
14
14
|
import { Slot } from '../Slot';
|
|
15
15
|
import { rippleColorFromBackground } from './rippleFromForegroundColor';
|
|
16
16
|
import { touchableRippleStyles } from './utils';
|
|
@@ -123,7 +123,7 @@ const TouchableRipple = (
|
|
|
123
123
|
rippleColor: rippleColorProp,
|
|
124
124
|
underlayColor: _underlayColor,
|
|
125
125
|
rippleAlpha = 0.24,
|
|
126
|
-
onPress,
|
|
126
|
+
onPress = noop,
|
|
127
127
|
children,
|
|
128
128
|
onPressIn: onPressInProp,
|
|
129
129
|
onPressOut: onPressOutProp,
|
|
@@ -157,25 +157,16 @@ const TouchableRipple = (
|
|
|
157
157
|
style,
|
|
158
158
|
];
|
|
159
159
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
//
|
|
163
|
-
|
|
160
|
+
// The active ripple is tracked so onPressOut can fade it. Driving the lifecycle
|
|
161
|
+
// off Pressable's press events (instead of raw pointer events) means a nested
|
|
162
|
+
// element that captures the gesture won't trigger an orphan ripple — Pressable
|
|
163
|
+
// only fires onPressIn when its own press is being handled.
|
|
164
|
+
const activeRippleRef = useRef<HTMLElement | null>(null);
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
onPressInProp?.(e as GestureResponderEvent);
|
|
170
|
-
|
|
171
|
-
if (disabled) return;
|
|
172
|
-
|
|
173
|
-
isPointerDownRef.current = true;
|
|
174
|
-
|
|
175
|
-
const button = e.currentTarget as HTMLElement;
|
|
176
|
-
currentTargetRef.current = button;
|
|
177
|
-
const computedStyle = window.getComputedStyle(button);
|
|
178
|
-
const dimensions = button.getBoundingClientRect();
|
|
166
|
+
const startRipple = useCallback(
|
|
167
|
+
(host: HTMLElement, x: number, y: number) => {
|
|
168
|
+
const computedStyle = window.getComputedStyle(host);
|
|
169
|
+
const dimensions = host.getBoundingClientRect();
|
|
179
170
|
|
|
180
171
|
const resolvedRippleColor =
|
|
181
172
|
rippleColorResolvedProp ??
|
|
@@ -187,46 +178,14 @@ const TouchableRipple = (
|
|
|
187
178
|
)
|
|
188
179
|
: String(themeRippleFallback));
|
|
189
180
|
|
|
190
|
-
let touchX: number;
|
|
191
|
-
let touchY: number;
|
|
192
|
-
|
|
193
|
-
if (centered) {
|
|
194
|
-
// If centered, always position ripple at center
|
|
195
|
-
touchX = dimensions.width / 2;
|
|
196
|
-
touchY = dimensions.height / 2;
|
|
197
|
-
} else if ('clientX' in e && 'clientY' in e) {
|
|
198
|
-
// Web pointer event - calculate position relative to element
|
|
199
|
-
touchX = e.clientX - dimensions.left;
|
|
200
|
-
touchY = e.clientY - dimensions.top;
|
|
201
|
-
} else if (e.nativeEvent) {
|
|
202
|
-
// React Native gesture event
|
|
203
|
-
const { changedTouches, touches } = e.nativeEvent;
|
|
204
|
-
const touch = touches?.[0] ?? changedTouches?.[0];
|
|
205
|
-
if (touch) {
|
|
206
|
-
touchX = touch.locationX ?? dimensions.width / 2;
|
|
207
|
-
touchY = touch.locationY ?? dimensions.height / 2;
|
|
208
|
-
} else {
|
|
209
|
-
touchX = dimensions.width / 2;
|
|
210
|
-
touchY = dimensions.height / 2;
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
// Fallback to center (keyboard activation)
|
|
214
|
-
touchX = dimensions.width / 2;
|
|
215
|
-
touchY = dimensions.height / 2;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Get the size of the button to determine how big the ripple should be
|
|
219
181
|
const size = centered
|
|
220
|
-
?
|
|
221
|
-
|
|
222
|
-
: // Otherwise make it twice as big so clicking on one end spreads ripple to other
|
|
223
|
-
Math.max(dimensions.width, dimensions.height) * 2;
|
|
182
|
+
? Math.min(dimensions.width, dimensions.height) * 1.25
|
|
183
|
+
: Math.max(dimensions.width, dimensions.height) * 2;
|
|
224
184
|
|
|
225
|
-
|
|
226
|
-
const container = document.createElement('span');
|
|
185
|
+
const expandDuration = Math.min(size * 1.5, 350);
|
|
227
186
|
|
|
187
|
+
const container = document.createElement('span');
|
|
228
188
|
container.setAttribute('data-molecules-ripple', '');
|
|
229
|
-
|
|
230
189
|
Object.assign(container.style, {
|
|
231
190
|
position: 'absolute',
|
|
232
191
|
pointerEvents: 'none',
|
|
@@ -241,39 +200,28 @@ const TouchableRipple = (
|
|
|
241
200
|
overflow: centered ? 'visible' : 'hidden',
|
|
242
201
|
});
|
|
243
202
|
|
|
244
|
-
// Create span to show the ripple effect
|
|
245
203
|
const ripple = document.createElement('span');
|
|
246
|
-
|
|
247
204
|
Object.assign(ripple.style, {
|
|
248
205
|
position: 'absolute',
|
|
249
206
|
pointerEvents: 'none',
|
|
250
207
|
backgroundColor: resolvedRippleColor,
|
|
251
208
|
borderRadius: '50%',
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
transitionProperty: 'transform opacity',
|
|
255
|
-
transitionDuration: `${Math.min(size * 1.5, 350)}ms`,
|
|
209
|
+
transitionProperty: 'transform, opacity',
|
|
210
|
+
transitionDuration: `${expandDuration}ms`,
|
|
256
211
|
transitionTimingFunction: 'linear',
|
|
257
212
|
transformOrigin: 'center',
|
|
258
|
-
|
|
259
|
-
/* We'll animate these properties */
|
|
260
213
|
transform: 'translate3d(-50%, -50%, 0) scale3d(0.1, 0.1, 0.1)',
|
|
261
214
|
opacity: '0.5',
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
left: `${touchX}px`,
|
|
265
|
-
top: `${touchY}px`,
|
|
215
|
+
left: `${x}px`,
|
|
216
|
+
top: `${y}px`,
|
|
266
217
|
width: `${size}px`,
|
|
267
218
|
height: `${size}px`,
|
|
268
219
|
});
|
|
269
220
|
|
|
270
|
-
// Finally, append it to DOM
|
|
271
221
|
container.appendChild(ripple);
|
|
272
|
-
|
|
222
|
+
host.appendChild(container);
|
|
223
|
+
activeRippleRef.current = container;
|
|
273
224
|
|
|
274
|
-
// rAF runs in the same frame as the event handler
|
|
275
|
-
// Use double rAF to ensure the transition class is added in next frame
|
|
276
|
-
// This will make sure that the transition animation is triggered
|
|
277
225
|
requestAnimationFrame(() => {
|
|
278
226
|
requestAnimationFrame(() => {
|
|
279
227
|
Object.assign(ripple.style, {
|
|
@@ -283,96 +231,71 @@ const TouchableRipple = (
|
|
|
283
231
|
});
|
|
284
232
|
});
|
|
285
233
|
},
|
|
286
|
-
[
|
|
287
|
-
onPressInProp,
|
|
288
|
-
disabled,
|
|
289
|
-
centered,
|
|
290
|
-
rippleColorResolvedProp,
|
|
291
|
-
themeRippleFallback,
|
|
292
|
-
rippleAlpha,
|
|
293
|
-
],
|
|
234
|
+
[centered, rippleColorResolvedProp, themeRippleFallback, rippleAlpha],
|
|
294
235
|
);
|
|
295
236
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (parentNode) {
|
|
316
|
-
parentNode.removeChild(container);
|
|
317
|
-
}
|
|
318
|
-
}, 500);
|
|
319
|
-
});
|
|
320
|
-
});
|
|
237
|
+
const fadeRipple = useCallback((container: HTMLElement | null) => {
|
|
238
|
+
if (!container) return;
|
|
239
|
+
const ripple = container.firstChild as HTMLElement | null;
|
|
240
|
+
if (!ripple) {
|
|
241
|
+
container.parentNode?.removeChild(container);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const onTransitionEnd = (ev: TransitionEvent) => {
|
|
246
|
+
if (ev.propertyName !== 'opacity') return;
|
|
247
|
+
ripple.removeEventListener('transitionend', onTransitionEnd);
|
|
248
|
+
container.parentNode?.removeChild(container);
|
|
249
|
+
};
|
|
250
|
+
ripple.addEventListener('transitionend', onTransitionEnd);
|
|
251
|
+
|
|
252
|
+
Object.assign(ripple.style, {
|
|
253
|
+
transitionDuration: '250ms',
|
|
254
|
+
opacity: '0',
|
|
321
255
|
});
|
|
322
256
|
}, []);
|
|
323
257
|
|
|
324
|
-
const
|
|
325
|
-
(e:
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (disabled || !isPointerDownRef.current) return;
|
|
329
|
-
|
|
330
|
-
isPointerDownRef.current = false;
|
|
331
|
-
currentTargetRef.current = null;
|
|
332
|
-
|
|
333
|
-
const target = e.currentTarget as HTMLElement;
|
|
334
|
-
fadeOutRipples(target);
|
|
335
|
-
},
|
|
336
|
-
[onPressOutProp, disabled, fadeOutRipples],
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const handlePointerLeave = useCallback(
|
|
340
|
-
(e: any) => {
|
|
341
|
-
// Only fade out if pointer was down (dragging out of element)
|
|
342
|
-
if (disabled || !isPointerDownRef.current) return;
|
|
258
|
+
const handlePressIn = useCallback(
|
|
259
|
+
(e: GestureResponderEvent) => {
|
|
260
|
+
onPressInProp?.(e);
|
|
261
|
+
if (disabled) return;
|
|
343
262
|
|
|
344
|
-
|
|
345
|
-
|
|
263
|
+
const host = e.currentTarget as unknown as HTMLElement | null;
|
|
264
|
+
if (!host || typeof host.appendChild !== 'function') return;
|
|
265
|
+
|
|
266
|
+
const rect = host.getBoundingClientRect();
|
|
267
|
+
let x = rect.width / 2;
|
|
268
|
+
let y = rect.height / 2;
|
|
269
|
+
|
|
270
|
+
if (!centered) {
|
|
271
|
+
const ne: any = e.nativeEvent;
|
|
272
|
+
if (ne) {
|
|
273
|
+
if (typeof ne.locationX === 'number' && typeof ne.locationY === 'number') {
|
|
274
|
+
x = ne.locationX;
|
|
275
|
+
y = ne.locationY;
|
|
276
|
+
} else if (typeof ne.clientX === 'number' && typeof ne.clientY === 'number') {
|
|
277
|
+
x = ne.clientX - rect.left;
|
|
278
|
+
y = ne.clientY - rect.top;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
346
282
|
|
|
347
|
-
|
|
348
|
-
fadeOutRipples(target);
|
|
283
|
+
startRipple(host, x, y);
|
|
349
284
|
},
|
|
350
|
-
[disabled,
|
|
285
|
+
[onPressInProp, disabled, centered, startRipple],
|
|
351
286
|
);
|
|
352
287
|
|
|
353
|
-
const
|
|
354
|
-
(e:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
const target = e.currentTarget as HTMLElement;
|
|
361
|
-
fadeOutRipples(target);
|
|
288
|
+
const handlePressOut = useCallback(
|
|
289
|
+
(e: GestureResponderEvent) => {
|
|
290
|
+
onPressOutProp?.(e);
|
|
291
|
+
const container = activeRippleRef.current;
|
|
292
|
+
activeRippleRef.current = null;
|
|
293
|
+
fadeRipple(container);
|
|
362
294
|
},
|
|
363
|
-
[
|
|
295
|
+
[onPressOutProp, fadeRipple],
|
|
364
296
|
);
|
|
365
297
|
|
|
366
|
-
const Component = asChild ? Slot :
|
|
367
|
-
|
|
368
|
-
// Use pointer events for universal compatibility (works on any HTML element)
|
|
369
|
-
// These events work with mouse, touch, and stylus inputs
|
|
370
|
-
const pointerEventProps = {
|
|
371
|
-
onPointerDown: handlePointerDown,
|
|
372
|
-
onPointerUp: handlePointerUp,
|
|
373
|
-
onPointerLeave: handlePointerLeave,
|
|
374
|
-
onPointerCancel: handlePointerCancel,
|
|
375
|
-
};
|
|
298
|
+
const Component = asChild ? Slot : Pressable;
|
|
376
299
|
|
|
377
300
|
const accessibilityRoleProp = (rest as { accessibilityRole?: unknown }).accessibilityRole;
|
|
378
301
|
const roleProp = (rest as { role?: unknown }).role;
|
|
@@ -386,8 +309,9 @@ const TouchableRipple = (
|
|
|
386
309
|
style={containerStyle}
|
|
387
310
|
ref={ref}
|
|
388
311
|
onPress={onPress}
|
|
389
|
-
|
|
390
|
-
{
|
|
312
|
+
onPressIn={handlePressIn}
|
|
313
|
+
onPressOut={handlePressOut}
|
|
314
|
+
disabled={disabled}>
|
|
391
315
|
{children}
|
|
392
316
|
</Component>
|
|
393
317
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-molecules",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.23",
|
|
4
4
|
"author": "Thet Aung <thetaung.dev@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"components/DatePickerInline/store.tsx",
|
|
10
10
|
"components/List/context.tsx",
|
|
11
11
|
"components/Select/context.tsx",
|
|
12
|
-
"components/TimePicker/context.tsx"
|
|
12
|
+
"components/TimePicker/context.tsx",
|
|
13
|
+
"components/Popover/common.ts"
|
|
13
14
|
],
|
|
14
15
|
"files": [
|
|
15
16
|
"components",
|