@utilitywarehouse/hearth-react-native 0.12.0 → 0.14.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 (155) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +22 -0
  4. package/build/components/Accordion/AccordionTrigger.js +1 -1
  5. package/build/components/BodyText/index.d.ts +1 -0
  6. package/build/components/Checkbox/CheckboxGroup.context.d.ts +2 -0
  7. package/build/components/Checkbox/CheckboxGroup.js +5 -5
  8. package/build/components/Checkbox/CheckboxIndicator.js +3 -3
  9. package/build/components/Checkbox/CheckboxTextContent.js +0 -1
  10. package/build/components/Checkbox/CheckboxTileRoot.js +9 -1
  11. package/build/components/DatePicker/DatePickerDay.js +3 -3
  12. package/build/components/FormField/FormField.d.ts +5 -5
  13. package/build/components/List/List.context.d.ts +2 -0
  14. package/build/components/List/List.context.js +2 -0
  15. package/build/components/List/List.js +19 -15
  16. package/build/components/List/ListAction/ListAction.js +3 -2
  17. package/build/components/List/ListAction/ListAction.props.d.ts +0 -1
  18. package/build/components/List/ListItem/ListItem.props.d.ts +4 -1
  19. package/build/components/List/ListItem/ListItemHeading.d.ts +13 -0
  20. package/build/components/List/ListItem/ListItemHeading.js +12 -0
  21. package/build/components/List/ListItem/ListItemHelperText.d.ts +2 -2
  22. package/build/components/List/ListItem/ListItemRoot.d.ts +1 -1
  23. package/build/components/List/ListItem/ListItemRoot.js +7 -6
  24. package/build/components/List/ListItem/index.d.ts +1 -1
  25. package/build/components/List/ListItem/index.js +1 -1
  26. package/build/components/PillGroup/Pill.js +2 -2
  27. package/build/components/Radio/RadioGroup.context.d.ts +2 -0
  28. package/build/components/Radio/RadioGroup.js +1 -1
  29. package/build/components/Radio/RadioIndicator.js +3 -3
  30. package/build/components/Radio/RadioTextContent.js +0 -1
  31. package/build/components/Radio/RadioTileRoot.js +9 -1
  32. package/build/components/RadioCard/RadioCardIndicator.js +3 -3
  33. package/build/components/RadioCard/RadioCardRoot.js +3 -3
  34. package/build/components/Tabs/Tab.js +5 -5
  35. package/build/components/ToggleButton/ToggleButtonRoot.js +2 -2
  36. package/build/components/ToggleButtonCard/ToggleButtonCardRoot.js +3 -3
  37. package/build/components/UnstyledIconButton/UnstyledIconButtonRoot.js +1 -1
  38. package/build/components/VerificationInput/VerificationInput.d.ts +6 -0
  39. package/build/components/VerificationInput/VerificationInput.js +35 -0
  40. package/build/components/VerificationInput/VerificationInput.props.d.ts +49 -0
  41. package/build/components/VerificationInput/VerificationInput.props.js +1 -0
  42. package/build/components/VerificationInput/VerificationInputSlot.d.ts +9 -0
  43. package/build/components/VerificationInput/VerificationInputSlot.js +72 -0
  44. package/build/components/VerificationInput/index.d.ts +4 -0
  45. package/build/components/VerificationInput/index.js +3 -0
  46. package/build/components/VerificationInput/useVerificationInput.d.ts +14 -0
  47. package/build/components/VerificationInput/useVerificationInput.js +58 -0
  48. package/build/components/index.d.ts +1 -0
  49. package/build/components/index.js +1 -0
  50. package/docs/components/AllComponents.web.tsx +9 -0
  51. package/package.json +10 -11
  52. package/src/components/Accordion/Accordion.figma.tsx +23 -0
  53. package/src/components/Accordion/AccordionItemRoot.figma.tsx +47 -0
  54. package/src/components/Accordion/AccordionTrigger.tsx +1 -1
  55. package/src/components/Alert/Alert.figma.tsx +47 -0
  56. package/src/components/Avatar/Avatar.figma.tsx +33 -0
  57. package/src/components/Badge/Badge.figma.tsx +48 -24
  58. package/src/components/Banner/Banner.figma.tsx +15 -0
  59. package/src/components/Banner/BannerIllustration.figma.tsx +30 -0
  60. package/src/components/BodyText/index.ts +1 -0
  61. package/src/components/BottomSheet/BottomSheetModal.figma.tsx +20 -0
  62. package/src/components/Button/Button.figma.tsx +60 -229
  63. package/src/components/Card/Card.figma.tsx +43 -71
  64. package/src/components/Card/CardAction/CardAction.figma.tsx +44 -0
  65. package/src/components/Card/CardAction/CardAction.stories.tsx +1 -1
  66. package/src/components/Carousel/Carousel.figma.tsx +19 -0
  67. package/src/components/Checkbox/Checkbox.figma.tsx +26 -41
  68. package/src/components/Checkbox/CheckboxGroup.context.ts +1 -0
  69. package/src/components/Checkbox/CheckboxGroup.figma.tsx +20 -0
  70. package/src/components/Checkbox/CheckboxGroup.tsx +7 -8
  71. package/src/components/Checkbox/CheckboxImage.figma.tsx +27 -0
  72. package/src/components/Checkbox/CheckboxIndicator.tsx +3 -3
  73. package/src/components/Checkbox/CheckboxTextContent.tsx +0 -1
  74. package/src/components/Checkbox/CheckboxTileRoot.figma.tsx +32 -0
  75. package/src/components/Checkbox/CheckboxTileRoot.tsx +9 -1
  76. package/src/components/CurrencyInput/CurrencyInput.figma.tsx +56 -0
  77. package/src/components/DateInput/DateInput.figma.tsx +75 -0
  78. package/src/components/DatePicker/DatePickerCalendar.figma.tsx +34 -0
  79. package/src/components/DatePicker/DatePickerDay.tsx +3 -3
  80. package/src/components/DatePickerInput/DatePickerInput.figma.tsx +62 -0
  81. package/src/components/DescriptionList/DescriptionList.figma.tsx +23 -0
  82. package/src/components/Divider/Divider.figma.tsx +23 -18
  83. package/src/components/ExpandableCard/ExpandableCard.figma.tsx +54 -0
  84. package/src/components/ExpandableCard/ExpandableCardGroup.figma.tsx +23 -0
  85. package/src/components/FormField/FormField.figma.tsx +23 -0
  86. package/src/components/Helper/HelperText.figma.tsx +23 -0
  87. package/src/components/IconButton/IconButton.figma.tsx +55 -161
  88. package/src/components/IconContainer/IconContainer.figma.tsx +50 -0
  89. package/src/components/InlineLink/InlineLink.figma.tsx +33 -0
  90. package/src/components/Input/Input.figma.tsx +52 -110
  91. package/src/components/Label/Label.figma.tsx +24 -0
  92. package/src/components/Link/Link.figma.tsx +42 -0
  93. package/src/components/List/List.context.ts +4 -0
  94. package/src/components/List/List.docs.mdx +30 -24
  95. package/src/components/List/List.figma.tsx +29 -108
  96. package/src/components/List/List.stories.tsx +26 -1
  97. package/src/components/List/List.tsx +26 -19
  98. package/src/components/List/ListAction/ListAction.figma.tsx +29 -0
  99. package/src/components/List/ListAction/ListAction.props.ts +0 -1
  100. package/src/components/List/ListAction/ListAction.tsx +3 -2
  101. package/src/components/List/ListItem/ListItem.figma.tsx +40 -220
  102. package/src/components/List/ListItem/ListItem.props.ts +4 -1
  103. package/src/components/List/ListItem/ListItemHeading.tsx +20 -0
  104. package/src/components/List/ListItem/ListItemHelperText.tsx +2 -3
  105. package/src/components/List/ListItem/ListItemLeadingContent.figma.tsx +29 -0
  106. package/src/components/List/ListItem/ListItemRoot.tsx +11 -6
  107. package/src/components/List/ListItem/ListItemTrailingContent.figma.tsx +27 -0
  108. package/src/components/List/ListItem/index.ts +1 -1
  109. package/src/components/Menu/Menu.figma.tsx +30 -0
  110. package/src/components/Menu/MenuItem.figma.tsx +31 -0
  111. package/src/components/Modal/Modal.docs.mdx +23 -20
  112. package/src/components/Modal/Modal.figma.tsx +56 -0
  113. package/src/components/PillGroup/Pill.figma.tsx +25 -0
  114. package/src/components/PillGroup/Pill.tsx +3 -3
  115. package/src/components/PillGroup/PillGroup.figma.tsx +21 -0
  116. package/src/components/ProgressStepper/ProgressStep.figma.tsx +30 -0
  117. package/src/components/ProgressStepper/ProgressStepper.figma.tsx +20 -0
  118. package/src/components/Radio/Radio.figma.tsx +22 -42
  119. package/src/components/Radio/Radio.stories.tsx +24 -0
  120. package/src/components/Radio/RadioGroup.context.ts +1 -0
  121. package/src/components/Radio/RadioGroup.figma.tsx +54 -0
  122. package/src/components/Radio/RadioGroup.tsx +2 -2
  123. package/src/components/Radio/RadioImage.figma.tsx +27 -0
  124. package/src/components/Radio/RadioIndicator.tsx +3 -3
  125. package/src/components/Radio/RadioTextContent.tsx +0 -1
  126. package/src/components/Radio/RadioTileRoot.figma.tsx +31 -0
  127. package/src/components/Radio/RadioTileRoot.tsx +9 -1
  128. package/src/components/RadioCard/RadioCardIndicator.tsx +3 -3
  129. package/src/components/RadioCard/RadioCardRoot.tsx +3 -3
  130. package/src/components/SectionHeader/SectionHeader.figma.tsx +30 -16
  131. package/src/components/Select/Select.figma.tsx +55 -0
  132. package/src/components/Select/SelectOption.figma.tsx +36 -0
  133. package/src/components/Spinner/Spinner.figma.tsx +20 -12
  134. package/src/components/Switch/Switch.figma.tsx +31 -23
  135. package/src/components/Tabs/Tab.tsx +5 -5
  136. package/src/components/Tabs/Tabs.figma.tsx +29 -0
  137. package/src/components/ThemedImage/ThemedImage.stories.tsx +1 -1
  138. package/src/components/Toast/ToastItem.figma.tsx +22 -0
  139. package/src/components/ToggleButton/ToggleButtonRoot.tsx +2 -2
  140. package/src/components/ToggleButtonCard/ToggleButtonCardRoot.tsx +3 -3
  141. package/src/components/UnstyledIconButton/UnstyledIconButton.figma.tsx +49 -0
  142. package/src/components/UnstyledIconButton/UnstyledIconButtonRoot.tsx +1 -1
  143. package/src/components/VerificationInput/VerificationInput.docs.mdx +68 -0
  144. package/src/components/VerificationInput/VerificationInput.props.ts +52 -0
  145. package/src/components/VerificationInput/VerificationInput.stories.tsx +140 -0
  146. package/src/components/VerificationInput/VerificationInput.tsx +89 -0
  147. package/src/components/VerificationInput/VerificationInputSlot.tsx +94 -0
  148. package/src/components/VerificationInput/index.ts +5 -0
  149. package/src/components/VerificationInput/useVerificationInput.ts +72 -0
  150. package/src/components/index.ts +1 -0
  151. package/build/components/List/ListItem/ListItemText.d.ts +0 -6
  152. package/build/components/List/ListItem/ListItemText.js +0 -7
  153. package/src/components/Checkbox/CheckboxIndicator.figma.tsx +0 -19
  154. package/src/components/List/ListItem/ListItemText.tsx +0 -14
  155. package/src/components/Radio/RadioIndicator.figma.tsx +0 -21
