@utilitywarehouse/hearth-react-native 0.22.0 → 0.23.0-test-list
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/.turbo/turbo-build.log +5 -4
- package/CHANGELOG.md +36 -0
- package/build/components/List/List.js +2 -2
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +17 -5
- package/build/components/Modal/Modal.props.d.ts +1 -0
- package/build/components/ProgressBar/ProgressBar.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBar.js +35 -0
- package/build/components/ProgressBar/ProgressBar.props.d.ts +60 -0
- package/build/components/ProgressBar/ProgressBar.props.js +1 -0
- package/build/components/ProgressBar/ProgressBarCircular.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBarCircular.js +115 -0
- package/build/components/ProgressBar/ProgressBarLinear.d.ts +6 -0
- package/build/components/ProgressBar/ProgressBarLinear.js +79 -0
- package/build/components/ProgressBar/index.d.ts +2 -0
- package/build/components/ProgressBar/index.js +1 -0
- package/build/components/SegmentedControl/SegmentedControl.context.d.ts +14 -0
- package/build/components/SegmentedControl/SegmentedControl.context.js +9 -0
- package/build/components/SegmentedControl/SegmentedControl.d.ts +6 -0
- package/build/components/SegmentedControl/SegmentedControl.js +199 -0
- package/build/components/SegmentedControl/SegmentedControl.props.d.ts +18 -0
- package/build/components/SegmentedControl/SegmentedControl.props.js +1 -0
- package/build/components/SegmentedControl/SegmentedControlOption.d.ts +18 -0
- package/build/components/SegmentedControl/SegmentedControlOption.js +144 -0
- package/build/components/SegmentedControl/SegmentedControlOption.props.d.ts +14 -0
- package/build/components/SegmentedControl/SegmentedControlOption.props.js +1 -0
- package/build/components/SegmentedControl/index.d.ts +4 -0
- package/build/components/SegmentedControl/index.js +2 -0
- package/build/components/TimePicker/TimePicker.d.ts +6 -0
- package/build/components/TimePicker/TimePicker.js +78 -0
- package/build/components/TimePicker/TimePicker.props.d.ts +45 -0
- package/build/components/TimePicker/TimePicker.props.js +1 -0
- package/build/components/TimePicker/TimePickerView.d.ts +12 -0
- package/build/components/TimePicker/TimePickerView.js +130 -0
- package/build/components/TimePicker/TimePickerWheel.d.ts +8 -0
- package/build/components/TimePicker/TimePickerWheel.js +86 -0
- package/build/components/TimePicker/TimePickerWheel.web.d.ts +8 -0
- package/build/components/TimePicker/TimePickerWheel.web.js +122 -0
- package/build/components/TimePicker/index.d.ts +6 -0
- package/build/components/TimePicker/index.js +3 -0
- package/build/components/TimePickerInput/TimePickerInput.d.ts +6 -0
- package/build/components/TimePickerInput/TimePickerInput.js +127 -0
- package/build/components/TimePickerInput/TimePickerInput.props.d.ts +52 -0
- package/build/components/TimePickerInput/TimePickerInput.props.js +1 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +8 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.js +19 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +5 -0
- package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +5 -0
- package/build/components/TimePickerInput/index.d.ts +2 -0
- package/build/components/TimePickerInput/index.js +1 -0
- package/build/components/VerificationInput/VerificationInput.js +182 -20
- package/build/components/VerificationInput/VerificationInput.utils.d.ts +8 -0
- package/build/components/VerificationInput/VerificationInput.utils.js +17 -0
- package/build/components/VerificationInput/VerificationInput.utils.test.d.ts +1 -0
- package/build/components/VerificationInput/VerificationInput.utils.test.js +36 -0
- package/build/components/VerificationInput/VerificationInputSlot.d.ts +7 -3
- package/build/components/VerificationInput/VerificationInputSlot.js +45 -7
- package/docs/changelog.mdx +249 -0
- package/package.json +3 -3
- package/src/components/List/List.tsx +5 -4
- package/src/components/Modal/Modal.docs.mdx +1 -0
- package/src/components/Modal/Modal.props.ts +1 -0
- package/src/components/Modal/Modal.stories.tsx +1 -0
- package/src/components/Modal/Modal.tsx +21 -3
- package/src/components/VerificationInput/VerificationInput.tsx +218 -29
- package/src/components/VerificationInput/VerificationInputSlot.tsx +90 -14
- package/.turbo/turbo-lint.log +0 -72
- package/build/components/VerificationInput/useVerificationInput.d.ts +0 -15
- package/build/components/VerificationInput/useVerificationInput.js +0 -73
- package/src/components/VerificationInput/useVerificationInput.ts +0 -88
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
4
|
-
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @utilitywarehouse/hearth-react-native@0.23.0-test-list build /Users/filmondaniels/Projects/Work/hearth/packages/react-native
|
|
4
|
+
> tsc
|
|
5
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @utilitywarehouse/hearth-react-native
|
|
2
2
|
|
|
3
|
+
## 0.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#975](https://github.com/utilitywarehouse/hearth/pull/975) [`102f04e`](https://github.com/utilitywarehouse/hearth/commit/102f04e0d560cf0faa21da5020c230e88e857251) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add a `background` option for Modal when used inside navigation modals
|
|
8
|
+
|
|
9
|
+
Modal now supports a `background` prop with `default` and `brand` values. When `background="brand"` is used in a navigation modal, the buttons and close icon invert for contrast, and the content area is scrollable.
|
|
10
|
+
|
|
11
|
+
**Components affected**:
|
|
12
|
+
- `Modal`
|
|
13
|
+
|
|
14
|
+
**Developer changes**:
|
|
15
|
+
|
|
16
|
+
No changes required. To opt in to the brand background:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<Modal background="brand" inNavModal>
|
|
20
|
+
...
|
|
21
|
+
</Modal>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 0.22.1
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- [#971](https://github.com/utilitywarehouse/hearth/pull/971) [`be1dfeb`](https://github.com/utilitywarehouse/hearth/commit/be1dfebd4b43f2df8ef6c5eaa42a88364e796479) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Improve VerificationInput OTP handling and accessibility
|
|
29
|
+
|
|
30
|
+
VerificationInput now uses a single hidden input to manage focus, selection, and paste behaviour across platforms, improving caret handling and bulk entry. Accessibility labels and hints are now derived from the form field to provide clearer screen reader output.
|
|
31
|
+
|
|
32
|
+
**Components affected**:
|
|
33
|
+
- `VerificationInput`
|
|
34
|
+
|
|
35
|
+
**Developer changes**:
|
|
36
|
+
|
|
37
|
+
No changes required.
|
|
38
|
+
|
|
3
39
|
## 0.22.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
|
@@ -6,7 +6,7 @@ import { Card } from '../Card';
|
|
|
6
6
|
import { SectionHeader } from '../SectionHeader';
|
|
7
7
|
import { ListContext } from './List.context';
|
|
8
8
|
const List = ({ children, heading, helperText, headerTrailingContent, invalidText, ...props }) => {
|
|
9
|
-
const { loading, disabled, container = 'none' } = props;
|
|
9
|
+
const { loading, disabled, container = 'none', testID, style, ...rest } = props;
|
|
10
10
|
const orderRef = useRef([]);
|
|
11
11
|
const [firstItemId, setFirstItemId] = useState(undefined);
|
|
12
12
|
const containerToCard = {
|
|
@@ -35,7 +35,7 @@ const List = ({ children, heading, helperText, headerTrailingContent, invalidTex
|
|
|
35
35
|
registerItem,
|
|
36
36
|
};
|
|
37
37
|
styles.useVariants({ disabled });
|
|
38
|
-
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...
|
|
38
|
+
return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...rest, style: [styles.container, style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent, invalidText: invalidText })) : null, container === 'none' ? (_jsx(View, { testID: testID, children: children })) : (React.Children.count(children) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, testID: testID, children: _jsx(_Fragment, { children: children }) })))] }) }));
|
|
39
39
|
};
|
|
40
40
|
List.displayName = 'List';
|
|
41
41
|
const styles = StyleSheet.create(theme => ({
|
|
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
|
|
|
3
3
|
type Modal<T = any> = BottomSheetModalMethods<T> & {
|
|
4
4
|
triggerCloseAnimation?: () => void;
|
|
5
5
|
};
|
|
6
|
-
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, background, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export default Modal;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
|
|
3
3
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
4
|
import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
|
5
|
-
import { AccessibilityInfo, Platform, View, findNodeHandle } from 'react-native';
|
|
5
|
+
import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
|
|
6
6
|
import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
|
|
7
7
|
import { StyleSheet } from 'react-native-unistyles';
|
|
8
8
|
import { useTheme } from '../../hooks';
|
|
@@ -13,7 +13,7 @@ import { Button } from '../Button';
|
|
|
13
13
|
import { Heading } from '../Heading';
|
|
14
14
|
import { Spinner } from '../Spinner';
|
|
15
15
|
import { UnstyledIconButton } from '../UnstyledIconButton';
|
|
16
|
-
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, ...props }) => {
|
|
16
|
+
const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, background = 'default', ...props }) => {
|
|
17
17
|
const bottomSheetModalRef = useRef(null);
|
|
18
18
|
const viewRef = useRef(null);
|
|
19
19
|
const scrollViewRef = useRef(null);
|
|
@@ -105,9 +105,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
105
105
|
noButtons,
|
|
106
106
|
stickyFooter,
|
|
107
107
|
showHandle: props.showHandle,
|
|
108
|
+
background: background === 'brand' ? 'brand' : 'primary',
|
|
108
109
|
});
|
|
109
|
-
const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
|
|
110
|
-
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, (!stickyFooter || inNavModal) && !noButtons ? footer : null] })) }));
|
|
110
|
+
const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: background === 'brand' && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: background === 'brand' && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
|
|
111
|
+
const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: background === 'brand' && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, inNavModal ? _jsx(ScrollView, { style: { flex: 1 }, children: children }) : children, (!stickyFooter || inNavModal) && !noButtons ? footer : null] })) }));
|
|
111
112
|
const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
|
|
112
113
|
onPressPrimaryButton,
|
|
113
114
|
primaryButtonText,
|
|
@@ -116,7 +117,10 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
|
|
|
116
117
|
primaryButtonProps,
|
|
117
118
|
secondaryButtonProps,
|
|
118
119
|
]);
|
|
119
|
-
return inNavModal ? (_jsxs(View, { style: {
|
|
120
|
+
return inNavModal ? (_jsxs(View, { style: {
|
|
121
|
+
flex: 1,
|
|
122
|
+
backgroundColor: theme.color.background[background === 'brand' ? 'brand' : 'primary'],
|
|
123
|
+
}, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(View, { style: styles.inNavModalContent, children: content }) })] })) : (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
|
|
120
124
|
};
|
|
121
125
|
const styles = StyleSheet.create((theme, rt) => ({
|
|
122
126
|
modal: {
|
|
@@ -225,6 +229,14 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
225
229
|
gap: theme.components.modal.gap,
|
|
226
230
|
padding: theme.components.modal.padding,
|
|
227
231
|
paddingBottom: theme.components.modal.padding + rt.insets.bottom,
|
|
232
|
+
variants: {
|
|
233
|
+
background: {
|
|
234
|
+
primary: {},
|
|
235
|
+
brand: {
|
|
236
|
+
backgroundColor: theme.color.background.brand,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
228
240
|
},
|
|
229
241
|
androidContainer: {
|
|
230
242
|
height: rt.insets.top + 18,
|
|
@@ -24,5 +24,6 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
|
|
|
24
24
|
primaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
25
25
|
secondaryButtonProps?: Omit<ButtonWithoutChildrenProps, 'children'>;
|
|
26
26
|
closeButtonProps?: Omit<UnstyledIconButtonProps, 'children'>;
|
|
27
|
+
background?: 'default' | 'brand';
|
|
27
28
|
}
|
|
28
29
|
export default ModalProps;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type ProgressBarProps from './ProgressBar.props';
|
|
2
|
+
declare const ProgressBar: {
|
|
3
|
+
({ variant, colorScheme, size, value, min, max, label, hideLabel, formatValueText, "aria-valuetext": ariaValueText, accessibilityLabel, ...rest }: ProgressBarProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
displayName: string;
|
|
5
|
+
};
|
|
6
|
+
export default ProgressBar;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import ProgressBarCircular from './ProgressBarCircular';
|
|
4
|
+
import ProgressBarLinear from './ProgressBarLinear';
|
|
5
|
+
const clampValue = (value, min, max) => {
|
|
6
|
+
if (max <= min)
|
|
7
|
+
return min;
|
|
8
|
+
return Math.min(Math.max(value, min), max);
|
|
9
|
+
};
|
|
10
|
+
const valueToPercent = (value, min, max) => {
|
|
11
|
+
const range = max - min;
|
|
12
|
+
if (range <= 0)
|
|
13
|
+
return 0;
|
|
14
|
+
return ((value - min) / range) * 100;
|
|
15
|
+
};
|
|
16
|
+
const ProgressBar = ({ variant = 'linear', colorScheme = 'default', size = 'md', value, min = 0, max = 100, label, hideLabel, formatValueText, 'aria-valuetext': ariaValueText, accessibilityLabel, ...rest }) => {
|
|
17
|
+
const effectiveValue = colorScheme === 'success' && !formatValueText ? max : clampValue(value, min, max);
|
|
18
|
+
const percentValue = valueToPercent(effectiveValue, min, max);
|
|
19
|
+
const clampedPercent = Math.max(0, Math.min(100, percentValue));
|
|
20
|
+
const valueText = formatValueText
|
|
21
|
+
? formatValueText(effectiveValue, { min, max, percent: clampedPercent })
|
|
22
|
+
: `${Math.round(clampedPercent)}%`;
|
|
23
|
+
const valueTextForAria = ariaValueText ?? valueText;
|
|
24
|
+
const internalProps = {
|
|
25
|
+
percent: clampedPercent,
|
|
26
|
+
label,
|
|
27
|
+
valueText,
|
|
28
|
+
hideLabel,
|
|
29
|
+
colorScheme,
|
|
30
|
+
size,
|
|
31
|
+
};
|
|
32
|
+
return (_jsx(View, { ...rest, accessible: true, role: "progressbar", accessibilityRole: "progressbar", accessibilityLabel: accessibilityLabel ?? label, accessibilityValue: { min, max, now: effectiveValue, text: valueTextForAria }, "aria-valuenow": effectiveValue, "aria-valuemin": min, "aria-valuemax": max, "aria-valuetext": valueTextForAria, "data-colorscheme": colorScheme, children: variant === 'circular' ? (_jsx(ProgressBarCircular, { ...internalProps })) : (_jsx(ProgressBarLinear, { ...internalProps })) }));
|
|
33
|
+
};
|
|
34
|
+
ProgressBar.displayName = 'ProgressBar';
|
|
35
|
+
export default ProgressBar;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ViewProps } from 'react-native';
|
|
2
|
+
export type ProgressBarVariant = 'linear' | 'circular';
|
|
3
|
+
export type ProgressBarColorScheme = 'default' | 'success' | 'danger';
|
|
4
|
+
export type ProgressBarSize = 'sm' | 'md';
|
|
5
|
+
export interface ProgressBarProps extends ViewProps {
|
|
6
|
+
variant?: ProgressBarVariant;
|
|
7
|
+
/**
|
|
8
|
+
* Set the visual appearance.
|
|
9
|
+
* @default 'default'
|
|
10
|
+
*/
|
|
11
|
+
colorScheme?: ProgressBarColorScheme;
|
|
12
|
+
/**
|
|
13
|
+
* Sets the circular variant size. Does not affect the appearance of the linear variant.
|
|
14
|
+
* @default 'md'
|
|
15
|
+
*/
|
|
16
|
+
size?: ProgressBarSize;
|
|
17
|
+
/**
|
|
18
|
+
* The current progress value.
|
|
19
|
+
*/
|
|
20
|
+
value: number;
|
|
21
|
+
/**
|
|
22
|
+
* The minimum value.
|
|
23
|
+
* @default 0
|
|
24
|
+
*/
|
|
25
|
+
min?: number;
|
|
26
|
+
/**
|
|
27
|
+
* The maximum value.
|
|
28
|
+
* @default 100
|
|
29
|
+
*/
|
|
30
|
+
max?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Required text label for the progress bar.
|
|
33
|
+
*/
|
|
34
|
+
label: string;
|
|
35
|
+
/**
|
|
36
|
+
* Visually hide the label and value text.
|
|
37
|
+
*/
|
|
38
|
+
hideLabel?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Override the default percentage value label formatting.
|
|
41
|
+
*/
|
|
42
|
+
formatValueText?: (value: number, meta: {
|
|
43
|
+
min: number;
|
|
44
|
+
max: number;
|
|
45
|
+
percent: number;
|
|
46
|
+
}) => string;
|
|
47
|
+
/**
|
|
48
|
+
* A human-readable text alternative for the current value.
|
|
49
|
+
*/
|
|
50
|
+
'aria-valuetext'?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ProgressBarInternalProps {
|
|
53
|
+
percent: number;
|
|
54
|
+
label: string;
|
|
55
|
+
valueText: string;
|
|
56
|
+
hideLabel?: boolean;
|
|
57
|
+
colorScheme: ProgressBarColorScheme;
|
|
58
|
+
size: ProgressBarSize;
|
|
59
|
+
}
|
|
60
|
+
export default ProgressBarProps;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProgressBarInternalProps } from './ProgressBar.props';
|
|
2
|
+
declare const ProgressBarCircular: {
|
|
3
|
+
({ percent, label, valueText, hideLabel, colorScheme, size, }: ProgressBarInternalProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
displayName: string;
|
|
5
|
+
};
|
|
6
|
+
export default ProgressBarCircular;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { Text, View } from 'react-native';
|
|
4
|
+
import Animated, { Easing, useAnimatedProps, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
5
|
+
import { Circle, G, Svg } from 'react-native-svg';
|
|
6
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
7
|
+
import useTheme from '../../hooks/useTheme';
|
|
8
|
+
import { BodyText } from '../BodyText';
|
|
9
|
+
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
|
|
10
|
+
const ProgressBarCircular = ({ percent, label, valueText, hideLabel, colorScheme, size, }) => {
|
|
11
|
+
const { components } = useTheme();
|
|
12
|
+
const isReducedMotion = useReducedMotion();
|
|
13
|
+
const progress = useSharedValue(0);
|
|
14
|
+
const hasMountedRef = useRef(false);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const target = Math.max(0, Math.min(100, percent)) / 100;
|
|
17
|
+
if (isReducedMotion) {
|
|
18
|
+
progress.value = target;
|
|
19
|
+
hasMountedRef.current = true;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!hasMountedRef.current) {
|
|
23
|
+
progress.value = target;
|
|
24
|
+
hasMountedRef.current = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
|
|
28
|
+
}, [percent, isReducedMotion, progress]);
|
|
29
|
+
const circularTokens = components.progressBar.circular[size];
|
|
30
|
+
const barWidth = 'bar' in circularTokens ? circularTokens.bar.width : circularTokens.barWidth;
|
|
31
|
+
const diameter = circularTokens.height;
|
|
32
|
+
const radius = (diameter - barWidth) / 2;
|
|
33
|
+
const circumference = 2 * Math.PI * radius;
|
|
34
|
+
const animatedCircleProps = useAnimatedProps(() => ({
|
|
35
|
+
strokeDashoffset: circumference * (1 - progress.value),
|
|
36
|
+
}));
|
|
37
|
+
const indicatorColor = colorScheme === 'success'
|
|
38
|
+
? components.progressBar.progress.successColor
|
|
39
|
+
: colorScheme === 'danger'
|
|
40
|
+
? components.progressBar.progress.dangerColor
|
|
41
|
+
: components.progressBar.progress.defaultColor;
|
|
42
|
+
styles.useVariants({ size });
|
|
43
|
+
return (_jsxs(View, { style: styles.container, children: [_jsx(Svg, { width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, style: styles.svg, children: _jsxs(G, { origin: `${diameter / 2}, ${diameter / 2}`, rotation: -90, children: [_jsx(Circle, { cx: "50%", cy: "50%", r: radius, stroke: components.progressBar.barColor, strokeWidth: barWidth, fill: "transparent" }), _jsx(AnimatedCircle, { cx: "50%", cy: "50%", r: radius, stroke: indicatorColor, strokeWidth: barWidth, fill: "transparent", strokeLinecap: "round", strokeDasharray: circumference, animatedProps: animatedCircleProps })] }) }), _jsxs(View, { style: styles.content, children: [_jsx(Text, { style: styles.valueText, children: valueText }), !hideLabel && size === 'md' ? (_jsx(BodyText, { style: styles.label, size: "md", weight: "semibold", children: label })) : null] })] }));
|
|
44
|
+
};
|
|
45
|
+
ProgressBarCircular.displayName = 'ProgressBarCircular';
|
|
46
|
+
const styles = StyleSheet.create(theme => ({
|
|
47
|
+
container: {
|
|
48
|
+
alignItems: 'center',
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
position: 'relative',
|
|
51
|
+
variants: {
|
|
52
|
+
size: {
|
|
53
|
+
md: {
|
|
54
|
+
width: theme.components.progressBar.circular.md.height,
|
|
55
|
+
height: theme.components.progressBar.circular.md.height,
|
|
56
|
+
},
|
|
57
|
+
sm: {
|
|
58
|
+
width: theme.components.progressBar.circular.sm.height,
|
|
59
|
+
height: theme.components.progressBar.circular.sm.height,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
svg: {
|
|
65
|
+
position: 'absolute',
|
|
66
|
+
top: 0,
|
|
67
|
+
left: 0,
|
|
68
|
+
},
|
|
69
|
+
content: {
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
justifyContent: 'center',
|
|
72
|
+
_web: {
|
|
73
|
+
position: 'absolute',
|
|
74
|
+
top: 0,
|
|
75
|
+
left: 0,
|
|
76
|
+
width: '100%',
|
|
77
|
+
height: '100%',
|
|
78
|
+
},
|
|
79
|
+
variants: {
|
|
80
|
+
size: {
|
|
81
|
+
md: {
|
|
82
|
+
gap: theme.components.progressBar.circular.md.gap,
|
|
83
|
+
},
|
|
84
|
+
sm: {
|
|
85
|
+
gap: 0,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
valueText: {
|
|
91
|
+
color: theme.color.text.primary,
|
|
92
|
+
textAlign: 'center',
|
|
93
|
+
variants: {
|
|
94
|
+
size: {
|
|
95
|
+
md: {
|
|
96
|
+
fontFamily: theme.components.progressBar.circular.md.label.fontFamily,
|
|
97
|
+
fontSize: theme.components.progressBar.circular.md.label.fontSize,
|
|
98
|
+
lineHeight: theme.components.progressBar.circular.md.label.lineHeight,
|
|
99
|
+
fontWeight: theme.components.progressBar.circular.md.label.fontWeight,
|
|
100
|
+
},
|
|
101
|
+
sm: {
|
|
102
|
+
fontFamily: theme.typography.mobile.bodyText.fontFamily,
|
|
103
|
+
fontSize: theme.typography.mobile.bodyText.md.fontSize,
|
|
104
|
+
lineHeight: theme.typography.mobile.bodyText.md.lineHeight,
|
|
105
|
+
fontWeight: theme.fontWeight.semibold,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
label: {
|
|
111
|
+
textAlign: 'center',
|
|
112
|
+
maxWidth: 90,
|
|
113
|
+
},
|
|
114
|
+
}));
|
|
115
|
+
export default ProgressBarCircular;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ProgressBarInternalProps } from './ProgressBar.props';
|
|
2
|
+
declare const ProgressBarLinear: {
|
|
3
|
+
({ percent, label, valueText, hideLabel, colorScheme, }: ProgressBarInternalProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
displayName: string;
|
|
5
|
+
};
|
|
6
|
+
export default ProgressBarLinear;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { Platform, View } from 'react-native';
|
|
4
|
+
import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated';
|
|
5
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
6
|
+
import useTheme from '../../hooks/useTheme';
|
|
7
|
+
import { BodyText } from '../BodyText';
|
|
8
|
+
const ProgressBarLinear = ({ percent, label, valueText, hideLabel, colorScheme, }) => {
|
|
9
|
+
const { components } = useTheme();
|
|
10
|
+
const isReducedMotion = useReducedMotion();
|
|
11
|
+
const progress = useSharedValue(0);
|
|
12
|
+
const hasMountedRef = useRef(false);
|
|
13
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const target = Math.max(0, Math.min(100, percent)) / 100;
|
|
16
|
+
if (isReducedMotion) {
|
|
17
|
+
progress.value = target;
|
|
18
|
+
hasMountedRef.current = true;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (!hasMountedRef.current) {
|
|
22
|
+
progress.value = target;
|
|
23
|
+
hasMountedRef.current = true;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
|
|
27
|
+
}, [percent, isReducedMotion, progress]);
|
|
28
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
29
|
+
width: trackWidth * progress.value,
|
|
30
|
+
}));
|
|
31
|
+
const indicatorColor = colorScheme === 'success'
|
|
32
|
+
? components.progressBar.progress.successColor
|
|
33
|
+
: colorScheme === 'danger'
|
|
34
|
+
? components.progressBar.progress.dangerColor
|
|
35
|
+
: components.progressBar.progress.defaultColor;
|
|
36
|
+
const handleTrackLayout = (event) => {
|
|
37
|
+
setTrackWidth(event.nativeEvent.layout.width);
|
|
38
|
+
};
|
|
39
|
+
return (_jsxs(View, { style: styles.container, children: [!hideLabel && (_jsxs(View, { style: styles.header, children: [_jsx(BodyText, { size: "md", weight: "semibold", style: styles.label, children: label }), _jsx(BodyText, { size: "md", style: styles.value, children: valueText })] })), _jsx(View, { style: styles.track, onLayout: handleTrackLayout, children: Platform.OS === 'web' ? (_jsx(View, { style: [
|
|
40
|
+
styles.indicator,
|
|
41
|
+
{ width: `${Math.max(0, Math.min(100, percent))}%` },
|
|
42
|
+
{ backgroundColor: indicatorColor },
|
|
43
|
+
] })) : (_jsx(Animated.View, { style: [styles.indicator, animatedStyle, { backgroundColor: indicatorColor }] })) })] }));
|
|
44
|
+
};
|
|
45
|
+
ProgressBarLinear.displayName = 'ProgressBarLinear';
|
|
46
|
+
const styles = StyleSheet.create(theme => ({
|
|
47
|
+
container: {
|
|
48
|
+
width: '100%',
|
|
49
|
+
gap: theme.components.progressBar.linear.gap,
|
|
50
|
+
},
|
|
51
|
+
header: {
|
|
52
|
+
flexDirection: 'row',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
justifyContent: 'space-between',
|
|
55
|
+
gap: theme.components.progressBar.linear.label.gap,
|
|
56
|
+
},
|
|
57
|
+
label: {
|
|
58
|
+
flex: 1,
|
|
59
|
+
},
|
|
60
|
+
value: {
|
|
61
|
+
flexShrink: 0,
|
|
62
|
+
textAlign: 'right',
|
|
63
|
+
},
|
|
64
|
+
track: {
|
|
65
|
+
width: '100%',
|
|
66
|
+
height: theme.components.progressBar.linear.bar.height,
|
|
67
|
+
backgroundColor: theme.components.progressBar.barColor,
|
|
68
|
+
borderRadius: theme.components.progressBar.linear.bar.borderRadius,
|
|
69
|
+
overflow: 'hidden',
|
|
70
|
+
},
|
|
71
|
+
indicator: {
|
|
72
|
+
height: '100%',
|
|
73
|
+
borderRadius: theme.components.progressBar.linear.bar.borderRadius,
|
|
74
|
+
_web: {
|
|
75
|
+
height: '100%',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
export default ProgressBarLinear;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ProgressBar } from './ProgressBar';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SegmentedControlContextValue = {
|
|
2
|
+
value?: string;
|
|
3
|
+
select: (value: string) => void;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
size: 'sm' | 'md';
|
|
6
|
+
registerOptionLayout: (value: string, layout: {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}) => void;
|
|
12
|
+
};
|
|
13
|
+
export declare const SegmentedControlContext: import("react").Context<SegmentedControlContextValue | null>;
|
|
14
|
+
export declare const useSegmentedControlContext: () => SegmentedControlContextValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
export const SegmentedControlContext = createContext(null);
|
|
3
|
+
export const useSegmentedControlContext = () => {
|
|
4
|
+
const context = useContext(SegmentedControlContext);
|
|
5
|
+
if (!context) {
|
|
6
|
+
throw new Error('SegmentedControlOption must be used within SegmentedControl');
|
|
7
|
+
}
|
|
8
|
+
return context;
|
|
9
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type SegmentedControlProps from './SegmentedControl.props';
|
|
2
|
+
declare const SegmentedControl: {
|
|
3
|
+
({ value: controlledValue, defaultValue, onValueChange, size, disabled, children, style, ...props }: SegmentedControlProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
displayName: string;
|
|
5
|
+
};
|
|
6
|
+
export default SegmentedControl;
|