@clicktap/ui 0.14.12 → 0.14.13
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/components/Accordion/Accordion.tsx +82 -0
- package/components/Accordion/index.ts +3 -0
- package/components/Avatar/Avatar.stories.tsx +99 -0
- package/components/Avatar/Avatar.tsx +120 -0
- package/components/Avatar/Avatar.types.ts +3 -0
- package/components/Avatar/AvatarGroup/AvatarGroup.tsx +32 -0
- package/components/Avatar/AvatarGroup/AvatarGroup.types.ts +8 -0
- package/components/Avatar/index.ts +4 -0
- package/components/Badge/Badge.stories.tsx +72 -0
- package/components/Badge/Badge.tsx +169 -0
- package/components/Badge/Badge.types.ts +3 -0
- package/components/Badge/index.ts +2 -0
- package/components/Breadcrumbs/BreadcrumbEllipsis.tsx +47 -0
- package/components/Breadcrumbs/BreadcrumbEllipsis.types.ts +5 -0
- package/components/Breadcrumbs/BreadcrumbItem.tsx +23 -0
- package/components/Breadcrumbs/BreadcrumbItem.types.ts +3 -0
- package/components/Breadcrumbs/BreadcrumbLink.tsx +30 -0
- package/components/Breadcrumbs/BreadcrumbLink.types.ts +3 -0
- package/components/Breadcrumbs/BreadcrumbSeparator.tsx +41 -0
- package/components/Breadcrumbs/BreadcrumbSeparator.types.ts +9 -0
- package/components/Breadcrumbs/Breadcrumbs.tsx +28 -0
- package/components/Breadcrumbs/Breadcrumbs.types.ts +6 -0
- package/components/Breadcrumbs/index.ts +10 -0
- package/components/Button/Button.tsx +72 -0
- package/components/Button/Button.types.ts +7 -0
- package/components/Button/index.ts +2 -0
- package/components/Card/Card.tsx +15 -0
- package/components/Card/Card.types.ts +3 -0
- package/components/Card/index.ts +2 -0
- package/components/Checkbox/Checkbox.tsx +122 -0
- package/components/Checkbox/Checkbox.types.ts +15 -0
- package/components/Checkbox/index.ts +2 -0
- package/components/Collapsible/Collapsible.tsx +34 -0
- package/components/Collapsible/Collapsible.types.ts +5 -0
- package/components/Collapsible/CollapsibleTrigger.tsx +57 -0
- package/components/Collapsible/CollapsibleTrigger.types.ts +14 -0
- package/components/Collapsible/index.ts +10 -0
- package/components/Container/Container.tsx +26 -0
- package/components/Container/Container.types.ts +3 -0
- package/components/Container/index.ts +2 -0
- package/components/ContextMenu/ContextMenu.tsx +74 -0
- package/components/ContextMenu/ContextMenu.types.ts +17 -0
- package/components/ContextMenu/index.ts +2 -0
- package/components/CreditCardExpirationInput/CreditCardExpirationInput.tsx +115 -0
- package/components/CreditCardExpirationInput/CreditCardExpirationInput.types.ts +10 -0
- package/components/CreditCardExpirationInput/index.ts +2 -0
- package/components/CreditCardInput/CreditCardInput.tsx +147 -0
- package/components/CreditCardInput/CreditCardInput.types.ts +12 -0
- package/components/CreditCardInput/index.ts +2 -0
- package/components/DateInput/DateInput.tsx +81 -0
- package/components/DateInput/DateInput.types.ts +15 -0
- package/components/DateInput/index.ts +2 -0
- package/components/DateTimeFormat/DateTimeFormat.tsx +16 -0
- package/components/DateTimeFormat/DateTimeFormat.types.ts +7 -0
- package/components/DateTimeFormat/index.ts +2 -0
- package/components/Dialog/Dialog.tsx +65 -0
- package/components/Dialog/Dialog.types.ts +9 -0
- package/components/Dialog/index.ts +2 -0
- package/components/DialogTrigger/DialogTrigger.tsx +45 -0
- package/components/DialogTrigger/DialogTrigger.types.ts +6 -0
- package/components/DialogTrigger/index.ts +5 -0
- package/components/Divider/Divider.stories.tsx +37 -0
- package/components/Divider/Divider.tsx +34 -0
- package/components/Divider/Divider.types.ts +5 -0
- package/components/Divider/index.ts +2 -0
- package/components/DobInput/DobInput.tsx +120 -0
- package/components/DobInput/index.ts +2 -0
- package/components/Drawer/Drawer.tsx +126 -0
- package/components/Drawer/Drawer.types.ts +11 -0
- package/components/Drawer/index.ts +2 -0
- package/components/Icon/Account.tsx +50 -0
- package/components/Icon/Cart.tsx +43 -0
- package/components/Icon/Checkmark.tsx +34 -0
- package/components/Icon/Cross.tsx +36 -0
- package/components/Icon/DownArrow.tsx +23 -0
- package/components/Icon/Hamburger.tsx +23 -0
- package/components/Icon/Icon.types.ts +8 -0
- package/components/Icon/LinkArrow.tsx +32 -0
- package/components/Icon/Minus.tsx +20 -0
- package/components/Icon/Plus.tsx +20 -0
- package/components/Icon/Search.tsx +36 -0
- package/components/Icon/Trash.tsx +27 -0
- package/components/Icon/Verified.tsx +20 -0
- package/components/Icon/index.ts +14 -0
- package/components/Image/Image.tsx +32 -0
- package/components/Image/index.ts +2 -0
- package/components/Input/Input.tsx +109 -0
- package/components/Input/Input.types.ts +17 -0
- package/components/Input/index.ts +2 -0
- package/components/Link/Link.stories.tsx +96 -0
- package/components/Link/Link.tsx +34 -0
- package/components/Link/Link.types.ts +3 -0
- package/components/Link/index.ts +2 -0
- package/components/Loader/CircularEasing.tsx +66 -0
- package/components/Loader/CircularEasing.types.ts +8 -0
- package/components/Loader/Pulse.tsx +45 -0
- package/components/Loader/Pulse.types.ts +5 -0
- package/components/Loader/index.ts +4 -0
- package/components/Menu/ContextMenu.tsx +83 -0
- package/components/Menu/Menu.tsx +143 -0
- package/components/Menu/Menu.types.ts +44 -0
- package/components/Menu/index.ts +4 -0
- package/components/Meter/Meter.stories.tsx +111 -0
- package/components/Meter/Meter.tsx +68 -0
- package/components/Meter/Meter.types.ts +10 -0
- package/components/Meter/index.ts +2 -0
- package/components/Modal/Modal.tsx +16 -0
- package/components/Modal/Modal.types.ts +6 -0
- package/components/Modal/index.ts +2 -0
- package/components/ModalOverlay/ModalOverlay.tsx +121 -0
- package/components/ModalOverlay/ModalOverlay.types.ts +18 -0
- package/components/ModalOverlay/index.ts +2 -0
- package/components/NumberFormat/NumberFormat.tsx +19 -0
- package/components/NumberFormat/NumberFormat.types.ts +8 -0
- package/components/NumberFormat/index.ts +2 -0
- package/components/NumberInput/NumberInput.tsx +164 -0
- package/components/NumberInput/NumberInput.types.ts +22 -0
- package/components/NumberInput/index.ts +2 -0
- package/components/NumberTicker/DigitResolver.tsx +119 -0
- package/components/NumberTicker/DigitResolver.types.ts +18 -0
- package/components/NumberTicker/NumberTicker.tsx +56 -0
- package/components/NumberTicker/NumberTicker.types.ts +96 -0
- package/components/NumberTicker/hooks/useColumnTransition.ts +36 -0
- package/components/NumberTicker/hooks/useNumberDelta.ts +19 -0
- package/components/NumberTicker/hooks/useNumberTicker.ts +36 -0
- package/components/NumberTicker/index.ts +10 -0
- package/components/Pagination/Pagination.tsx +44 -0
- package/components/Pagination/index.ts +2 -0
- package/components/PasswordCheck/PasswordCheck.tsx +59 -0
- package/components/PasswordCheck/PasswordCheck.types.ts +4 -0
- package/components/PasswordCheck/PasswordCheck.utils.ts +47 -0
- package/components/PasswordCheck/index.ts +2 -0
- package/components/PhoneInput/PhoneInput.tsx +191 -0
- package/components/PhoneInput/index.ts +2 -0
- package/components/PinInput/PinInput.tsx +314 -0
- package/components/PinInput/PinInput.types.ts +21 -0
- package/components/PinInput/index.ts +2 -0
- package/components/Progressbar/CircularProgressbar.tsx +71 -0
- package/components/Progressbar/CircularProgressbar.types.ts +10 -0
- package/components/Progressbar/LinearProgressbar.tsx +75 -0
- package/components/Progressbar/LinearProgressbar.types.ts +11 -0
- package/components/Progressbar/index.ts +4 -0
- package/components/Radio/Radio.tsx +88 -0
- package/components/Radio/Radio.types.ts +16 -0
- package/components/Radio/index.ts +2 -0
- package/components/RadioGroup/RadioGroup.tsx +49 -0
- package/components/RadioGroup/RadioGroup.types.ts +7 -0
- package/components/RadioGroup/index.ts +2 -0
- package/components/Select/Option.tsx +32 -0
- package/components/Select/Option.types.ts +3 -0
- package/components/Select/Select.tsx +253 -0
- package/components/Select/Select.types.ts +42 -0
- package/components/Select/index.ts +8 -0
- package/components/Skeleton/Skeleton.tsx +15 -0
- package/components/Skeleton/Skeleton.types.ts +3 -0
- package/components/Skeleton/index.ts +2 -0
- package/components/Slider/Slider.tsx +110 -0
- package/components/Slider/Slider.types.ts +11 -0
- package/components/Slider/index.ts +2 -0
- package/components/Switch/Switch.tsx +63 -0
- package/components/Switch/Switch.types.ts +8 -0
- package/components/Switch/index.ts +2 -0
- package/components/Table/Table.tsx +52 -0
- package/components/Table/Table.types.ts +22 -0
- package/components/Table/index.ts +2 -0
- package/components/Tabs/Tab.tsx +118 -0
- package/components/Tabs/Tab.types.ts +10 -0
- package/components/Tabs/TabList.tsx +51 -0
- package/components/Tabs/TabList.types.ts +12 -0
- package/components/Tabs/TabPanel.tsx +19 -0
- package/components/Tabs/TabPanel.types.ts +3 -0
- package/components/Tabs/Tabs.context.tsx +9 -0
- package/components/Tabs/Tabs.tsx +39 -0
- package/components/Tabs/Tabs.types.ts +3 -0
- package/components/Tabs/index.ts +9 -0
- package/components/TimeInput/TimeInput.stories.tsx +125 -0
- package/components/TimeInput/TimeInput.tsx +81 -0
- package/components/TimeInput/TimeInput.types.ts +15 -0
- package/components/TimeInput/index.ts +2 -0
- package/components/ToggleButton/ToggleButton.stories.tsx +89 -0
- package/components/ToggleButton/ToggleButton.tsx +69 -0
- package/components/ToggleButton/ToggleButton.types.ts +6 -0
- package/components/ToggleButton/index.ts +2 -0
- package/components/Tooltip/Tooltip.tsx +59 -0
- package/components/Tooltip/Tooltip.types.ts +3 -0
- package/components/Tooltip/index.ts +2 -0
- package/components/UploadImage/UploadImage.tsx +206 -0
- package/components/UploadImage/UploadImage.types.ts +15 -0
- package/components/UploadImage/index.ts +2 -0
- package/package.json +1 -1
- package/tailwind.config.js +3 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { NumberFormatBase, NumberFormatValues } from 'react-number-format';
|
|
4
|
+
import type { InputAttributes } from 'react-number-format';
|
|
5
|
+
import {
|
|
6
|
+
FieldError,
|
|
7
|
+
Input,
|
|
8
|
+
Label,
|
|
9
|
+
Text,
|
|
10
|
+
TextField,
|
|
11
|
+
} from 'react-aria-components';
|
|
12
|
+
import { useEffect, useState } from 'react';
|
|
13
|
+
import { cn } from '../../utils/cn';
|
|
14
|
+
import type { CreditCardExpirationInputProps } from './CreditCardExpirationInput.types';
|
|
15
|
+
|
|
16
|
+
function AriaInput({ className, ...props }: InputAttributes) {
|
|
17
|
+
return (
|
|
18
|
+
<Input
|
|
19
|
+
className={cn(
|
|
20
|
+
'border-solid border border-slate-300 rounded-md',
|
|
21
|
+
'text-sm text-slate-900 placeholder-slate-400',
|
|
22
|
+
'h-10 px-2 py-0 m-0 w-full',
|
|
23
|
+
'bg-white',
|
|
24
|
+
'transition-all duration-200 ease-in-out',
|
|
25
|
+
'hover:border-slate-400',
|
|
26
|
+
'focus:outline-2 focus:outline focus:outline-slate-200 focus:border-slate-400',
|
|
27
|
+
'disabled:border-slate-200 disabled:bg-slate-100',
|
|
28
|
+
'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
|
|
29
|
+
'invalid:hover:border-red-600 invalid:focus:border-red-600 invalid:focus:outline-red-200',
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
33
|
+
{...props}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function CreditCardExpirationInput({
|
|
39
|
+
label,
|
|
40
|
+
description,
|
|
41
|
+
errorMessage,
|
|
42
|
+
placeholder,
|
|
43
|
+
value,
|
|
44
|
+
className,
|
|
45
|
+
classNames,
|
|
46
|
+
onChange,
|
|
47
|
+
...props
|
|
48
|
+
}: CreditCardExpirationInputProps) {
|
|
49
|
+
const [inputValue, setInputValue] = useState(value || '');
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
setInputValue(value || '');
|
|
53
|
+
}, [value]);
|
|
54
|
+
|
|
55
|
+
const format = (val: string) => {
|
|
56
|
+
if (val === '') return '';
|
|
57
|
+
let month = val.substring(0, 2);
|
|
58
|
+
const year = val.substring(2, 4);
|
|
59
|
+
|
|
60
|
+
if (month.length === 1 && Number(month[0]) > 1) {
|
|
61
|
+
month = `0${month[0]}`;
|
|
62
|
+
} else if (month.length === 2) {
|
|
63
|
+
// set the lower and upper boundary
|
|
64
|
+
if (Number(month) === 0) {
|
|
65
|
+
month = `01`;
|
|
66
|
+
} else if (Number(month) > 12) {
|
|
67
|
+
month = '12';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return `${month}/${year}`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleValueChange = (values: NumberFormatValues) => {
|
|
75
|
+
setInputValue(values.formattedValue);
|
|
76
|
+
if (onChange) {
|
|
77
|
+
onChange(values.formattedValue);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<TextField
|
|
83
|
+
className={cn('flex flex-col w-full text-slate-900', className)}
|
|
84
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
<Label className={cn('flex text-slate-500 text-xs', classNames?.label)}>
|
|
88
|
+
{label}
|
|
89
|
+
</Label>
|
|
90
|
+
<NumberFormatBase
|
|
91
|
+
format={format}
|
|
92
|
+
customInput={AriaInput}
|
|
93
|
+
className={cn(classNames?.input)}
|
|
94
|
+
placeholder={placeholder}
|
|
95
|
+
value={inputValue}
|
|
96
|
+
onValueChange={handleValueChange}
|
|
97
|
+
/>
|
|
98
|
+
{description && (
|
|
99
|
+
<Text
|
|
100
|
+
className={cn('flex text-slate-500 text-sm', classNames?.description)}
|
|
101
|
+
slot="description"
|
|
102
|
+
>
|
|
103
|
+
{description}
|
|
104
|
+
</Text>
|
|
105
|
+
)}
|
|
106
|
+
<FieldError
|
|
107
|
+
className={cn('flex text-red-500 text-sm', classNames?.error)}
|
|
108
|
+
>
|
|
109
|
+
{errorMessage}
|
|
110
|
+
</FieldError>
|
|
111
|
+
</TextField>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default CreditCardExpirationInput;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TextFieldProps, ValidationResult } from 'react-aria-components';
|
|
2
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
3
|
+
|
|
4
|
+
export interface CreditCardExpirationInputProps extends TextFieldProps {
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
errorMessage?: string | ((validation: ValidationResult) => string);
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
classNames?: SlotsToClasses<'label' | 'input' | 'description' | 'error'>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { NumberFormatBase } from 'react-number-format';
|
|
5
|
+
import type { InputAttributes, NumberFormatValues } from 'react-number-format';
|
|
6
|
+
import {
|
|
7
|
+
FieldError,
|
|
8
|
+
Input,
|
|
9
|
+
Label,
|
|
10
|
+
Text,
|
|
11
|
+
TextField,
|
|
12
|
+
} from 'react-aria-components';
|
|
13
|
+
import { cn } from '../../utils/cn';
|
|
14
|
+
import type { CreditCardInputProps } from './CreditCardInput.types';
|
|
15
|
+
|
|
16
|
+
const getCardFormat = (cardNumber: string) => {
|
|
17
|
+
const cardPatterns = [
|
|
18
|
+
{ regex: /^4[0-9]{0,15}/, format: '#### #### #### ####' }, // Visa
|
|
19
|
+
{ regex: /^5[1-5][0-9]{0,14}/, format: '#### #### #### ####' }, // MasterCard
|
|
20
|
+
{ regex: /^3[47][0-9]{0,13}/, format: '#### ###### #####' }, // American Express
|
|
21
|
+
{ regex: /^6(?:011|5[0-9]{2})[0-9]{0,12}/, format: '#### #### #### ####' }, // Discover
|
|
22
|
+
{ regex: /^3(?:0[0-5]|[68][0-9])[0-9]{0,11}/, format: '#### ###### ####' }, // Diners Club
|
|
23
|
+
{ regex: /^(?:2131|1800|35\d{3})\d{0,11}/, format: '#### #### #### ####' }, // JCB
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const matchingPattern = cardPatterns.find((cardPattern) =>
|
|
27
|
+
cardPattern.regex.test(cardNumber)
|
|
28
|
+
);
|
|
29
|
+
if (matchingPattern) {
|
|
30
|
+
return matchingPattern.format;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Default format if no match found
|
|
34
|
+
return '#### #### #### #### ###';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function AriaInput({ className, ...props }: InputAttributes) {
|
|
38
|
+
return (
|
|
39
|
+
<Input
|
|
40
|
+
className={cn(
|
|
41
|
+
'border-solid border border-slate-300 rounded-md',
|
|
42
|
+
'text-sm text-slate-900 placeholder-slate-400',
|
|
43
|
+
'h-10 px-2 py-0 m-0 w-full',
|
|
44
|
+
'bg-white',
|
|
45
|
+
'transition-all duration-200 ease-in-out',
|
|
46
|
+
'hover:border-slate-400',
|
|
47
|
+
'focus:outline-2 focus:outline focus:outline-slate-200 focus:border-slate-400',
|
|
48
|
+
'disabled:border-slate-200 disabled:bg-slate-100',
|
|
49
|
+
'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
|
|
50
|
+
'invalid:hover:border-red-600 invalid:focus:border-red-600 invalid:focus:outline-red-200',
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function CreditCardInput({
|
|
60
|
+
label,
|
|
61
|
+
description,
|
|
62
|
+
errorMessage,
|
|
63
|
+
placeholder,
|
|
64
|
+
value,
|
|
65
|
+
onChange,
|
|
66
|
+
className,
|
|
67
|
+
classNames,
|
|
68
|
+
...props
|
|
69
|
+
}: CreditCardInputProps) {
|
|
70
|
+
const [inputValue, setInputValue] = useState(value || '');
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
setInputValue(value || '');
|
|
74
|
+
}, [value]);
|
|
75
|
+
|
|
76
|
+
const format = (val: string) => {
|
|
77
|
+
if (val === '') return '';
|
|
78
|
+
const cleanedVal = val.replace(/\s+/g, '');
|
|
79
|
+
const cardFormat = getCardFormat(cleanedVal);
|
|
80
|
+
|
|
81
|
+
// Add spaces according to the format
|
|
82
|
+
let formattedVal = '';
|
|
83
|
+
let position = 0;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < cardFormat.length; i += 1) {
|
|
86
|
+
if (cardFormat[i] === '#') {
|
|
87
|
+
if (position < cleanedVal.length) {
|
|
88
|
+
formattedVal += cleanedVal[position];
|
|
89
|
+
position += 1;
|
|
90
|
+
} else {
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
formattedVal += cardFormat[i];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return formattedVal;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleValueChange = (values: NumberFormatValues) => {
|
|
102
|
+
const cleanedVal = values.value.replace(/\s+/g, '');
|
|
103
|
+
const cardFormat = getCardFormat(cleanedVal);
|
|
104
|
+
const maxLength = cardFormat.replace(/[^#]/g, '').length;
|
|
105
|
+
const truncatedVal = cleanedVal.slice(0, maxLength);
|
|
106
|
+
|
|
107
|
+
setInputValue(truncatedVal);
|
|
108
|
+
if (onChange) {
|
|
109
|
+
onChange(truncatedVal);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<TextField
|
|
115
|
+
className={cn('flex flex-col w-full text-slate-900', className)}
|
|
116
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
117
|
+
{...props}
|
|
118
|
+
>
|
|
119
|
+
<Label className={cn('flex text-slate-500 text-sm', classNames?.label)}>
|
|
120
|
+
{label}
|
|
121
|
+
</Label>
|
|
122
|
+
<NumberFormatBase
|
|
123
|
+
value={inputValue}
|
|
124
|
+
format={format}
|
|
125
|
+
customInput={AriaInput}
|
|
126
|
+
className={cn(classNames?.input)}
|
|
127
|
+
placeholder={placeholder}
|
|
128
|
+
onValueChange={handleValueChange}
|
|
129
|
+
/>
|
|
130
|
+
{description && (
|
|
131
|
+
<Text
|
|
132
|
+
className={cn('flex text-slate-500 text-sm', classNames?.description)}
|
|
133
|
+
slot="description"
|
|
134
|
+
>
|
|
135
|
+
{description}
|
|
136
|
+
</Text>
|
|
137
|
+
)}
|
|
138
|
+
<FieldError
|
|
139
|
+
className={cn('flex text-red-500 text-sm', classNames?.error)}
|
|
140
|
+
>
|
|
141
|
+
{errorMessage}
|
|
142
|
+
</FieldError>
|
|
143
|
+
</TextField>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export default CreditCardInput;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TextFieldProps, ValidationResult } from 'react-aria-components';
|
|
2
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
3
|
+
|
|
4
|
+
export interface CreditCardInputProps extends TextFieldProps {
|
|
5
|
+
label?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
errorMessage?: string | ((validation: ValidationResult) => string);
|
|
8
|
+
placeholder?: string;
|
|
9
|
+
value?: string;
|
|
10
|
+
onChange?: (value: string) => void;
|
|
11
|
+
classNames?: SlotsToClasses<'label' | 'input' | 'description' | 'error'>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DateField,
|
|
5
|
+
DateInput as AriaDateInput,
|
|
6
|
+
DateSegment,
|
|
7
|
+
FieldError,
|
|
8
|
+
Label,
|
|
9
|
+
Text,
|
|
10
|
+
} from 'react-aria-components';
|
|
11
|
+
import { cn } from '../../utils/cn';
|
|
12
|
+
import type { DateInputProps } from './DateInput.types';
|
|
13
|
+
|
|
14
|
+
export function DateInput({
|
|
15
|
+
label,
|
|
16
|
+
description,
|
|
17
|
+
errorMessage,
|
|
18
|
+
className,
|
|
19
|
+
classNames,
|
|
20
|
+
...props
|
|
21
|
+
}: DateInputProps) {
|
|
22
|
+
return (
|
|
23
|
+
<DateField
|
|
24
|
+
className={cn('text-slate-900', className)}
|
|
25
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<Label className={cn('flex text-slate-500 text-xs', classNames?.label)}>
|
|
29
|
+
{label}
|
|
30
|
+
</Label>
|
|
31
|
+
<AriaDateInput
|
|
32
|
+
className={cn(
|
|
33
|
+
'flex items-center',
|
|
34
|
+
'border-solid border border-slate-300 rounded-md',
|
|
35
|
+
'text-sm text-slate-900',
|
|
36
|
+
'h-10 px-1 py-0 m-0 w-full',
|
|
37
|
+
'bg-white',
|
|
38
|
+
'transition-all duration-200 ease-in-out',
|
|
39
|
+
'hover:border-slate-400',
|
|
40
|
+
'focus-within:outline-2 focus-within:outline focus-within:outline-slate-200 focus-within:border-slate-400',
|
|
41
|
+
'disabled:border-slate-200 disabled:bg-slate-100',
|
|
42
|
+
'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
|
|
43
|
+
'invalid:hover:border-red-600 invalid:focus-within:border-red-600 invalid:focus-within:outline-red-200',
|
|
44
|
+
classNames?.input
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{(segment) => (
|
|
48
|
+
<DateSegment
|
|
49
|
+
segment={segment}
|
|
50
|
+
className={cn(
|
|
51
|
+
'p-1 tabular-nums text-end text-slate-900',
|
|
52
|
+
'invalid:text-red-500',
|
|
53
|
+
'disabled:cursor-default disabled:select-none disabled:text-slate-400',
|
|
54
|
+
'focus:text-slate-900 focus:bg-slate-200 focus:outline-0 focus:rounded-md focus:caret-transparent focus:invalid:bg-red-500 focus:invalid:text-white',
|
|
55
|
+
'type-literal:p-0',
|
|
56
|
+
'data-[placeholder]:text-slate-400 data-[placeholder]:invalid:text-red-500 data-[placeholder]:invalid:focus:text-white',
|
|
57
|
+
'aria-[readonly]:focus-visible:outline aria-[readonly]:focus-visible:outline-slate-500 aria-[readonly]:focus-visible:outline-1',
|
|
58
|
+
'aria-[readonly]:focus:outline aria-[readonly]:focus:outline-slate-500 aria-[readonly]:focus:bg-transparent aria-[readonly]:focus:outline-2',
|
|
59
|
+
classNames?.segment
|
|
60
|
+
)}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
</AriaDateInput>
|
|
64
|
+
{description && (
|
|
65
|
+
<Text
|
|
66
|
+
className={cn('flex text-slate-500 text-sm', classNames?.description)}
|
|
67
|
+
slot="description"
|
|
68
|
+
>
|
|
69
|
+
{description}
|
|
70
|
+
</Text>
|
|
71
|
+
)}
|
|
72
|
+
<FieldError
|
|
73
|
+
className={cn('flex text-red-500 text-sm', classNames?.error)}
|
|
74
|
+
>
|
|
75
|
+
{errorMessage}
|
|
76
|
+
</FieldError>
|
|
77
|
+
</DateField>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default DateInput;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DateFieldProps,
|
|
3
|
+
DateValue,
|
|
4
|
+
ValidationResult,
|
|
5
|
+
} from 'react-aria-components';
|
|
6
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
7
|
+
|
|
8
|
+
export interface DateInputProps extends DateFieldProps<DateValue> {
|
|
9
|
+
label?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
errorMessage?: string | ((validation: ValidationResult) => string);
|
|
12
|
+
classNames?: SlotsToClasses<
|
|
13
|
+
'label' | 'input' | 'description' | 'error' | 'segment'
|
|
14
|
+
>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { DateTimeFormatProps } from './DateTimeFormat.types';
|
|
4
|
+
|
|
5
|
+
export function DateTimeFormat({
|
|
6
|
+
locale = 'en-US',
|
|
7
|
+
fallback = '-',
|
|
8
|
+
children,
|
|
9
|
+
...options
|
|
10
|
+
}: DateTimeFormatProps) {
|
|
11
|
+
return children && !Number.isNaN(new Date(children).valueOf())
|
|
12
|
+
? new Intl.DateTimeFormat(locale, options).format(new Date(children))
|
|
13
|
+
: fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default DateTimeFormat;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import type { Ref, ReactNode } from 'react';
|
|
5
|
+
import { Dialog as AriaDialog } from 'react-aria-components';
|
|
6
|
+
import { motion } from 'framer-motion';
|
|
7
|
+
import { cn } from '../../utils/cn';
|
|
8
|
+
import type { DialogProps } from './Dialog.types';
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const ForwardedDialog = forwardRef<HTMLElement, any>(
|
|
12
|
+
({ style, animationVariants, ...props }, ref: Ref<HTMLElement>) => {
|
|
13
|
+
// Separate the dynamic style logic
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
|
15
|
+
const ariaStyle = typeof style === 'function' ? style(props) : style;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
// Pass only static styles to framer-motion
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, react/jsx-props-no-spreading
|
|
20
|
+
<AriaDialog {...props} ref={ref} style={ariaStyle} />
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const MotionDialog = motion.create(ForwardedDialog);
|
|
26
|
+
|
|
27
|
+
export function Dialog({
|
|
28
|
+
className,
|
|
29
|
+
children,
|
|
30
|
+
animationVariants,
|
|
31
|
+
...props
|
|
32
|
+
}: DialogProps) {
|
|
33
|
+
return (
|
|
34
|
+
<MotionDialog
|
|
35
|
+
className={cn(
|
|
36
|
+
'p-8 outline-0 max-w-max w-screen absolute top-2/4 left-2/4',
|
|
37
|
+
'shadow-[0_8px_24px_rgba(0,0,0,0.1)] rounded-lg bg-white border border-solid border-slate-400',
|
|
38
|
+
'transform -translate-x-1/2 -translate-y-1/2',
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
42
|
+
{...props}
|
|
43
|
+
variants={
|
|
44
|
+
animationVariants || {
|
|
45
|
+
hidden: {
|
|
46
|
+
transform: 'translate(-50%, -50%) scale(0.8)',
|
|
47
|
+
transition: {
|
|
48
|
+
ease: 'backIn',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
visible: {
|
|
52
|
+
transform: 'translate(-50%, -50%) scale(1)',
|
|
53
|
+
transition: {
|
|
54
|
+
ease: 'backOut',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
>
|
|
60
|
+
{children as ReactNode}
|
|
61
|
+
</MotionDialog>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default Dialog;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Key } from 'react';
|
|
2
|
+
import type { DialogProps as UIDialogProps } from 'react-aria-components';
|
|
3
|
+
import { MotionStyle, Variants } from 'framer-motion';
|
|
4
|
+
|
|
5
|
+
export type DialogProps = UIDialogProps & {
|
|
6
|
+
key?: Key;
|
|
7
|
+
style?: MotionStyle;
|
|
8
|
+
animationVariants?: Variants;
|
|
9
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useState, useMemo, useContext } from 'react';
|
|
4
|
+
import type { Dispatch, SetStateAction } from 'react';
|
|
5
|
+
import { DialogTrigger as UIDialogTrigger } from 'react-aria-components';
|
|
6
|
+
import type { DialogTriggerProps } from 'react-aria-components';
|
|
7
|
+
import type { DriverAnimationState } from './DialogTrigger.types';
|
|
8
|
+
|
|
9
|
+
const DialogTriggerContext = createContext<{
|
|
10
|
+
animation: DriverAnimationState;
|
|
11
|
+
setAnimation: Dispatch<SetStateAction<DriverAnimationState>>;
|
|
12
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
13
|
+
}>({
|
|
14
|
+
animation: 'unmounted',
|
|
15
|
+
setAnimation: () => {},
|
|
16
|
+
onOpenChange: () => {},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const useDialogTrigger = () => useContext(DialogTriggerContext);
|
|
20
|
+
|
|
21
|
+
export function DialogTrigger(props: DialogTriggerProps) {
|
|
22
|
+
const [animation, setAnimation] = useState<DriverAnimationState>('unmounted');
|
|
23
|
+
|
|
24
|
+
const onOpenChange = (isOpen: boolean) => {
|
|
25
|
+
setAnimation(isOpen ? 'visible' : 'hidden');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const value = useMemo(
|
|
29
|
+
() => ({
|
|
30
|
+
animation,
|
|
31
|
+
setAnimation,
|
|
32
|
+
onOpenChange,
|
|
33
|
+
}),
|
|
34
|
+
[animation]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<DialogTriggerContext.Provider value={value}>
|
|
39
|
+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
40
|
+
<UIDialogTrigger {...props} onOpenChange={onOpenChange} />
|
|
41
|
+
</DialogTriggerContext.Provider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default DialogTrigger;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DialogTriggerProps as UIDialogTriggerProps } from 'react-aria-components';
|
|
2
|
+
|
|
3
|
+
/** @todo this feels like it should be renamed, maybe moved to modal directly */
|
|
4
|
+
export type DriverAnimationState = 'unmounted' | 'visible' | 'hidden';
|
|
5
|
+
|
|
6
|
+
export type DialogTriggerProps = UIDialogTriggerProps;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { SeparatorProps } from 'react-aria';
|
|
3
|
+
import { Divider } from './Divider';
|
|
4
|
+
|
|
5
|
+
type Story = StoryObj<typeof Divider>;
|
|
6
|
+
|
|
7
|
+
function Component(props: SeparatorProps) {
|
|
8
|
+
const { orientation } = props;
|
|
9
|
+
const display = orientation === 'vertical' ? 'flex' : 'block';
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div style={{ display }}>
|
|
13
|
+
<div>Lorem ipsum dolor sit amet consectetur, adipisicing elit.</div>
|
|
14
|
+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
15
|
+
<Divider {...props} />
|
|
16
|
+
<div>Lorem ipsum dolor sit amet consectetur, adipisicing elit.</div>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const meta: Meta<typeof Divider> = {
|
|
22
|
+
component: Component,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default meta;
|
|
26
|
+
|
|
27
|
+
export const Example: Story = {
|
|
28
|
+
argTypes: {
|
|
29
|
+
orientation: {
|
|
30
|
+
options: ['horizontal', 'vertical'],
|
|
31
|
+
control: 'select',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
args: {
|
|
35
|
+
orientation: 'horizontal',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSeparator } from 'react-aria';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import type { DividerProps } from './Divider.types';
|
|
6
|
+
|
|
7
|
+
export function Divider({
|
|
8
|
+
orientation = 'horizontal',
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: DividerProps) {
|
|
12
|
+
const { separatorProps } = useSeparator({ ...props, orientation });
|
|
13
|
+
const combinedProps = { ...props, ...separatorProps };
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
18
|
+
{...combinedProps}
|
|
19
|
+
className={cn(
|
|
20
|
+
'bg-slate-200',
|
|
21
|
+
['w-full', 'h-px', 'my-4', 'mx-0'],
|
|
22
|
+
[
|
|
23
|
+
'aria-[orientation=vertical]:w-px',
|
|
24
|
+
'aria-[orientation=vertical]:h-auto',
|
|
25
|
+
'aria-[orientation=vertical]:my-0',
|
|
26
|
+
'aria-[orientation=vertical]:mx-4',
|
|
27
|
+
],
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default Divider;
|