@@ -1,29 +1,37 @@
1
- import { Switch } from './';
2
1
  import figma from '@figma/code-connect';
2
+ import Switch from './Switch';
3
3
 
4
4
  /**
5
5
  * -- This file was auto-generated by Code Connect --
6
- * `props` includes a mapping from Figma properties and variants to
7
- * suggested values. You should update this to match the props of your
8
- * code component, and update the `example` function to return the
9
- * code example you'd like to see in Figma
6
+ * `props` includes a mapping from your code props to Figma properties.
7
+ * You should check this is correct, and update the `example` function
8
+ * to return the code example you'd like to see in Figma
10
9
  */
11
10
 
12
- figma.connect(
13
- Switch,
14
- 'https://www.figma.com/design/3RY3OvLA88yZksRjOfjQJm/UW-App-UI?node-id=9551-7352&t=fDsV1m42i8hVbZo3-4',
15
- {
16
- props: {
17
- value: figma.boolean('checked'),
18
- state: figma.enum('state', {
19
- default: 'default',
20
- }),
21
- size: figma.enum('size', {
22
- 'medium - 32': 'medium',
23
- 'small - 24': 'small',
24
- }),
25
- disabled: figma.boolean('disabled'),
26
- },
27
- example: props => <Switch {...props} />,
28
- }
29
- );
11
+ figma.connect(Switch, 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=3044%3A243', {
12
+ props: {
13
+ // These props were automatically mapped based on your linked code:
14
+ disabled: figma.enum('State', {
15
+ Disabled: true,
16
+ }),
17
+ 'aria-disabled': figma.enum('State', {
18
+ Disabled: true,
19
+ }),
20
+ focusable: figma.enum('State', {
21
+ Focus: true,
22
+ }),
23
+ checked: figma.boolean('Checked?'),
24
+ size: figma.enum('Size', {
25
+ 'MD-32': 'medium',
26
+ 'SM-24': 'small',
27
+ }),
28
+ },
29
+ example: props => (
30
+ <Switch
31
+ value={props.checked}
32
+ disabled={props.disabled}
33
+ focusable={props.focusable}
34
+ size={props.size}
35
+ />
36
+ ),
37
+ });
@@ -76,11 +76,11 @@ const styles = StyleSheet.create(theme => ({
76
76
  _hover: {
77
77
  backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
78
78
  },
79
- '_focus-visible': {
80
- ...theme.helpers.focusVisible,
81
- outlineOffset: -2,
82
- borderRadius: theme.borderRadius.sm,
83
- },
79
+ // '_focus-visible': {
80
+ // ...theme.helpers.focusVisible,
81
+ // outlineOffset: -2,
82
+ // borderRadius: theme.borderRadius.sm,
83
+ // },
84
84
  _active: {
85
85
  backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
86
86
  },
@@ -0,0 +1,29 @@
1
+ import React from "react"
2
+ import Tabs from "./Tabs"
3
+ import figma from "@figma/code-connect"
4
+
5
+ /**
6
+ * -- This file was auto-generated by Code Connect --
7
+ * `props` includes a mapping from your code props to Figma properties.
8
+ * You should check this is correct, and update the `example` function
9
+ * to return the code example you'd like to see in Figma
10
+ */
11
+
12
+ figma.connect(
13
+ Tabs,
14
+ "https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=6464%3A8744",
15
+ {
16
+ props: {
17
+ // These props were automatically mapped based on your linked code:
18
+ size: figma.enum("Size", {
19
+ "MD-56": "md",
20
+ "LG-64": "lg",
21
+ }),
22
+ // No matching props could be found for these Figma properties:
23
+ // "arrowLeft": figma.boolean('Arrow left?'),
24
+ // "arrowRight": figma.boolean('Arrow right?'),
25
+ // "condensed": figma.boolean('Condensed?')
26
+ },
27
+ example: (props) => <Tabs size={props.size} />,
28
+ },
29
+ )
@@ -1,4 +1,4 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
1
+ import type { Meta, StoryObj } from '@storybook/react-native';
2
2
  import MascotEnergyDark from '@utilitywarehouse/hearth-svg-assets/lib/mascot-energy-dark.svg';
3
3
  import MascotEnergyLight from '@utilitywarehouse/hearth-svg-assets/lib/mascot-energy-light.svg';
4
4
  import SceneBroadbandDark from '@utilitywarehouse/hearth-svg-assets/lib/scene-broadband-dark.svg';
@@ -0,0 +1,22 @@
1
+ import figma from '@figma/code-connect';
2
+ import ToastItem from './ToastItem';
3
+
4
+ /**
5
+ * -- This file was auto-generated by Code Connect --
6
+ * None of your props could be automatically mapped to Figma properties.
7
+ * You should update the `props` object to include a mapping from your
8
+ * code props to Figma properties, and update the `example` function to
9
+ * return the code example you'd like to see in Figma
10
+ */
11
+
12
+ figma.connect(ToastItem, 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=7072%3A913', {
13
+ props: {
14
+ // No matching props could be found for these Figma properties:
15
+ // "link": figma.boolean('Link?'),
16
+ // "text": figma.string('Text'),
17
+ // "icon": figma.boolean('Icon?'),
18
+ // "icon24": figma.instance('Icon-24'),
19
+ // "dismiss": figma.boolean('Dismiss?')
20
+ },
21
+ example: props => <ToastItem toast={null} onClose={null} />,
22
+ });
@@ -16,7 +16,7 @@ const ButtonRoot = ({
16
16
 
17
17
  styles.useVariants({
18
18
  toggled,
19
- active: active || pressed,
19
+ active,
20
20
  });
21
21
 
22
22
  const handlePress = (e: GestureResponderEvent) => {
@@ -56,7 +56,7 @@ const styles = StyleSheet.create(theme => ({
56
56
  borderColor: theme.color.interactive.neutral.border.subtle,
57
57
  height: theme.components.toggleButton.height,
58
58
  _web: {
59
- '_focus-visible': theme.helpers.focusVisible,
59
+ // '_focus-visible': theme.helpers.focusVisible,
60
60
  _hover: {
61
61
  backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
62
62
  },
@@ -77,9 +77,9 @@ const styles = StyleSheet.create(theme => ({
77
77
  },
78
78
  },
79
79
  _web: {
80
- '_focus-visible': {
81
- ...theme.helpers.focusVisible,
82
- },
80
+ // '_focus-visible': {
81
+ // ...theme.helpers.focusVisible,
82
+ // },
83
83
  },
84
84
  },
85
85
  buttonContainer: {
@@ -0,0 +1,49 @@
1
+ import figma from '@figma/code-connect';
2
+ import UnstyledIconButton from './UnstyledIconButton';
3
+
4
+ /**
5
+ * -- This file was auto-generated by Code Connect --
6
+ * `props` includes a mapping from your code props to Figma properties.
7
+ * You should check this is correct, and update the `example` function
8
+ * to return the code example you'd like to see in Figma
9
+ */
10
+
11
+ figma.connect(
12
+ UnstyledIconButton,
13
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR?node-id=2926%3A2430',
14
+ {
15
+ props: {
16
+ // These props were automatically mapped based on your linked code:
17
+ disabled: figma.enum('State', {
18
+ Disabled: true,
19
+ }),
20
+ loading: figma.enum('State', {
21
+ Loading: true,
22
+ }),
23
+ size: figma.enum('Size', {
24
+ 'SM-20': 'sm',
25
+ 'MD-24': 'md',
26
+ }),
27
+ inverted: figma.boolean('Inverted?'),
28
+ 'aria-disabled': figma.enum('State', {
29
+ Disabled: true,
30
+ }),
31
+ focusable: figma.enum('State', {
32
+ Focus: true,
33
+ }),
34
+ // No matching props could be found for these Figma properties:
35
+ // "icon24": figma.instance('Icon-24'),
36
+ // "icon20": figma.instance('Icon-20')
37
+ },
38
+ example: props => (
39
+ <UnstyledIconButton
40
+ disabled={props.disabled}
41
+ icon={null}
42
+ loading={props.loading}
43
+ size={props.size}
44
+ inverted={props.inverted}
45
+ focusable={props.focusable}
46
+ />
47
+ ),
48
+ }
49
+ );
@@ -36,7 +36,7 @@ const styles = StyleSheet.create(theme => ({
36
36
  alignItems: 'center',
37
37
  borderRadius: theme.borderRadius.sm,
38
38
  _web: {
39
- '_focus-visible': theme.helpers.focusVisible,
39
+ // '_focus-visible': theme.helpers.focusVisible,
40
40
  },
41
41
  variants: {
42
42
  disabled: {
@@ -0,0 +1,68 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { InfoMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
+ import { VerificationInput } from '../../';
4
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
5
+ import * as Stories from './VerificationInput.stories';
6
+
7
+ <Meta title="Forms / Verification Input" />
8
+
9
+ <BackToTopButton />
10
+
11
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=4049-3615&t=m0WHtPBmyE9YVP8Q-0" />
12
+
13
+ # Verification Input
14
+
15
+ The verification input component is used to capture OTP (One Time Password) or other verification codes.
16
+
17
+ - [Playground](#playground)
18
+ - [Usage](#usage)
19
+ - [Props](#props)
20
+ - [Variants](#variants)
21
+ - [States](#states)
22
+ - [Secure Text Entry](#secure-text-entry)
23
+
24
+ ## Playground
25
+
26
+ <Canvas of={Stories.Playground} />
27
+
28
+ <Controls of={Stories.Playground} />
29
+
30
+ ## Usage
31
+
32
+ <UsageWrap>
33
+ <VerificationInput label="Enter Code" onChangeText={code => console.log(code)} />
34
+ </UsageWrap>
35
+
36
+ ```tsx
37
+ import { VerificationInput } from '@utilitywarehouse/hearth-react-native';
38
+
39
+ const MyComponent = () => {
40
+ const [code, setCode] = useState('');
41
+
42
+ return <VerificationInput label="Enter Code" value={code} onChangeText={setCode} />;
43
+ };
44
+ ```
45
+
46
+ ## Props
47
+
48
+ ### `VerificationInput`
49
+
50
+ The component accepts the following props:
51
+
52
+ | Prop | Type | Default | Description |
53
+ | :----------------- | :---------------------------------- | :---------- | :----------------------------------------------------------- |
54
+ | `value` | `string` | - | The value of the input. |
55
+ | `onChangeText` | `(text: string) => void` | - | Callback when the value changes. |
56
+ | `label` | `string` | - | The label for the input. |
57
+ | `helperText` | `string` | - | Helper text to display below the input. |
58
+ | `helperIcon` | `ComponentType` | - | Icon to display alongside the helper text. |
59
+ | `validationStatus` | `'initial' \| 'valid' \| 'invalid'` | `'initial'` | The validation status of the input. |
60
+ | `validText` | `string` | - | Text to display when validation status is 'valid'. |
61
+ | `invalidText` | `string` | - | Text to display when validation status is 'invalid'. |
62
+ | `disabled` | `boolean` | `false` | Whether the input is disabled. |
63
+ | `readonly` | `boolean` | `false` | Whether the input is read-only. |
64
+ | `secureTextEntry` | `boolean` | `false` | Whether to obscure the text entry (e.g. for passwords/OTPs). |
65
+
66
+ ## Variants
67
+
68
+ <Canvas of={Stories.Variants} />
@@ -0,0 +1,52 @@
1
+ import type { ComponentType } from 'react';
2
+ import { ViewProps } from 'react-native';
3
+
4
+ export interface VerificationInputProps extends ViewProps {
5
+ /**
6
+ * The value of the input.
7
+ */
8
+ value?: string;
9
+ /**
10
+ * Callback when the value changes.
11
+ */
12
+ onChangeText?: (text: string) => void;
13
+ /**
14
+ * The label for the input.
15
+ */
16
+ label?: string;
17
+ /**
18
+ * Helper text to display below the input.
19
+ */
20
+ helperText?: string;
21
+ /**
22
+ * Icon to display alongside the helper text.
23
+ */
24
+ helperIcon?: ComponentType;
25
+ /**
26
+ * The validation status of the input.
27
+ */
28
+ validationStatus?: 'initial' | 'valid' | 'invalid';
29
+ /**
30
+ * Text to display when validation status is 'valid'.
31
+ */
32
+ validText?: string;
33
+ /**
34
+ * Text to display when validation status is 'invalid'.
35
+ */
36
+ invalidText?: string;
37
+ /**
38
+ * Whether the input is disabled.
39
+ */
40
+ disabled?: boolean;
41
+ /**
42
+ * Whether the input is read-only.
43
+ */
44
+ readonly?: boolean;
45
+ /**
46
+ * Whether to obscure the text entry (e.g. for passwords/OTPs).
47
+ */
48
+ secureTextEntry?: boolean;
49
+ }
50
+
51
+ export default VerificationInputProps;
52
+
@@ -0,0 +1,140 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { InfoMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
+ import React, { useState } from 'react';
4
+ import { VerificationInput } from '.';
5
+ import { VariantTitle } from '../../../docs/components';
6
+ import { Flex } from '../Flex';
7
+
8
+ const meta = {
9
+ title: 'Stories / VerificationInput',
10
+ component: VerificationInput,
11
+ parameters: {
12
+ layout: 'centered',
13
+ },
14
+ argTypes: {
15
+ value: { control: 'text' },
16
+ label: { control: 'text' },
17
+ helperText: { control: 'text' },
18
+ validationStatus: {
19
+ control: 'select',
20
+ options: ['initial', 'valid', 'invalid'],
21
+ },
22
+ validText: { control: 'text' },
23
+ invalidText: { control: 'text' },
24
+ disabled: { control: 'boolean' },
25
+ readonly: { control: 'boolean' },
26
+ secureTextEntry: { control: 'boolean' },
27
+ },
28
+ args: {
29
+ label: 'Verification Code',
30
+ validationStatus: 'initial',
31
+ },
32
+ } satisfies Meta<typeof VerificationInput>;
33
+
34
+ export default meta;
35
+ type Story = StoryObj<typeof meta>;
36
+
37
+ export const Playground: Story = {
38
+ render: args => {
39
+ const [value, setValue] = useState(args.value || '');
40
+ return (
41
+ <VerificationInput
42
+ {...args}
43
+ value={value}
44
+ onChangeText={text => {
45
+ setValue(text);
46
+ args.onChangeText?.(text);
47
+ }}
48
+ />
49
+ );
50
+ },
51
+ };
52
+
53
+ export const Variants: Story = {
54
+ parameters: {
55
+ controls: { include: [] },
56
+ },
57
+ render: () => {
58
+ const [values, setValues] = useState({
59
+ default: '',
60
+ filled: '123456',
61
+ invalid: '123',
62
+ valid: '123456',
63
+ disabled: '',
64
+ secure: '123',
65
+ });
66
+
67
+ const updateValue = (key: keyof typeof values) => (text: string) => {
68
+ setValues(prev => ({ ...prev, [key]: text }));
69
+ };
70
+
71
+ return (
72
+ <Flex direction="column" space="lg" style={{ width: 400 }}>
73
+ <VariantTitle title="Default">
74
+ <VerificationInput
75
+ label="Verification Code"
76
+ helperText="Enter the code sent to your phone"
77
+ value={values.default}
78
+ onChangeText={updateValue('default')}
79
+ />
80
+ </VariantTitle>
81
+
82
+ <VariantTitle title="Filled">
83
+ <VerificationInput
84
+ label="Filled Input"
85
+ value={values.filled}
86
+ onChangeText={updateValue('filled')}
87
+ />
88
+ </VariantTitle>
89
+
90
+ <VariantTitle title="Invalid">
91
+ <VerificationInput
92
+ label="Invalid Input"
93
+ validationStatus="invalid"
94
+ invalidText="The code you entered is incorrect"
95
+ value={values.invalid}
96
+ onChangeText={updateValue('invalid')}
97
+ />
98
+ </VariantTitle>
99
+
100
+ <VariantTitle title="Valid">
101
+ <VerificationInput
102
+ label="Valid Input"
103
+ validationStatus="valid"
104
+ validText="Code verified!"
105
+ value={values.valid}
106
+ onChangeText={updateValue('valid')}
107
+ />
108
+ </VariantTitle>
109
+
110
+ <VariantTitle title="Disabled">
111
+ <VerificationInput
112
+ label="Disabled Input"
113
+ disabled
114
+ value={values.disabled}
115
+ onChangeText={updateValue('disabled')}
116
+ />
117
+ </VariantTitle>
118
+
119
+ <VariantTitle title="Secure Text Entry">
120
+ <VerificationInput
121
+ label="Secure Input"
122
+ secureTextEntry
123
+ value={values.secure}
124
+ onChangeText={updateValue('secure')}
125
+ />
126
+ </VariantTitle>
127
+
128
+ <VariantTitle title="With Helper Icon">
129
+ <VerificationInput
130
+ label="Helper Icon"
131
+ helperText="Some information"
132
+ helperIcon={InfoMediumIcon}
133
+ value={values.default}
134
+ onChangeText={updateValue('default')}
135
+ />
136
+ </VariantTitle>
137
+ </Flex>
138
+ );
139
+ },
140
+ };
@@ -0,0 +1,89 @@
1
+ import { View } from 'react-native';
2
+ import { StyleSheet } from 'react-native-unistyles';
3
+ import { FormField } from '../FormField';
4
+ import { useVerificationInput } from './useVerificationInput';
5
+ import VerificationInputProps from './VerificationInput.props';
6
+ import { VerificationInputSlot } from './VerificationInputSlot';
7
+
8
+ const VerificationInput = ({
9
+ value = '',
10
+ onChangeText,
11
+ label,
12
+ helperText,
13
+ helperIcon,
14
+ validationStatus = 'initial',
15
+ validText,
16
+ invalidText,
17
+ disabled = false,
18
+ readonly = false,
19
+ secureTextEntry = false,
20
+ style,
21
+ ...props
22
+ }: VerificationInputProps) => {
23
+ const length = 6;
24
+ const { inputRefs, focusedIndex, handleFocus, handleBlur, handleChangeText, handleKeyPress } =
25
+ useVerificationInput({
26
+ value,
27
+ onChangeText,
28
+ });
29
+
30
+ const slots = Array.from({ length }, (_, index) => index);
31
+
32
+ return (
33
+ <FormField
34
+ label={label}
35
+ helperText={helperText}
36
+ helperIcon={helperIcon}
37
+ validationStatus={validationStatus}
38
+ validText={validText}
39
+ invalidText={invalidText}
40
+ disabled={disabled}
41
+ readonly={readonly}
42
+ style={[styles.root, style]}
43
+ {...props}
44
+ >
45
+ <View style={styles.slotsContainer}>
46
+ {slots.map(index => {
47
+ const char = value[index] || '';
48
+ const isActive = focusedIndex === index;
49
+
50
+ return (
51
+ <VerificationInputSlot
52
+ key={index}
53
+ ref={ref => {
54
+ inputRefs.current[index] = ref;
55
+ }}
56
+ value={char}
57
+ isActive={isActive}
58
+ validationStatus={validationStatus}
59
+ disabled={disabled}
60
+ readonly={readonly}
61
+ secureTextEntry={secureTextEntry}
62
+ onChangeText={text => handleChangeText(text, index)}
63
+ onKeyPress={e => handleKeyPress(e, index)}
64
+ onFocus={() => handleFocus(index)}
65
+ onBlur={handleBlur}
66
+ />
67
+ );
68
+ })}
69
+ </View>
70
+ </FormField>
71
+ );
72
+ };
73
+
74
+ const styles = StyleSheet.create(theme => ({
75
+ root: {
76
+ gap: theme.components.input.verification.gap,
77
+ width: '100%',
78
+ maxWidth: theme.components.input.maxWidth,
79
+ },
80
+ slotsContainer: {
81
+ flexDirection: 'row',
82
+ gap: theme.components.input.verification.gap,
83
+ width: '100%',
84
+ },
85
+ }));
86
+
87
+ VerificationInput.displayName = 'VerificationInput';
88
+
89
+ export default VerificationInput;
@@ -0,0 +1,94 @@
1
+ import { forwardRef } from 'react';
2
+ import { TextInput, TextInputProps } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import InputField from '../Input/InputField';
5
+
6
+ interface VerificationInputSlotProps extends TextInputProps {
7
+ isActive: boolean;
8
+ validationStatus: 'initial' | 'valid' | 'invalid';
9
+ disabled?: boolean;
10
+ readonly?: boolean;
11
+ }
12
+
13
+ export const VerificationInputSlot = forwardRef<TextInput, VerificationInputSlotProps>(
14
+ ({ isActive, validationStatus, disabled, readonly, style, ...props }, ref) => {
15
+ styles.useVariants({
16
+ disabled,
17
+ readonly,
18
+ validationStatus,
19
+ active: isActive,
20
+ });
21
+
22
+ return (
23
+ <InputField
24
+ ref={ref}
25
+ {...props}
26
+ editable={!disabled && !readonly}
27
+ selectTextOnFocus
28
+ keyboardType="number-pad"
29
+ style={[styles.slot, style]}
30
+ />
31
+ );
32
+ }
33
+ );
34
+
35
+ VerificationInputSlot.displayName = 'VerificationInputSlot';
36
+
37
+ const styles = StyleSheet.create(theme => ({
38
+ slot: {
39
+ flex: 0,
40
+ width: theme.components.input.height,
41
+ height: theme.components.input.height,
42
+ borderWidth: theme.components.input.borderWidth,
43
+ borderColor: theme.color.border.strong,
44
+ borderRadius: theme.components.input.borderRadius,
45
+ backgroundColor: theme.color.surface.neutral.strong,
46
+ textAlign: 'center',
47
+ padding: 0,
48
+ variants: {
49
+ disabled: {
50
+ true: {
51
+ opacity: theme.opacity.disabled,
52
+ color: theme.color.text.secondary,
53
+ },
54
+ },
55
+ readonly: {
56
+ true: {
57
+ borderColor: theme.color.border.subtle,
58
+ backgroundColor: theme.color.surface.neutral.subtle,
59
+ },
60
+ },
61
+ validationStatus: {
62
+ initial: {},
63
+ valid: {
64
+ borderColor: theme.color.feedback.positive.border,
65
+ },
66
+ invalid: {
67
+ borderColor: theme.color.feedback.danger.border,
68
+ },
69
+ },
70
+ active: {
71
+ true: {
72
+ borderColor: theme.color.border.strong,
73
+ borderWidth: theme.components.input.borderWidthFocused,
74
+ },
75
+ },
76
+ },
77
+ compoundVariants: [
78
+ {
79
+ validationStatus: 'invalid',
80
+ active: true,
81
+ styles: {
82
+ borderColor: theme.color.feedback.danger.border,
83
+ },
84
+ },
85
+ {
86
+ validationStatus: 'valid',
87
+ active: true,
88
+ styles: {
89
+ borderColor: theme.color.border.strong,
90
+ },
91
+ },
92
+ ],
93
+ },
94
+ }));
@@ -0,0 +1,5 @@
1
+ import VerificationInput from './VerificationInput';
2
+ export { default as VerificationInput } from './VerificationInput';
3
+ export type { default as VerificationInputProps } from './VerificationInput.props';
4
+ export default VerificationInput;
5
+