@utilitywarehouse/hearth-react-native 0.27.2 → 0.28.0-testid-fix-1

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 (155) hide show
  1. package/.turbo/turbo-build.log +5 -4
  2. package/.turbo/turbo-lint.log +70 -69
  3. package/CHANGELOG.md +149 -0
  4. package/build/components/Button/ButtonRoot.js +8 -0
  5. package/build/components/Combobox/Combobox.context.d.ts +13 -0
  6. package/build/components/Combobox/Combobox.context.js +9 -0
  7. package/build/components/Combobox/Combobox.d.ts +6 -0
  8. package/build/components/Combobox/Combobox.js +246 -0
  9. package/build/components/Combobox/Combobox.props.d.ts +180 -0
  10. package/build/components/Combobox/Combobox.props.js +1 -0
  11. package/build/components/Combobox/ComboboxOption.d.ts +6 -0
  12. package/build/components/Combobox/ComboboxOption.js +56 -0
  13. package/build/components/Combobox/index.d.ts +4 -0
  14. package/build/components/Combobox/index.js +3 -0
  15. package/build/components/DatePicker/TimePicker.d.ts +3 -0
  16. package/build/components/DatePicker/TimePicker.js +84 -0
  17. package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
  18. package/build/components/DatePicker/time-picker/animated-math.js +19 -0
  19. package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
  20. package/build/components/DatePicker/time-picker/period-native.js +17 -0
  21. package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
  22. package/build/components/DatePicker/time-picker/period-picker.js +10 -0
  23. package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
  24. package/build/components/DatePicker/time-picker/period-web.js +21 -0
  25. package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
  26. package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
  27. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
  28. package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
  29. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
  30. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
  31. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
  32. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
  33. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
  34. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
  35. package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
  36. package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
  37. package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
  38. package/build/components/DatePicker/time-picker/wheel.js +10 -0
  39. package/build/components/List/List.js +2 -2
  40. package/build/components/Modal/Modal.js +31 -42
  41. package/build/components/Modal/Modal.web.js +3 -3
  42. package/build/components/Pagination/Pagination.d.ts +6 -0
  43. package/build/components/Pagination/Pagination.js +125 -0
  44. package/build/components/Pagination/Pagination.props.d.ts +26 -0
  45. package/build/components/Pagination/Pagination.props.js +1 -0
  46. package/build/components/Pagination/Pagination.utils.d.ts +2 -0
  47. package/build/components/Pagination/Pagination.utils.js +20 -0
  48. package/build/components/Pagination/Pagination.utils.test.d.ts +1 -0
  49. package/build/components/Pagination/Pagination.utils.test.js +16 -0
  50. package/build/components/Pagination/index.d.ts +2 -0
  51. package/build/components/Pagination/index.js +1 -0
  52. package/build/components/SafeAreaView/SafeAreaView.d.ts +5 -0
  53. package/build/components/SafeAreaView/SafeAreaView.js +117 -0
  54. package/build/components/SafeAreaView/SafeAreaView.props.d.ts +17 -0
  55. package/build/components/SafeAreaView/SafeAreaView.props.js +1 -0
  56. package/build/components/SafeAreaView/index.d.ts +2 -0
  57. package/build/components/SafeAreaView/index.js +1 -0
  58. package/build/components/Select/Select.d.ts +1 -1
  59. package/build/components/Select/Select.js +6 -5
  60. package/build/components/Select/Select.props.d.ts +4 -0
  61. package/build/components/Select/SelectOption.d.ts +1 -1
  62. package/build/components/Select/SelectOption.js +2 -2
  63. package/build/components/Table/Table.context.d.ts +12 -0
  64. package/build/components/Table/Table.context.js +9 -0
  65. package/build/components/Table/Table.d.ts +6 -0
  66. package/build/components/Table/Table.js +71 -0
  67. package/build/components/Table/Table.props.d.ts +56 -0
  68. package/build/components/Table/Table.props.js +1 -0
  69. package/build/components/Table/Table.utils.d.ts +5 -0
  70. package/build/components/Table/Table.utils.js +48 -0
  71. package/build/components/Table/Table.utils.test.d.ts +1 -0
  72. package/build/components/Table/Table.utils.test.js +71 -0
  73. package/build/components/Table/TableBody.d.ts +6 -0
  74. package/build/components/Table/TableBody.js +16 -0
  75. package/build/components/Table/TableCell.d.ts +10 -0
  76. package/build/components/Table/TableCell.js +44 -0
  77. package/build/components/Table/TableHeader.d.ts +6 -0
  78. package/build/components/Table/TableHeader.js +24 -0
  79. package/build/components/Table/TableHeaderCell.d.ts +10 -0
  80. package/build/components/Table/TableHeaderCell.js +97 -0
  81. package/build/components/Table/TablePagination.d.ts +6 -0
  82. package/build/components/Table/TablePagination.js +7 -0
  83. package/build/components/Table/TableRow.d.ts +8 -0
  84. package/build/components/Table/TableRow.js +25 -0
  85. package/build/components/Table/index.d.ts +8 -0
  86. package/build/components/Table/index.js +7 -0
  87. package/build/components/Timeline/Timeline.d.ts +6 -0
  88. package/build/components/Timeline/Timeline.js +34 -0
  89. package/build/components/Timeline/Timeline.props.d.ts +47 -0
  90. package/build/components/Timeline/Timeline.props.js +1 -0
  91. package/build/components/Timeline/TimelineItem.d.ts +6 -0
  92. package/build/components/Timeline/TimelineItem.js +235 -0
  93. package/build/components/Timeline/index.d.ts +3 -0
  94. package/build/components/Timeline/index.js +2 -0
  95. package/build/components/VerificationInput/VerificationInput.js +3 -3
  96. package/build/components/index.d.ts +5 -0
  97. package/build/components/index.js +5 -0
  98. package/build/tokens/components/dark/timeline.d.ts +2 -2
  99. package/build/tokens/components/dark/timeline.js +2 -2
  100. package/docs/components/AllComponents.web.tsx +106 -23
  101. package/docs/llm-docs/unistyles-llms-full.txt +1132 -534
  102. package/docs/llm-docs/unistyles-llms-small.txt +37 -37
  103. package/package.json +4 -4
  104. package/src/components/Button/Button.stories.tsx +43 -7
  105. package/src/components/Button/ButtonRoot.tsx +8 -0
  106. package/src/components/Combobox/Combobox.context.ts +26 -0
  107. package/src/components/Combobox/Combobox.docs.mdx +277 -0
  108. package/src/components/Combobox/Combobox.figma.tsx +60 -0
  109. package/src/components/Combobox/Combobox.props.ts +187 -0
  110. package/src/components/Combobox/Combobox.stories.tsx +233 -0
  111. package/src/components/Combobox/Combobox.tsx +446 -0
  112. package/src/components/Combobox/ComboboxOption.tsx +100 -0
  113. package/src/components/Combobox/index.ts +9 -0
  114. package/src/components/List/List.tsx +5 -4
  115. package/src/components/Modal/Modal.tsx +67 -74
  116. package/src/components/Modal/Modal.web.tsx +3 -3
  117. package/src/components/Pagination/Pagination.docs.mdx +99 -0
  118. package/src/components/Pagination/Pagination.figma.tsx +20 -0
  119. package/src/components/Pagination/Pagination.props.ts +28 -0
  120. package/src/components/Pagination/Pagination.stories.tsx +88 -0
  121. package/src/components/Pagination/Pagination.tsx +248 -0
  122. package/src/components/Pagination/Pagination.utils.test.ts +20 -0
  123. package/src/components/Pagination/Pagination.utils.ts +37 -0
  124. package/src/components/Pagination/index.ts +2 -0
  125. package/src/components/SafeAreaView/SafeAreaView.props.ts +20 -0
  126. package/src/components/SafeAreaView/SafeAreaView.tsx +173 -0
  127. package/src/components/SafeAreaView/index.ts +2 -0
  128. package/src/components/Select/Select.props.ts +4 -0
  129. package/src/components/Select/Select.tsx +35 -28
  130. package/src/components/Select/SelectOption.tsx +2 -0
  131. package/src/components/Table/Table.context.tsx +23 -0
  132. package/src/components/Table/Table.docs.mdx +239 -0
  133. package/src/components/Table/Table.figma.tsx +65 -0
  134. package/src/components/Table/Table.props.ts +65 -0
  135. package/src/components/Table/Table.stories.tsx +399 -0
  136. package/src/components/Table/Table.tsx +127 -0
  137. package/src/components/Table/Table.utils.test.ts +82 -0
  138. package/src/components/Table/Table.utils.ts +72 -0
  139. package/src/components/Table/TableBody.tsx +25 -0
  140. package/src/components/Table/TableCell.tsx +67 -0
  141. package/src/components/Table/TableHeader.tsx +41 -0
  142. package/src/components/Table/TableHeaderCell.tsx +136 -0
  143. package/src/components/Table/TablePagination.tsx +10 -0
  144. package/src/components/Table/TableRow.tsx +42 -0
  145. package/src/components/Table/index.ts +16 -0
  146. package/src/components/Timeline/Timeline.docs.mdx +177 -0
  147. package/src/components/Timeline/Timeline.figma.tsx +89 -0
  148. package/src/components/Timeline/Timeline.props.ts +51 -0
  149. package/src/components/Timeline/Timeline.stories.tsx +102 -0
  150. package/src/components/Timeline/Timeline.tsx +48 -0
  151. package/src/components/Timeline/TimelineItem.tsx +293 -0
  152. package/src/components/Timeline/index.ts +9 -0
  153. package/src/components/VerificationInput/VerificationInput.tsx +3 -0
  154. package/src/components/index.ts +5 -0
  155. package/src/tokens/components/dark/timeline.ts +2 -2
