@utilitywarehouse/hearth-react-native 0.27.3 → 0.28.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 (118) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +18 -19
  3. package/CHANGELOG.md +110 -0
  4. package/build/components/Combobox/Combobox.context.d.ts +13 -0
  5. package/build/components/Combobox/Combobox.context.js +9 -0
  6. package/build/components/Combobox/Combobox.d.ts +6 -0
  7. package/build/components/Combobox/Combobox.js +246 -0
  8. package/build/components/Combobox/Combobox.props.d.ts +180 -0
  9. package/build/components/Combobox/Combobox.props.js +1 -0
  10. package/build/components/Combobox/ComboboxOption.d.ts +6 -0
  11. package/build/components/Combobox/ComboboxOption.js +56 -0
  12. package/build/components/Combobox/index.d.ts +4 -0
  13. package/build/components/Combobox/index.js +3 -0
  14. package/build/components/Modal/Modal.js +26 -42
  15. package/build/components/Modal/Modal.web.js +3 -3
  16. package/build/components/Pagination/Pagination.d.ts +6 -0
  17. package/build/components/Pagination/Pagination.js +125 -0
  18. package/build/components/Pagination/Pagination.props.d.ts +26 -0
  19. package/build/components/Pagination/Pagination.props.js +1 -0
  20. package/build/components/Pagination/Pagination.utils.d.ts +2 -0
  21. package/build/components/Pagination/Pagination.utils.js +20 -0
  22. package/build/components/Pagination/Pagination.utils.test.d.ts +1 -0
  23. package/build/components/Pagination/Pagination.utils.test.js +16 -0
  24. package/build/components/Pagination/index.d.ts +2 -0
  25. package/build/components/Pagination/index.js +1 -0
  26. package/build/components/SafeAreaView/SafeAreaView.d.ts +5 -0
  27. package/build/components/SafeAreaView/SafeAreaView.js +117 -0
  28. package/build/components/SafeAreaView/SafeAreaView.props.d.ts +17 -0
  29. package/build/components/SafeAreaView/SafeAreaView.props.js +1 -0
  30. package/build/components/SafeAreaView/index.d.ts +2 -0
  31. package/build/components/SafeAreaView/index.js +1 -0
  32. package/build/components/Select/Select.js +3 -2
  33. package/build/components/Table/Table.context.d.ts +12 -0
  34. package/build/components/Table/Table.context.js +9 -0
  35. package/build/components/Table/Table.d.ts +6 -0
  36. package/build/components/Table/Table.js +71 -0
  37. package/build/components/Table/Table.props.d.ts +56 -0
  38. package/build/components/Table/Table.props.js +1 -0
  39. package/build/components/Table/Table.utils.d.ts +5 -0
  40. package/build/components/Table/Table.utils.js +48 -0
  41. package/build/components/Table/Table.utils.test.d.ts +1 -0
  42. package/build/components/Table/Table.utils.test.js +71 -0
  43. package/build/components/Table/TableBody.d.ts +6 -0
  44. package/build/components/Table/TableBody.js +16 -0
  45. package/build/components/Table/TableCell.d.ts +10 -0
  46. package/build/components/Table/TableCell.js +44 -0
  47. package/build/components/Table/TableHeader.d.ts +6 -0
  48. package/build/components/Table/TableHeader.js +24 -0
  49. package/build/components/Table/TableHeaderCell.d.ts +10 -0
  50. package/build/components/Table/TableHeaderCell.js +97 -0
  51. package/build/components/Table/TablePagination.d.ts +6 -0
  52. package/build/components/Table/TablePagination.js +7 -0
  53. package/build/components/Table/TableRow.d.ts +8 -0
  54. package/build/components/Table/TableRow.js +25 -0
  55. package/build/components/Table/index.d.ts +8 -0
  56. package/build/components/Table/index.js +7 -0
  57. package/build/components/Timeline/Timeline.d.ts +6 -0
  58. package/build/components/Timeline/Timeline.js +34 -0
  59. package/build/components/Timeline/Timeline.props.d.ts +47 -0
  60. package/build/components/Timeline/Timeline.props.js +1 -0
  61. package/build/components/Timeline/TimelineItem.d.ts +6 -0
  62. package/build/components/Timeline/TimelineItem.js +235 -0
  63. package/build/components/Timeline/index.d.ts +3 -0
  64. package/build/components/Timeline/index.js +2 -0
  65. package/build/components/index.d.ts +5 -0
  66. package/build/components/index.js +5 -0
  67. package/build/tokens/components/dark/timeline.d.ts +2 -2
  68. package/build/tokens/components/dark/timeline.js +2 -2
  69. package/docs/components/AllComponents.web.tsx +106 -23
  70. package/docs/llm-docs/unistyles-llms-full.txt +1132 -534
  71. package/docs/llm-docs/unistyles-llms-small.txt +37 -37
  72. package/package.json +2 -2
  73. package/src/components/Combobox/Combobox.context.ts +26 -0
  74. package/src/components/Combobox/Combobox.docs.mdx +277 -0
  75. package/src/components/Combobox/Combobox.figma.tsx +60 -0
  76. package/src/components/Combobox/Combobox.props.ts +187 -0
  77. package/src/components/Combobox/Combobox.stories.tsx +233 -0
  78. package/src/components/Combobox/Combobox.tsx +446 -0
  79. package/src/components/Combobox/ComboboxOption.tsx +100 -0
  80. package/src/components/Combobox/index.ts +9 -0
  81. package/src/components/Modal/Modal.tsx +52 -74
  82. package/src/components/Modal/Modal.web.tsx +3 -3
  83. package/src/components/Pagination/Pagination.docs.mdx +99 -0
  84. package/src/components/Pagination/Pagination.figma.tsx +20 -0
  85. package/src/components/Pagination/Pagination.props.ts +28 -0
  86. package/src/components/Pagination/Pagination.stories.tsx +88 -0
  87. package/src/components/Pagination/Pagination.tsx +248 -0
  88. package/src/components/Pagination/Pagination.utils.test.ts +20 -0
  89. package/src/components/Pagination/Pagination.utils.ts +37 -0
  90. package/src/components/Pagination/index.ts +2 -0
  91. package/src/components/SafeAreaView/SafeAreaView.props.ts +20 -0
  92. package/src/components/SafeAreaView/SafeAreaView.tsx +173 -0
  93. package/src/components/SafeAreaView/index.ts +2 -0
  94. package/src/components/Select/Select.tsx +30 -27
  95. package/src/components/Table/Table.context.tsx +23 -0
  96. package/src/components/Table/Table.docs.mdx +239 -0
  97. package/src/components/Table/Table.figma.tsx +65 -0
  98. package/src/components/Table/Table.props.ts +65 -0
  99. package/src/components/Table/Table.stories.tsx +399 -0
  100. package/src/components/Table/Table.tsx +127 -0
  101. package/src/components/Table/Table.utils.test.ts +82 -0
  102. package/src/components/Table/Table.utils.ts +72 -0
  103. package/src/components/Table/TableBody.tsx +25 -0
  104. package/src/components/Table/TableCell.tsx +67 -0
  105. package/src/components/Table/TableHeader.tsx +41 -0
  106. package/src/components/Table/TableHeaderCell.tsx +136 -0
  107. package/src/components/Table/TablePagination.tsx +10 -0
  108. package/src/components/Table/TableRow.tsx +42 -0
  109. package/src/components/Table/index.ts +16 -0
  110. package/src/components/Timeline/Timeline.docs.mdx +177 -0
  111. package/src/components/Timeline/Timeline.figma.tsx +89 -0
  112. package/src/components/Timeline/Timeline.props.ts +51 -0
  113. package/src/components/Timeline/Timeline.stories.tsx +102 -0
  114. package/src/components/Timeline/Timeline.tsx +48 -0
  115. package/src/components/Timeline/TimelineItem.tsx +293 -0
  116. package/src/components/Timeline/index.ts +9 -0
  117. package/src/components/index.ts +5 -0
  118. 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';
