@utilitywarehouse/hearth-react-native 0.31.0 → 0.32.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 (123) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +13 -13
  3. package/CHANGELOG.md +71 -0
  4. package/build/components/Rating/Rating.d.ts +6 -0
  5. package/build/components/Rating/Rating.js +76 -0
  6. package/build/components/Rating/Rating.props.d.ts +18 -0
  7. package/build/components/Rating/Rating.props.js +1 -0
  8. package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
  9. package/build/components/Rating/RatingStarEmpty.js +9 -0
  10. package/build/components/Rating/RatingStarFilled.d.ts +6 -0
  11. package/build/components/Rating/RatingStarFilled.js +9 -0
  12. package/build/components/Rating/index.d.ts +2 -0
  13. package/build/components/Rating/index.js +1 -0
  14. package/build/components/Roundel/Roundel.d.ts +6 -0
  15. package/build/components/Roundel/Roundel.js +40 -0
  16. package/build/components/Roundel/Roundel.props.d.ts +6 -0
  17. package/build/components/Roundel/Roundel.props.js +1 -0
  18. package/build/components/Roundel/index.d.ts +2 -0
  19. package/build/components/Roundel/index.js +1 -0
  20. package/build/components/StepperInput/StepperButton.d.ts +22 -0
  21. package/build/components/StepperInput/StepperButton.js +55 -0
  22. package/build/components/StepperInput/StepperInput.d.ts +6 -0
  23. package/build/components/StepperInput/StepperInput.js +196 -0
  24. package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
  25. package/build/components/StepperInput/StepperInput.props.js +1 -0
  26. package/build/components/StepperInput/index.d.ts +2 -0
  27. package/build/components/StepperInput/index.js +1 -0
  28. package/build/components/Table/TableHeaderCell.js +10 -1
  29. package/build/components/Textarea/Textarea.d.ts +1 -1
  30. package/build/components/Textarea/Textarea.js +10 -3
  31. package/build/components/Textarea/Textarea.props.d.ts +11 -0
  32. package/build/components/index.d.ts +3 -0
  33. package/build/components/index.js +3 -0
  34. package/build/core/themes.d.ts +92 -88
  35. package/build/tokens/color.d.ts +82 -80
  36. package/build/tokens/color.js +41 -40
  37. package/build/tokens/components/dark/alert.d.ts +6 -6
  38. package/build/tokens/components/dark/alert.js +6 -6
  39. package/build/tokens/components/dark/bottom-navigation.d.ts +2 -2
  40. package/build/tokens/components/dark/bottom-navigation.js +2 -2
  41. package/build/tokens/components/dark/checkbox.d.ts +1 -1
  42. package/build/tokens/components/dark/checkbox.js +1 -1
  43. package/build/tokens/components/dark/icon-button.d.ts +3 -3
  44. package/build/tokens/components/dark/icon-button.js +3 -3
  45. package/build/tokens/components/dark/inline-link.d.ts +1 -1
  46. package/build/tokens/components/dark/inline-link.js +1 -1
  47. package/build/tokens/components/dark/link.d.ts +3 -3
  48. package/build/tokens/components/dark/link.js +3 -3
  49. package/build/tokens/components/dark/navigation.d.ts +2 -2
  50. package/build/tokens/components/dark/navigation.js +2 -2
  51. package/build/tokens/components/dark/parts.d.ts +2 -2
  52. package/build/tokens/components/dark/parts.js +2 -2
  53. package/build/tokens/components/dark/progress-bar.d.ts +3 -3
  54. package/build/tokens/components/dark/progress-bar.js +3 -3
  55. package/build/tokens/components/dark/progress-stepper.d.ts +1 -1
  56. package/build/tokens/components/dark/progress-stepper.js +1 -1
  57. package/build/tokens/components/dark/spinner.d.ts +1 -1
  58. package/build/tokens/components/dark/spinner.js +1 -1
  59. package/build/tokens/components/dark/table.d.ts +2 -0
  60. package/build/tokens/components/dark/table.js +2 -0
  61. package/build/tokens/components/dark/time-picker.d.ts +1 -0
  62. package/build/tokens/components/dark/time-picker.js +1 -0
  63. package/build/tokens/components/light/parts.d.ts +3 -3
  64. package/build/tokens/components/light/parts.js +3 -3
  65. package/build/tokens/components/light/table.d.ts +2 -0
  66. package/build/tokens/components/light/table.js +2 -0
  67. package/build/tokens/components/light/time-picker.d.ts +1 -0
  68. package/build/tokens/components/light/time-picker.js +1 -0
  69. package/build/tokens/semantic-dark.d.ts +40 -40
  70. package/build/tokens/semantic-dark.js +40 -40
  71. package/docs/adding-shadows.mdx +2 -2
  72. package/docs/changelog.mdx +165 -0
  73. package/docs/components/AllComponents.web.tsx +30 -1
  74. package/docs/dark-mode-best-practice.mdx +328 -0
  75. package/package.json +1 -1
  76. package/src/components/Modal/Modal.docs.mdx +58 -4
  77. package/src/components/NavModal/NavModal.docs.mdx +2 -2
  78. package/src/components/Rating/Rating.docs.mdx +178 -0
  79. package/src/components/Rating/Rating.figma.tsx +20 -0
  80. package/src/components/Rating/Rating.props.ts +22 -0
  81. package/src/components/Rating/Rating.stories.tsx +95 -0
  82. package/src/components/Rating/Rating.tsx +140 -0
  83. package/src/components/Rating/RatingStarEmpty.tsx +22 -0
  84. package/src/components/Rating/RatingStarFilled.tsx +27 -0
  85. package/src/components/Rating/index.ts +2 -0
  86. package/src/components/Roundel/Roundel.docs.mdx +48 -0
  87. package/src/components/Roundel/Roundel.figma.tsx +17 -0
  88. package/src/components/Roundel/Roundel.props.ts +8 -0
  89. package/src/components/Roundel/Roundel.stories.tsx +49 -0
  90. package/src/components/Roundel/Roundel.tsx +51 -0
  91. package/src/components/Roundel/index.ts +2 -0
  92. package/src/components/StepperInput/StepperButton.tsx +83 -0
  93. package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
  94. package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
  95. package/src/components/StepperInput/StepperInput.props.ts +39 -0
  96. package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
  97. package/src/components/StepperInput/StepperInput.tsx +349 -0
  98. package/src/components/StepperInput/index.ts +2 -0
  99. package/src/components/Table/TableHeaderCell.tsx +10 -1
  100. package/src/components/Textarea/Textarea.docs.mdx +2 -0
  101. package/src/components/Textarea/Textarea.props.ts +11 -0
  102. package/src/components/Textarea/Textarea.stories.tsx +14 -0
  103. package/src/components/Textarea/Textarea.tsx +11 -2
  104. package/src/components/index.ts +3 -0
  105. package/src/tokens/color.ts +41 -40
  106. package/src/tokens/components/dark/alert.ts +6 -6
  107. package/src/tokens/components/dark/bottom-navigation.ts +2 -2
  108. package/src/tokens/components/dark/checkbox.ts +1 -1
  109. package/src/tokens/components/dark/icon-button.ts +3 -3
  110. package/src/tokens/components/dark/inline-link.ts +1 -1
  111. package/src/tokens/components/dark/link.ts +3 -3
  112. package/src/tokens/components/dark/navigation.ts +2 -2
  113. package/src/tokens/components/dark/parts.ts +2 -2
  114. package/src/tokens/components/dark/progress-bar.ts +3 -3
  115. package/src/tokens/components/dark/progress-stepper.ts +1 -1
  116. package/src/tokens/components/dark/spinner.ts +1 -1
  117. package/src/tokens/components/dark/table.ts +2 -0
  118. package/src/tokens/components/dark/time-picker.ts +1 -0
  119. package/src/tokens/components/light/parts.ts +3 -3
  120. package/src/tokens/components/light/table.ts +2 -0
  121. package/src/tokens/components/light/time-picker.ts +1 -0
  122. package/src/tokens/semantic-dark.ts +40 -40
  123. package/vercel.json +0 -21
