@utilitywarehouse/hearth-react-native 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +13 -13
  3. package/CHANGELOG.md +77 -0
  4. package/build/components/DatePicker/DatePickerCalendar.js +4 -9
  5. package/build/components/Modal/Modal.js +5 -4
  6. package/build/components/Modal/Modal.props.d.ts +10 -4
  7. package/build/components/ProgressBar/ProgressBar.d.ts +6 -0
  8. package/build/components/ProgressBar/ProgressBar.js +35 -0
  9. package/build/components/ProgressBar/ProgressBar.props.d.ts +60 -0
  10. package/build/components/ProgressBar/ProgressBar.props.js +1 -0
  11. package/build/components/ProgressBar/ProgressBarCircular.d.ts +6 -0
  12. package/build/components/ProgressBar/ProgressBarCircular.js +115 -0
  13. package/build/components/ProgressBar/ProgressBarLinear.d.ts +6 -0
  14. package/build/components/ProgressBar/ProgressBarLinear.js +79 -0
  15. package/build/components/ProgressBar/index.d.ts +2 -0
  16. package/build/components/ProgressBar/index.js +1 -0
  17. package/build/components/TimePicker/TimePicker.d.ts +6 -0
  18. package/build/components/TimePicker/TimePicker.js +78 -0
  19. package/build/components/TimePicker/TimePicker.props.d.ts +45 -0
  20. package/build/components/TimePicker/TimePicker.props.js +1 -0
  21. package/build/components/TimePicker/TimePickerView.d.ts +12 -0
  22. package/build/components/TimePicker/TimePickerView.js +130 -0
  23. package/build/components/TimePicker/TimePickerWheel.d.ts +8 -0
  24. package/build/components/TimePicker/TimePickerWheel.js +78 -0
  25. package/build/components/{DatePicker/time-picker/wheel-web.d.ts → TimePicker/TimePickerWheel.web.d.ts} +4 -4
  26. package/build/components/TimePicker/TimePickerWheel.web.js +122 -0
  27. package/build/components/TimePicker/index.d.ts +6 -0
  28. package/build/components/TimePicker/index.js +3 -0
  29. package/build/components/TimePickerInput/TimePickerInput.d.ts +6 -0
  30. package/build/components/TimePickerInput/TimePickerInput.js +127 -0
  31. package/build/components/TimePickerInput/TimePickerInput.props.d.ts +52 -0
  32. package/build/components/TimePickerInput/TimePickerInput.props.js +1 -0
  33. package/build/components/TimePickerInput/TimePickerInputDoneButton.d.ts +8 -0
  34. package/build/components/TimePickerInput/TimePickerInputDoneButton.js +19 -0
  35. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.d.ts +5 -0
  36. package/build/components/TimePickerInput/TimePickerInputDoneButton.web.js +5 -0
  37. package/build/components/TimePickerInput/index.d.ts +2 -0
  38. package/build/components/TimePickerInput/index.js +1 -0
  39. package/build/components/index.d.ts +3 -0
  40. package/build/components/index.js +3 -0
  41. package/docs/components/AllComponents.web.tsx +36 -0
  42. package/package.json +2 -1
  43. package/src/components/DatePicker/DatePickerCalendar.tsx +30 -13
  44. package/src/components/Modal/Modal.props.ts +13 -4
  45. package/src/components/Modal/Modal.stories.tsx +1 -1
  46. package/src/components/Modal/Modal.tsx +28 -11
  47. package/src/components/ProgressBar/ProgressBar.docs.mdx +90 -0
  48. package/src/components/ProgressBar/ProgressBar.figma.tsx +79 -0
  49. package/src/components/ProgressBar/ProgressBar.props.ts +60 -0
  50. package/src/components/ProgressBar/ProgressBar.stories.tsx +117 -0
  51. package/src/components/ProgressBar/ProgressBar.tsx +74 -0
  52. package/src/components/ProgressBar/ProgressBarCircular.tsx +181 -0
  53. package/src/components/ProgressBar/ProgressBarLinear.tsx +127 -0
  54. package/src/components/ProgressBar/index.ts +7 -0
  55. package/src/components/TimePicker/TimePicker.docs.mdx +84 -0
  56. package/src/components/TimePicker/TimePicker.figma.tsx +29 -0
  57. package/src/components/TimePicker/TimePicker.props.ts +45 -0
  58. package/src/components/TimePicker/TimePicker.stories.tsx +85 -0
  59. package/src/components/TimePicker/TimePicker.tsx +150 -0
  60. package/src/components/TimePicker/TimePickerView.tsx +216 -0
  61. package/src/components/TimePicker/TimePickerWheel.tsx +154 -0
  62. package/src/components/TimePicker/TimePickerWheel.web.tsx +217 -0
  63. package/src/components/TimePicker/index.ts +8 -0
  64. package/src/components/TimePickerInput/TimePickerInput.docs.mdx +135 -0
  65. package/src/components/TimePickerInput/TimePickerInput.figma.tsx +34 -0
  66. package/src/components/TimePickerInput/TimePickerInput.props.ts +55 -0
  67. package/src/components/TimePickerInput/TimePickerInput.stories.tsx +175 -0
  68. package/src/components/TimePickerInput/TimePickerInput.tsx +283 -0
  69. package/src/components/TimePickerInput/TimePickerInputDoneButton.tsx +42 -0
  70. package/src/components/TimePickerInput/TimePickerInputDoneButton.web.tsx +7 -0
  71. package/src/components/TimePickerInput/index.ts +2 -0
  72. package/src/components/index.ts +3 -0
  73. package/build/components/DatePicker/TimePicker.d.ts +0 -3
  74. package/build/components/DatePicker/TimePicker.js +0 -84
  75. package/build/components/DatePicker/time-picker/animated-math.d.ts +0 -4
  76. package/build/components/DatePicker/time-picker/animated-math.js +0 -19
  77. package/build/components/DatePicker/time-picker/period-native.d.ts +0 -6
  78. package/build/components/DatePicker/time-picker/period-native.js +0 -17
  79. package/build/components/DatePicker/time-picker/period-picker.d.ts +0 -6
  80. package/build/components/DatePicker/time-picker/period-picker.js +0 -10
  81. package/build/components/DatePicker/time-picker/period-web.d.ts +0 -6
  82. package/build/components/DatePicker/time-picker/period-web.js +0 -21
  83. package/build/components/DatePicker/time-picker/wheel-native.d.ts +0 -8
  84. package/build/components/DatePicker/time-picker/wheel-native.js +0 -19
  85. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +0 -2
  86. package/build/components/DatePicker/time-picker/wheel-picker/index.js +0 -2
  87. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +0 -16
  88. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +0 -97
  89. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +0 -21
  90. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +0 -88
  91. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +0 -23
  92. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +0 -21
  93. package/build/components/DatePicker/time-picker/wheel-web.js +0 -146
  94. package/build/components/DatePicker/time-picker/wheel.d.ts +0 -8
  95. package/build/components/DatePicker/time-picker/wheel.js +0 -10
  96. package/src/components/DatePicker/TimePicker.tsx +0 -141
  97. package/src/components/DatePicker/time-picker/animated-math.ts +0 -33
  98. package/src/components/DatePicker/time-picker/period-native.tsx +0 -34
  99. package/src/components/DatePicker/time-picker/period-picker.tsx +0 -16
  100. package/src/components/DatePicker/time-picker/period-web.tsx +0 -36
  101. package/src/components/DatePicker/time-picker/wheel-native.tsx +0 -37
  102. package/src/components/DatePicker/time-picker/wheel-picker/index.ts +0 -3
  103. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.tsx +0 -132
  104. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.ts +0 -22
  105. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.tsx +0 -200
  106. package/src/components/DatePicker/time-picker/wheel-web.tsx +0 -180
  107. package/src/components/DatePicker/time-picker/wheel.tsx +0 -18