@@ -6,15 +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 {
11
- AccessibilityInfo,
12
- Dimensions,
13
- Platform,
14
- ScrollView,
15
- View,
16
- findNodeHandle,
17
- } from 'react-native';
9
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
10
+ import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
18
11
  import Animated, {
19
12
  Easing,
20
13
  useAnimatedStyle,
@@ -29,6 +22,7 @@ import { BodyText } from '../BodyText';
29
22
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
30
23
  import { Button } from '../Button';
31
24
  import { Heading } from '../Heading';
25
+ import { SafeAreaView } from '../SafeAreaView';
32
26
  import { Spinner } from '../Spinner';
33
27
  import { UnstyledIconButton } from '../UnstyledIconButton';
34
28
  import ModalProps from './Modal.props';
@@ -69,16 +63,6 @@ const Modal = ({
69
63
  const pretendContentTranslateY = useSharedValue(20);
70
64
  const isBrandBackground = background === 'brand';
71
65
 
72
- const [inNavModalHeight, setInNavModalHeight] = useState<number>();
73
-
74
- const isNavModalFullScreen = useMemo(() => {
75
- if (!inNavModalHeight || !inNavModal) return false;
76
-
77
- const screenHeight = Dimensions.get('window').height;
78
-
79
- return inNavModalHeight >= screenHeight;
80
- }, [inNavModalHeight, inNavModal]);
81
-
82
66
  const triggerCloseAnimation = useCallback(() => {
83
67
  if (Platform.OS === 'android' && inNavModal) {
84
68
  pretendContentTranslateY.value = withTiming(20, {
@@ -90,7 +74,7 @@ const Modal = ({
90
74
  easing: Easing.in(Easing.quad),
91
75
  });
92
76
  }
93
- }, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
77
+ }, [inNavModal, pretendContentTranslateY, backgroundOpacity]);
94
78
 
95
79
  useImperativeHandle(ref, () => ({
96
80
  ...(bottomSheetModalRef.current as BottomSheetModal),
@@ -157,30 +141,30 @@ const Modal = ({
157
141
  props.onChange?.(index, position, type);
158
142
  };
159
143
 
160
- const handleCloseButtonPress = () => {
144
+ const handleCloseButtonPress = useCallback(() => {
161
145
  bottomSheetModalRef.current?.dismiss();
162
146
  if (onPressCloseButton) {
163
147
  onPressCloseButton();
164
148
  }
165
- };
149
+ }, [onPressCloseButton]);
166
150
 
167
- const handlePrimaryButtonPress = () => {
151
+ const handlePrimaryButtonPress = useCallback(() => {
168
152
  if (onPressPrimaryButton) {
169
153
  onPressPrimaryButton();
170
154
  }
171
155
  if (closeOnPrimaryButtonPress) {
172
156
  bottomSheetModalRef.current?.dismiss();
173
157
  }
174
- };
158
+ }, [closeOnPrimaryButtonPress, onPressPrimaryButton]);
175
159
 
176
- const handleSecondaryButtonPress = () => {
160
+ const handleSecondaryButtonPress = useCallback(() => {
177
161
  if (onPressSecondaryButton) {
178
162
  onPressSecondaryButton();
179
163
  }
180
164
  if (closeOnSecondaryButtonPress) {
181
165
  bottomSheetModalRef.current?.dismiss();
182
166
  }
183
- };
167
+ }, [closeOnSecondaryButtonPress, onPressSecondaryButton]);
184
168
 
185
169
  const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
186
170
 
@@ -191,34 +175,45 @@ const Modal = ({
191
175
  stickyFooter,
192
176
  showHandle: props.showHandle,
193
177
  background: isBrandBackground ? 'brand' : 'primary',
194
- ...(inNavModal && {
195
- fullscreen: isNavModalFullScreen,
196
- }),
197
178
  });
198
179
 
199
- const footer = (
200
- <View style={styles.footer}>
201
- {onPressPrimaryButton && primaryButtonText ? (
202
- <Button
203
- onPress={handlePrimaryButtonPress}
204
- text={primaryButtonText}
205
- inverted={isBrandBackground && inNavModal}
206
- {...primaryButtonProps}
207
- variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
208
- colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
209
- />
210
- ) : null}
211
- {onPressSecondaryButton && secondaryButtonText ? (
212
- <Button
213
- onPress={handleSecondaryButtonPress}
214
- text={secondaryButtonText}
215
- inverted={isBrandBackground && inNavModal}
216
- {...secondaryButtonProps}
217
- variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
218
- colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
219
- />
220
- ) : null}
221
- </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
+ ]
222
217
  );
223
218
 
224
219
  const InNavModalContainer = scrollable ? ScrollView : View;
@@ -327,21 +322,11 @@ const Modal = ({
327
322
  <View style={styles.footerWrap}>{footer}</View>
328
323
  </BottomSheetFooter>
329
324
  ),
330
- [
331
- onPressPrimaryButton,
332
- primaryButtonText,
333
- onPressSecondaryButton,
334
- secondaryButtonText,
335
- primaryButtonProps,
336
- secondaryButtonProps,
337
- ]
325
+ [footer]
338
326
  );
339
327
 
340
328
  return inNavModal ? (
341
329
  <View
342
- onLayout={e => {
343
- setInNavModalHeight(e.nativeEvent.layout.height);
344
- }}
345
330
  style={{
346
331
  flex: 1,
347
332
  backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
@@ -355,7 +340,9 @@ const Modal = ({
355
340
  <Animated.View
356
341
  style={[styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle]}
357
342
  >
358
- <View style={styles.inNavModalContent}>{content}</View>
343
+ <SafeAreaView edges={['top', 'bottom']} style={styles.inNavModalContent}>
344
+ {content}
345
+ </SafeAreaView>
359
346
  </Animated.View>
360
347
  </View>
361
348
  ) : (
@@ -486,7 +473,7 @@ const styles = StyleSheet.create((theme, rt) => ({
486
473
  borderTopLeftRadius: theme.components.modal.borderRadius,
487
474
  borderTopRightRadius: theme.components.modal.borderRadius,
488
475
  backgroundColor: theme.color.surface.neutral.strong,
489
- paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
476
+ padding: theme.components.bottomSheet.padding,
490
477
  variants: {
491
478
  background: {
492
479
  primary: {},
@@ -494,15 +481,6 @@ const styles = StyleSheet.create((theme, rt) => ({
494
481
  backgroundColor: theme.color.background.brand,
495
482
  },
496
483
  },
497
- fullscreen: {
498
- true: {
499
- padding: theme.components.bottomSheet.padding,
500
- paddingTop: rt.insets.top,
501
- },
502
- false: {
503
- padding: theme.components.bottomSheet.padding,
504
- },
505
- },
506
484
  },
507
485
  },
508
486
  inNavModalFooterContainer: {
@@ -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
+ };