@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,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Switch as AriaSwitch } from 'react-aria-components';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
6
|
+
import type { SwitchProps, SwitchRenderProps } from './Switch.types';
|
|
7
|
+
|
|
8
|
+
export function Switch({
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
classNames,
|
|
12
|
+
...props
|
|
13
|
+
}: SwitchProps & {
|
|
14
|
+
classNames?: SlotsToClasses<'indicator'>;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<AriaSwitch
|
|
18
|
+
className={cn(
|
|
19
|
+
'flex items-center gap-2 text-sm text-slate-500 forced-color-adjust-none group',
|
|
20
|
+
'',
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
{(renderProps: SwitchRenderProps) => {
|
|
27
|
+
const { isSelected, isHovered, isFocused, isPressed, isDisabled } =
|
|
28
|
+
renderProps;
|
|
29
|
+
return (
|
|
30
|
+
<>
|
|
31
|
+
<div
|
|
32
|
+
className={cn(
|
|
33
|
+
'w-12 h-7 bg-transparent border-solid border border-slate-300 rounded-3xl transition-all duration-200 ease-in-out',
|
|
34
|
+
'before:block before:w-5 before:h-5 before:m-[3px] before:bg-slate-300 before:rounded-full',
|
|
35
|
+
'before:transition-all before:duration-200 before:ease-in-out',
|
|
36
|
+
[
|
|
37
|
+
isHovered && 'border-slate-400',
|
|
38
|
+
isSelected && [
|
|
39
|
+
' bg-slate-300 before:translate-x-full before:bg-white',
|
|
40
|
+
(isHovered || isFocused) && 'border-slate-400',
|
|
41
|
+
],
|
|
42
|
+
isFocused && ['outline outline-2 outline-slate-200'],
|
|
43
|
+
isPressed && 'before:bg-slate-400',
|
|
44
|
+
isDisabled && 'bg-slate-100',
|
|
45
|
+
classNames?.indicator,
|
|
46
|
+
]
|
|
47
|
+
)}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
{typeof children === 'function'
|
|
51
|
+
? children({
|
|
52
|
+
defaultChildren: undefined,
|
|
53
|
+
...renderProps,
|
|
54
|
+
})
|
|
55
|
+
: children}
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
}}
|
|
59
|
+
</AriaSwitch>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default Switch;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/cn';
|
|
4
|
+
import type { TableProps } from './Table.types';
|
|
5
|
+
|
|
6
|
+
export function Table<T>({
|
|
7
|
+
columns,
|
|
8
|
+
rows,
|
|
9
|
+
onRowClick = () => {},
|
|
10
|
+
classNames,
|
|
11
|
+
}: TableProps<T>) {
|
|
12
|
+
return (
|
|
13
|
+
<table className={cn(classNames?.table)}>
|
|
14
|
+
<thead className={cn(classNames?.thead)}>
|
|
15
|
+
<tr className={cn(classNames?.theadTr)}>
|
|
16
|
+
{columns.map((column) => (
|
|
17
|
+
<th
|
|
18
|
+
key={String(column.id)}
|
|
19
|
+
className={cn(classNames?.th)}
|
|
20
|
+
data-th-header={column.label.toLowerCase()}
|
|
21
|
+
>
|
|
22
|
+
{column.label}
|
|
23
|
+
</th>
|
|
24
|
+
))}
|
|
25
|
+
</tr>
|
|
26
|
+
</thead>
|
|
27
|
+
<tbody className={cn(classNames?.tbody)}>
|
|
28
|
+
{Object.entries(rows).map(([key, row]) => (
|
|
29
|
+
<tr
|
|
30
|
+
key={key}
|
|
31
|
+
onClick={() => onRowClick(row)}
|
|
32
|
+
className={cn(classNames?.tbodyTr)}
|
|
33
|
+
>
|
|
34
|
+
{columns.map((column) => (
|
|
35
|
+
<td
|
|
36
|
+
key={`${String(column.id)}_${key}`}
|
|
37
|
+
data-th={column.label.toLowerCase()}
|
|
38
|
+
className={cn(classNames?.td)}
|
|
39
|
+
>
|
|
40
|
+
{column.renderer
|
|
41
|
+
? column.renderer(row)
|
|
42
|
+
: String(row[column.id as keyof T])}
|
|
43
|
+
</td>
|
|
44
|
+
))}
|
|
45
|
+
</tr>
|
|
46
|
+
))}
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default Table;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
3
|
+
|
|
4
|
+
export type TableProps<T> = {
|
|
5
|
+
columns: (
|
|
6
|
+
| {
|
|
7
|
+
id: keyof T;
|
|
8
|
+
label: string;
|
|
9
|
+
renderer?: (props: T) => ReactNode;
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
renderer: (props: T) => ReactNode;
|
|
15
|
+
}
|
|
16
|
+
)[];
|
|
17
|
+
rows: T[];
|
|
18
|
+
onRowClick?: (row: T) => void;
|
|
19
|
+
classNames?: SlotsToClasses<
|
|
20
|
+
'table' | 'thead' | 'tbody' | 'theadTr' | 'tbodyTr' | 'th' | 'td'
|
|
21
|
+
>;
|
|
22
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import { Tab as AriaTab } from 'react-aria-components';
|
|
5
|
+
import { motion } from 'framer-motion';
|
|
6
|
+
import { cn } from '../../utils/cn';
|
|
7
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
8
|
+
import type { TabProps, TabRenderProps } from './Tab.types';
|
|
9
|
+
import type { TabsProps } from './Tabs.types';
|
|
10
|
+
import { TabsOrientationContext } from './Tabs.context';
|
|
11
|
+
|
|
12
|
+
export function BaseTab({
|
|
13
|
+
orientation,
|
|
14
|
+
variant = 'base',
|
|
15
|
+
className,
|
|
16
|
+
children,
|
|
17
|
+
...props
|
|
18
|
+
}: TabProps & { orientation: TabsProps['orientation'] }) {
|
|
19
|
+
return (
|
|
20
|
+
<AriaTab
|
|
21
|
+
className={cn(
|
|
22
|
+
'flex items-center relative z-10',
|
|
23
|
+
'text-sm cursor-pointer forced-color-adjust-none',
|
|
24
|
+
'transition-all duration-300 ease-in-out',
|
|
25
|
+
'justify-center',
|
|
26
|
+
'disabled:opacity-50',
|
|
27
|
+
'selected:outline-0 hover:outline-0 hover:text-slate-500',
|
|
28
|
+
variant === 'underline' &&
|
|
29
|
+
orientation === 'vertical' &&
|
|
30
|
+
'justify-start',
|
|
31
|
+
'py-2 px-3',
|
|
32
|
+
variant === 'underline' && orientation === 'horizontal' && 'p-3',
|
|
33
|
+
variant === 'underline' &&
|
|
34
|
+
orientation === 'vertical' &&
|
|
35
|
+
'py-3 pr-3 pl-0',
|
|
36
|
+
(variant === 'underline' ||
|
|
37
|
+
variant === 'outline' ||
|
|
38
|
+
variant === 'enclosed') &&
|
|
39
|
+
'text-slate-800',
|
|
40
|
+
variant === 'solid' && 'text-slate-400',
|
|
41
|
+
(variant === 'underline' || variant === 'outline') &&
|
|
42
|
+
'selected:text-slate-800',
|
|
43
|
+
(variant === 'solid' || variant === 'enclosed') &&
|
|
44
|
+
'selected:text-slate-100',
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</AriaTab>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function Tab({
|
|
56
|
+
variant = 'base',
|
|
57
|
+
children,
|
|
58
|
+
className,
|
|
59
|
+
classNames,
|
|
60
|
+
...props
|
|
61
|
+
}: TabProps & {
|
|
62
|
+
classNames?: SlotsToClasses<'overflow'>;
|
|
63
|
+
}) {
|
|
64
|
+
const orientation = useContext(TabsOrientationContext);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<BaseTab
|
|
68
|
+
orientation={orientation}
|
|
69
|
+
variant={variant}
|
|
70
|
+
className={className}
|
|
71
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{(renderProps: TabRenderProps) => (
|
|
75
|
+
<>
|
|
76
|
+
{typeof children === 'function'
|
|
77
|
+
? children({
|
|
78
|
+
defaultChildren: undefined,
|
|
79
|
+
...renderProps,
|
|
80
|
+
})
|
|
81
|
+
: children}
|
|
82
|
+
|
|
83
|
+
{(renderProps.isFocusVisible || renderProps.isSelected) && (
|
|
84
|
+
<motion.span
|
|
85
|
+
className={cn(
|
|
86
|
+
'absolute z-0',
|
|
87
|
+
variant === 'solid' &&
|
|
88
|
+
'inset-0 rounded-lg bg-slate-600 mix-blend-color',
|
|
89
|
+
variant === 'outline' &&
|
|
90
|
+
'inset-0 rounded-lg border-solid border-2 border-slate-800',
|
|
91
|
+
variant === 'underline' &&
|
|
92
|
+
'rounded-lg bg-slate-800 mix-blend-color bottom-0',
|
|
93
|
+
variant === 'underline' &&
|
|
94
|
+
orientation === 'horizontal' &&
|
|
95
|
+
'left-0 w-full h-px',
|
|
96
|
+
variant === 'underline' &&
|
|
97
|
+
orientation === 'vertical' &&
|
|
98
|
+
'right-0 w-px h-full',
|
|
99
|
+
variant === 'enclosed' &&
|
|
100
|
+
'inset-0 border-solid border-1 border-slate-800 -mb-px border-b-0 rounded-t-lg rounded-b-none bg-white mix-blend-difference',
|
|
101
|
+
variant === 'base' && 'hidden',
|
|
102
|
+
classNames?.overflow
|
|
103
|
+
)}
|
|
104
|
+
layoutId={variant}
|
|
105
|
+
transition={{
|
|
106
|
+
type: 'spring',
|
|
107
|
+
bounce: 0.2,
|
|
108
|
+
duration: 0.75,
|
|
109
|
+
}}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
</>
|
|
113
|
+
)}
|
|
114
|
+
</BaseTab>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export default Tab;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TabProps as AriaTabProps,
|
|
3
|
+
TabRenderProps as AriaTabRenderProps,
|
|
4
|
+
} from 'react-aria-components';
|
|
5
|
+
|
|
6
|
+
export interface TabProps extends AriaTabProps {
|
|
7
|
+
variant?: 'solid' | 'outline' | 'underline' | 'enclosed' | 'base';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type TabRenderProps = AriaTabRenderProps;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import { TabList as AriaTabList } from 'react-aria-components';
|
|
5
|
+
import { cn } from '../../utils/cn';
|
|
6
|
+
import { BaseTab } from './Tab';
|
|
7
|
+
import type { TabListProps } from './TabList.types';
|
|
8
|
+
import { TabsOrientationContext } from './Tabs.context';
|
|
9
|
+
|
|
10
|
+
export function TabList({
|
|
11
|
+
items,
|
|
12
|
+
children,
|
|
13
|
+
variant = 'base',
|
|
14
|
+
className,
|
|
15
|
+
...props
|
|
16
|
+
}: TabListProps) {
|
|
17
|
+
const orientation = useContext(TabsOrientationContext);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<AriaTabList
|
|
21
|
+
className={cn(
|
|
22
|
+
'flex flex-row w-full flex-wrap gap-2 relative',
|
|
23
|
+
orientation === 'vertical' &&
|
|
24
|
+
'orientation-vertical:max-w-max orientation-vertical:flex-col orientation-vertical:justify-start',
|
|
25
|
+
[
|
|
26
|
+
variant === 'solid' && ['bg-slate-200 rounded-xl p-1'],
|
|
27
|
+
variant === 'outline' && [
|
|
28
|
+
'border-solid border-2 border-slate-200 rounded-xl p-1',
|
|
29
|
+
],
|
|
30
|
+
variant === 'underline' &&
|
|
31
|
+
orientation === 'vertical' && [
|
|
32
|
+
'before:block before:absolute before:inset-y-0 before:right-0 before:w-px before:bg-slate-200',
|
|
33
|
+
],
|
|
34
|
+
variant === 'underline' &&
|
|
35
|
+
orientation === 'horizontal' && [
|
|
36
|
+
'before:block before:absolute before:inset-x-0 before:bottom-0 before:h-px before:bg-slate-200',
|
|
37
|
+
],
|
|
38
|
+
],
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
items={items}
|
|
42
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
{children ||
|
|
46
|
+
((item) => <BaseTab orientation={orientation}>{item.title}</BaseTab>)}
|
|
47
|
+
</AriaTabList>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default TabList;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TabListProps as AriaTabListProps } from 'react-aria-components';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export type TabItemType = {
|
|
5
|
+
id: number | string;
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
content: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface TabListProps extends AriaTabListProps<TabItemType> {
|
|
11
|
+
variant?: 'solid' | 'outline' | 'underline' | 'base';
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { TabPanel as AriaTabPanel } from 'react-aria-components';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import type { TabPanelProps } from './TabPanel.types';
|
|
6
|
+
|
|
7
|
+
export function TabPanel({ children, className, ...props }: TabPanelProps) {
|
|
8
|
+
return (
|
|
9
|
+
<AriaTabPanel
|
|
10
|
+
className={cn('flex grow py-4 px-0', className)}
|
|
11
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
12
|
+
{...props}
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</AriaTabPanel>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default TabPanel;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Tabs as AriaTabs } from 'react-aria-components';
|
|
4
|
+
import { LayoutGroup } from 'framer-motion';
|
|
5
|
+
import { useId } from 'react';
|
|
6
|
+
import { cn } from '../../utils/cn';
|
|
7
|
+
import type { TabsProps } from './Tabs.types';
|
|
8
|
+
import { TabList } from './TabList';
|
|
9
|
+
import { Tab } from './Tab';
|
|
10
|
+
import { TabPanel } from './TabPanel';
|
|
11
|
+
import { TabsOrientationContext } from './Tabs.context';
|
|
12
|
+
|
|
13
|
+
export function Tabs({ children, className, ...props }: TabsProps) {
|
|
14
|
+
const id = useId();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<TabsOrientationContext.Provider value={props.orientation ?? 'horizontal'}>
|
|
18
|
+
<LayoutGroup id={id}>
|
|
19
|
+
<AriaTabs
|
|
20
|
+
className={cn(
|
|
21
|
+
'flex flex-col w-full px-0 py-2',
|
|
22
|
+
props.orientation === 'vertical' && 'orientation-vertical:flex-row',
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</AriaTabs>
|
|
30
|
+
</LayoutGroup>
|
|
31
|
+
</TabsOrientationContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Tabs.List = TabList;
|
|
36
|
+
Tabs.Tab = Tab;
|
|
37
|
+
Tabs.Panel = TabPanel;
|
|
38
|
+
|
|
39
|
+
export default Tabs;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BaseTab, Tab } from './Tab';
|
|
2
|
+
export { TabList } from './TabList';
|
|
3
|
+
export { TabPanel } from './TabPanel';
|
|
4
|
+
export { Tabs } from './Tabs';
|
|
5
|
+
export { TabsOrientationContext } from './Tabs.context';
|
|
6
|
+
export type { TabProps, TabRenderProps } from './Tab.types';
|
|
7
|
+
export type { TabItemType, TabListProps } from './TabList.types';
|
|
8
|
+
export type { TabPanelProps } from './TabPanel.types';
|
|
9
|
+
export type { TabsProps } from './Tabs.types';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Form } from 'react-aria-components';
|
|
3
|
+
import { action } from '@storybook/addon-actions';
|
|
4
|
+
import { TimeInput } from './TimeInput';
|
|
5
|
+
import { TimeInputProps } from './TimeInput.types';
|
|
6
|
+
import { Button } from '../Button/Button';
|
|
7
|
+
|
|
8
|
+
type Story = StoryObj<typeof TimeInput>;
|
|
9
|
+
|
|
10
|
+
function Component({ children, ...props }: TimeInputProps) {
|
|
11
|
+
return (
|
|
12
|
+
<Form
|
|
13
|
+
style={{
|
|
14
|
+
display: 'flex',
|
|
15
|
+
flexDirection: 'column',
|
|
16
|
+
gap: '0.5rem',
|
|
17
|
+
width: '20rem',
|
|
18
|
+
}}
|
|
19
|
+
onSubmit={(e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
action('onPress');
|
|
22
|
+
}}
|
|
23
|
+
>
|
|
24
|
+
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
|
25
|
+
<TimeInput {...props}>{children}</TimeInput>
|
|
26
|
+
<Button size="sm" type="submit">
|
|
27
|
+
Submit
|
|
28
|
+
</Button>
|
|
29
|
+
</Form>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const meta: Meta<typeof TimeInput> = {
|
|
34
|
+
component: Component,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
|
|
39
|
+
export const Base: Story = {
|
|
40
|
+
argTypes: {
|
|
41
|
+
label: {
|
|
42
|
+
control: 'text',
|
|
43
|
+
description: 'The content to display as label',
|
|
44
|
+
},
|
|
45
|
+
description: {
|
|
46
|
+
control: 'text',
|
|
47
|
+
description: 'The content to display as description',
|
|
48
|
+
},
|
|
49
|
+
isDisabled: {
|
|
50
|
+
control: 'boolean',
|
|
51
|
+
description: 'Whether the input is disabled.',
|
|
52
|
+
},
|
|
53
|
+
isInvalid: {
|
|
54
|
+
control: 'boolean',
|
|
55
|
+
description: 'Whether the input value is invalid.',
|
|
56
|
+
},
|
|
57
|
+
isRequired: {
|
|
58
|
+
control: 'boolean',
|
|
59
|
+
description:
|
|
60
|
+
'Whether user input is required on the input before form submission.',
|
|
61
|
+
},
|
|
62
|
+
isReadOnly: {
|
|
63
|
+
control: 'boolean',
|
|
64
|
+
description:
|
|
65
|
+
'Whether the input can be selected but not changed by the user.',
|
|
66
|
+
},
|
|
67
|
+
hideTimeZone: {
|
|
68
|
+
control: 'boolean',
|
|
69
|
+
description: 'Whether to hide the time zone abbreviation.',
|
|
70
|
+
},
|
|
71
|
+
shouldForceLeadingZeros: {
|
|
72
|
+
control: 'boolean',
|
|
73
|
+
description:
|
|
74
|
+
'Whether to always show leading zeros in the hour field. By default, this is determined by the user&aposs locale.',
|
|
75
|
+
},
|
|
76
|
+
hourCycle: {
|
|
77
|
+
options: [12, 24],
|
|
78
|
+
control: { type: 'inline-radio' },
|
|
79
|
+
description:
|
|
80
|
+
'Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale.',
|
|
81
|
+
},
|
|
82
|
+
granularity: {
|
|
83
|
+
options: ['hour', 'minute', 'second'],
|
|
84
|
+
control: { type: 'inline-radio' },
|
|
85
|
+
description:
|
|
86
|
+
'Determines the smallest unit that is displayed in the time picker',
|
|
87
|
+
table: {
|
|
88
|
+
defaultValue: { summary: 'minute' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
errorMessage: {
|
|
92
|
+
control: 'text',
|
|
93
|
+
description: 'The current error messages for the input if it is invalid.',
|
|
94
|
+
},
|
|
95
|
+
style: {
|
|
96
|
+
control: 'object',
|
|
97
|
+
description:
|
|
98
|
+
'The inline style for the element. A function may be provided to compute the style based on component state',
|
|
99
|
+
},
|
|
100
|
+
className: {
|
|
101
|
+
control: 'text',
|
|
102
|
+
description: 'The CSS className for the element.',
|
|
103
|
+
},
|
|
104
|
+
autoFocus: {
|
|
105
|
+
control: 'boolean',
|
|
106
|
+
description: 'Whether the element should receive focus on render.',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
args: {
|
|
110
|
+
label: '',
|
|
111
|
+
description: '',
|
|
112
|
+
isDisabled: false,
|
|
113
|
+
isInvalid: false,
|
|
114
|
+
isRequired: false,
|
|
115
|
+
isReadOnly: false,
|
|
116
|
+
errorMessage: 'Please fill out this field.',
|
|
117
|
+
shouldForceLeadingZeros: false,
|
|
118
|
+
hideTimeZone: false,
|
|
119
|
+
hourCycle: 24,
|
|
120
|
+
granularity: 'minute',
|
|
121
|
+
autoFocus: false,
|
|
122
|
+
style: {},
|
|
123
|
+
className: '',
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DateInput,
|
|
5
|
+
DateSegment,
|
|
6
|
+
FieldError,
|
|
7
|
+
Label,
|
|
8
|
+
Text,
|
|
9
|
+
TimeField,
|
|
10
|
+
} from 'react-aria-components';
|
|
11
|
+
import { TimeInputProps } from './TimeInput.types';
|
|
12
|
+
import { cn } from '../../utils/cn';
|
|
13
|
+
|
|
14
|
+
export function TimeInput({
|
|
15
|
+
label,
|
|
16
|
+
description,
|
|
17
|
+
errorMessage,
|
|
18
|
+
className,
|
|
19
|
+
classNames,
|
|
20
|
+
...props
|
|
21
|
+
}: TimeInputProps) {
|
|
22
|
+
return (
|
|
23
|
+
<TimeField
|
|
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-sm', classNames?.label)}>
|
|
29
|
+
{label}
|
|
30
|
+
</Label>
|
|
31
|
+
<DateInput
|
|
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.5',
|
|
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
|
+
</DateInput>
|
|
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
|
+
</TimeField>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default TimeInput;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TimeFieldProps,
|
|
3
|
+
TimeValue,
|
|
4
|
+
ValidationResult,
|
|
5
|
+
} from 'react-aria-components';
|
|
6
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
7
|
+
|
|
8
|
+
export interface TimeInputProps extends TimeFieldProps<TimeValue> {
|
|
9
|
+
label?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
errorMessage?: string | ((validation: ValidationResult) => string);
|
|
12
|
+
classNames?: SlotsToClasses<
|
|
13
|
+
'label' | 'input' | 'description' | 'error' | 'segment'
|
|
14
|
+
>;
|
|
15
|
+
}
|