@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,89 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { action } from '@storybook/addon-actions';
|
|
3
|
+
import { ToggleButtonProps } from 'react-aria-components';
|
|
4
|
+
import { ToggleButton } from './ToggleButton';
|
|
5
|
+
|
|
6
|
+
type Story = StoryObj<typeof ToggleButton>;
|
|
7
|
+
|
|
8
|
+
function Component({ children, ...props }: ToggleButtonProps) {
|
|
9
|
+
return <ToggleButton {...props}>{children}</ToggleButton>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const meta: Meta<typeof ToggleButton> = {
|
|
13
|
+
component: Component,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
export const Example: Story = {
|
|
19
|
+
argTypes: {
|
|
20
|
+
variant: {
|
|
21
|
+
options: ['solid', 'outline', 'ghost'],
|
|
22
|
+
control: 'select',
|
|
23
|
+
},
|
|
24
|
+
size: {
|
|
25
|
+
options: ['sm', 'md', 'lg'],
|
|
26
|
+
control: 'select',
|
|
27
|
+
},
|
|
28
|
+
isSelected: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
},
|
|
31
|
+
isDisabled: {
|
|
32
|
+
control: 'boolean',
|
|
33
|
+
},
|
|
34
|
+
defaultSelected: {
|
|
35
|
+
control: 'boolean',
|
|
36
|
+
},
|
|
37
|
+
autoFocus: {
|
|
38
|
+
control: 'boolean',
|
|
39
|
+
},
|
|
40
|
+
type: {
|
|
41
|
+
options: ['button', 'submit', 'reset'],
|
|
42
|
+
control: { type: 'radio' },
|
|
43
|
+
},
|
|
44
|
+
excludeFromTabOrder: {
|
|
45
|
+
control: 'boolean',
|
|
46
|
+
},
|
|
47
|
+
children: {
|
|
48
|
+
control: 'text',
|
|
49
|
+
},
|
|
50
|
+
className: {
|
|
51
|
+
control: 'object',
|
|
52
|
+
},
|
|
53
|
+
style: {
|
|
54
|
+
control: 'object',
|
|
55
|
+
},
|
|
56
|
+
onChange: {},
|
|
57
|
+
onPress: {},
|
|
58
|
+
onPressStart: {},
|
|
59
|
+
onPressEnd: {},
|
|
60
|
+
onPressChange: {},
|
|
61
|
+
onPressUp: {},
|
|
62
|
+
onFocus: {},
|
|
63
|
+
onBlur: {},
|
|
64
|
+
onFocusChange: {},
|
|
65
|
+
onKeyDown: {},
|
|
66
|
+
onKeyUp: {},
|
|
67
|
+
},
|
|
68
|
+
args: {
|
|
69
|
+
variant: 'solid',
|
|
70
|
+
size: 'md',
|
|
71
|
+
isSelected: false,
|
|
72
|
+
isDisabled: false,
|
|
73
|
+
autoFocus: false,
|
|
74
|
+
defaultSelected: false,
|
|
75
|
+
excludeFromTabOrder: false,
|
|
76
|
+
type: 'button',
|
|
77
|
+
children: 'Press me',
|
|
78
|
+
onPress: action('onPress'),
|
|
79
|
+
onPressStart: action('onPressStart'),
|
|
80
|
+
onPressEnd: action('onPressEnd'),
|
|
81
|
+
onPressChange: action('onPressChange'),
|
|
82
|
+
onPressUp: action('onPressUp'),
|
|
83
|
+
onFocus: action('onFocus'),
|
|
84
|
+
onBlur: action('onBlur'),
|
|
85
|
+
onFocusChange: action('onFocusChange'),
|
|
86
|
+
onKeyDown: action('onKeyDown'),
|
|
87
|
+
onKeyUp: action('onKeyUp'),
|
|
88
|
+
},
|
|
89
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { ToggleButton as AriaToggleButton } from 'react-aria-components';
|
|
4
|
+
import { cn } from '../../utils/cn';
|
|
5
|
+
import { ToggleButtonProps } from './ToggleButton.types';
|
|
6
|
+
|
|
7
|
+
export function ToggleButton({
|
|
8
|
+
children,
|
|
9
|
+
size = 'md',
|
|
10
|
+
variant = 'solid',
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}: ToggleButtonProps) {
|
|
14
|
+
return (
|
|
15
|
+
<AriaToggleButton
|
|
16
|
+
className={(renderProps) => {
|
|
17
|
+
const { isDisabled } = renderProps;
|
|
18
|
+
return cn(
|
|
19
|
+
'flex items-center justify-center',
|
|
20
|
+
'font-semibold text-sm',
|
|
21
|
+
'rounded-md border-solid border',
|
|
22
|
+
'px-4',
|
|
23
|
+
'transition-all duration-200 ease-in-out',
|
|
24
|
+
'focus:outline-2 focus:outline focus:outline-slate-200 pressed:scale-95',
|
|
25
|
+
{
|
|
26
|
+
'h-8': size === 'sm',
|
|
27
|
+
'h-10': size === 'md',
|
|
28
|
+
'h-12': size === 'lg',
|
|
29
|
+
},
|
|
30
|
+
[
|
|
31
|
+
variant === 'ghost' && [
|
|
32
|
+
'bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent',
|
|
33
|
+
'border-transparent hover:border-transparent focus:border-transparent disabled:border-transparent',
|
|
34
|
+
'text-slate-900 disabled:text-slate-400',
|
|
35
|
+
isDisabled && ['disabled:text-slate-900', 'opacity-75'],
|
|
36
|
+
],
|
|
37
|
+
variant === 'outline' && [
|
|
38
|
+
'bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent',
|
|
39
|
+
'border-slate-300 hover:border-slate-400 focus:border-slate-400 disabled:border-slate-200',
|
|
40
|
+
'text-slate-900 disabled:text-slate-500',
|
|
41
|
+
isDisabled && [
|
|
42
|
+
'disabled:border-slate-300',
|
|
43
|
+
'disabled:text-slate-900',
|
|
44
|
+
'opacity-75',
|
|
45
|
+
],
|
|
46
|
+
],
|
|
47
|
+
variant === 'solid' && [
|
|
48
|
+
'bg-slate-800 hover:bg-slate-900 focus:bg-slate-900 disabled:bg-slate-900',
|
|
49
|
+
'border-slate-800 hover:border-slate-900 focus:border-slate-900 disabled:border-slate-200',
|
|
50
|
+
'text-white disabled:text-slate-400',
|
|
51
|
+
isDisabled && [
|
|
52
|
+
'disabled:border-slate-900',
|
|
53
|
+
'disabled:text-white',
|
|
54
|
+
'opacity-75',
|
|
55
|
+
],
|
|
56
|
+
],
|
|
57
|
+
],
|
|
58
|
+
typeof className === 'function' ? className(renderProps) : className
|
|
59
|
+
);
|
|
60
|
+
}}
|
|
61
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</AriaToggleButton>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default ToggleButton;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { Tooltip as UiTooltip } from '@nextui-org/tooltip';
|
|
5
|
+
import { cn } from '../../utils/cn';
|
|
6
|
+
import type { TooltipProps } from './Tooltip.types';
|
|
7
|
+
|
|
8
|
+
export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
|
9
|
+
function Tooltip(
|
|
10
|
+
{ classNames, placement, showArrow = false, ...props },
|
|
11
|
+
ref
|
|
12
|
+
) {
|
|
13
|
+
return (
|
|
14
|
+
<UiTooltip
|
|
15
|
+
classNames={{
|
|
16
|
+
base: [
|
|
17
|
+
cn([
|
|
18
|
+
'z-0 relative bg-transparent outline-none',
|
|
19
|
+
'focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-500',
|
|
20
|
+
|
|
21
|
+
// arrow styles
|
|
22
|
+
'before:absolute before:rotate-45 before:w-2.5 before:h-2.5 before:rounded-sm before:bg-slate-500 before:shadow-sm',
|
|
23
|
+
'before:hidden data-[arrow=true]:before:block',
|
|
24
|
+
'data-[placement=bottom]:before:-top-1 data-[placement=bottom]:before:left-1/2 data-[placement=bottom]:before:rotate-45 data-[placement=bottom]:before:-translate-x-1/2',
|
|
25
|
+
'data-[placement=bottom-end]:before:-top-1 data-[placement=bottom-end]:before:left-1/2 data-[placement=bottom-end]:before:rotate-45 data-[placement=bottom-end]:before:-translate-x-1/2',
|
|
26
|
+
'data-[placement=bottom-start]:before:-top-1 data-[placement=bottom-start]:before:left-1/2 data-[placement=bottom-start]:before:rotate-45 data-[placement=bottom-start]:before:-translate-x-1/2',
|
|
27
|
+
'data-[placement=left]:before:-right-1 data-[placement=left]:before:top-1/2 data-[placement=left]:before:-translate-y-1/2',
|
|
28
|
+
'data-[placement=left-end]:before:-right-1 data-[placement=left-end]:before:top-1/2 data-[placement=left-end]:before:-translate-y-1/2',
|
|
29
|
+
'data-[placement=left-start]:before:-right-1 data-[placement=left-start]:before:top-1/2 data-[placement=left-start]:before:-translate-y-1/2',
|
|
30
|
+
'data-[placement=right]:before:-left-1 data-[placement=right]:before:top-1/2 data-[placement=right]:before:-translate-y-1/2',
|
|
31
|
+
'data-[placement=right-end]:before:-left-1 data-[placement=right-end]:before:top-1/2 data-[placement=right-end]:before:-translate-y-1/2',
|
|
32
|
+
'data-[placement=right-start]:before:-left-1 data-[placement=right-start]:before:top-1/2 data-[placement=right-start]:before:-translate-y-1/2',
|
|
33
|
+
'data-[placement=top]:before:-bottom-1 data-[placement=top]:before:left-1/2 data-[placement=top]:before:rotate-45 data-[placement=top]:before:-translate-x-1/2',
|
|
34
|
+
'data-[placement=top-end]:before:-bottom-1 data-[placement=top-end]:before:left-1/2 data-[placement=top-end]:before:rotate-45 data-[placement=top-end]:before:-translate-x-1/2',
|
|
35
|
+
'data-[placement=top-start]:before:-bottom-1 data-[placement=top-start]:before:left-1/2 data-[placement=top-start]:before:rotate-45 data-[placement=top-start]:before:-translate-x-1/2',
|
|
36
|
+
|
|
37
|
+
classNames?.base,
|
|
38
|
+
]),
|
|
39
|
+
],
|
|
40
|
+
content: [
|
|
41
|
+
cn([
|
|
42
|
+
'inline-flex flex-col items-center justify-center outline-none',
|
|
43
|
+
'w-full py-1 px-2.5 z-10 box-border bg-slate-500 rounded-md shadow-sm',
|
|
44
|
+
'text-sm text-white subpixel-antialiased',
|
|
45
|
+
classNames?.content,
|
|
46
|
+
]),
|
|
47
|
+
],
|
|
48
|
+
}}
|
|
49
|
+
placement={placement}
|
|
50
|
+
showArrow={showArrow}
|
|
51
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
52
|
+
{...props}
|
|
53
|
+
ref={ref}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
export default Tooltip;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { forwardRef, useState } from 'react';
|
|
4
|
+
import type { ChangeEvent } from 'react';
|
|
5
|
+
import { motion } from 'framer-motion';
|
|
6
|
+
import Image from 'next/image';
|
|
7
|
+
|
|
8
|
+
import { FieldError } from 'react-aria-components';
|
|
9
|
+
import { cn } from '../../utils/cn';
|
|
10
|
+
import type { UploadImageProps } from './UploadImage.types';
|
|
11
|
+
import { useIsClient } from '../../hooks/useIsClient';
|
|
12
|
+
import { Skeleton } from '../Skeleton';
|
|
13
|
+
|
|
14
|
+
function UploadImageLoader({
|
|
15
|
+
hasTitle,
|
|
16
|
+
className,
|
|
17
|
+
}: {
|
|
18
|
+
hasTitle: boolean;
|
|
19
|
+
className: NonNullable<UploadImageProps['classNames']>['skeleton'];
|
|
20
|
+
}) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="w-full h-full flex flex-col gap-4">
|
|
23
|
+
{hasTitle && <Skeleton className="w-1/2 h-8 mx-auto rounded-md z-20" />}
|
|
24
|
+
<Skeleton
|
|
25
|
+
className={cn('w-full h-56 rounded-md z-20 relative', className)}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const UploadImage = forwardRef<HTMLInputElement, UploadImageProps>(
|
|
32
|
+
(
|
|
33
|
+
{
|
|
34
|
+
title,
|
|
35
|
+
description = 'Preview will display here.',
|
|
36
|
+
fileExtension,
|
|
37
|
+
actionTitle = 'select file',
|
|
38
|
+
variant,
|
|
39
|
+
errorMessage,
|
|
40
|
+
defaultImagePath,
|
|
41
|
+
classNames,
|
|
42
|
+
...props
|
|
43
|
+
},
|
|
44
|
+
ref
|
|
45
|
+
) => {
|
|
46
|
+
const isClient = useIsClient();
|
|
47
|
+
const [image, setImage] = useState<{
|
|
48
|
+
src: string | null;
|
|
49
|
+
alt: string | null;
|
|
50
|
+
}>({
|
|
51
|
+
src: null,
|
|
52
|
+
alt: null,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const invalid = !!errorMessage;
|
|
56
|
+
|
|
57
|
+
const accept = fileExtension
|
|
58
|
+
? fileExtension
|
|
59
|
+
.split(',')
|
|
60
|
+
.map((val) => `image/${val.trim()}`)
|
|
61
|
+
.join(', ')
|
|
62
|
+
: 'image/*';
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className={cn('w-full h-full', 'flex flex-col gap-4')}>
|
|
66
|
+
{isClient ? (
|
|
67
|
+
<>
|
|
68
|
+
{title && (
|
|
69
|
+
<h6 className={cn('text-center', 'text-2xl', 'm-0 mb-4')}>
|
|
70
|
+
{title}
|
|
71
|
+
</h6>
|
|
72
|
+
)}
|
|
73
|
+
<div
|
|
74
|
+
className={cn(
|
|
75
|
+
'w-full h-full',
|
|
76
|
+
'flex flex-col justify-between gap-5 items-center',
|
|
77
|
+
['bg-slate-100', variant === 'base' && 'bg-transparent'],
|
|
78
|
+
'rounded-md',
|
|
79
|
+
[variant === 'base' && 'rounded-none'],
|
|
80
|
+
['p-6 lg:p-7', variant === 'base' && 'p-0 lg:p-0']
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<label
|
|
84
|
+
htmlFor={`${props?.name}-upload-file`}
|
|
85
|
+
aria-label="image upload"
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
87
|
+
// @ts-ignore
|
|
88
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
|
89
|
+
const {
|
|
90
|
+
target: { files },
|
|
91
|
+
} = e;
|
|
92
|
+
if (files) {
|
|
93
|
+
setImage({
|
|
94
|
+
src: URL.createObjectURL(files[0]),
|
|
95
|
+
alt: files[0].name,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}}
|
|
99
|
+
className={cn(
|
|
100
|
+
'inline-flex justify-center items-center',
|
|
101
|
+
'uppercase text-base border-slate-200',
|
|
102
|
+
'bg-transparent',
|
|
103
|
+
'border-1 border-solid',
|
|
104
|
+
'px-4 py-0',
|
|
105
|
+
'rounded-2xl',
|
|
106
|
+
'cursor-pointer',
|
|
107
|
+
'w-full h-9',
|
|
108
|
+
['max-w-48', variant === 'base' && 'max-w-none'],
|
|
109
|
+
'my-0 mx-auto',
|
|
110
|
+
'has-[:disabled]:bg-transparent has-[:disabled]:text-slate-950',
|
|
111
|
+
'hover:bg-slate-100s hover:text-black',
|
|
112
|
+
classNames?.label
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{actionTitle}
|
|
116
|
+
<input
|
|
117
|
+
id={`${props?.name}-upload-file`}
|
|
118
|
+
accept={accept}
|
|
119
|
+
type="file"
|
|
120
|
+
hidden
|
|
121
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
122
|
+
{...props}
|
|
123
|
+
ref={ref}
|
|
124
|
+
className="hidden"
|
|
125
|
+
/>
|
|
126
|
+
</label>
|
|
127
|
+
|
|
128
|
+
{defaultImagePath && !image.src ? (
|
|
129
|
+
<div className="flex justify-center items-center w-60">
|
|
130
|
+
<a href={defaultImagePath} target="_blank" rel="noreferrer">
|
|
131
|
+
<img
|
|
132
|
+
src={defaultImagePath}
|
|
133
|
+
alt=""
|
|
134
|
+
className="w-full h-auto"
|
|
135
|
+
/>
|
|
136
|
+
</a>
|
|
137
|
+
</div>
|
|
138
|
+
) : (
|
|
139
|
+
<>
|
|
140
|
+
<motion.div
|
|
141
|
+
initial={{ opacity: 0 }}
|
|
142
|
+
animate={{ opacity: 1 }}
|
|
143
|
+
transition={{ type: 'spring', duration: 3, bounce: 0 }}
|
|
144
|
+
key={image.src}
|
|
145
|
+
className="flex justify-center items-center w-52 h-auto"
|
|
146
|
+
>
|
|
147
|
+
{image.src ? (
|
|
148
|
+
<Image
|
|
149
|
+
src={image.src}
|
|
150
|
+
height={96}
|
|
151
|
+
width={200}
|
|
152
|
+
alt={image.alt || ''}
|
|
153
|
+
className="max-h-full object-contain"
|
|
154
|
+
/>
|
|
155
|
+
) : (
|
|
156
|
+
<svg
|
|
157
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
158
|
+
width="31"
|
|
159
|
+
height="55"
|
|
160
|
+
viewBox="0 0 31 55"
|
|
161
|
+
fill="none"
|
|
162
|
+
>
|
|
163
|
+
<g clipPath="url(#clip0_15607_32844)">
|
|
164
|
+
<path
|
|
165
|
+
d="M15.9521 0.721924C24.2431 0.721924 30.9521 7.36392 30.9521 15.5719V43.9219C30.9521 49.8889 26.0703 54.7219 20.0431 54.7219C14.0158 54.7219 9.13397 49.8889 9.13397 43.9219V20.9719C9.13397 17.2459 12.1885 14.2219 15.9521 14.2219C19.7158 14.2219 22.7703 17.2459 22.7703 20.9719V41.2219H17.3158V20.7289C17.3158 19.2439 14.5885 19.2439 14.5885 20.7289V43.9219C14.5885 46.8919 17.0431 49.3219 20.0431 49.3219C23.0431 49.3219 25.4976 46.8919 25.4976 43.9219V15.5719C25.4976 10.3609 21.2158 6.12192 15.9521 6.12192C10.6885 6.12192 6.40669 10.3609 6.40669 15.5719V41.2219H0.952148V15.5719C0.952148 7.36392 7.66124 0.721924 15.9521 0.721924Z"
|
|
166
|
+
fill="#646464"
|
|
167
|
+
/>
|
|
168
|
+
</g>
|
|
169
|
+
<defs>
|
|
170
|
+
<clipPath id="clip0_15607_32844">
|
|
171
|
+
<rect
|
|
172
|
+
width="30"
|
|
173
|
+
height="54"
|
|
174
|
+
fill="white"
|
|
175
|
+
transform="translate(0.952148 0.721924)"
|
|
176
|
+
/>
|
|
177
|
+
</clipPath>
|
|
178
|
+
</defs>
|
|
179
|
+
</svg>
|
|
180
|
+
)}
|
|
181
|
+
</motion.div>
|
|
182
|
+
<p className="m-0 text-slate-950 text-center">
|
|
183
|
+
{image.alt || description}
|
|
184
|
+
</p>
|
|
185
|
+
</>
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
188
|
+
</>
|
|
189
|
+
) : (
|
|
190
|
+
<UploadImageLoader
|
|
191
|
+
hasTitle={Boolean(title)}
|
|
192
|
+
className={classNames?.skeleton}
|
|
193
|
+
/>
|
|
194
|
+
)}
|
|
195
|
+
|
|
196
|
+
{invalid && (
|
|
197
|
+
<FieldError className={cn('-mt-2', 'self-start')}>
|
|
198
|
+
{errorMessage}
|
|
199
|
+
</FieldError>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
export default UploadImage;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { UseFormRegisterReturn } from 'react-hook-form';
|
|
3
|
+
import type { SlotsToClasses } from '../../types/SlotsToClasses';
|
|
4
|
+
|
|
5
|
+
export type UploadImageProps = UseFormRegisterReturn & {
|
|
6
|
+
title?: string;
|
|
7
|
+
description?: ReactNode;
|
|
8
|
+
name?: string;
|
|
9
|
+
fileExtension?: string;
|
|
10
|
+
actionTitle?: string;
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
defaultImagePath?: string;
|
|
13
|
+
variant?: 'default' | 'base';
|
|
14
|
+
classNames?: SlotsToClasses<'label' | 'skeleton'>;
|
|
15
|
+
};
|
package/package.json
CHANGED