@@ -0,0 +1,117 @@
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
+ import type { ComponentProps } from 'react';
3
+ import { ProgressBar } from '.';
4
+ import { VariantTitle } from '../../../docs/components';
5
+ import { Box } from '../Box';
6
+
7
+ const meta = {
8
+ title: 'Stories / ProgressBar',
9
+ component: ProgressBar,
10
+ parameters: {
11
+ layout: 'centered',
12
+ },
13
+ argTypes: {
14
+ variant: {
15
+ options: ['linear', 'circular'],
16
+ control: 'select',
17
+ description: 'The progress bar variant.',
18
+ },
19
+ colorScheme: {
20
+ options: ['default', 'success', 'danger'],
21
+ control: 'select',
22
+ description: 'The color scheme for the progress indicator.',
23
+ },
24
+ size: {
25
+ options: ['sm', 'md'],
26
+ control: 'select',
27
+ description: 'The circular size. Only applies to the circular variant.',
28
+ },
29
+ },
30
+ args: {
31
+ variant: 'linear',
32
+ colorScheme: 'default',
33
+ size: 'md',
34
+ value: 35,
35
+ min: 0,
36
+ max: 100,
37
+ label: 'Progress',
38
+ },
39
+ } satisfies Meta<typeof ProgressBar>;
40
+
41
+ export default meta;
42
+
43
+ type Story = StoryObj<typeof meta>;
44
+ type ProgressBarStoryArgs = ComponentProps<typeof ProgressBar>;
45
+
46
+ export const Playground: Story = {};
47
+
48
+ export const Variants: Story = {
49
+ args: {
50
+ value: 55,
51
+ label: 'Downloads',
52
+ },
53
+ render: (args: ProgressBarStoryArgs) => (
54
+ <Box gap="300" style={{ width: 260 }}>
55
+ <VariantTitle title="Linear">
56
+ <ProgressBar {...args} variant="linear" />
57
+ </VariantTitle>
58
+ <VariantTitle title="Circular">
59
+ <ProgressBar {...args} variant="circular" />
60
+ </VariantTitle>
61
+ </Box>
62
+ ),
63
+ };
64
+
65
+ export const ColorSchemes: Story = {
66
+ args: {
67
+ value: 72,
68
+ label: 'Storage',
69
+ variant: 'linear',
70
+ },
71
+ render: (args: ProgressBarStoryArgs) => (
72
+ <Box gap="300" style={{ width: 260 }}>
73
+ <VariantTitle title="Default">
74
+ <ProgressBar {...args} colorScheme="default" />
75
+ </VariantTitle>
76
+ <VariantTitle title="Success">
77
+ <ProgressBar {...args} colorScheme="success" />
78
+ </VariantTitle>
79
+ <VariantTitle title="Danger">
80
+ <ProgressBar {...args} colorScheme="danger" />
81
+ </VariantTitle>
82
+ </Box>
83
+ ),
84
+ };
85
+
86
+ export const CircularSizes: Story = {
87
+ args: {
88
+ value: 65,
89
+ label: 'Usage',
90
+ variant: 'circular',
91
+ },
92
+ render: (args: ProgressBarStoryArgs) => (
93
+ <Box gap="300">
94
+ <VariantTitle title="Small">
95
+ <ProgressBar {...args} size="sm" />
96
+ </VariantTitle>
97
+ <VariantTitle title="Medium">
98
+ <ProgressBar {...args} size="md" />
99
+ </VariantTitle>
100
+ </Box>
101
+ ),
102
+ };
103
+
104
+ export const CustomValueLabels: Story = {
105
+ args: {
106
+ value: 68,
107
+ max: 100,
108
+ label: 'Data allowance',
109
+ variant: 'linear',
110
+ formatValueText: (value: number, { max }: { max: number }) => `${max - value}GB remaining`,
111
+ },
112
+ render: (args: ProgressBarStoryArgs) => (
113
+ <Box style={{ width: 260 }}>
114
+ <ProgressBar {...args} />
115
+ </Box>
116
+ ),
117
+ };
@@ -0,0 +1,74 @@
1
+ import { View } from 'react-native';
2
+ import type ProgressBarProps from './ProgressBar.props';
3
+ import ProgressBarCircular from './ProgressBarCircular';
4
+ import ProgressBarLinear from './ProgressBarLinear';
5
+
6
+ const clampValue = (value: number, min: number, max: number) => {
7
+ if (max <= min) return min;
8
+ return Math.min(Math.max(value, min), max);
9
+ };
10
+
11
+ const valueToPercent = (value: number, min: number, max: number) => {
12
+ const range = max - min;
13
+ if (range <= 0) return 0;
14
+ return ((value - min) / range) * 100;
15
+ };
16
+
17
+ const ProgressBar = ({
18
+ variant = 'linear',
19
+ colorScheme = 'default',
20
+ size = 'md',
21
+ value,
22
+ min = 0,
23
+ max = 100,
24
+ label,
25
+ hideLabel,
26
+ formatValueText,
27
+ 'aria-valuetext': ariaValueText,
28
+ accessibilityLabel,
29
+ ...rest
30
+ }: ProgressBarProps) => {
31
+ const effectiveValue =
32
+ colorScheme === 'success' && !formatValueText ? max : clampValue(value, min, max);
33
+ const percentValue = valueToPercent(effectiveValue, min, max);
34
+ const clampedPercent = Math.max(0, Math.min(100, percentValue));
35
+ const valueText = formatValueText
36
+ ? formatValueText(effectiveValue, { min, max, percent: clampedPercent })
37
+ : `${Math.round(clampedPercent)}%`;
38
+ const valueTextForAria = ariaValueText ?? valueText;
39
+
40
+ const internalProps = {
41
+ percent: clampedPercent,
42
+ label,
43
+ valueText,
44
+ hideLabel,
45
+ colorScheme,
46
+ size,
47
+ };
48
+
49
+ return (
50
+ <View
51
+ {...rest}
52
+ accessible
53
+ role="progressbar"
54
+ accessibilityRole="progressbar"
55
+ accessibilityLabel={accessibilityLabel ?? label}
56
+ accessibilityValue={{ min, max, now: effectiveValue, text: valueTextForAria }}
57
+ aria-valuenow={effectiveValue}
58
+ aria-valuemin={min}
59
+ aria-valuemax={max}
60
+ aria-valuetext={valueTextForAria}
61
+ data-colorscheme={colorScheme}
62
+ >
63
+ {variant === 'circular' ? (
64
+ <ProgressBarCircular {...internalProps} />
65
+ ) : (
66
+ <ProgressBarLinear {...internalProps} />
67
+ )}
68
+ </View>
69
+ );
70
+ };
71
+
72
+ ProgressBar.displayName = 'ProgressBar';
73
+
74
+ export default ProgressBar;
@@ -0,0 +1,181 @@
1
+ import React, { useEffect, useRef } from 'react';
2
+ import { Text, View } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ useAnimatedProps,
6
+ useReducedMotion,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import { Circle, G, Svg } from 'react-native-svg';
11
+ import { StyleSheet } from 'react-native-unistyles';
12
+ import useTheme from '../../hooks/useTheme';
13
+ import { BodyText } from '../BodyText';
14
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
15
+
16
+ const AnimatedCircle = Animated.createAnimatedComponent(Circle as React.ComponentType<any>);
17
+
18
+ const ProgressBarCircular = ({
19
+ percent,
20
+ label,
21
+ valueText,
22
+ hideLabel,
23
+ colorScheme,
24
+ size,
25
+ }: ProgressBarInternalProps) => {
26
+ const { components } = useTheme();
27
+ const isReducedMotion = useReducedMotion();
28
+ const progress = useSharedValue(0);
29
+ const hasMountedRef = useRef(false);
30
+
31
+ useEffect(() => {
32
+ const target = Math.max(0, Math.min(100, percent)) / 100;
33
+ if (isReducedMotion) {
34
+ progress.value = target;
35
+ hasMountedRef.current = true;
36
+ return;
37
+ }
38
+
39
+ if (!hasMountedRef.current) {
40
+ progress.value = target;
41
+ hasMountedRef.current = true;
42
+ return;
43
+ }
44
+
45
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
46
+ }, [percent, isReducedMotion, progress]);
47
+
48
+ const circularTokens = components.progressBar.circular[size];
49
+ const barWidth = 'bar' in circularTokens ? circularTokens.bar.width : circularTokens.barWidth;
50
+ const diameter = circularTokens.height;
51
+ const radius = (diameter - barWidth) / 2;
52
+ const circumference = 2 * Math.PI * radius;
53
+
54
+ const animatedCircleProps = useAnimatedProps(() => ({
55
+ strokeDashoffset: circumference * (1 - progress.value),
56
+ }));
57
+
58
+ const indicatorColor =
59
+ colorScheme === 'success'
60
+ ? components.progressBar.progress.successColor
61
+ : colorScheme === 'danger'
62
+ ? components.progressBar.progress.dangerColor
63
+ : components.progressBar.progress.defaultColor;
64
+
65
+ styles.useVariants({ size });
66
+
67
+ return (
68
+ <View style={styles.container}>
69
+ <Svg
70
+ width={diameter}
71
+ height={diameter}
72
+ viewBox={`0 0 ${diameter} ${diameter}`}
73
+ style={styles.svg}
74
+ >
75
+ <G origin={`${diameter / 2}, ${diameter / 2}`} rotation={-90}>
76
+ <Circle
77
+ cx="50%"
78
+ cy="50%"
79
+ r={radius}
80
+ stroke={components.progressBar.barColor}
81
+ strokeWidth={barWidth}
82
+ fill="transparent"
83
+ />
84
+ <AnimatedCircle
85
+ cx="50%"
86
+ cy="50%"
87
+ r={radius}
88
+ stroke={indicatorColor}
89
+ strokeWidth={barWidth}
90
+ fill="transparent"
91
+ strokeLinecap="round"
92
+ strokeDasharray={circumference}
93
+ animatedProps={animatedCircleProps}
94
+ />
95
+ </G>
96
+ </Svg>
97
+ <View style={styles.content}>
98
+ <Text style={styles.valueText}>{valueText}</Text>
99
+ {!hideLabel && size === 'md' ? (
100
+ <BodyText style={styles.label} size="md" weight="semibold">
101
+ {label}
102
+ </BodyText>
103
+ ) : null}
104
+ </View>
105
+ </View>
106
+ );
107
+ };
108
+
109
+ ProgressBarCircular.displayName = 'ProgressBarCircular';
110
+
111
+ const styles = StyleSheet.create(theme => ({
112
+ container: {
113
+ alignItems: 'center',
114
+ justifyContent: 'center',
115
+ position: 'relative',
116
+ variants: {
117
+ size: {
118
+ md: {
119
+ width: theme.components.progressBar.circular.md.height,
120
+ height: theme.components.progressBar.circular.md.height,
121
+ },
122
+ sm: {
123
+ width: theme.components.progressBar.circular.sm.height,
124
+ height: theme.components.progressBar.circular.sm.height,
125
+ },
126
+ },
127
+ },
128
+ },
129
+ svg: {
130
+ position: 'absolute',
131
+ top: 0,
132
+ left: 0,
133
+ },
134
+ content: {
135
+ alignItems: 'center',
136
+ justifyContent: 'center',
137
+ _web: {
138
+ position: 'absolute',
139
+ top: 0,
140
+ left: 0,
141
+ width: '100%',
142
+ height: '100%',
143
+ },
144
+ variants: {
145
+ size: {
146
+ md: {
147
+ gap: theme.components.progressBar.circular.md.gap,
148
+ },
149
+ sm: {
150
+ gap: 0,
151
+ },
152
+ },
153
+ },
154
+ },
155
+ valueText: {
156
+ color: theme.color.text.primary,
157
+ textAlign: 'center',
158
+ variants: {
159
+ size: {
160
+ md: {
161
+ fontFamily: theme.components.progressBar.circular.md.label.fontFamily,
162
+ fontSize: theme.components.progressBar.circular.md.label.fontSize,
163
+ lineHeight: theme.components.progressBar.circular.md.label.lineHeight,
164
+ fontWeight: theme.components.progressBar.circular.md.label.fontWeight,
165
+ },
166
+ sm: {
167
+ fontFamily: theme.typography.mobile.bodyText.fontFamily,
168
+ fontSize: theme.typography.mobile.bodyText.md.fontSize,
169
+ lineHeight: theme.typography.mobile.bodyText.md.lineHeight,
170
+ fontWeight: theme.fontWeight.semibold,
171
+ },
172
+ },
173
+ },
174
+ },
175
+ label: {
176
+ textAlign: 'center',
177
+ maxWidth: 90,
178
+ },
179
+ }));
180
+
181
+ export default ProgressBarCircular;
@@ -0,0 +1,127 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { LayoutChangeEvent, Platform, View } from 'react-native';
3
+ import Animated, {
4
+ Easing,
5
+ useAnimatedStyle,
6
+ useReducedMotion,
7
+ useSharedValue,
8
+ withTiming,
9
+ } from 'react-native-reanimated';
10
+ import { StyleSheet } from 'react-native-unistyles';
11
+ import useTheme from '../../hooks/useTheme';
12
+ import { BodyText } from '../BodyText';
13
+ import type { ProgressBarInternalProps } from './ProgressBar.props';
14
+
15
+ const ProgressBarLinear = ({
16
+ percent,
17
+ label,
18
+ valueText,
19
+ hideLabel,
20
+ colorScheme,
21
+ }: ProgressBarInternalProps) => {
22
+ const { components } = useTheme();
23
+ const isReducedMotion = useReducedMotion();
24
+ const progress = useSharedValue(0);
25
+ const hasMountedRef = useRef(false);
26
+ const [trackWidth, setTrackWidth] = useState(0);
27
+
28
+ useEffect(() => {
29
+ const target = Math.max(0, Math.min(100, percent)) / 100;
30
+ if (isReducedMotion) {
31
+ progress.value = target;
32
+ hasMountedRef.current = true;
33
+ return;
34
+ }
35
+
36
+ if (!hasMountedRef.current) {
37
+ progress.value = target;
38
+ hasMountedRef.current = true;
39
+ return;
40
+ }
41
+
42
+ progress.value = withTiming(target, { duration: 300, easing: Easing.out(Easing.ease) });
43
+ }, [percent, isReducedMotion, progress]);
44
+
45
+ const animatedStyle = useAnimatedStyle(() => ({
46
+ width: trackWidth * progress.value,
47
+ }));
48
+
49
+ const indicatorColor =
50
+ colorScheme === 'success'
51
+ ? components.progressBar.progress.successColor
52
+ : colorScheme === 'danger'
53
+ ? components.progressBar.progress.dangerColor
54
+ : components.progressBar.progress.defaultColor;
55
+
56
+ const handleTrackLayout = (event: LayoutChangeEvent) => {
57
+ setTrackWidth(event.nativeEvent.layout.width);
58
+ };
59
+
60
+ return (
61
+ <View style={styles.container}>
62
+ {!hideLabel && (
63
+ <View style={styles.header}>
64
+ <BodyText size="md" weight="semibold" style={styles.label}>
65
+ {label}
66
+ </BodyText>
67
+ <BodyText size="md" style={styles.value}>
68
+ {valueText}
69
+ </BodyText>
70
+ </View>
71
+ )}
72
+ <View style={styles.track} onLayout={handleTrackLayout}>
73
+ {Platform.OS === 'web' ? (
74
+ <View
75
+ style={[
76
+ styles.indicator,
77
+ { width: `${Math.max(0, Math.min(100, percent))}%` },
78
+ { backgroundColor: indicatorColor },
79
+ ]}
80
+ />
81
+ ) : (
82
+ <Animated.View
83
+ style={[styles.indicator, animatedStyle, { backgroundColor: indicatorColor }]}
84
+ />
85
+ )}
86
+ </View>
87
+ </View>
88
+ );
89
+ };
90
+
91
+ ProgressBarLinear.displayName = 'ProgressBarLinear';
92
+
93
+ const styles = StyleSheet.create(theme => ({
94
+ container: {
95
+ width: '100%',
96
+ gap: theme.components.progressBar.linear.gap,
97
+ },
98
+ header: {
99
+ flexDirection: 'row',
100
+ alignItems: 'center',
101
+ justifyContent: 'space-between',
102
+ gap: theme.components.progressBar.linear.label.gap,
103
+ },
104
+ label: {
105
+ flex: 1,
106
+ },
107
+ value: {
108
+ flexShrink: 0,
109
+ textAlign: 'right',
110
+ },
111
+ track: {
112
+ width: '100%',
113
+ height: theme.components.progressBar.linear.bar.height,
114
+ backgroundColor: theme.components.progressBar.barColor,
115
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
116
+ overflow: 'hidden',
117
+ },
118
+ indicator: {
119
+ height: '100%',
120
+ borderRadius: theme.components.progressBar.linear.bar.borderRadius,
121
+ _web: {
122
+ height: '100%',
123
+ },
124
+ },
125
+ }));
126
+
127
+ export default ProgressBarLinear;
@@ -0,0 +1,7 @@
1
+ export { default as ProgressBar } from './ProgressBar';
2
+ export type {
3
+ ProgressBarColorScheme,
4
+ ProgressBarProps,
5
+ ProgressBarSize,
6
+ ProgressBarVariant,
7
+ } from './ProgressBar.props';
@@ -0,0 +1,84 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { Button, Center } from '../../';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './TimePicker.stories';
5
+
6
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10334-16770&t=Jg2fPJPQNzOyspmQ-4" />
7
+
8
+ <Meta title="Components / Time Picker" />
9
+
10
+ <BackToTopButton />
11
+
12
+ # Time Picker
13
+
14
+ `TimePicker` presents a wheel-based time selector inside a bottom sheet, letting people pick hours and minutes without leaving the current context. It supports 12-hour and 24-hour clocks and returns a JavaScript `Date` whenever the selection changes.
15
+
16
+ - [Playground](#playground)
17
+ - [Usage](#usage)
18
+ - [Props](#props)
19
+ - [Accessibility](#accessibility)
20
+
21
+ ## Playground
22
+
23
+ <Canvas of={Stories.Playground} />
24
+
25
+ <Controls of={Stories.Playground} />
26
+
27
+ ## Usage
28
+
29
+ Use the `TimePicker` with a ref to present the bottom sheet when people tap a trigger button.
30
+
31
+ <UsageWrap>
32
+ <Center>
33
+ <Button onPress={() => {}}>Pick a time</Button>
34
+ </Center>
35
+ </UsageWrap>
36
+
37
+ ```tsx
38
+ import { useRef, useState } from 'react';
39
+ import {
40
+ BottomSheetModalProvider,
41
+ Button,
42
+ TimePicker,
43
+ type DateType,
44
+ } from '@utilitywarehouse/hearth-react-native';
45
+
46
+ const BookingTime = () => {
47
+ const pickerRef = useRef(null);
48
+ const [time, setTime] = useState<DateType>();
49
+
50
+ return (
51
+ <BottomSheetModalProvider>
52
+ <Button onPress={() => pickerRef.current?.present()}>Choose time</Button>
53
+
54
+ <TimePicker
55
+ ref={pickerRef}
56
+ date={time}
57
+ onChange={({ date }) => setTime(date)}
58
+ onCancel={() => setTime(undefined)}
59
+ use12Hours={false}
60
+ />
61
+ </BottomSheetModalProvider>
62
+ );
63
+ };
64
+ ```
65
+
66
+ ## Props
67
+
68
+ `TimePicker` extends the `BottomSheetModal` component. The table below highlights the main props.
69
+
70
+ | Prop | Type | Default | Description |
71
+ | ---------------- | --------------------------------------- | ---------- | --------------------------------------------------------------- |
72
+ | `date` | `DateType` | `-` | Selected time value. |
73
+ | `timeZone` | `string` | `-` | IANA time zone identifier applied to the selected time. |
74
+ | `use12Hours` | `boolean` | `false` | Displays an AM/PM selector and formats hours from 1 to 12. |
75
+ | `minuteInterval` | `number` | `1` | Step interval for minutes shown in the picker. |
76
+ | `hideFooter` | `boolean` | `false` | Hides the Cancel/Ok actions. |
77
+ | `onChange` | `(payload: { date: DateType }) => void` | `-` | Fired whenever the selected time changes. |
78
+ | `onCancel` | `() => void` | `() => {}` | Fired when the cancel action is triggered. |
79
+ | `ref` | `Ref<BottomSheetModalMethods>` | `-` | Gives imperative access to present or dismiss the bottom sheet. |
80
+
81
+ ## Accessibility
82
+
83
+ - Screen readers announce the picker when the sheet opens and focus the wheel area on Android.
84
+ - Action buttons are exposed as standard buttons with clear labels.
@@ -0,0 +1,29 @@
1
+ import figma from '@figma/code-connect';
2
+ import { useRef, useState } from 'react';
3
+ import { BottomSheetModalProvider, Button, DateType, TimePicker } from '../';
4
+
5
+ figma.connect(
6
+ TimePicker,
7
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10334-16770&t=Jg2fPJPQNzOyspmQ-4',
8
+ {
9
+ props: {},
10
+ example: props => {
11
+ const pickerRef = useRef(null);
12
+ const [time, setTime] = useState<DateType>();
13
+
14
+ return (
15
+ <BottomSheetModalProvider>
16
+ <Button onPress={() => pickerRef.current?.present()}>Choose time</Button>
17
+
18
+ <TimePicker
19
+ ref={pickerRef}
20
+ date={time}
21
+ onChange={({ date }) => setTime(date)}
22
+ onCancel={() => setTime(undefined)}
23
+ use12Hours={false}
24
+ />
25
+ </BottomSheetModalProvider>
26
+ );
27
+ },
28
+ }
29
+ );
@@ -0,0 +1,45 @@
1
+ import type { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
2
+ import type { Ref } from 'react';
3
+ import type { ViewStyle } from 'react-native';
4
+ import type { DateType, PickerOption } from '../DatePicker/DatePicker.props';
5
+
6
+ export interface TimePickerProps {
7
+ /**
8
+ * IANA time zone identifier applied when normalising and comparing times.
9
+ */
10
+ timeZone?: string;
11
+ /**
12
+ * Controlled time value.
13
+ */
14
+ date?: DateType;
15
+ /**
16
+ * Fired whenever a time is picked.
17
+ */
18
+ onChange?: (params: { date: DateType }) => void;
19
+ /**
20
+ * Display a 12-hour clock with AM/PM selector.
21
+ */
22
+ use12Hours?: boolean;
23
+ /**
24
+ * Step interval for minutes shown in the picker.
25
+ */
26
+ minuteInterval?: number;
27
+ /**
28
+ * Hide the footer actions.
29
+ */
30
+ hideFooter?: boolean;
31
+ /**
32
+ * Custom container styling for the time picker surface.
33
+ */
34
+ style?: ViewStyle;
35
+ /**
36
+ * Gives imperative access to the bottom sheet instance.
37
+ */
38
+ ref?: Ref<BottomSheetModalMethods<any>>;
39
+ /**
40
+ * Fired when the cancel action is triggered.
41
+ */
42
+ onCancel?: () => void;
43
+ }
44
+
45
+ export type { DateType, PickerOption };