react-native-molecules 0.5.0-beta.2 → 0.5.0-beta.20
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 +1 -1
- package/components/Accordion/Accordion.tsx +2 -6
- package/components/Accordion/AccordionItem.tsx +16 -12
- package/components/Accordion/AccordionItemContent.tsx +6 -1
- package/components/Accordion/AccordionItemHeader.tsx +1 -1
- package/components/Accordion/utils.ts +6 -0
- package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Appbar/AppbarBase.tsx +18 -13
- package/components/Button/Button.tsx +209 -264
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -208
- package/components/Checkbox/CheckboxBase.tsx +23 -128
- package/components/Checkbox/utils.ts +0 -25
- package/components/Chip/Chip.tsx +40 -52
- package/components/Chip/utils.ts +3 -7
- package/components/DateField/DateField.tsx +110 -0
- package/components/DateField/index.tsx +6 -0
- package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +17 -49
- package/components/DatePicker/DateCalendar.tsx +83 -0
- package/components/DatePicker/DatePickerActions.tsx +73 -0
- package/components/DatePicker/DatePickerModal.tsx +234 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +152 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +82 -0
- package/components/DatePicker/index.tsx +44 -0
- package/components/DatePicker/utils.ts +293 -0
- package/components/DatePickerInline/DatePickerContext.tsx +1 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +113 -0
- package/components/DatePickerInline/DatePickerInline.tsx +16 -15
- package/components/DatePickerInline/DatePickerInlineBase.tsx +7 -1
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +42 -27
- package/components/DatePickerInline/Month.tsx +45 -65
- package/components/DatePickerInline/MonthPicker.tsx +25 -41
- package/components/DatePickerInline/Swiper.native.tsx +21 -4
- package/components/DatePickerInline/Swiper.tsx +168 -13
- package/components/DatePickerInline/Week.tsx +6 -1
- package/components/DatePickerInline/YearPicker.tsx +206 -53
- package/components/DatePickerInline/dateUtils.tsx +17 -12
- package/components/DatePickerInline/types.ts +3 -0
- package/components/DatePickerInline/utils.ts +66 -29
- package/components/Drawer/Drawer.tsx +17 -6
- package/components/ElementGroup/ElementGroup.tsx +16 -14
- package/components/FilePicker/FilePicker.tsx +48 -78
- package/components/FilePicker/index.tsx +2 -1
- package/components/FilePicker/utils.ts +9 -0
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/Icon/iconFactory.tsx +3 -3
- package/components/Icon/index.tsx +1 -1
- package/components/Icon/types.ts +17 -6
- package/components/IconButton/IconButton.tsx +42 -57
- package/components/IconButton/utils.ts +142 -33
- package/components/ListItem/ListItem.tsx +3 -1
- package/components/ListItem/utils.ts +1 -1
- package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
- package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
- package/components/LoadingIndicator/index.tsx +13 -0
- package/components/LoadingIndicator/utils.ts +117 -0
- package/components/Menu/Menu.tsx +3 -18
- package/components/NavigationRail/NavigationRail.tsx +15 -9
- package/components/Popover/Popover.tsx +122 -145
- package/components/Popover/PopoverRoot.tsx +74 -0
- package/components/Popover/common.ts +50 -34
- package/components/Popover/index.ts +18 -1
- package/components/Popover/usePlatformMeasure.native.ts +90 -0
- package/components/Popover/usePlatformMeasure.ts +118 -0
- package/components/Popover/utils.ts +34 -0
- package/components/Select/Select.tsx +368 -507
- package/components/Select/context.tsx +72 -0
- package/components/Select/index.ts +8 -14
- package/components/Select/types.ts +2 -4
- package/components/Select/utils.ts +144 -0
- package/components/Slot/Slot.tsx +244 -0
- package/components/Slot/compose-refs.tsx +62 -0
- package/components/Slot/index.tsx +8 -0
- package/components/Surface/Surface.android.tsx +34 -8
- package/components/Surface/Surface.ios.tsx +36 -29
- package/components/Surface/Surface.tsx +31 -4
- package/components/Surface/utils.ts +44 -30
- package/components/Switch/Switch.tsx +8 -2
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +154 -148
- package/components/Tabs/utils.ts +15 -2
- package/components/TextInput/TextInput.tsx +658 -575
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +76 -27
- package/components/TextInput/utils.ts +225 -145
- package/components/TimeField/TimeField.tsx +75 -0
- package/components/TimeField/index.tsx +6 -0
- package/components/TimeField/useTimeFieldState.ts +70 -0
- package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
- package/components/TimePicker/TimeInput.tsx +87 -37
- package/components/TimePicker/TimeInputs.tsx +137 -49
- package/components/TimePicker/TimePicker.tsx +73 -10
- package/components/TimePicker/TimePickerModal.tsx +186 -0
- package/components/TimePicker/context.tsx +17 -0
- package/components/TimePicker/index.tsx +15 -3
- package/components/TimePicker/utils.ts +93 -0
- package/components/Tooltip/Tooltip.tsx +42 -67
- package/components/Tooltip/TooltipContent.tsx +32 -5
- package/components/Tooltip/TooltipTrigger.tsx +20 -20
- package/components/Tooltip/index.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.native.tsx +50 -14
- package/components/TouchableRipple/TouchableRipple.tsx +137 -47
- package/hocs/withPortal.tsx +1 -1
- package/hooks/index.tsx +0 -6
- package/hooks/useActionState.tsx +19 -8
- package/hooks/useControlledValue.tsx +20 -4
- package/hooks/useFilePicker.tsx +6 -16
- package/hooks/useWhatHasUpdated.tsx +48 -0
- package/package.json +17 -13
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +5 -2
- package/styles/shadow.ts +2 -1
- package/styles/themes/LightTheme.tsx +1 -1
- package/utils/DocumentPicker/documentPicker.ts +78 -27
- package/utils/DocumentPicker/types.ts +0 -1
- package/utils/extractPropertiesFromStyles.ts +25 -0
- package/utils/extractSubcomponents.ts +89 -0
- package/utils/lodash.ts +77 -5
- package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
- package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
- package/components/DatePickerDocked/index.tsx +0 -17
- package/components/DatePickerDocked/types.ts +0 -11
- package/components/DatePickerDocked/utils.ts +0 -157
- package/components/DatePickerInput/DatePickerInput.tsx +0 -139
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -10
- package/components/DatePickerInput/types.ts +0 -28
- package/components/DatePickerInput/utils.ts +0 -15
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -139
- package/components/DatePickerModal/DatePickerModal.tsx +0 -85
- package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
- package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
- package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
- package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
- package/components/DatePickerModal/index.tsx +0 -16
- package/components/DatePickerModal/types.ts +0 -92
- package/components/DatePickerModal/utils.ts +0 -122
- package/components/DateTimePicker/DateTimePicker.tsx +0 -172
- package/components/DateTimePicker/index.tsx +0 -10
- package/components/DateTimePicker/utils.ts +0 -12
- package/components/Popover/Popover.native.tsx +0 -185
- package/components/TimePickerField/TimePickerField.tsx +0 -152
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -115
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
- package/hooks/useSearchable.tsx +0 -74
- package/hooks/useSubcomponents.tsx +0 -59
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { forwardRef, memo, type ReactNode, useCallback, useMemo, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type GestureResponderEvent,
|
|
4
4
|
Pressable,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from 'react-native';
|
|
10
10
|
import { StyleSheet } from 'react-native-unistyles';
|
|
11
11
|
|
|
12
|
+
import { Slot } from '../Slot';
|
|
12
13
|
import { touchableRippleStyles } from './utils';
|
|
13
14
|
|
|
14
15
|
export type Props = PressableProps & {
|
|
@@ -50,6 +51,28 @@ export type Props = PressableProps & {
|
|
|
50
51
|
*/
|
|
51
52
|
children: ReactNode;
|
|
52
53
|
style?: StyleProp<ViewStyle>;
|
|
54
|
+
/**
|
|
55
|
+
* When `true`, the component will not render a wrapper element. Instead, it will
|
|
56
|
+
* merge its props (styles, event handlers, ref) onto its immediate child element.
|
|
57
|
+
* This follows the Radix UI "Slot" pattern for flexible component composition.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* // Without asChild - renders a Pressable wrapper
|
|
62
|
+
* <TouchableRipple onPress={handlePress}>
|
|
63
|
+
* <View><Text>Click me</Text></View>
|
|
64
|
+
* </TouchableRipple>
|
|
65
|
+
*
|
|
66
|
+
* // With asChild - merges props onto the child
|
|
67
|
+
* <TouchableRipple asChild onPress={handlePress}>
|
|
68
|
+
* <Link href="/page"><Text>Navigate</Text></Link>
|
|
69
|
+
* </TouchableRipple>
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @note When `asChild` is `true`, only a single child element is allowed.
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
asChild?: boolean;
|
|
53
76
|
};
|
|
54
77
|
|
|
55
78
|
/**
|
|
@@ -96,12 +119,13 @@ const TouchableRipple = (
|
|
|
96
119
|
onPressIn: onPressInProp,
|
|
97
120
|
onPressOut: onPressOutProp,
|
|
98
121
|
centered,
|
|
122
|
+
asChild = false,
|
|
99
123
|
...rest
|
|
100
124
|
}: Props,
|
|
101
125
|
ref: any,
|
|
102
126
|
) => {
|
|
103
127
|
// TODO - enable ripple onLongPress, need to check for mobile as well
|
|
104
|
-
const disabled = disabledProp
|
|
128
|
+
const disabled = disabledProp;
|
|
105
129
|
|
|
106
130
|
const componentStyles = touchableRippleStyles;
|
|
107
131
|
|
|
@@ -121,29 +145,52 @@ const TouchableRipple = (
|
|
|
121
145
|
};
|
|
122
146
|
}, [borderless, componentStyles.root, rippleColorProp, style]);
|
|
123
147
|
|
|
124
|
-
|
|
148
|
+
// Track whether pointer is currently down for handling pointer leave
|
|
149
|
+
const isPointerDownRef = useRef(false);
|
|
150
|
+
// Store current target element to clean up ripples on pointer up/leave
|
|
151
|
+
const currentTargetRef = useRef<HTMLElement | null>(null);
|
|
152
|
+
|
|
153
|
+
// Using 'any' for event types to support both React DOM PointerEvent and React Native events
|
|
154
|
+
// This is a web-only file, so we primarily handle DOM pointer events
|
|
155
|
+
const handlePointerDown = useCallback(
|
|
125
156
|
(e: any) => {
|
|
126
|
-
onPressInProp?.(e);
|
|
157
|
+
onPressInProp?.(e as GestureResponderEvent);
|
|
127
158
|
|
|
128
159
|
if (disabled) return;
|
|
129
160
|
|
|
130
|
-
|
|
161
|
+
isPointerDownRef.current = true;
|
|
162
|
+
|
|
163
|
+
const button = e.currentTarget as HTMLElement;
|
|
164
|
+
currentTargetRef.current = button;
|
|
131
165
|
const computedStyle = window.getComputedStyle(button);
|
|
132
166
|
const dimensions = button.getBoundingClientRect();
|
|
133
167
|
|
|
134
|
-
let touchX;
|
|
135
|
-
let touchY;
|
|
136
|
-
|
|
137
|
-
const { changedTouches, touches } = e.nativeEvent;
|
|
138
|
-
const touch = touches?.[0] ?? changedTouches?.[0];
|
|
168
|
+
let touchX: number;
|
|
169
|
+
let touchY: number;
|
|
139
170
|
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
if (centered) {
|
|
172
|
+
// If centered, always position ripple at center
|
|
142
173
|
touchX = dimensions.width / 2;
|
|
143
174
|
touchY = dimensions.height / 2;
|
|
175
|
+
} else if ('clientX' in e && 'clientY' in e) {
|
|
176
|
+
// Web pointer event - calculate position relative to element
|
|
177
|
+
touchX = e.clientX - dimensions.left;
|
|
178
|
+
touchY = e.clientY - dimensions.top;
|
|
179
|
+
} else if (e.nativeEvent) {
|
|
180
|
+
// React Native gesture event
|
|
181
|
+
const { changedTouches, touches } = e.nativeEvent;
|
|
182
|
+
const touch = touches?.[0] ?? changedTouches?.[0];
|
|
183
|
+
if (touch) {
|
|
184
|
+
touchX = touch.locationX ?? dimensions.width / 2;
|
|
185
|
+
touchY = touch.locationY ?? dimensions.height / 2;
|
|
186
|
+
} else {
|
|
187
|
+
touchX = dimensions.width / 2;
|
|
188
|
+
touchY = dimensions.height / 2;
|
|
189
|
+
}
|
|
144
190
|
} else {
|
|
145
|
-
|
|
146
|
-
|
|
191
|
+
// Fallback to center (keyboard activation)
|
|
192
|
+
touchX = dimensions.width / 2;
|
|
193
|
+
touchY = dimensions.height / 2;
|
|
147
194
|
}
|
|
148
195
|
|
|
149
196
|
// Get the size of the button to determine how big the ripple should be
|
|
@@ -156,7 +203,7 @@ const TouchableRipple = (
|
|
|
156
203
|
// Create a container for our ripple effect so we don't need to change the parent's style
|
|
157
204
|
const container = document.createElement('span');
|
|
158
205
|
|
|
159
|
-
container.setAttribute('data-
|
|
206
|
+
container.setAttribute('data-molecules-ripple', '');
|
|
160
207
|
|
|
161
208
|
Object.assign(container.style, {
|
|
162
209
|
position: 'absolute',
|
|
@@ -217,42 +264,86 @@ const TouchableRipple = (
|
|
|
217
264
|
[onPressInProp, disabled, centered, rippleColor],
|
|
218
265
|
);
|
|
219
266
|
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (disabled) return;
|
|
225
|
-
|
|
226
|
-
const containers = e.currentTarget.querySelectorAll(
|
|
227
|
-
'[data-paper-ripple]',
|
|
228
|
-
) as HTMLElement[];
|
|
267
|
+
const fadeOutRipples = useCallback((target: HTMLElement) => {
|
|
268
|
+
const containers = target.querySelectorAll(
|
|
269
|
+
'[data-molecules-ripple]',
|
|
270
|
+
) as NodeListOf<HTMLElement>;
|
|
229
271
|
|
|
272
|
+
requestAnimationFrame(() => {
|
|
230
273
|
requestAnimationFrame(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
opacity: 0,
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Finally remove the span after the transition
|
|
241
|
-
setTimeout(() => {
|
|
242
|
-
const { parentNode } = container;
|
|
243
|
-
|
|
244
|
-
if (parentNode) {
|
|
245
|
-
parentNode.removeChild(container);
|
|
246
|
-
}
|
|
247
|
-
}, 500);
|
|
274
|
+
containers.forEach(container => {
|
|
275
|
+
const ripple = container.firstChild as HTMLSpanElement;
|
|
276
|
+
|
|
277
|
+
Object.assign(ripple.style, {
|
|
278
|
+
transitionDuration: '250ms',
|
|
279
|
+
opacity: 0,
|
|
248
280
|
});
|
|
281
|
+
|
|
282
|
+
// Finally remove the span after the transition
|
|
283
|
+
setTimeout(() => {
|
|
284
|
+
const { parentNode } = container;
|
|
285
|
+
|
|
286
|
+
if (parentNode) {
|
|
287
|
+
parentNode.removeChild(container);
|
|
288
|
+
}
|
|
289
|
+
}, 500);
|
|
249
290
|
});
|
|
250
291
|
});
|
|
292
|
+
});
|
|
293
|
+
}, []);
|
|
294
|
+
|
|
295
|
+
const handlePointerUp = useCallback(
|
|
296
|
+
(e: any) => {
|
|
297
|
+
onPressOutProp?.(e as GestureResponderEvent);
|
|
298
|
+
|
|
299
|
+
if (disabled || !isPointerDownRef.current) return;
|
|
300
|
+
|
|
301
|
+
isPointerDownRef.current = false;
|
|
302
|
+
currentTargetRef.current = null;
|
|
303
|
+
|
|
304
|
+
const target = e.currentTarget as HTMLElement;
|
|
305
|
+
fadeOutRipples(target);
|
|
251
306
|
},
|
|
252
|
-
[onPressOutProp, disabled],
|
|
307
|
+
[onPressOutProp, disabled, fadeOutRipples],
|
|
253
308
|
);
|
|
254
309
|
|
|
255
|
-
const
|
|
310
|
+
const handlePointerLeave = useCallback(
|
|
311
|
+
(e: any) => {
|
|
312
|
+
// Only fade out if pointer was down (dragging out of element)
|
|
313
|
+
if (disabled || !isPointerDownRef.current) return;
|
|
314
|
+
|
|
315
|
+
isPointerDownRef.current = false;
|
|
316
|
+
currentTargetRef.current = null;
|
|
317
|
+
|
|
318
|
+
const target = e.currentTarget as HTMLElement;
|
|
319
|
+
fadeOutRipples(target);
|
|
320
|
+
},
|
|
321
|
+
[disabled, fadeOutRipples],
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const handlePointerCancel = useCallback(
|
|
325
|
+
(e: any) => {
|
|
326
|
+
if (disabled || !isPointerDownRef.current) return;
|
|
327
|
+
|
|
328
|
+
isPointerDownRef.current = false;
|
|
329
|
+
currentTargetRef.current = null;
|
|
330
|
+
|
|
331
|
+
const target = e.currentTarget as HTMLElement;
|
|
332
|
+
fadeOutRipples(target);
|
|
333
|
+
},
|
|
334
|
+
[disabled, fadeOutRipples],
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const Component = asChild ? Slot : onPress ? Pressable : View;
|
|
338
|
+
|
|
339
|
+
// Use pointer events for universal compatibility (works on any HTML element)
|
|
340
|
+
// These events work with mouse, touch, and stylus inputs
|
|
341
|
+
const pointerEventProps = {
|
|
342
|
+
onPointerDown: handlePointerDown,
|
|
343
|
+
onPointerUp: handlePointerUp,
|
|
344
|
+
onPointerLeave: handlePointerLeave,
|
|
345
|
+
onPointerCancel: handlePointerCancel,
|
|
346
|
+
};
|
|
256
347
|
|
|
257
348
|
return (
|
|
258
349
|
<Component
|
|
@@ -261,10 +352,9 @@ const TouchableRipple = (
|
|
|
261
352
|
style={containerStyle}
|
|
262
353
|
ref={ref}
|
|
263
354
|
onPress={onPress}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
{Children.only(children)}
|
|
355
|
+
disabled={disabled}
|
|
356
|
+
{...pointerEventProps}>
|
|
357
|
+
{children}
|
|
268
358
|
</Component>
|
|
269
359
|
);
|
|
270
360
|
};
|
package/hocs/withPortal.tsx
CHANGED
package/hooks/index.tsx
CHANGED
|
@@ -18,11 +18,5 @@ export { useMediaQuery } from './useMediaQuery';
|
|
|
18
18
|
export { useMergedRefs } from './useMergedRefs';
|
|
19
19
|
export { default as usePrevious } from './usePrevious';
|
|
20
20
|
export * from './useQueryFilter';
|
|
21
|
-
export {
|
|
22
|
-
default as useSearchable,
|
|
23
|
-
type UseSearchableProps,
|
|
24
|
-
useSearchInputProps,
|
|
25
|
-
} from './useSearchable';
|
|
26
|
-
export { default as useSubcomponents, type UseSubcomponentsProps } from './useSubcomponents';
|
|
27
21
|
export * from './useTheme';
|
|
28
22
|
export { default as useToggle } from './useToggle';
|
package/hooks/useActionState.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RefObject, useRef } from 'react';
|
|
1
|
+
import { type RefObject, useCallback, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useActive } from './useActive';
|
|
4
4
|
import { useFocus } from './useFocus';
|
|
@@ -14,16 +14,27 @@ export type UseActionStateProps = {
|
|
|
14
14
|
export const useActionState = (
|
|
15
15
|
props: UseActionStateProps & { ref?: RefObject<any> | React.ForwardedRef<any> } = {},
|
|
16
16
|
) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const internalRef = useRef(null);
|
|
18
|
+
const externalRef = props.ref;
|
|
19
|
+
|
|
20
|
+
const actionsRef = useCallback(
|
|
21
|
+
(node: any) => {
|
|
22
|
+
internalRef.current = node;
|
|
23
|
+
if (typeof externalRef === 'function') {
|
|
24
|
+
externalRef(node);
|
|
25
|
+
} else if (externalRef) {
|
|
26
|
+
(externalRef as RefObject<any>).current = node;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
[externalRef],
|
|
30
|
+
) as unknown as RefObject<any>;
|
|
31
|
+
|
|
21
32
|
const hovered =
|
|
22
|
-
useHover(
|
|
33
|
+
useHover(internalRef, props.actionsToListen?.includes('hover')) || !!props.hovered;
|
|
23
34
|
const pressed =
|
|
24
|
-
useActive(
|
|
35
|
+
useActive(internalRef, props.actionsToListen?.includes('press')) || !!props.pressed;
|
|
25
36
|
const focused =
|
|
26
|
-
useFocus(
|
|
37
|
+
useFocus(internalRef, props.actionsToListen?.includes('focus')) || !!props.focused;
|
|
27
38
|
|
|
28
39
|
return {
|
|
29
40
|
actionsRef,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
|
|
3
|
+
import useLatest from './useLatest';
|
|
4
|
+
|
|
3
5
|
type ReturnType<T> = [T, (value: T, ...args: any[]) => void];
|
|
4
6
|
|
|
5
7
|
type Args<T> = {
|
|
@@ -31,21 +33,35 @@ const useControlledValue = <T,>({
|
|
|
31
33
|
|
|
32
34
|
const isUncontrolled = useRef(valueProp).current === undefined;
|
|
33
35
|
const [uncontrolledValue, setValue] = useState(value);
|
|
36
|
+
const valuePropRef = useLatest(valueProp);
|
|
37
|
+
const onChangeRef = useLatest(onChange);
|
|
38
|
+
const manipulateValueRef = useLatest(manipulateValue);
|
|
39
|
+
const uncontrolledValueRef = useLatest(uncontrolledValue);
|
|
34
40
|
|
|
35
41
|
const updateValue = useCallback(
|
|
36
42
|
(val: T, ...rest: any[]) => {
|
|
37
43
|
if (disabled) return;
|
|
38
44
|
|
|
39
45
|
if (isUncontrolled) {
|
|
40
|
-
setValue(
|
|
46
|
+
setValue(manipulateValueRef.current(val, uncontrolledValueRef.current));
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
onChangeRef.current?.(
|
|
50
|
+
manipulateValueRef.current(
|
|
51
|
+
val,
|
|
52
|
+
isUncontrolled ? uncontrolledValueRef.current : valuePropRef.current,
|
|
53
|
+
),
|
|
45
54
|
...rest,
|
|
46
55
|
);
|
|
47
56
|
},
|
|
48
|
-
[
|
|
57
|
+
[
|
|
58
|
+
disabled,
|
|
59
|
+
isUncontrolled,
|
|
60
|
+
manipulateValueRef,
|
|
61
|
+
onChangeRef,
|
|
62
|
+
uncontrolledValueRef,
|
|
63
|
+
valuePropRef,
|
|
64
|
+
],
|
|
49
65
|
);
|
|
50
66
|
|
|
51
67
|
useEffect(() => {
|
package/hooks/useFilePicker.tsx
CHANGED
|
@@ -7,15 +7,11 @@ import {
|
|
|
7
7
|
} from '../utils/DocumentPicker';
|
|
8
8
|
import { isNil, omitBy } from '../utils/lodash';
|
|
9
9
|
|
|
10
|
-
export const useFilePicker = ({
|
|
11
|
-
multiple,
|
|
12
|
-
onCancel,
|
|
13
|
-
onError,
|
|
14
|
-
...options
|
|
15
|
-
}: DocumentPickerOptions) => {
|
|
10
|
+
export const useFilePicker = (options: DocumentPickerOptions) => {
|
|
16
11
|
const openFilePicker = useCallback(
|
|
17
|
-
async (callback: (response: DocumentResult
|
|
18
|
-
const
|
|
12
|
+
async (callback: (response: DocumentResult[]) => void) => {
|
|
13
|
+
const { multiple, ...rest } = options;
|
|
14
|
+
const omittedOptions = omitBy(rest, isNil);
|
|
19
15
|
|
|
20
16
|
try {
|
|
21
17
|
let response;
|
|
@@ -30,16 +26,10 @@ export const useFilePicker = ({
|
|
|
30
26
|
} catch (e: any) {
|
|
31
27
|
// eslint-disable-next-line no-console
|
|
32
28
|
console.log('FilePicker Error', e, e?.code);
|
|
33
|
-
|
|
34
|
-
if (e?.code === 'DOCUMENT_PICKER_CANCELED.') {
|
|
35
|
-
onCancel?.();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// It might result in an error.
|
|
39
|
-
onError?.(e);
|
|
29
|
+
// Error and cancel callbacks are handled by DocumentPicker itself
|
|
40
30
|
}
|
|
41
31
|
},
|
|
42
|
-
[
|
|
32
|
+
[options],
|
|
43
33
|
);
|
|
44
34
|
|
|
45
35
|
return { openFilePicker };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import useLatest from './useLatest';
|
|
4
|
+
|
|
5
|
+
const map = new Map();
|
|
6
|
+
|
|
7
|
+
export const whatHasUpdatedFactory = <T extends Record<string, unknown>>(
|
|
8
|
+
name: string,
|
|
9
|
+
prevObject: T,
|
|
10
|
+
{ debug = false, useCached = false } = {},
|
|
11
|
+
) => {
|
|
12
|
+
const getPrev = () => {
|
|
13
|
+
if (!map.has(name)) map.set(name, prevObject);
|
|
14
|
+
return useCached ? map.get(name) : prevObject;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const setPrev = (value: T) => {
|
|
18
|
+
if (useCached) map.set(name, value);
|
|
19
|
+
else prevObject = value;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (nextObject: T) => {
|
|
23
|
+
const old = getPrev();
|
|
24
|
+
const changes = Object.keys({ ...nextObject, ...old }).reduce((all, key) => {
|
|
25
|
+
const newValue = nextObject?.[key];
|
|
26
|
+
const oldValue = old[key];
|
|
27
|
+
if (oldValue === newValue) return all;
|
|
28
|
+
return { ...all, [key]: { newValue, oldValue } };
|
|
29
|
+
}, {});
|
|
30
|
+
|
|
31
|
+
setPrev(nextObject);
|
|
32
|
+
|
|
33
|
+
if (!debug && !Object.keys(changes).length) return;
|
|
34
|
+
// eslint-disable-next-line no-console
|
|
35
|
+
console.log('🚨🕵️ UPDATED', name, changes);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const useWhatHasUpdated = (name: string, props: Record<string, any>) => {
|
|
40
|
+
const argRef = useLatest({ name, props });
|
|
41
|
+
const checkFunc = useMemo(
|
|
42
|
+
() => whatHasUpdatedFactory(argRef.current.name, argRef.current.props),
|
|
43
|
+
[argRef],
|
|
44
|
+
);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
checkFunc(props);
|
|
47
|
+
});
|
|
48
|
+
};
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-molecules",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.20",
|
|
4
4
|
"author": "Thet Aung <thetaung.dev@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.ts",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"components/DatePicker/context.tsx",
|
|
9
|
+
"components/Select/context.tsx",
|
|
10
|
+
"components/TimePicker/context.tsx"
|
|
11
|
+
],
|
|
7
12
|
"files": [
|
|
8
13
|
"components",
|
|
9
14
|
"context-bridge",
|
|
@@ -42,39 +47,38 @@
|
|
|
42
47
|
},
|
|
43
48
|
"dependencies": {
|
|
44
49
|
"@gorhom/portal": "^1.0.14",
|
|
50
|
+
"@radix-ui/react-compose-refs": "^1.1.2",
|
|
45
51
|
"@react-native-vector-icons/feather": "^12.4.0",
|
|
46
52
|
"@react-native-vector-icons/material-design-icons": "^12.4.0",
|
|
47
53
|
"color": "^4.2.3",
|
|
48
54
|
"date-fns": "^4.1.0",
|
|
49
55
|
"eventemitter2": "^6.4.9",
|
|
50
|
-
"
|
|
51
|
-
"lodash.keyby": "^4.6.0",
|
|
52
|
-
"lodash.memoize": "^4.1.2",
|
|
53
|
-
"lodash.omitby": "^4.6.0",
|
|
56
|
+
"flubber": "^0.4.2",
|
|
54
57
|
"use-sync-external-store": "^1.2.0"
|
|
55
58
|
},
|
|
56
59
|
"peerDependencies": {
|
|
57
60
|
"@react-native-documents/picker": "^10.1.2",
|
|
58
61
|
"react": "19.1.0",
|
|
59
62
|
"react-native": "0.81.4",
|
|
60
|
-
"react-native-
|
|
61
|
-
"react-native-
|
|
63
|
+
"react-native-svg": ">=12.1.0",
|
|
64
|
+
"react-native-unistyles": "^3.0.22",
|
|
65
|
+
"react-native-web": "~0.21.1",
|
|
66
|
+
"react-native-reanimated": ">=4.0.0",
|
|
67
|
+
"react-native-redash": ">=18.0.0"
|
|
62
68
|
},
|
|
63
69
|
"devDependencies": {
|
|
70
|
+
"@types/flubber": "^0.4.0",
|
|
71
|
+
"react-native-svg": "^15.10.1",
|
|
64
72
|
"@react-native-documents/picker": "^10.1.2",
|
|
65
73
|
"@types/color": "^3.0.3",
|
|
66
74
|
"@types/jest": "^29.1.2",
|
|
67
|
-
"@types/lodash": "^4.17.13",
|
|
68
|
-
"@types/lodash.get": "^4.4.7",
|
|
69
|
-
"@types/lodash.keyby": "^4.6.7",
|
|
70
|
-
"@types/lodash.memoize": "^4.1.7",
|
|
71
|
-
"@types/lodash.omitby": "^4.6.7",
|
|
72
75
|
"@types/react": "~19.1.10",
|
|
73
76
|
"@types/use-sync-external-store": "^1.5.0",
|
|
74
77
|
"react": "19.1.0",
|
|
75
78
|
"react-native": "0.81.4",
|
|
76
79
|
"react-native-builder-bob": "^0.17.1",
|
|
77
|
-
"react-native-
|
|
80
|
+
"react-native-reanimated": "~4.1.1",
|
|
81
|
+
"react-native-unistyles": "^3.0.22",
|
|
78
82
|
"react-native-web": "~0.21.1"
|
|
79
83
|
},
|
|
80
84
|
"eslintIgnore": [
|
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
type ShortcutsManagerProps,
|
|
11
11
|
} from './utils';
|
|
12
12
|
|
|
13
|
+
const defaultScopes: ShortcutsManagerProps['scopes'] = [];
|
|
14
|
+
const defaultShortcuts: Shortcut[] = [];
|
|
15
|
+
|
|
13
16
|
const _ShortcutsManager = ({ shortcuts, scopes, children }: ShortcutsManagerProps) => {
|
|
14
|
-
const shortcutsRef = useRef<Shortcut[]>(shortcuts ||
|
|
15
|
-
const scopesRef = useRef(scopes);
|
|
17
|
+
const shortcutsRef = useRef<Shortcut[]>(shortcuts || defaultShortcuts);
|
|
18
|
+
const scopesRef = useRef(scopes || defaultScopes);
|
|
16
19
|
const parentContextRef = useContext(ShortcutsManagerContext);
|
|
17
20
|
|
|
18
21
|
const contextValue = useMemo(() => {
|
package/styles/shadow.ts
CHANGED
|
@@ -8,7 +8,8 @@ export const inputRange: MD3Elevation[] = [0, 1, 2, 3, 4, 5];
|
|
|
8
8
|
export const shadowHeight = [0, 1, 2, 4, 6, 8];
|
|
9
9
|
export const shadowRadius = [0, 3, 6, 8, 10, 12];
|
|
10
10
|
|
|
11
|
-
export default function shadow(
|
|
11
|
+
export default function shadow(_elevation: number) {
|
|
12
|
+
const elevation = typeof _elevation === 'number' ? (_elevation > 5 ? 5 : _elevation) : 0;
|
|
12
13
|
return {
|
|
13
14
|
shadowColor: MD3_SHADOW_COLOR,
|
|
14
15
|
shadowOpacity: elevation ? MD3_SHADOW_OPACITY : 0,
|
|
@@ -25,7 +25,7 @@ export const generateLightThemeColors = (
|
|
|
25
25
|
error: palette.error40,
|
|
26
26
|
errorContainer: palette.error90,
|
|
27
27
|
onPrimary: palette.primary100,
|
|
28
|
-
onPrimaryContainer: palette.
|
|
28
|
+
onPrimaryContainer: palette.primary30,
|
|
29
29
|
onSecondary: palette.secondary100,
|
|
30
30
|
onSecondaryContainer: palette.secondary10,
|
|
31
31
|
onTertiary: palette.tertiary100,
|