@verifiedinc-public/shared-ui-elements 0.14.3 → 0.14.5-beta.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/dist/components/Banners/Banner.d.ts +10 -0
- package/dist/components/Banners/ExactBirthdayBanner.d.ts +7 -0
- package/dist/components/Banners/ResendPhoneBanner.d.ts +15 -0
- package/dist/components/Banners/TestPhoneNumbersBanner.d.ts +7 -0
- package/dist/components/form/DateInput.d.ts +5 -12
- package/dist/shared-ui-elements.mjs +2294 -2301
- package/package.json +1 -1
- package/src/components/Banners/Banner.tsx +42 -0
- package/src/components/Banners/ExactBirthdayBanner.tsx +18 -0
- package/src/components/Banners/ResendPhoneBanner.tsx +55 -0
- package/src/components/Banners/TestPhoneNumbersBanner.tsx +25 -0
- package/src/components/Snackbar/index.tsx +1 -0
- package/src/components/form/DateInput.tsx +24 -63
- package/src/components/form/OTPInput.tsx +19 -16
- package/src/stories/components/form/DateInput.stories.ts +6 -9
package/package.json
CHANGED
@@ -0,0 +1,42 @@
|
|
1
|
+
import {
|
2
|
+
AlertTitle,
|
3
|
+
Box,
|
4
|
+
type SxProps,
|
5
|
+
useTheme,
|
6
|
+
type AlertColor,
|
7
|
+
} from '@mui/material';
|
8
|
+
|
9
|
+
import { type ReactNode } from 'react';
|
10
|
+
import { FullWidthAlert } from '../Alert/FullWidthAlert';
|
11
|
+
import { Typography } from '../Typography';
|
12
|
+
|
13
|
+
interface BannerProps {
|
14
|
+
title: string;
|
15
|
+
severity: AlertColor;
|
16
|
+
children: ReactNode;
|
17
|
+
sx?: SxProps;
|
18
|
+
}
|
19
|
+
|
20
|
+
export function Banner({
|
21
|
+
title,
|
22
|
+
severity,
|
23
|
+
children,
|
24
|
+
sx,
|
25
|
+
}: BannerProps): React.JSX.Element {
|
26
|
+
const theme = useTheme();
|
27
|
+
return (
|
28
|
+
<Box sx={{ mt: 3, ...sx }}>
|
29
|
+
<FullWidthAlert severity={severity} sx={{ alignItems: 'start' }}>
|
30
|
+
<AlertTitle>
|
31
|
+
<Typography
|
32
|
+
sx={{ color: theme.palette.info.contrastText }}
|
33
|
+
fontWeight='bold'
|
34
|
+
>
|
35
|
+
{title}
|
36
|
+
</Typography>
|
37
|
+
</AlertTitle>
|
38
|
+
{children}
|
39
|
+
</FullWidthAlert>
|
40
|
+
</Box>
|
41
|
+
);
|
42
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { type SxProps } from '@mui/material';
|
2
|
+
import { Banner } from './Banner';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* "Enter your exact Birthday" Banner
|
6
|
+
*/
|
7
|
+
export function ExactBirthdayBanner({
|
8
|
+
sx,
|
9
|
+
}: {
|
10
|
+
sx?: SxProps;
|
11
|
+
}): React.JSX.Element {
|
12
|
+
return (
|
13
|
+
<Banner title='Enter your exact Birthday' severity='info' sx={sx}>
|
14
|
+
We need this to verify your identity with your phone carrier and pre-fill
|
15
|
+
your signup info.
|
16
|
+
</Banner>
|
17
|
+
);
|
18
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { type SxProps, useTheme } from '@mui/material';
|
2
|
+
import { FullWidthAlert } from '../Alert/FullWidthAlert';
|
3
|
+
import { Button } from '../Button';
|
4
|
+
import { parseToPhoneNational } from '../../utils';
|
5
|
+
|
6
|
+
interface ResendPhoneBannerProps {
|
7
|
+
phone: string;
|
8
|
+
onClick: () => void;
|
9
|
+
disabled?: boolean;
|
10
|
+
sx?: SxProps;
|
11
|
+
}
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Banner to verify and resend the phone verification code.
|
15
|
+
* @param phone
|
16
|
+
* @param onClick
|
17
|
+
* @constructor
|
18
|
+
*/
|
19
|
+
export function ResendPhoneBanner({
|
20
|
+
phone,
|
21
|
+
onClick,
|
22
|
+
disabled = false,
|
23
|
+
sx,
|
24
|
+
}: ResendPhoneBannerProps): React.JSX.Element {
|
25
|
+
const theme = useTheme();
|
26
|
+
return (
|
27
|
+
<>
|
28
|
+
<FullWidthAlert
|
29
|
+
action={
|
30
|
+
<Button
|
31
|
+
onClick={onClick}
|
32
|
+
disabled={disabled}
|
33
|
+
sx={{
|
34
|
+
color: theme.palette.info.contrastText,
|
35
|
+
fontWeight: 800,
|
36
|
+
fontSize: '13px',
|
37
|
+
padding: '0',
|
38
|
+
'&:hover': {
|
39
|
+
backgroundColor: 'initial',
|
40
|
+
},
|
41
|
+
...sx,
|
42
|
+
}}
|
43
|
+
size='small'
|
44
|
+
variant='text'
|
45
|
+
color='info'
|
46
|
+
>
|
47
|
+
Resend
|
48
|
+
</Button>
|
49
|
+
}
|
50
|
+
>
|
51
|
+
Use the text we sent to <strong>{parseToPhoneNational(phone)}</strong>{' '}
|
52
|
+
</FullWidthAlert>
|
53
|
+
</>
|
54
|
+
);
|
55
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Box, type SxProps } from '@mui/material';
|
2
|
+
import { Banner } from './Banner';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Banner to inform about the test phone numbers
|
6
|
+
*/
|
7
|
+
export function TestPhoneNumbersBanner({
|
8
|
+
sx,
|
9
|
+
}: {
|
10
|
+
sx?: SxProps;
|
11
|
+
}): React.JSX.Element {
|
12
|
+
return (
|
13
|
+
<Banner title='Test Phone Numbers' severity='info' sx={sx}>
|
14
|
+
<Box
|
15
|
+
component='ul'
|
16
|
+
sx={{
|
17
|
+
listStyle: 'inside',
|
18
|
+
}}
|
19
|
+
>
|
20
|
+
<li>Phone Only Input: +10123456789</li>
|
21
|
+
<li>Phone and Birth Date Inputs: +10019999999</li>
|
22
|
+
</Box>
|
23
|
+
</Banner>
|
24
|
+
);
|
25
|
+
}
|
@@ -1,64 +1,46 @@
|
|
1
|
-
import { Box, TextField } from '@mui/material';
|
2
|
-
import {
|
3
|
-
forwardRef,
|
4
|
-
useEffect,
|
5
|
-
useState,
|
6
|
-
type ChangeEventHandler,
|
7
|
-
} from 'react';
|
8
|
-
import {
|
9
|
-
formatDateMMDDYYYY,
|
10
|
-
getMaxDateInstance,
|
11
|
-
getMinDateInstance,
|
12
|
-
} from '../../utils/date';
|
1
|
+
import { Box, TextField, type TextFieldProps } from '@mui/material';
|
2
|
+
import { forwardRef, useState, type ChangeEventHandler } from 'react';
|
13
3
|
import { masks } from '../../utils/masks';
|
14
|
-
import { USDateSchema } from '../../validations';
|
15
|
-
import { type TextFieldProps } from '../TextField';
|
16
4
|
import { InputMask } from './InputMask';
|
17
5
|
import { inputStyle } from './styles/input';
|
18
6
|
|
19
|
-
interface DateInputProps {
|
20
|
-
name?: string;
|
21
|
-
value?: string;
|
7
|
+
interface DateInputProps extends Omit<TextFieldProps, 'onBlur' | 'onChange'> {
|
22
8
|
label?: string;
|
23
|
-
|
9
|
+
value?: string;
|
24
10
|
helperText?: string;
|
25
|
-
onChange?: (
|
11
|
+
onChange?: (value: string) => void;
|
26
12
|
onBlur?: ChangeEventHandler<HTMLInputElement>;
|
27
|
-
disabled?: boolean;
|
28
|
-
allowFutureDates?: boolean;
|
29
13
|
}
|
30
14
|
|
31
|
-
/**
|
32
|
-
* The input with date format.
|
33
|
-
* @constructor
|
34
|
-
*/
|
35
15
|
function DateInputComponent(
|
36
16
|
{
|
37
|
-
label = 'Date
|
38
|
-
value
|
17
|
+
label = 'Date',
|
18
|
+
value: controlledValue,
|
39
19
|
error,
|
40
20
|
helperText,
|
41
21
|
onChange,
|
42
22
|
onBlur,
|
43
23
|
disabled,
|
44
|
-
allowFutureDates = true,
|
45
24
|
...rest
|
46
25
|
}: Readonly<DateInputProps>,
|
47
26
|
ref: any,
|
48
27
|
): React.JSX.Element {
|
49
|
-
|
50
|
-
const [localValue, setLocalValue] = useState<string>(
|
51
|
-
value ? formatDateMMDDYYYY(value) : '',
|
52
|
-
);
|
28
|
+
const [internalValue, setInternalValue] = useState<string>('');
|
53
29
|
|
54
|
-
|
55
|
-
|
56
|
-
|
30
|
+
// Determine the value to display
|
31
|
+
const isControlled = controlledValue !== undefined;
|
32
|
+
const value = isControlled ? controlledValue : internalValue;
|
33
|
+
|
34
|
+
const handleChange = (e: any): void => {
|
35
|
+
const date = e.target.value;
|
36
|
+
if (!isControlled) {
|
37
|
+
setInternalValue(date); // Update internal state only if uncontrolled
|
57
38
|
}
|
58
|
-
}, [value]);
|
59
39
|
|
60
|
-
|
61
|
-
|
40
|
+
if (onChange) {
|
41
|
+
onChange(date);
|
42
|
+
}
|
43
|
+
};
|
62
44
|
|
63
45
|
const textFieldStyle: TextFieldProps = {
|
64
46
|
...inputStyle,
|
@@ -74,6 +56,7 @@ function DateInputComponent(
|
|
74
56
|
mask: masks.DOB_MASK,
|
75
57
|
},
|
76
58
|
fullWidth: true,
|
59
|
+
...rest,
|
77
60
|
};
|
78
61
|
|
79
62
|
return (
|
@@ -82,33 +65,11 @@ function DateInputComponent(
|
|
82
65
|
mask={masks.DOB_MASK}
|
83
66
|
maskPlaceholder={null}
|
84
67
|
disabled={disabled}
|
85
|
-
value={
|
68
|
+
value={value}
|
86
69
|
onBlur={onBlur}
|
87
|
-
onChange={
|
88
|
-
const value = e.target.value;
|
89
|
-
const valid = USDateSchema.safeParse(value);
|
90
|
-
|
91
|
-
// Update the facade input value, so it let user input wrong/right values.
|
92
|
-
setLocalValue(value);
|
93
|
-
|
94
|
-
if (!valid.success) {
|
95
|
-
// A way to make sure the data field state is invalid is to empty the value.
|
96
|
-
return onChange?.({ target: { value: '' } });
|
97
|
-
}
|
98
|
-
|
99
|
-
const date = new Date(value);
|
100
|
-
if (date < minDateInstance || date > maxDateInstance) {
|
101
|
-
// A way to make sure the data field state is invalid is to empty the value.
|
102
|
-
return onChange?.({ target: { value: '' } });
|
103
|
-
}
|
104
|
-
|
105
|
-
date.setUTCHours(12);
|
106
|
-
|
107
|
-
// The date is valid in the US date format and is in between the valid min-max date range.
|
108
|
-
onChange?.({ target: { value: String(+date) } });
|
109
|
-
}}
|
70
|
+
onChange={handleChange}
|
110
71
|
>
|
111
|
-
<TextField {...textFieldStyle} inputRef={ref}
|
72
|
+
<TextField {...textFieldStyle} inputRef={ref} />
|
112
73
|
</InputMask>
|
113
74
|
</Box>
|
114
75
|
);
|
@@ -20,6 +20,7 @@ import {
|
|
20
20
|
type InputBaseProps,
|
21
21
|
Typography,
|
22
22
|
useTheme,
|
23
|
+
FormControl,
|
23
24
|
} from '@mui/material';
|
24
25
|
import { v4 as uuid } from 'uuid';
|
25
26
|
|
@@ -192,22 +193,24 @@ function OTPInputComponent(
|
|
192
193
|
(startIndex: number) => {
|
193
194
|
return new Array(3).fill(undefined).map((_, index) => {
|
194
195
|
return (
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
(
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
196
|
+
// FormControl is required for InputBase to avoid bad setState in handleBlur event, and it is one per input.
|
197
|
+
<FormControl key={ids.current[index + startIndex]}>
|
198
|
+
<InputBase
|
199
|
+
inputRef={(input) =>
|
200
|
+
((inputsRef.current[index + startIndex] as any) = input)
|
201
|
+
}
|
202
|
+
autoComplete='one-time-code'
|
203
|
+
autoFocus={index + startIndex === 0}
|
204
|
+
value={values[index + startIndex] || ''}
|
205
|
+
disabled={props.disabled}
|
206
|
+
onChange={handleChange}
|
207
|
+
onKeyUp={handleKeyUp}
|
208
|
+
onFocus={handleFocus}
|
209
|
+
onBlur={() => setIsFocused(false)}
|
210
|
+
{...inputProps}
|
211
|
+
data-testid={`otp-input-${index + startIndex}`}
|
212
|
+
/>
|
213
|
+
</FormControl>
|
211
214
|
);
|
212
215
|
});
|
213
216
|
},
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react';
|
2
|
-
|
3
2
|
import { fn } from '@storybook/test';
|
4
3
|
import { DateInput } from '../../../components/form/DateInput';
|
5
4
|
|
@@ -13,6 +12,10 @@ const meta = {
|
|
13
12
|
},
|
14
13
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
15
14
|
tags: ['autodocs'],
|
15
|
+
argTypes: {
|
16
|
+
size: { control: 'select', options: ['small', 'medium'] },
|
17
|
+
value: { control: 'date' },
|
18
|
+
},
|
16
19
|
} satisfies Meta<typeof DateInput>;
|
17
20
|
|
18
21
|
export default meta;
|
@@ -26,14 +29,8 @@ export const Default: Story = {
|
|
26
29
|
onChange: fn(),
|
27
30
|
disabled: false,
|
28
31
|
error: false,
|
32
|
+
size: 'small',
|
29
33
|
helperText: 'Helper text',
|
30
|
-
allowFutureDates: true,
|
31
|
-
},
|
32
|
-
argTypes: {
|
33
|
-
allowFutureDates: {
|
34
|
-
control: 'boolean',
|
35
|
-
description:
|
36
|
-
'Allow future dates. If false and the limit is reached, onChange will return an empty string.',
|
37
|
-
},
|
38
34
|
},
|
35
|
+
argTypes: {},
|
39
36
|
};
|