@@ -0,0 +1,100 @@
1
+ import { TickSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
2
+ import { Pressable, View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { BodyText } from '../BodyText';
5
+ import { Icon } from '../Icon';
6
+ import { useComboboxContext } from './Combobox.context';
7
+ import { ComboboxOptionProps } from './Combobox.props';
8
+
9
+ const ComboboxOption = ({
10
+ label,
11
+ value,
12
+ leadingIcon: LeftIcon,
13
+ trailingIcon: RightIcon,
14
+ selected,
15
+ disabled,
16
+ onPress,
17
+ }: ComboboxOptionProps) => {
18
+ const { selectedValue, selectOption } = useComboboxContext();
19
+ const isSelected = selected !== undefined ? selected : selectedValue === value;
20
+
21
+ styles.useVariants({ disabled });
22
+
23
+ const handlePress = () => {
24
+ if (disabled) {
25
+ return;
26
+ }
27
+
28
+ if (onPress) {
29
+ onPress(value);
30
+ return;
31
+ }
32
+
33
+ selectOption({ label, value });
34
+ };
35
+
36
+ return (
37
+ <Pressable
38
+ onPress={handlePress}
39
+ disabled={disabled}
40
+ style={({ pressed }) => [styles.container, pressed && styles.pressed]}
41
+ >
42
+ {!!LeftIcon && (
43
+ <View>
44
+ <Icon as={LeftIcon} style={styles.icon} />
45
+ </View>
46
+ )}
47
+
48
+ <View style={styles.labelContainer}>
49
+ <BodyText>{label}</BodyText>
50
+ </View>
51
+
52
+ {isSelected && (
53
+ <View>
54
+ <Icon as={TickSmallIcon} style={styles.icon} />
55
+ </View>
56
+ )}
57
+ {!!RightIcon && !isSelected && (
58
+ <View>
59
+ <Icon as={RightIcon} style={styles.icon} />
60
+ </View>
61
+ )}
62
+ </Pressable>
63
+ );
64
+ };
65
+
66
+ const styles = StyleSheet.create(theme => ({
67
+ container: {
68
+ flexDirection: 'row',
69
+ alignItems: 'center',
70
+ gap: theme.components.select.dropdown.item.gap,
71
+ borderRadius: theme.components.select.dropdown.item.borderRadius,
72
+ paddingVertical: theme.components.select.dropdown.item.padding,
73
+ paddingHorizontal: theme.components.select.dropdown.padding,
74
+ variants: {
75
+ disabled: {
76
+ true: {
77
+ opacity: theme.opacity.disabled,
78
+ },
79
+ },
80
+ },
81
+ _web: {
82
+ _hover: {
83
+ backgroundColor: theme.color.interactive.functional.surface.subtle.hover,
84
+ },
85
+ },
86
+ },
87
+ icon: {
88
+ color: theme.color.interactive.functional.foreground.subtle,
89
+ },
90
+ pressed: {
91
+ backgroundColor: theme.color.interactive.functional.surface.subtle.active,
92
+ },
93
+ labelContainer: {
94
+ flex: 1,
95
+ },
96
+ }));
97
+
98
+ ComboboxOption.displayName = 'ComboboxOption';
99
+
100
+ export default ComboboxOption;
@@ -0,0 +1,9 @@
1
+ export { default as Combobox } from './Combobox';
2
+ export { ComboboxContext, useComboboxContext } from './Combobox.context';
3
+ export { default as ComboboxOption } from './ComboboxOption';
4
+ export type {
5
+ default as ComboboxProps,
6
+ ComboboxOptionItemProps,
7
+ ComboboxOptionProps,
8
+ ComboboxRenderContentProps,
9
+ } from './Combobox.props';
@@ -14,7 +14,8 @@ const List = ({
14
14
  invalidText,
15
15
  ...props
16
16
  }: ListProps) => {
17
- const { loading, disabled, container = 'none' } = props;
17
+ const { loading, disabled, container = 'none', testID, style, ...rest } = props;
18
+
18
19
  const orderRef = useRef<string[]>([]);
19
20
  const [firstItemId, setFirstItemId] = useState<string | undefined>(undefined);
20
21
  const containerToCard: {
@@ -51,7 +52,7 @@ const List = ({
51
52
  styles.useVariants({ disabled });
52
53
  return (
53
54
  <ListContext.Provider value={value}>
54
- <View {...props} style={[styles.container, props.style]}>
55
+ <View {...rest} style={[styles.container, style]}>
55
56
  {heading ? (
56
57
  <SectionHeader
57
58
  heading={heading}
@@ -61,10 +62,10 @@ const List = ({
61
62
  />
62
63
  ) : null}
63
64
  {container === 'none' ? (
64
- <View>{children}</View>
65
+ <View testID={testID}>{children}</View>
65
66
  ) : (
66
67
  React.Children.count(children) > 0 && (
67
- <Card {...containerToCard} noPadding style={styles.card}>
68
+ <Card {...containerToCard} noPadding style={styles.card} testID={testID}>
68
69
  <>{children}</>
69
70
  </Card>
70
71
  )
@@ -6,8 +6,8 @@ import {
6
6
  } from '@gorhom/bottom-sheet';
7
7
  import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
8
8
  import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
9
- import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
10
- import { AccessibilityInfo, Dimensions, Platform, ScrollView, View, findNodeHandle } from 'react-native';
9
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
10
+ import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
11
11
  import Animated, {
12
12
  Easing,
13
13
  useAnimatedStyle,
@@ -22,6 +22,7 @@ import { BodyText } from '../BodyText';
22
22
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
23
23
  import { Button } from '../Button';
24
24
  import { Heading } from '../Heading';
25
+ import { SafeAreaView } from '../SafeAreaView';
25
26
  import { Spinner } from '../Spinner';
26
27
  import { UnstyledIconButton } from '../UnstyledIconButton';
27
28
  import ModalProps from './Modal.props';
@@ -62,16 +63,6 @@ const Modal = ({
62
63
  const pretendContentTranslateY = useSharedValue(20);
63
64
  const isBrandBackground = background === 'brand';
64
65
 
65
- const [inNavModalHeight, setInNavModalHeight] = useState<number>();
66
-
67
- const isNavModalFullScreen = useMemo(() => {
68
- if (!inNavModalHeight || !inNavModal) return false;
69
-
70
- const screenHeight = Dimensions.get('window').height;
71
-
72
- return inNavModalHeight >= screenHeight;
73
- }, [inNavModalHeight, inNavModal]);
74
-
75
66
  const triggerCloseAnimation = useCallback(() => {
76
67
  if (Platform.OS === 'android' && inNavModal) {
77
68
  pretendContentTranslateY.value = withTiming(20, {
@@ -83,7 +74,7 @@ const Modal = ({
83
74
  easing: Easing.in(Easing.quad),
84
75
  });
85
76
  }
86
- }, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
77
+ }, [inNavModal, pretendContentTranslateY, backgroundOpacity]);
87
78
 
88
79
  useImperativeHandle(ref, () => ({
89
80
  ...(bottomSheetModalRef.current as BottomSheetModal),
@@ -150,30 +141,30 @@ const Modal = ({
150
141
  props.onChange?.(index, position, type);
151
142
  };
152
143
 
153
- const handleCloseButtonPress = () => {
144
+ const handleCloseButtonPress = useCallback(() => {
154
145
  bottomSheetModalRef.current?.dismiss();
155
146
  if (onPressCloseButton) {
156
147
  onPressCloseButton();
157
148
  }
158
- };
149
+ }, [onPressCloseButton]);
159
150
 
160
- const handlePrimaryButtonPress = () => {
151
+ const handlePrimaryButtonPress = useCallback(() => {
161
152
  if (onPressPrimaryButton) {
162
153
  onPressPrimaryButton();
163
154
  }
164
155
  if (closeOnPrimaryButtonPress) {
165
156
  bottomSheetModalRef.current?.dismiss();
166
157
  }
167
- };
158
+ }, [closeOnPrimaryButtonPress, onPressPrimaryButton]);
168
159
 
169
- const handleSecondaryButtonPress = () => {
160
+ const handleSecondaryButtonPress = useCallback(() => {
170
161
  if (onPressSecondaryButton) {
171
162
  onPressSecondaryButton();
172
163
  }
173
164
  if (closeOnSecondaryButtonPress) {
174
165
  bottomSheetModalRef.current?.dismiss();
175
166
  }
176
- };
167
+ }, [closeOnSecondaryButtonPress, onPressSecondaryButton]);
177
168
 
178
169
  const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
179
170
 
@@ -184,34 +175,45 @@ const Modal = ({
184
175
  stickyFooter,
185
176
  showHandle: props.showHandle,
186
177
  background: isBrandBackground ? 'brand' : 'primary',
187
- ...(inNavModal && {
188
- fullscreen: isNavModalFullScreen,
189
- }),
190
178
  });
191
179
 
192
- const footer = (
193
- <View style={styles.footer}>
194
- {onPressPrimaryButton && primaryButtonText ? (
195
- <Button
196
- onPress={handlePrimaryButtonPress}
197
- text={primaryButtonText}
198
- inverted={isBrandBackground && inNavModal}
199
- {...primaryButtonProps}
200
- variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
201
- colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
202
- />
203
- ) : null}
204
- {onPressSecondaryButton && secondaryButtonText ? (
205
- <Button
206
- onPress={handleSecondaryButtonPress}
207
- text={secondaryButtonText}
208
- inverted={isBrandBackground && inNavModal}
209
- {...secondaryButtonProps}
210
- variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
211
- colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
212
- />
213
- ) : null}
214
- </View>
180
+ const footer = useMemo(
181
+ () => (
182
+ <View style={styles.footer}>
183
+ {onPressPrimaryButton && primaryButtonText ? (
184
+ <Button
185
+ onPress={handlePrimaryButtonPress}
186
+ text={primaryButtonText}
187
+ inverted={isBrandBackground && inNavModal}
188
+ {...primaryButtonProps}
189
+ variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
190
+ colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
191
+ />
192
+ ) : null}
193
+ {onPressSecondaryButton && secondaryButtonText ? (
194
+ <Button
195
+ onPress={handleSecondaryButtonPress}
196
+ text={secondaryButtonText}
197
+ inverted={isBrandBackground && inNavModal}
198
+ {...secondaryButtonProps}
199
+ variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
200
+ colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
201
+ />
202
+ ) : null}
203
+ </View>
204
+ ),
205
+ [
206
+ handlePrimaryButtonPress,
207
+ handleSecondaryButtonPress,
208
+ inNavModal,
209
+ isBrandBackground,
210
+ onPressPrimaryButton,
211
+ onPressSecondaryButton,
212
+ primaryButtonProps,
213
+ primaryButtonText,
214
+ secondaryButtonProps,
215
+ secondaryButtonText,
216
+ ]
215
217
  );
216
218
 
217
219
  const InNavModalContainer = scrollable ? ScrollView : View;
@@ -292,13 +294,23 @@ const Modal = ({
292
294
  </View>
293
295
  ) : null}
294
296
  {inNavModal && (
295
- <InNavModalContainer style={{ flex: stickyFooter ? 1 : 0 }}>
297
+ <InNavModalContainer
298
+ style={{
299
+ flex: stickyFooter ? 1 : 0,
300
+ ...(scrollable ? { marginHorizontal: -1 } : {}),
301
+ }}
302
+ {...(scrollable ? { contentContainerStyle: { paddingHorizontal: 1 } } : {})}
303
+ >
296
304
  {children}
297
- {!stickyFooter ? <View style={styles.inNavModalFooterContainer}>{footer}</View> : null}
305
+ {!stickyFooter ? (
306
+ <View style={styles.inNavModalFooterContainer}>{footer}</View>
307
+ ) : null}
298
308
  </InNavModalContainer>
299
309
  )}
300
310
  {!inNavModal && children}
301
- {((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons ? footer : null}
311
+ {((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons
312
+ ? footer
313
+ : null}
302
314
  </View>
303
315
  )}
304
316
  </>
@@ -310,21 +322,11 @@ const Modal = ({
310
322
  <View style={styles.footerWrap}>{footer}</View>
311
323
  </BottomSheetFooter>
312
324
  ),
313
- [
314
- onPressPrimaryButton,
315
- primaryButtonText,
316
- onPressSecondaryButton,
317
- secondaryButtonText,
318
- primaryButtonProps,
319
- secondaryButtonProps,
320
- ]
325
+ [footer]
321
326
  );
322
327
 
323
328
  return inNavModal ? (
324
329
  <View
325
- onLayout={(e) => {
326
- setInNavModalHeight(e.nativeEvent.layout.height);
327
- }}
328
330
  style={{
329
331
  flex: 1,
330
332
  backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
@@ -338,9 +340,9 @@ const Modal = ({
338
340
  <Animated.View
339
341
  style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
340
342
  >
341
- <View style={styles.inNavModalContent}>
343
+ <SafeAreaView edges={['top', 'bottom']} style={styles.inNavModalContent}>
342
344
  {content}
343
- </View>
345
+ </SafeAreaView>
344
346
  </Animated.View>
345
347
  </View>
346
348
  ) : (
@@ -471,7 +473,7 @@ const styles = StyleSheet.create((theme, rt) => ({
471
473
  borderTopLeftRadius: theme.components.modal.borderRadius,
472
474
  borderTopRightRadius: theme.components.modal.borderRadius,
473
475
  backgroundColor: theme.color.surface.neutral.strong,
474
- paddingBottom: theme.components.modal.padding + rt.insets.bottom,
476
+ padding: theme.components.bottomSheet.padding,
475
477
  variants: {
476
478
  background: {
477
479
  primary: {},
@@ -479,24 +481,15 @@ const styles = StyleSheet.create((theme, rt) => ({
479
481
  backgroundColor: theme.color.background.brand,
480
482
  },
481
483
  },
482
- fullscreen: {
483
- true: {
484
- padding: theme.components.modal.padding,
485
- paddingTop: rt.insets.top,
486
- },
487
- false: {
488
- padding: theme.components.modal.padding,
489
- }
490
- }
491
484
  },
492
485
  },
493
486
  inNavModalFooterContainer: {
494
- paddingTop: theme.components.modal.padding,
487
+ paddingTop: theme.components.bottomSheet.padding,
495
488
  },
496
489
  androidContainer: {
497
490
  height: rt.insets.top + 18,
498
- paddingLeft: theme.components.modal.padding,
499
- paddingRight: theme.components.modal.padding,
491
+ paddingLeft: theme.components.bottomSheet.padding,
492
+ paddingRight: theme.components.bottomSheet.padding,
500
493
  justifyContent: 'flex-end',
501
494
  },
502
495
  pretendContent: {
@@ -310,13 +310,13 @@ const styles = StyleSheet.create((theme, rt) => ({
310
310
  borderTopRightRadius: theme.components.modal.borderRadius,
311
311
  backgroundColor: theme.color.surface.neutral.strong,
312
312
  gap: theme.components.modal.gap,
313
- padding: theme.components.modal.padding,
313
+ padding: theme.components.bottomSheet.padding,
314
314
  paddingBottom: theme.components.modal.padding + rt.insets.bottom,
315
315
  },
316
316
  androidContainer: {
317
317
  height: rt.insets.top + 18,
318
- paddingLeft: theme.components.modal.padding,
319
- paddingRight: theme.components.modal.padding,
318
+ paddingLeft: theme.components.bottomSheet.padding,
319
+ paddingRight: theme.components.bottomSheet.padding,
320
320
  justifyContent: 'flex-end',
321
321
  },
322
322
  pretendContent: {
@@ -0,0 +1,99 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { Box, Center, Pagination } from '../..';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './Pagination.stories';
5
+
6
+ <Meta title="Components / Pagination" />
7
+
8
+ <BackToTopButton />
9
+
10
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=6416-5836&t=pZwKJYFo1y1QRQD1-4" />
11
+
12
+ # Pagination
13
+
14
+ Pagination helps users move between pages of content while keeping the current position visible. It supports both a full page list and a condensed “Page X of Y” layout.
15
+
16
+ - [Playground](#playground)
17
+ - [Usage](#usage)
18
+ - [Props](#props)
19
+ - [Examples](#examples)
20
+
21
+ ## Playground
22
+
23
+ <Canvas of={Stories.Playground} />
24
+
25
+ <Controls of={Stories.Playground} />
26
+
27
+ ## Usage
28
+
29
+ <UsageWrap>
30
+ <Center>
31
+ <Box style={{ width: 520, maxWidth: '100%' }}>
32
+ <Pagination currentPage={3} totalPages={10} onPageChange={() => {}} />
33
+ </Box>
34
+ </Center>
35
+ </UsageWrap>
36
+
37
+ ```tsx
38
+ import { Pagination } from '@utilitywarehouse/hearth-react-native';
39
+ import { useState } from 'react';
40
+
41
+ const MyComponent = () => {
42
+ const [page, setPage] = useState(3);
43
+
44
+ return <Pagination currentPage={page} totalPages={10} onPageChange={setPage} />;
45
+ };
46
+ ```
47
+
48
+ ## Props
49
+
50
+ | Property | Type | Description | Default |
51
+ | ----------------- | ------------------------ | ------------------------------------------------- | -------- |
52
+ | `currentPage` | `number` | The current active page number. | Required |
53
+ | `totalPages` | `number` | Total number of pages available. | Required |
54
+ | `onPageChange` | `(page: number) => void` | Called when the user selects a different page. | Required |
55
+ | `condensed` | `boolean` | Displays “Page X of Y” instead of numbered items. | `false` |
56
+ | `hideSkipButtons` | `boolean` | Hides the first and last page controls. | `false` |
57
+
58
+ ## Examples
59
+
60
+ ### Condensed
61
+
62
+ <Canvas of={Stories.Condensed} />
63
+
64
+ ```tsx
65
+ const [currentPage, setCurrentPage] = useState(1);
66
+
67
+ <Pagination condensed currentPage={currentPage} onPageChange={setCurrentPage} totalPages={10} />;
68
+ ```
69
+
70
+ ### Without skip buttons
71
+
72
+ <Canvas of={Stories.WithoutSkipButtons} />
73
+
74
+ ```tsx
75
+ const [currentPage, setCurrentPage] = useState(3);
76
+
77
+ <Pagination
78
+ currentPage={currentPage}
79
+ hideSkipButtons
80
+ onPageChange={setCurrentPage}
81
+ totalPages={10}
82
+ />;
83
+ ```
84
+
85
+ ### Edge cases
86
+
87
+ <Canvas of={Stories.EdgeCases} />
88
+
89
+ ```tsx
90
+ const [nearStartPage, setNearStartPage] = useState(2);
91
+ const [middlePage, setMiddlePage] = useState(5);
92
+ const [nearEndPage, setNearEndPage] = useState(9);
93
+
94
+ <Flex direction="column" spacing="lg" style={{ width: '100%', maxWidth: 520 }}>
95
+ <Pagination currentPage={nearStartPage} onPageChange={setNearStartPage} totalPages={10} />
96
+ <Pagination currentPage={middlePage} onPageChange={setMiddlePage} totalPages={10} />
97
+ <Pagination currentPage={nearEndPage} onPageChange={setNearEndPage} totalPages={10} />
98
+ </Flex>;
99
+ ```
@@ -0,0 +1,20 @@
1
+ import figma from '@figma/code-connect';
2
+ import { Pagination } from './';
3
+
4
+ figma.connect(
5
+ Pagination,
6
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=6416-5836&t=pZwKJYFo1y1QRQD1-4',
7
+ {
8
+ props: {
9
+ condensed: figma.boolean('Condensed?'),
10
+ },
11
+ example: props => (
12
+ <Pagination
13
+ currentPage={1}
14
+ totalPages={10}
15
+ onPageChange={() => {}}
16
+ condensed={props.condensed}
17
+ />
18
+ ),
19
+ }
20
+ );
@@ -0,0 +1,28 @@
1
+ import type { ViewProps } from 'react-native';
2
+
3
+ export interface PaginationProps extends ViewProps {
4
+ /**
5
+ * The current active page number (1-indexed).
6
+ */
7
+ currentPage: number;
8
+ /**
9
+ * The total number of pages.
10
+ */
11
+ totalPages: number;
12
+ /**
13
+ * Callback fired when the page changes.
14
+ */
15
+ onPageChange: (page: number) => void;
16
+ /**
17
+ * Whether to show condensed copy instead of individual page items.
18
+ * @default false
19
+ */
20
+ condensed?: boolean;
21
+ /**
22
+ * Whether to hide the first and last page controls.
23
+ * @default false
24
+ */
25
+ hideSkipButtons?: boolean;
26
+ }
27
+
28
+ export default PaginationProps;
@@ -0,0 +1,88 @@
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
+ import { useState } from 'react';
3
+ import { BodyText } from '../BodyText';
4
+ import { Flex } from '../Flex';
5
+ import { Pagination } from './';
6
+
7
+ const meta = {
8
+ title: 'Stories / Pagination',
9
+ component: Pagination,
10
+ args: {
11
+ currentPage: 1,
12
+ totalPages: 10,
13
+ condensed: false,
14
+ hideSkipButtons: false,
15
+ },
16
+ argTypes: {
17
+ currentPage: { control: { type: 'number', min: 1 } },
18
+ totalPages: { control: { type: 'number', min: 1 } },
19
+ condensed: { control: 'boolean' },
20
+ hideSkipButtons: { control: 'boolean' },
21
+ },
22
+ } satisfies Meta<typeof Pagination>;
23
+
24
+ export default meta;
25
+ type Story = StoryObj<typeof meta>;
26
+
27
+ export const Playground: Story = {
28
+ render: (args: StoryObj<typeof meta.args>) => {
29
+ const [currentPage, setCurrentPage] = useState(args.currentPage);
30
+
31
+ return <Pagination {...args} currentPage={currentPage} onPageChange={setCurrentPage} />;
32
+ },
33
+ };
34
+
35
+ export const Condensed: Story = {
36
+ render: () => {
37
+ const [currentPage, setCurrentPage] = useState(1);
38
+
39
+ return (
40
+ <Pagination
41
+ condensed
42
+ currentPage={currentPage}
43
+ onPageChange={setCurrentPage}
44
+ totalPages={10}
45
+ />
46
+ );
47
+ },
48
+ };
49
+
50
+ export const WithoutSkipButtons: Story = {
51
+ render: () => {
52
+ const [currentPage, setCurrentPage] = useState(3);
53
+
54
+ return (
55
+ <Pagination
56
+ currentPage={currentPage}
57
+ hideSkipButtons
58
+ onPageChange={setCurrentPage}
59
+ totalPages={10}
60
+ />
61
+ );
62
+ },
63
+ };
64
+
65
+ export const EdgeCases: Story = {
66
+ render: () => {
67
+ const [nearStartPage, setNearStartPage] = useState(2);
68
+ const [middlePage, setMiddlePage] = useState(5);
69
+ const [nearEndPage, setNearEndPage] = useState(9);
70
+
71
+ return (
72
+ <Flex direction="column" spacing="lg" style={{ width: '100%', maxWidth: 520 }}>
73
+ <Flex direction="column" spacing="xs">
74
+ <BodyText size="sm">Near start</BodyText>
75
+ <Pagination currentPage={nearStartPage} onPageChange={setNearStartPage} totalPages={10} />
76
+ </Flex>
77
+ <Flex direction="column" spacing="xs">
78
+ <BodyText size="sm">Middle</BodyText>
79
+ <Pagination currentPage={middlePage} onPageChange={setMiddlePage} totalPages={10} />
80
+ </Flex>
81
+ <Flex direction="column" spacing="xs">
82
+ <BodyText size="sm">Near end</BodyText>
83
+ <Pagination currentPage={nearEndPage} onPageChange={setNearEndPage} totalPages={10} />
84
+ </Flex>
85
+ </Flex>
86
+ );
87
+ },
88
+ };