@@ -0,0 +1,51 @@
1
+ import { CloseSmallIcon, TickSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
2
+ import { View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { Icon } from '../Icon';
5
+ import type RoundelProps from './Roundel.props';
6
+
7
+ const Roundel = ({ variant = 'success', style, ...props }: RoundelProps) => {
8
+ styles.useVariants({ variant });
9
+
10
+ const icon =
11
+ variant === 'error' ? CloseSmallIcon : variant === 'success' ? TickSmallIcon : undefined;
12
+
13
+ return (
14
+ <View {...props} style={[styles.container, style]}>
15
+ {icon ? <Icon as={icon} size="sm" /> : null}
16
+ </View>
17
+ );
18
+ };
19
+
20
+ Roundel.displayName = 'Roundel';
21
+
22
+ const styles = StyleSheet.create(theme => ({
23
+ container: {
24
+ width: theme.space[300],
25
+ height: theme.space[300],
26
+ borderRadius: theme.components.parts.roundel.borderRadius,
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ overflow: 'hidden',
30
+ backgroundColor: 'transparent',
31
+ borderWidth: 0,
32
+ borderStyle: 'solid',
33
+ variants: {
34
+ variant: {
35
+ success: {
36
+ backgroundColor: theme.color.feedback.positive.surface.default,
37
+ },
38
+ error: {
39
+ backgroundColor: theme.color.feedback.danger.surface.default,
40
+ },
41
+ pending: {
42
+ borderWidth: theme.components.parts.roundel.pending.borderWidth,
43
+ borderStyle: 'dashed',
44
+ borderColor: theme.color.border.strong,
45
+ },
46
+ },
47
+ },
48
+ },
49
+ }));
50
+
51
+ export default Roundel;
@@ -0,0 +1,2 @@
1
+ export { default as Roundel } from './Roundel';
2
+ export type { RoundelProps } from './Roundel.props';
@@ -0,0 +1,83 @@
1
+ import { createPressable } from '@gluestack-ui/pressable';
2
+ import type { ComponentType } from 'react';
3
+ import type { PressableProps, ViewStyle } from 'react-native';
4
+ import { Pressable } from 'react-native';
5
+ import { StyleSheet } from 'react-native-unistyles';
6
+ import { Icon } from '../Icon';
7
+
8
+ type StepperButtonProps = {
9
+ icon: ComponentType;
10
+ disabled?: boolean;
11
+ } & Omit<PressableProps, 'children'>;
12
+
13
+ const StepperButtonRoot = ({
14
+ icon,
15
+ disabled,
16
+ states,
17
+ ...props
18
+ }: StepperButtonProps & { states?: { active?: boolean; disabled?: boolean } }) => {
19
+ const isDisabled = disabled ?? states?.disabled ?? false;
20
+ const isActive = states?.active ?? false;
21
+
22
+ styles.useVariants({ active: isActive, disabled: isDisabled });
23
+
24
+ return (
25
+ <Pressable
26
+ {...props}
27
+ accessibilityRole="button"
28
+ accessibilityState={{ disabled: isDisabled, ...props.accessibilityState }}
29
+ disabled={isDisabled}
30
+ style={[styles.button, props.style as ViewStyle]}
31
+ >
32
+ <Icon as={icon} size="sm" style={styles.icon} />
33
+ </Pressable>
34
+ );
35
+ };
36
+
37
+ const StepperButton = createPressable({ Root: StepperButtonRoot });
38
+
39
+ StepperButton.displayName = 'StepperButton';
40
+
41
+ const styles = StyleSheet.create(theme => ({
42
+ button: {
43
+ width: theme.components.iconButton.md.width,
44
+ height: theme.components.iconButton.md.height,
45
+ alignItems: 'center',
46
+ justifyContent: 'center',
47
+ borderRadius: theme.components.input.borderRadius,
48
+ borderWidth: theme.components.input.borderWidth,
49
+ borderColor: theme.color.border.strong,
50
+ backgroundColor: theme.color.surface.neutral.strong,
51
+ _web: {
52
+ _hover: {
53
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
54
+ },
55
+ _active: {
56
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
57
+ },
58
+ '_focus-visible': {
59
+ outlineStyle: 'solid',
60
+ outlineWidth: 2,
61
+ outlineColor: theme.color.focus.primary,
62
+ outlineOffset: 2,
63
+ },
64
+ },
65
+ variants: {
66
+ active: {
67
+ true: {
68
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
69
+ },
70
+ },
71
+ disabled: {
72
+ true: {
73
+ opacity: theme.opacity.disabled,
74
+ },
75
+ },
76
+ },
77
+ },
78
+ icon: {
79
+ color: theme.color.icon.primary,
80
+ },
81
+ }));
82
+
83
+ export default StepperButton;
@@ -0,0 +1,121 @@
1
+ import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
2
+ import { StepperInput } from '../../';
3
+ import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
4
+ import * as Stories from './StepperInput.stories';
5
+
6
+ <Meta title="Forms / Stepper Input" />
7
+
8
+ <BackToTopButton />
9
+
10
+ <ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10612-1860&t=aFS22MoPV7Y08jdq-4" />
11
+
12
+ # Stepper Input
13
+
14
+ The Stepper Input combines a numeric text input with decrement and increment controls. Use it when the value should stay within a defined range but still needs direct text entry.
15
+
16
+ - [Playground](#playground)
17
+ - [Usage](#usage)
18
+ - [Props](#props)
19
+ - [Examples](#examples)
20
+ - [Accessibility](#accessibility)
21
+
22
+ ## Playground
23
+
24
+ <Canvas of={Stories.Playground} />
25
+
26
+ <Controls of={Stories.Playground} />
27
+
28
+ ## Usage
29
+
30
+ <UsageWrap>
31
+ <StepperInput
32
+ label="Guests"
33
+ helperText="Choose between 1 and 10"
34
+ value="2"
35
+ onChangeText={() => undefined}
36
+ />
37
+ </UsageWrap>
38
+
39
+ ```tsx
40
+ import { useState } from 'react';
41
+ import { StepperInput } from '@utilitywarehouse/hearth-react-native';
42
+
43
+ const MyComponent = () => {
44
+ const [value, setValue] = useState('2');
45
+
46
+ return (
47
+ <StepperInput
48
+ label="Guests"
49
+ helperText="Choose between 1 and 10"
50
+ min={1}
51
+ max={10}
52
+ value={value}
53
+ onChangeText={setValue}
54
+ />
55
+ );
56
+ };
57
+ ```
58
+
59
+ ## Props
60
+
61
+ The Stepper Input inherits React Native `TextInput` props except `children`, `value`, `defaultValue`, `onChangeText`, `editable`, and `keyboardType`. It also supports the following props:
62
+
63
+ | Prop | Type | Default | Description |
64
+ | ----------------------------- | ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------- |
65
+ | `value` | `string \| number` | - | Controlled value displayed in the input. |
66
+ | `defaultValue` | `number` | - | Initial value for uncontrolled usage. |
67
+ | `onChangeText` | `(text: string) => void` | - | Called whenever the displayed string changes. |
68
+ | `onChangeValue` | `(value: number) => void` | - | Called whenever the current text can be parsed as a number. |
69
+ | `min` | `number` | - | Minimum allowed numeric value. |
70
+ | `max` | `number` | - | Maximum allowed numeric value. |
71
+ | `step` | `number` | `1` | Amount added or removed when the side buttons are pressed. Fractional values are supported. |
72
+ | `focusInputOnStepPress` | `boolean` | `false` | When `true`, pressing a step button moves focus to the text input after updating the value. |
73
+ | `validationStatus` | `'initial' \| 'valid' \| 'invalid'` | `'initial'` | Validation styling for the center input. |
74
+ | `label` | `string` | - | Label shown above the stepper. |
75
+ | `labelVariant` | `'heading' \| 'body'` | `'body'` | Typography variant used for the label. |
76
+ | `helperText` | `string` | - | Helper text shown below the label. |
77
+ | `helperIcon` | `ComponentType` | - | Optional helper icon shown next to helper text. |
78
+ | `invalidText` | `string` | - | Validation text shown when `validationStatus="invalid"`. |
79
+ | `validText` | `string` | - | Validation text shown when `validationStatus="valid"`. |
80
+ | `disabled` | `boolean` | `false` | Disables the input and both stepper buttons. |
81
+ | `readonly` | `boolean` | `false` | Prevents editing and disables both buttons. |
82
+ | `focused` | `boolean` | `false` | Forces the focused visual state on the center input. |
83
+ | `decrementAccessibilityLabel` | `string` | `'Decrease value'` | Accessibility label for the decrement button. |
84
+ | `incrementAccessibilityLabel` | `string` | `'Increase value'` | Accessibility label for the increment button. |
85
+
86
+ ## Examples
87
+
88
+ ### States
89
+
90
+ <Canvas of={Stories.States} />
91
+
92
+ ### Bounds
93
+
94
+ <Canvas of={Stories.Bounds} />
95
+
96
+ ### Fractional Values
97
+
98
+ `StepperInput` supports fractional steps and negative ranges. When `step`, `min`, `max`, or the current value uses decimals, the input switches to a numeric keyboard path that preserves decimal editing.
99
+
100
+ <Canvas of={Stories.DecimalStep} />
101
+
102
+ ```tsx
103
+ <StepperInput label="Weight" min={-2} max={2} step={0.5} value={value} onChangeText={setValue} />
104
+ ```
105
+
106
+ ### Opt-In Focus On Step Press
107
+
108
+ By default, the increment and decrement buttons update the value without moving focus into the text field. Enable `focusInputOnStepPress` when keyboard-first flows need the caret to stay in the input after a button press.
109
+
110
+ <Canvas of={Stories.FocusOnStepPress} />
111
+
112
+ ```tsx
113
+ <StepperInput label="Guests" value={value} onChangeText={setValue} focusInputOnStepPress />
114
+ ```
115
+
116
+ ## Accessibility
117
+
118
+ - The center field remains a standard text input, so screen readers can edit the value directly.
119
+ - The side controls expose button semantics with separate accessibility labels for increment and decrement.
120
+ - Button presses do not move focus into the text field unless `focusInputOnStepPress` is enabled.
121
+ - Validation, helper text, and disabled state follow the same `FormField` treatment as other input components.
@@ -0,0 +1,45 @@
1
+ import figma from '@figma/code-connect';
2
+ import { StepperInput } from '../';
3
+
4
+ const props = {
5
+ value: figma.string('Value'),
6
+ label: figma.string('Label'),
7
+ labelVariant: figma.enum('Label variant', {
8
+ Body: 'body',
9
+ Heading: 'heading',
10
+ }),
11
+ helperText: figma.boolean('Helper text?', {
12
+ true: figma.string('Helper text'),
13
+ false: undefined,
14
+ }),
15
+ validationStatus: figma.enum('State', {
16
+ Default: 'initial',
17
+ Invalid: 'invalid',
18
+ }),
19
+ invalidText: figma.enum('State', {
20
+ Invalid: figma.string('Validation'),
21
+ }),
22
+ };
23
+
24
+ const handleChangeText = () => {
25
+ // Placeholder for Code Connect examples.
26
+ };
27
+
28
+ figma.connect(
29
+ StepperInput,
30
+ 'https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=10612%3A1860&m=dev',
31
+ {
32
+ props,
33
+ example: props => (
34
+ <StepperInput
35
+ value={props.value}
36
+ label={props.label}
37
+ labelVariant={props.labelVariant}
38
+ helperText={props.helperText}
39
+ validationStatus={props.validationStatus}
40
+ invalidText={props.invalidText}
41
+ onChangeText={handleChangeText}
42
+ />
43
+ ),
44
+ }
45
+ );
@@ -0,0 +1,39 @@
1
+ import type { ComponentType, Ref } from 'react';
2
+ import type { TextInput, TextInputProps, ViewProps } from 'react-native';
3
+
4
+ export interface StepperBaseProps {
5
+ ref?: Ref<TextInput>;
6
+ disabled?: boolean;
7
+ validationStatus?: 'initial' | 'valid' | 'invalid';
8
+ readonly?: boolean;
9
+ focused?: boolean;
10
+ placeholder?: string;
11
+ inBottomSheet?: boolean;
12
+ required?: boolean;
13
+ label?: string;
14
+ labelVariant?: 'heading' | 'body';
15
+ helperText?: string;
16
+ helperIcon?: ComponentType;
17
+ validText?: string;
18
+ invalidText?: string;
19
+ value?: number | string;
20
+ defaultValue?: number;
21
+ min?: number;
22
+ max?: number;
23
+ step?: number;
24
+ onChangeValue?: (value: number) => void;
25
+ focusInputOnStepPress?: boolean;
26
+ decrementAccessibilityLabel?: string;
27
+ incrementAccessibilityLabel?: string;
28
+ }
29
+
30
+ export type StepperInputProps = StepperBaseProps &
31
+ Omit<
32
+ TextInputProps,
33
+ 'children' | 'value' | 'defaultValue' | 'onChangeText' | 'editable' | 'keyboardType'
34
+ > &
35
+ ViewProps & {
36
+ onChangeText?: (text: string) => void;
37
+ };
38
+
39
+ export default StepperInputProps;
@@ -0,0 +1,270 @@
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
+ import type { ComponentProps } from 'react';
3
+ import { useEffect, useState } from 'react';
4
+ import { StepperInput } from '.';
5
+ import { VariantTitle } from '../../../docs/components';
6
+ import { Flex } from '../Flex';
7
+
8
+ type StepperInputStoryProps = ComponentProps<typeof StepperInput>;
9
+
10
+ const meta = {
11
+ title: 'Stories / StepperInput',
12
+ component: StepperInput,
13
+ parameters: {
14
+ layout: 'centered',
15
+ },
16
+ argTypes: {
17
+ value: {
18
+ control: 'text',
19
+ },
20
+ label: {
21
+ control: 'text',
22
+ },
23
+ helperText: {
24
+ control: 'text',
25
+ },
26
+ labelVariant: {
27
+ control: 'radio',
28
+ options: ['body', 'heading'],
29
+ },
30
+ validationStatus: {
31
+ control: 'select',
32
+ options: ['initial', 'valid', 'invalid'],
33
+ },
34
+ validText: {
35
+ control: 'text',
36
+ },
37
+ invalidText: {
38
+ control: 'text',
39
+ },
40
+ disabled: {
41
+ control: 'boolean',
42
+ },
43
+ readonly: {
44
+ control: 'boolean',
45
+ },
46
+ focused: {
47
+ control: 'boolean',
48
+ },
49
+ min: {
50
+ control: 'number',
51
+ },
52
+ max: {
53
+ control: 'number',
54
+ },
55
+ step: {
56
+ control: 'number',
57
+ },
58
+ focusInputOnStepPress: {
59
+ control: 'boolean',
60
+ },
61
+ },
62
+ args: {
63
+ label: 'Label',
64
+ helperText: 'Helper text',
65
+ value: '10',
66
+ validationStatus: 'initial',
67
+ disabled: false,
68
+ readonly: false,
69
+ focused: false,
70
+ step: 1,
71
+ min: 0,
72
+ focusInputOnStepPress: false,
73
+ },
74
+ } satisfies Meta<typeof StepperInput>;
75
+
76
+ export default meta;
77
+ type Story = StoryObj<typeof meta>;
78
+
79
+ export const Playground: Story = {
80
+ render: ({
81
+ value: initialValue,
82
+ onChangeText: handleChangeText,
83
+ onChangeValue: handleChangeValue,
84
+ ...args
85
+ }: StepperInputStoryProps) => {
86
+ const [value, setValue] = useState(initialValue ?? '10');
87
+
88
+ useEffect(() => {
89
+ setValue(initialValue ?? '');
90
+ }, [initialValue]);
91
+
92
+ return (
93
+ <StepperInput
94
+ {...args}
95
+ value={value}
96
+ onChangeText={text => {
97
+ setValue(text);
98
+ handleChangeText?.(text);
99
+ }}
100
+ onChangeValue={nextValue => {
101
+ setValue(`${nextValue}`);
102
+ handleChangeValue?.(nextValue);
103
+ }}
104
+ />
105
+ );
106
+ },
107
+ };
108
+
109
+ export const States: Story = {
110
+ parameters: {
111
+ controls: { include: [] },
112
+ },
113
+ render: () => {
114
+ const [values, setValues] = useState({
115
+ default: '10',
116
+ focused: '10',
117
+ invalid: '10',
118
+ invalidFocused: '10',
119
+ heading: '10',
120
+ headingInvalid: '10',
121
+ disabled: '10',
122
+ });
123
+
124
+ const updateValue = (key: keyof typeof values) => (text: string) => {
125
+ setValues(currentValues => ({ ...currentValues, [key]: text }));
126
+ };
127
+
128
+ return (
129
+ <Flex direction="column" spacing="lg" style={{ width: 420 }}>
130
+ <VariantTitle title="Default">
131
+ <StepperInput
132
+ label="Label"
133
+ helperText="Helper text"
134
+ value={values.default}
135
+ onChangeText={updateValue('default')}
136
+ />
137
+ </VariantTitle>
138
+ <VariantTitle title="Focused">
139
+ <StepperInput
140
+ label="Label"
141
+ helperText="Helper text"
142
+ focused
143
+ value={values.focused}
144
+ onChangeText={updateValue('focused')}
145
+ />
146
+ </VariantTitle>
147
+ <VariantTitle title="Invalid">
148
+ <StepperInput
149
+ label="Label"
150
+ helperText="Helper text"
151
+ validationStatus="invalid"
152
+ invalidText="Validation text"
153
+ value={values.invalid}
154
+ onChangeText={updateValue('invalid')}
155
+ />
156
+ </VariantTitle>
157
+ <VariantTitle title="Invalid Focused">
158
+ <StepperInput
159
+ label="Label"
160
+ helperText="Helper text"
161
+ focused
162
+ validationStatus="invalid"
163
+ invalidText="Validation text"
164
+ value={values.invalidFocused}
165
+ onChangeText={updateValue('invalidFocused')}
166
+ />
167
+ </VariantTitle>
168
+ <VariantTitle title="Heading Label">
169
+ <StepperInput
170
+ label="Label"
171
+ helperText="Helper text"
172
+ labelVariant="heading"
173
+ value={values.heading}
174
+ onChangeText={updateValue('heading')}
175
+ />
176
+ </VariantTitle>
177
+ <VariantTitle title="Heading Invalid">
178
+ <StepperInput
179
+ label="Label"
180
+ helperText="Helper text"
181
+ labelVariant="heading"
182
+ validationStatus="invalid"
183
+ invalidText="Validation text"
184
+ value={values.headingInvalid}
185
+ onChangeText={updateValue('headingInvalid')}
186
+ />
187
+ </VariantTitle>
188
+ <VariantTitle title="Disabled">
189
+ <StepperInput
190
+ label="Label"
191
+ helperText="Helper text"
192
+ disabled
193
+ value={values.disabled}
194
+ onChangeText={updateValue('disabled')}
195
+ />
196
+ </VariantTitle>
197
+ </Flex>
198
+ );
199
+ },
200
+ };
201
+
202
+ export const Bounds: Story = {
203
+ parameters: {
204
+ controls: { include: ['min', 'max', 'step', 'focusInputOnStepPress'] },
205
+ },
206
+ args: {
207
+ min: 0,
208
+ max: 12,
209
+ step: 2,
210
+ },
211
+ render: (args: StepperInputStoryProps) => {
212
+ const [value, setValue] = useState('10');
213
+
214
+ return <StepperInput {...args} value={value} onChangeText={setValue} />;
215
+ },
216
+ };
217
+
218
+ export const FocusOnStepPress: Story = {
219
+ parameters: {
220
+ controls: { include: ['focusInputOnStepPress'] },
221
+ },
222
+ args: {
223
+ focusInputOnStepPress: true,
224
+ label: 'Guests',
225
+ helperText: 'Button presses will keep focus in the input',
226
+ min: 1,
227
+ max: 10,
228
+ },
229
+ render: (args: StepperInputStoryProps) => {
230
+ const [value, setValue] = useState('2');
231
+
232
+ return <StepperInput {...args} value={value} onChangeText={setValue} />;
233
+ },
234
+ };
235
+
236
+ export const LargeStep: Story = {
237
+ parameters: {
238
+ controls: { include: ['step'] },
239
+ },
240
+ args: {
241
+ step: 10,
242
+ label: 'Large Step',
243
+ helperText: 'Step value of 10',
244
+ min: 0,
245
+ max: 100,
246
+ },
247
+ render: (args: StepperInputStoryProps) => {
248
+ const [value, setValue] = useState('20');
249
+
250
+ return <StepperInput {...args} value={value} onChangeText={setValue} />;
251
+ },
252
+ };
253
+
254
+ export const DecimalStep: Story = {
255
+ parameters: {
256
+ controls: { include: ['min', 'max', 'step'] },
257
+ },
258
+ args: {
259
+ step: 0.5,
260
+ label: 'Weight',
261
+ helperText: 'Supports fractional values',
262
+ min: -2,
263
+ max: 2,
264
+ },
265
+ render: (args: StepperInputStoryProps) => {
266
+ const [value, setValue] = useState('0.5');
267
+
268
+ return <StepperInput {...args} value={value} onChangeText={setValue} />;
269
+ },
270
+ };