@utilitywarehouse/hearth-react-native 0.10.0 → 0.12.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +18 -0
- package/build/components/Avatar/Avatar.d.ts +6 -0
- package/build/components/Avatar/Avatar.js +80 -0
- package/build/components/Avatar/Avatar.props.d.ts +28 -0
- package/build/components/Avatar/Avatar.props.js +1 -0
- package/build/components/Avatar/index.d.ts +2 -0
- package/build/components/Avatar/index.js +1 -0
- package/build/components/Banner/Banner.context.d.ts +7 -0
- package/build/components/Banner/Banner.context.js +8 -0
- package/build/components/Banner/Banner.js +10 -40
- package/build/components/Banner/Banner.props.d.ts +3 -5
- package/build/components/Banner/BannerIllustration.d.ts +4 -0
- package/build/components/Banner/BannerIllustration.js +53 -0
- package/build/components/Banner/BannerImage.d.ts +4 -0
- package/build/components/Banner/BannerImage.js +53 -0
- package/build/components/Banner/index.d.ts +2 -0
- package/build/components/Banner/index.js +2 -0
- package/build/components/Card/CardAction/CardAction.props.d.ts +2 -3
- package/build/components/Card/CardAction/CardActionRoot.js +1 -2
- package/build/components/Checkbox/Checkbox.js +1 -2
- package/build/components/Checkbox/Checkbox.props.d.ts +3 -3
- package/build/components/Checkbox/CheckboxImage.d.ts +2 -1
- package/build/components/Checkbox/CheckboxImage.js +8 -1
- package/build/components/DateInput/DateInput.d.ts +6 -0
- package/build/components/DateInput/DateInput.js +19 -0
- package/build/components/DateInput/DateInput.props.d.ts +79 -0
- package/build/components/DateInput/DateInput.props.js +1 -0
- package/build/components/DateInput/DateInputSegment.d.ts +20 -0
- package/build/components/DateInput/DateInputSegment.js +31 -0
- package/build/components/DateInput/index.d.ts +2 -0
- package/build/components/DateInput/index.js +1 -0
- package/build/components/ExpandableCard/ExpandableCard.props.d.ts +1 -2
- package/build/components/ExpandableCard/ExpandableCardTrigger.props.d.ts +4 -5
- package/build/components/ExpandableCard/ExpandableCardTriggerRoot.js +1 -14
- package/build/components/HighlightBanner/HighlightBanner.js +2 -6
- package/build/components/HighlightBanner/HighlightBanner.props.d.ts +2 -3
- package/build/components/HighlightBanner/HighlightBannerImage.d.ts +4 -0
- package/build/components/HighlightBanner/HighlightBannerImage.js +18 -0
- package/build/components/HighlightBanner/index.d.ts +1 -0
- package/build/components/HighlightBanner/index.js +1 -0
- package/build/components/Input/Input.d.ts +5 -7
- package/build/components/Input/Input.js +11 -4
- package/build/components/Input/InputField.d.ts +4 -7
- package/build/components/Input/InputField.js +6 -5
- package/build/components/List/ListItem/ListItem.props.d.ts +2 -2
- package/build/components/List/ListItem/ListItemRoot.js +1 -2
- package/build/components/Modal/Modal.js +2 -6
- package/build/components/Modal/Modal.props.d.ts +3 -2
- package/build/components/Modal/Modal.web.js +2 -6
- package/build/components/Modal/ModalImage.d.ts +4 -0
- package/build/components/Modal/ModalImage.js +18 -0
- package/build/components/Modal/index.d.ts +1 -0
- package/build/components/Modal/index.js +1 -0
- package/build/components/Radio/Radio.js +1 -2
- package/build/components/Radio/Radio.props.d.ts +3 -3
- package/build/components/Radio/RadioImage.d.ts +2 -1
- package/build/components/Radio/RadioImage.js +8 -1
- package/build/components/index.d.ts +2 -0
- package/build/components/index.js +2 -0
- package/build/utils/getInitials.d.ts +1 -0
- package/build/utils/getInitials.js +8 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.js +2 -0
- package/build/utils/isThemedImageProps.d.ts +4 -0
- package/build/utils/isThemedImageProps.js +4 -0
- package/docs/components/AllComponents.web.tsx +18 -1
- package/package.json +2 -2
- package/src/components/Avatar/Avatar.docs.mdx +105 -0
- package/src/components/Avatar/Avatar.props.ts +31 -0
- package/src/components/Avatar/Avatar.stories.tsx +77 -0
- package/src/components/Avatar/Avatar.tsx +136 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/Banner/Banner.context.ts +11 -0
- package/src/components/Banner/Banner.docs.mdx +55 -37
- package/src/components/Banner/Banner.props.ts +3 -5
- package/src/components/Banner/Banner.stories.tsx +86 -57
- package/src/components/Banner/Banner.tsx +24 -67
- package/src/components/Banner/BannerIllustration.tsx +63 -0
- package/src/components/Banner/BannerImage.tsx +63 -0
- package/src/components/Banner/index.ts +2 -0
- package/src/components/Card/Card.docs.mdx +4 -4
- package/src/components/Card/CardAction/CardAction.props.ts +2 -3
- package/src/components/Card/CardAction/CardAction.stories.tsx +4 -3
- package/src/components/Card/CardAction/CardActionRoot.tsx +4 -5
- package/src/components/Checkbox/Checkbox.docs.mdx +23 -4
- package/src/components/Checkbox/Checkbox.props.ts +3 -3
- package/src/components/Checkbox/Checkbox.stories.tsx +14 -8
- package/src/components/Checkbox/Checkbox.tsx +1 -2
- package/src/components/Checkbox/CheckboxImage.tsx +8 -3
- package/src/components/DateInput/DateInput.docs.mdx +163 -0
- package/src/components/DateInput/DateInput.props.ts +80 -0
- package/src/components/DateInput/DateInput.stories.tsx +269 -0
- package/src/components/DateInput/DateInput.tsx +117 -0
- package/src/components/DateInput/DateInputSegment.tsx +83 -0
- package/src/components/DateInput/index.ts +2 -0
- package/src/components/ExpandableCard/ExpandableCard.docs.mdx +2 -2
- package/src/components/ExpandableCard/ExpandableCard.props.ts +1 -2
- package/src/components/ExpandableCard/ExpandableCard.stories.tsx +3 -3
- package/src/components/ExpandableCard/ExpandableCardTrigger.props.ts +4 -5
- package/src/components/ExpandableCard/ExpandableCardTriggerRoot.tsx +2 -17
- package/src/components/HighlightBanner/HighlightBanner.docs.mdx +73 -42
- package/src/components/HighlightBanner/HighlightBanner.props.ts +2 -3
- package/src/components/HighlightBanner/HighlightBanner.stories.tsx +85 -60
- package/src/components/HighlightBanner/HighlightBanner.tsx +3 -10
- package/src/components/HighlightBanner/HighlightBannerImage.tsx +20 -0
- package/src/components/HighlightBanner/index.ts +1 -0
- package/src/components/Input/Input.stories.tsx +76 -3
- package/src/components/Input/Input.tsx +110 -98
- package/src/components/Input/InputField.tsx +27 -26
- package/src/components/List/List.docs.mdx +15 -9
- package/src/components/List/List.stories.tsx +2 -2
- package/src/components/List/ListItem/ListItem.props.ts +2 -2
- package/src/components/List/ListItem/ListItemRoot.tsx +2 -3
- package/src/components/Modal/Modal.docs.mdx +16 -4
- package/src/components/Modal/Modal.props.ts +3 -2
- package/src/components/Modal/Modal.stories.tsx +2 -5
- package/src/components/Modal/Modal.tsx +2 -6
- package/src/components/Modal/Modal.web.tsx +2 -6
- package/src/components/Modal/ModalImage.tsx +20 -0
- package/src/components/Modal/index.ts +1 -0
- package/src/components/PillGroup/PillGroup.stories.tsx +1 -1
- package/src/components/Radio/Radio.docs.mdx +21 -8
- package/src/components/Radio/Radio.props.ts +3 -3
- package/src/components/Radio/Radio.stories.tsx +15 -11
- package/src/components/Radio/Radio.tsx +1 -2
- package/src/components/Radio/RadioImage.tsx +8 -3
- package/src/components/index.ts +2 -0
- package/src/utils/getInitials.ts +7 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/isThemedImageProps.ts +8 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
2
|
import React, { useEffect } from 'react';
|
|
3
3
|
import { ImageSourcePropType } from 'react-native';
|
|
4
|
-
import { Checkbox, CheckboxGroup } from '.';
|
|
4
|
+
import { Checkbox, CheckboxGroup, CheckboxImage } from '.';
|
|
5
5
|
import bankLogo from '../../../docs/assets/bank-logo.png';
|
|
6
6
|
import bankLogo1 from '../../../docs/assets/bank-logo1.png';
|
|
7
7
|
import { VariantTitle } from '../../../docs/components';
|
|
@@ -106,7 +106,12 @@ export const WithImage: Story = {
|
|
|
106
106
|
{...args}
|
|
107
107
|
value="visa"
|
|
108
108
|
label="Visa"
|
|
109
|
-
image={
|
|
109
|
+
image={
|
|
110
|
+
<CheckboxImage
|
|
111
|
+
source={bankLogo1 as ImageSourcePropType}
|
|
112
|
+
style={{ width: 40, height: 24 }}
|
|
113
|
+
/>
|
|
114
|
+
}
|
|
110
115
|
/>
|
|
111
116
|
<Checkbox
|
|
112
117
|
aria-label="Mastercard"
|
|
@@ -117,11 +122,12 @@ export const WithImage: Story = {
|
|
|
117
122
|
{...args}
|
|
118
123
|
value="mastercard"
|
|
119
124
|
label="Mastercard"
|
|
120
|
-
image={
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
image={
|
|
126
|
+
<CheckboxImage
|
|
127
|
+
source={bankLogo as ImageSourcePropType}
|
|
128
|
+
style={{ width: 40, height: 24, resizeMode: 'contain' }}
|
|
129
|
+
/>
|
|
130
|
+
}
|
|
125
131
|
/>
|
|
126
132
|
</CheckboxGroup>
|
|
127
133
|
),
|
|
@@ -5,7 +5,6 @@ import CheckboxProps from './Checkbox.props';
|
|
|
5
5
|
import { useCheckboxGroupContext } from './CheckboxGroup.context';
|
|
6
6
|
import StyledCheckboxGroup from './CheckboxGroupRoot';
|
|
7
7
|
import StyledCheckboxIcon from './CheckboxIcon';
|
|
8
|
-
import CheckboxImage from './CheckboxImage';
|
|
9
8
|
import StyledCheckboxIndicator from './CheckboxIndicator';
|
|
10
9
|
import StyledCheckboxLabel from './CheckboxLabel';
|
|
11
10
|
import StyledCheckbox from './CheckboxRoot';
|
|
@@ -57,7 +56,7 @@ const Checkbox = ({
|
|
|
57
56
|
<CheckboxIndicator>
|
|
58
57
|
<CheckboxIcon />
|
|
59
58
|
</CheckboxIndicator>
|
|
60
|
-
{image ?
|
|
59
|
+
{image ? image : null}
|
|
61
60
|
<CheckboxTextContent>
|
|
62
61
|
{!!label && <CheckboxLabel>{label}</CheckboxLabel>}
|
|
63
62
|
{!!helperText && <Helper disabled={disabled} icon={helperIcon} text={helperText} />}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Image, ImageProps } from 'react-native';
|
|
2
|
+
import { isThemedImageProps } from '../../utils';
|
|
3
|
+
import { ThemedImage, ThemedImageProps } from '../ThemedImage';
|
|
2
4
|
|
|
3
|
-
const CheckboxImage = ({
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
const CheckboxImage = ({ ...props }: ImageProps | ThemedImageProps) => {
|
|
6
|
+
if (isThemedImageProps(props)) {
|
|
7
|
+
return <ThemedImage {...props} />;
|
|
8
|
+
}
|
|
9
|
+
return <Image {...props} />;
|
|
10
|
+
};
|
|
6
11
|
|
|
7
12
|
CheckboxImage.displayName = 'CheckboxImage';
|
|
8
13
|
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Canvas, Controls, Meta } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { BackToTopButton, ViewFigmaButton } from '../../../docs/components';
|
|
3
|
+
import * as Stories from './DateInput.stories';
|
|
4
|
+
|
|
5
|
+
<Meta title="Forms / Date Input" />
|
|
6
|
+
|
|
7
|
+
<ViewFigmaButton url="https://www.figma.com/design/6NKZXZhFSExXrcbBgc6zTR/Hearth-Components---Tokens?node-id=2277-14708&t=dbFGPEbIX93BQlur-4" />
|
|
8
|
+
|
|
9
|
+
<BackToTopButton />
|
|
10
|
+
|
|
11
|
+
# Date Input
|
|
12
|
+
|
|
13
|
+
The `DateInput` component allows users to enter dates manually using separate input fields for day, month, and year. It provides flexibility to show only the date segments you need, supports validation states, and integrates seamlessly with the React Native form system.
|
|
14
|
+
|
|
15
|
+
- [Playground](#playground)
|
|
16
|
+
- [Usage](#usage)
|
|
17
|
+
- [Props](#props)
|
|
18
|
+
- [Examples](#examples)
|
|
19
|
+
- [Date of Birth](#date-of-birth)
|
|
20
|
+
- [Card Expiry](#card-expiry)
|
|
21
|
+
- [Year Only](#year-only)
|
|
22
|
+
- [Validation](#validation)
|
|
23
|
+
- [Disabled](#disabled)
|
|
24
|
+
- [Default Value](#default-value)
|
|
25
|
+
- [Custom Validation](#custom-validation)
|
|
26
|
+
- [Flexible Segments](#flexible-segments)
|
|
27
|
+
- [Grouping Inputs](#grouping-inputs)
|
|
28
|
+
- [With State](#with-state)
|
|
29
|
+
- [Accessibility](#accessibility)
|
|
30
|
+
|
|
31
|
+
## Playground
|
|
32
|
+
|
|
33
|
+
<Canvas of={Stories.Playground} />
|
|
34
|
+
|
|
35
|
+
<Controls of={Stories.Playground} />
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Use state to control the date values:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useState } from 'react';
|
|
43
|
+
import { DateInput } from '@utilitywarehouse/hearth-react-native';
|
|
44
|
+
|
|
45
|
+
const MyComponent = () => {
|
|
46
|
+
const [day, setDay] = useState('');
|
|
47
|
+
const [month, setMonth] = useState('');
|
|
48
|
+
const [year, setYear] = useState('');
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<DateInput
|
|
52
|
+
label="Date of birth"
|
|
53
|
+
dayValue={day}
|
|
54
|
+
monthValue={month}
|
|
55
|
+
yearValue={year}
|
|
56
|
+
onDayChange={setDay}
|
|
57
|
+
onMonthChange={setMonth}
|
|
58
|
+
onYearChange={setYear}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Props
|
|
65
|
+
|
|
66
|
+
### DateInputProps
|
|
67
|
+
|
|
68
|
+
| Prop | Type | Default | Description |
|
|
69
|
+
| ------------------ | ----------------------------------- | ----------- | ----------------------------------------------------- |
|
|
70
|
+
| `label` | `string` | - | Label text displayed above the inputs |
|
|
71
|
+
| `helperText` | `string` | - | Helper text displayed below the inputs |
|
|
72
|
+
| `helperIcon` | `ComponentType` | - | Icon component to display with helper/validation text |
|
|
73
|
+
| `validationStatus` | `'initial' \| 'valid' \| 'invalid'` | `'initial'` | Validation status of the input |
|
|
74
|
+
| `validText` | `string` | - | Text to display when validation status is valid |
|
|
75
|
+
| `invalidText` | `string` | - | Text to display when validation status is invalid |
|
|
76
|
+
| `required` | `boolean` | `false` | Whether the input is required |
|
|
77
|
+
| `disabled` | `boolean` | `false` | Whether the input is disabled |
|
|
78
|
+
| `readonly` | `boolean` | `false` | Whether the input is read-only |
|
|
79
|
+
| `hideDay` | `boolean` | `false` | Whether to hide the day segment |
|
|
80
|
+
| `hideMonth` | `boolean` | `false` | Whether to hide the month segment |
|
|
81
|
+
| `hideYear` | `boolean` | `false` | Whether to hide the year segment |
|
|
82
|
+
| `dayPlaceholder` | `string` | `'DD'` | Placeholder text for the day segment |
|
|
83
|
+
| `monthPlaceholder` | `string` | `'MM'` | Placeholder text for the month segment |
|
|
84
|
+
| `yearPlaceholder` | `string` | `'YYYY'` | Placeholder text for the year segment |
|
|
85
|
+
| `dayValue` | `string` | - | Controlled value for the day segment |
|
|
86
|
+
| `monthValue` | `string` | - | Controlled value for the month segment |
|
|
87
|
+
| `yearValue` | `string` | - | Controlled value for the year segment |
|
|
88
|
+
| `onDayChange` | `(text: string) => void` | - | Callback fired when the day value changes |
|
|
89
|
+
| `onMonthChange` | `(text: string) => void` | - | Callback fired when the month value changes |
|
|
90
|
+
| `onYearChange` | `(text: string) => void` | - | Callback fired when the year value changes |
|
|
91
|
+
| `onDayFocus` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the day segment receives focus |
|
|
92
|
+
| `onMonthFocus` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the month segment receives focus |
|
|
93
|
+
| `onYearFocus` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the year segment receives focus |
|
|
94
|
+
| `onDayBlur` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the day segment loses focus |
|
|
95
|
+
| `onMonthBlur` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the month segment loses focus |
|
|
96
|
+
| `onYearBlur` | `(e: NativeSyntheticEvent) => void` | - | Callback fired when the year segment loses focus |
|
|
97
|
+
|
|
98
|
+
## Examples
|
|
99
|
+
|
|
100
|
+
### Date of Birth
|
|
101
|
+
|
|
102
|
+
<Canvas of={Stories.DateOfBirth} />
|
|
103
|
+
|
|
104
|
+
### Card Expiry
|
|
105
|
+
|
|
106
|
+
Hide the day segment for card expiry dates:
|
|
107
|
+
|
|
108
|
+
<Canvas of={Stories.CardExpiry} />
|
|
109
|
+
|
|
110
|
+
### Year Only
|
|
111
|
+
|
|
112
|
+
Hide day and month segments for year-only inputs:
|
|
113
|
+
|
|
114
|
+
<Canvas of={Stories.YearOnly} />
|
|
115
|
+
|
|
116
|
+
### Validation
|
|
117
|
+
|
|
118
|
+
Show validation states with helper text and icons:
|
|
119
|
+
|
|
120
|
+
<Canvas of={Stories.Validation} />
|
|
121
|
+
|
|
122
|
+
### Disabled
|
|
123
|
+
|
|
124
|
+
Disabled date inputs:
|
|
125
|
+
|
|
126
|
+
<Canvas of={Stories.Disabled} />
|
|
127
|
+
|
|
128
|
+
### Default Value
|
|
129
|
+
|
|
130
|
+
Set initial values with `value` props:
|
|
131
|
+
|
|
132
|
+
<Canvas of={Stories.DefaultValue} />
|
|
133
|
+
|
|
134
|
+
### Custom Validation
|
|
135
|
+
|
|
136
|
+
Implement custom validation logic:
|
|
137
|
+
|
|
138
|
+
<Canvas of={Stories.WithCustomValidation} />
|
|
139
|
+
|
|
140
|
+
### Flexible Segments
|
|
141
|
+
|
|
142
|
+
Control which segments are visible:
|
|
143
|
+
|
|
144
|
+
<Canvas of={Stories.FlexibleSegments} />
|
|
145
|
+
|
|
146
|
+
### Grouping Inputs
|
|
147
|
+
|
|
148
|
+
Group date inputs with other form fields in a card:
|
|
149
|
+
|
|
150
|
+
<Canvas of={Stories.GroupingInputs} />
|
|
151
|
+
|
|
152
|
+
### With State
|
|
153
|
+
|
|
154
|
+
Programmatically control date values:
|
|
155
|
+
|
|
156
|
+
<Canvas of={Stories.WithState} />
|
|
157
|
+
|
|
158
|
+
## Accessibility
|
|
159
|
+
|
|
160
|
+
- Each segment has a descriptive label ("Day", "Month", "Year") for screen readers
|
|
161
|
+
- Keyboard type is set to `number-pad` for easier numeric input
|
|
162
|
+
- Validation states are communicated through helper text and icons
|
|
163
|
+
- Disabled state properly communicated to assistive technologies
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { TextInputProps } from 'react-native';
|
|
2
|
+
import type { FormFieldBaseProps } from '../FormField/FormField.props';
|
|
3
|
+
|
|
4
|
+
export interface DateInputProps extends FormFieldBaseProps {
|
|
5
|
+
/**
|
|
6
|
+
* Whether the day segment is hidden.
|
|
7
|
+
* @default false
|
|
8
|
+
*/
|
|
9
|
+
hideDay?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether the month segment is hidden.
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
hideMonth?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the year segment is hidden.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
hideYear?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Placeholder text for the day segment.
|
|
22
|
+
*/
|
|
23
|
+
dayPlaceholder?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Placeholder text for the month segment.
|
|
26
|
+
*/
|
|
27
|
+
monthPlaceholder?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Placeholder text for the year segment.
|
|
30
|
+
*/
|
|
31
|
+
yearPlaceholder?: string;
|
|
32
|
+
/**
|
|
33
|
+
* The controlled value for the day segment. Must be used with an `onDayChange` handler.
|
|
34
|
+
*/
|
|
35
|
+
dayValue?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The controlled value for the month segment. Must be used with an `onMonthChange` handler.
|
|
38
|
+
*/
|
|
39
|
+
monthValue?: string;
|
|
40
|
+
/**
|
|
41
|
+
* The controlled value for the year segment. Must be used with an `onYearChange` handler.
|
|
42
|
+
*/
|
|
43
|
+
yearValue?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Callback fired when the day value changes.
|
|
46
|
+
*/
|
|
47
|
+
onDayChange?: (text: string) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Callback fired when the month value changes.
|
|
50
|
+
*/
|
|
51
|
+
onMonthChange?: (text: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Callback fired when the year value changes.
|
|
54
|
+
*/
|
|
55
|
+
onYearChange?: (text: string) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Callback fired when the day segment receives focus.
|
|
58
|
+
*/
|
|
59
|
+
onDayFocus?: TextInputProps['onFocus'];
|
|
60
|
+
/**
|
|
61
|
+
* Callback fired when the month segment receives focus.
|
|
62
|
+
*/
|
|
63
|
+
onMonthFocus?: TextInputProps['onFocus'];
|
|
64
|
+
/**
|
|
65
|
+
* Callback fired when the year segment receives focus.
|
|
66
|
+
*/
|
|
67
|
+
onYearFocus?: TextInputProps['onFocus'];
|
|
68
|
+
/**
|
|
69
|
+
* Callback fired when the day segment loses focus.
|
|
70
|
+
*/
|
|
71
|
+
onDayBlur?: TextInputProps['onBlur'];
|
|
72
|
+
/**
|
|
73
|
+
* Callback fired when the month segment loses focus.
|
|
74
|
+
*/
|
|
75
|
+
onMonthBlur?: TextInputProps['onBlur'];
|
|
76
|
+
/**
|
|
77
|
+
* Callback fired when the year segment loses focus.
|
|
78
|
+
*/
|
|
79
|
+
onYearBlur?: TextInputProps['onBlur'];
|
|
80
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native';
|
|
2
|
+
import { TickSmallIcon, WarningSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { Button, Card, Flex, Heading } from '../../components';
|
|
6
|
+
import { DateInput } from './';
|
|
7
|
+
|
|
8
|
+
const DateInputMeta: Meta<typeof DateInput> = {
|
|
9
|
+
title: 'Stories / DateInput',
|
|
10
|
+
component: DateInput,
|
|
11
|
+
parameters: {
|
|
12
|
+
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
|
|
13
|
+
layout: 'centered',
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
label: {
|
|
17
|
+
control: 'text',
|
|
18
|
+
},
|
|
19
|
+
helperText: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
},
|
|
22
|
+
validationStatus: {
|
|
23
|
+
control: 'select',
|
|
24
|
+
options: ['initial', 'valid', 'invalid'],
|
|
25
|
+
},
|
|
26
|
+
disabled: {
|
|
27
|
+
control: 'boolean',
|
|
28
|
+
},
|
|
29
|
+
required: {
|
|
30
|
+
control: 'boolean',
|
|
31
|
+
},
|
|
32
|
+
hideDay: {
|
|
33
|
+
control: 'boolean',
|
|
34
|
+
},
|
|
35
|
+
hideMonth: {
|
|
36
|
+
control: 'boolean',
|
|
37
|
+
},
|
|
38
|
+
hideYear: {
|
|
39
|
+
control: 'boolean',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default DateInputMeta;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<typeof DateInput>;
|
|
47
|
+
|
|
48
|
+
export const Playground: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
label: 'Date',
|
|
51
|
+
helperText: 'Helper text',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const DateOfBirth: Story = {
|
|
56
|
+
render: () => {
|
|
57
|
+
return <DateInput label="Date of birth" helperText="Enter your date of birth" required />;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const CardExpiry: Story = {
|
|
62
|
+
render: () => {
|
|
63
|
+
const [month, setMonth] = useState('');
|
|
64
|
+
const [year, setYear] = useState('');
|
|
65
|
+
return (
|
|
66
|
+
<DateInput
|
|
67
|
+
label="Card expiry"
|
|
68
|
+
helperText="Enter the expiry month and year"
|
|
69
|
+
monthValue={month}
|
|
70
|
+
yearValue={year}
|
|
71
|
+
onMonthChange={setMonth}
|
|
72
|
+
onYearChange={setYear}
|
|
73
|
+
hideDay
|
|
74
|
+
required
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const YearOnly: Story = {
|
|
81
|
+
render: () => {
|
|
82
|
+
return <DateInput label="Year" helperText="Enter the year" hideDay hideMonth />;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const Validation: Story = {
|
|
87
|
+
render: () => {
|
|
88
|
+
const [day, setDay] = useState('01');
|
|
89
|
+
const [month, setMonth] = useState('02');
|
|
90
|
+
const [year, setYear] = useState('2025');
|
|
91
|
+
return (
|
|
92
|
+
<View style={{ gap: 16 }}>
|
|
93
|
+
<DateInput
|
|
94
|
+
label="Valid date"
|
|
95
|
+
dayValue={day}
|
|
96
|
+
monthValue={month}
|
|
97
|
+
yearValue={year}
|
|
98
|
+
onDayChange={setDay}
|
|
99
|
+
onMonthChange={setMonth}
|
|
100
|
+
onYearChange={setYear}
|
|
101
|
+
validationStatus="valid"
|
|
102
|
+
validText="Date is valid"
|
|
103
|
+
helperIcon={TickSmallIcon}
|
|
104
|
+
required
|
|
105
|
+
/>
|
|
106
|
+
<DateInput
|
|
107
|
+
label="Invalid date"
|
|
108
|
+
dayValue="32"
|
|
109
|
+
monthValue="13"
|
|
110
|
+
yearValue="2025"
|
|
111
|
+
validationStatus="invalid"
|
|
112
|
+
invalidText="Please enter a valid date"
|
|
113
|
+
helperIcon={WarningSmallIcon}
|
|
114
|
+
required
|
|
115
|
+
/>
|
|
116
|
+
</View>
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const Disabled: Story = {
|
|
122
|
+
render: () => {
|
|
123
|
+
return (
|
|
124
|
+
<DateInput
|
|
125
|
+
label="Date of birth"
|
|
126
|
+
helperText="This field is disabled"
|
|
127
|
+
dayValue="15"
|
|
128
|
+
monthValue="06"
|
|
129
|
+
yearValue="1990"
|
|
130
|
+
disabled
|
|
131
|
+
/>
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const DefaultValue: Story = {
|
|
137
|
+
render: () => {
|
|
138
|
+
return <DateInput label="Date of birth" dayValue="01" monthValue="01" yearValue="2000" />;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const WithCustomValidation: Story = {
|
|
143
|
+
render: () => {
|
|
144
|
+
const [day, setDay] = useState('');
|
|
145
|
+
const [month, setMonth] = useState('');
|
|
146
|
+
const [year, setYear] = useState('');
|
|
147
|
+
|
|
148
|
+
const validateDate = () => {
|
|
149
|
+
if (!day || !month || !year) return { status: 'initial' as const, message: '' };
|
|
150
|
+
|
|
151
|
+
const dayNum = parseInt(day, 10);
|
|
152
|
+
const monthNum = parseInt(month, 10);
|
|
153
|
+
const yearNum = parseInt(year, 10);
|
|
154
|
+
|
|
155
|
+
// Basic validation
|
|
156
|
+
if (dayNum < 1 || dayNum > 31) {
|
|
157
|
+
return { status: 'invalid' as const, message: 'Day must be between 1 and 31' };
|
|
158
|
+
}
|
|
159
|
+
if (monthNum < 1 || monthNum > 12) {
|
|
160
|
+
return { status: 'invalid' as const, message: 'Month must be between 1 and 12' };
|
|
161
|
+
}
|
|
162
|
+
if (yearNum < 1900 || yearNum > new Date().getFullYear()) {
|
|
163
|
+
return {
|
|
164
|
+
status: 'invalid' as const,
|
|
165
|
+
message: `Year must be between 1900 and ${new Date().getFullYear()}`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Check valid date
|
|
170
|
+
const date = new Date(yearNum, monthNum - 1, dayNum);
|
|
171
|
+
if (
|
|
172
|
+
date.getDate() !== dayNum ||
|
|
173
|
+
date.getMonth() !== monthNum - 1 ||
|
|
174
|
+
date.getFullYear() !== yearNum
|
|
175
|
+
) {
|
|
176
|
+
return { status: 'invalid' as const, message: 'Please enter a valid date' };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { status: 'valid' as const, message: 'Valid date' };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const validation = validateDate();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<DateInput
|
|
186
|
+
label="Date of birth"
|
|
187
|
+
helperText="Enter a valid date between 1900 and today"
|
|
188
|
+
dayValue={day}
|
|
189
|
+
monthValue={month}
|
|
190
|
+
yearValue={year}
|
|
191
|
+
onDayChange={setDay}
|
|
192
|
+
onMonthChange={setMonth}
|
|
193
|
+
onYearChange={setYear}
|
|
194
|
+
validationStatus={validation.status}
|
|
195
|
+
validText={validation.status === 'valid' ? validation.message : undefined}
|
|
196
|
+
invalidText={validation.status === 'invalid' ? validation.message : undefined}
|
|
197
|
+
required
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const FlexibleSegments: Story = {
|
|
204
|
+
render: () => (
|
|
205
|
+
<View style={{ gap: 16 }}>
|
|
206
|
+
<DateInput label="Full date" helperText="DD/MM/YYYY" />
|
|
207
|
+
<DateInput label="Month and year" helperText="MM/YYYY" hideDay required />
|
|
208
|
+
<DateInput label="Year only" helperText="YYYY" hideDay hideMonth required />
|
|
209
|
+
</View>
|
|
210
|
+
),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const GroupingInputs: Story = {
|
|
214
|
+
render: () => (
|
|
215
|
+
<Flex space="sm">
|
|
216
|
+
<Heading size="lg">Event Registration</Heading>
|
|
217
|
+
<Card variant="subtle" gap="250">
|
|
218
|
+
<DateInput label="Date of birth" helperText="Enter your date of birth" required />
|
|
219
|
+
<DateInput
|
|
220
|
+
label="Event date preference"
|
|
221
|
+
helperText="Select your preferred date"
|
|
222
|
+
required={false}
|
|
223
|
+
/>
|
|
224
|
+
</Card>
|
|
225
|
+
</Flex>
|
|
226
|
+
),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const WithState: Story = {
|
|
230
|
+
render: () => {
|
|
231
|
+
const [day, setDay] = useState('');
|
|
232
|
+
const [month, setMonth] = useState('');
|
|
233
|
+
const [year, setYear] = useState('');
|
|
234
|
+
|
|
235
|
+
const handleReset = () => {
|
|
236
|
+
setDay('');
|
|
237
|
+
setMonth('');
|
|
238
|
+
setYear('');
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const handleSetToday = () => {
|
|
242
|
+
const today = new Date();
|
|
243
|
+
setDay(String(today.getDate()).padStart(2, '0'));
|
|
244
|
+
setMonth(String(today.getMonth() + 1).padStart(2, '0'));
|
|
245
|
+
setYear(String(today.getFullYear()));
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<Flex space="md">
|
|
250
|
+
<DateInput
|
|
251
|
+
label="Date"
|
|
252
|
+
helperText="Select or enter a date"
|
|
253
|
+
dayValue={day}
|
|
254
|
+
monthValue={month}
|
|
255
|
+
yearValue={year}
|
|
256
|
+
onDayChange={setDay}
|
|
257
|
+
onMonthChange={setMonth}
|
|
258
|
+
onYearChange={setYear}
|
|
259
|
+
/>
|
|
260
|
+
<Flex space="xs">
|
|
261
|
+
<Button onPress={handleSetToday}>Set to Today</Button>
|
|
262
|
+
<Button onPress={handleReset} variant="solid">
|
|
263
|
+
Reset
|
|
264
|
+
</Button>
|
|
265
|
+
</Flex>
|
|
266
|
+
</Flex>
|
|
267
|
+
);
|
|
268
|
+
},
|
|
269
|
+
};
|