@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.
Files changed (191) hide show
  1. package/components/Accordion/Accordion.tsx +82 -0
  2. package/components/Accordion/index.ts +3 -0
  3. package/components/Avatar/Avatar.stories.tsx +99 -0
  4. package/components/Avatar/Avatar.tsx +120 -0
  5. package/components/Avatar/Avatar.types.ts +3 -0
  6. package/components/Avatar/AvatarGroup/AvatarGroup.tsx +32 -0
  7. package/components/Avatar/AvatarGroup/AvatarGroup.types.ts +8 -0
  8. package/components/Avatar/index.ts +4 -0
  9. package/components/Badge/Badge.stories.tsx +72 -0
  10. package/components/Badge/Badge.tsx +169 -0
  11. package/components/Badge/Badge.types.ts +3 -0
  12. package/components/Badge/index.ts +2 -0
  13. package/components/Breadcrumbs/BreadcrumbEllipsis.tsx +47 -0
  14. package/components/Breadcrumbs/BreadcrumbEllipsis.types.ts +5 -0
  15. package/components/Breadcrumbs/BreadcrumbItem.tsx +23 -0
  16. package/components/Breadcrumbs/BreadcrumbItem.types.ts +3 -0
  17. package/components/Breadcrumbs/BreadcrumbLink.tsx +30 -0
  18. package/components/Breadcrumbs/BreadcrumbLink.types.ts +3 -0
  19. package/components/Breadcrumbs/BreadcrumbSeparator.tsx +41 -0
  20. package/components/Breadcrumbs/BreadcrumbSeparator.types.ts +9 -0
  21. package/components/Breadcrumbs/Breadcrumbs.tsx +28 -0
  22. package/components/Breadcrumbs/Breadcrumbs.types.ts +6 -0
  23. package/components/Breadcrumbs/index.ts +10 -0
  24. package/components/Button/Button.tsx +72 -0
  25. package/components/Button/Button.types.ts +7 -0
  26. package/components/Button/index.ts +2 -0
  27. package/components/Card/Card.tsx +15 -0
  28. package/components/Card/Card.types.ts +3 -0
  29. package/components/Card/index.ts +2 -0
  30. package/components/Checkbox/Checkbox.tsx +122 -0
  31. package/components/Checkbox/Checkbox.types.ts +15 -0
  32. package/components/Checkbox/index.ts +2 -0
  33. package/components/Collapsible/Collapsible.tsx +34 -0
  34. package/components/Collapsible/Collapsible.types.ts +5 -0
  35. package/components/Collapsible/CollapsibleTrigger.tsx +57 -0
  36. package/components/Collapsible/CollapsibleTrigger.types.ts +14 -0
  37. package/components/Collapsible/index.ts +10 -0
  38. package/components/Container/Container.tsx +26 -0
  39. package/components/Container/Container.types.ts +3 -0
  40. package/components/Container/index.ts +2 -0
  41. package/components/ContextMenu/ContextMenu.tsx +74 -0
  42. package/components/ContextMenu/ContextMenu.types.ts +17 -0
  43. package/components/ContextMenu/index.ts +2 -0
  44. package/components/CreditCardExpirationInput/CreditCardExpirationInput.tsx +115 -0
  45. package/components/CreditCardExpirationInput/CreditCardExpirationInput.types.ts +10 -0
  46. package/components/CreditCardExpirationInput/index.ts +2 -0
  47. package/components/CreditCardInput/CreditCardInput.tsx +147 -0
  48. package/components/CreditCardInput/CreditCardInput.types.ts +12 -0
  49. package/components/CreditCardInput/index.ts +2 -0
  50. package/components/DateInput/DateInput.tsx +81 -0
  51. package/components/DateInput/DateInput.types.ts +15 -0
  52. package/components/DateInput/index.ts +2 -0
  53. package/components/DateTimeFormat/DateTimeFormat.tsx +16 -0
  54. package/components/DateTimeFormat/DateTimeFormat.types.ts +7 -0
  55. package/components/DateTimeFormat/index.ts +2 -0
  56. package/components/Dialog/Dialog.tsx +65 -0
  57. package/components/Dialog/Dialog.types.ts +9 -0
  58. package/components/Dialog/index.ts +2 -0
  59. package/components/DialogTrigger/DialogTrigger.tsx +45 -0
  60. package/components/DialogTrigger/DialogTrigger.types.ts +6 -0
  61. package/components/DialogTrigger/index.ts +5 -0
  62. package/components/Divider/Divider.stories.tsx +37 -0
  63. package/components/Divider/Divider.tsx +34 -0
  64. package/components/Divider/Divider.types.ts +5 -0
  65. package/components/Divider/index.ts +2 -0
  66. package/components/DobInput/DobInput.tsx +120 -0
  67. package/components/DobInput/index.ts +2 -0
  68. package/components/Drawer/Drawer.tsx +126 -0
  69. package/components/Drawer/Drawer.types.ts +11 -0
  70. package/components/Drawer/index.ts +2 -0
  71. package/components/Icon/Account.tsx +50 -0
  72. package/components/Icon/Cart.tsx +43 -0
  73. package/components/Icon/Checkmark.tsx +34 -0
  74. package/components/Icon/Cross.tsx +36 -0
  75. package/components/Icon/DownArrow.tsx +23 -0
  76. package/components/Icon/Hamburger.tsx +23 -0
  77. package/components/Icon/Icon.types.ts +8 -0
  78. package/components/Icon/LinkArrow.tsx +32 -0
  79. package/components/Icon/Minus.tsx +20 -0
  80. package/components/Icon/Plus.tsx +20 -0
  81. package/components/Icon/Search.tsx +36 -0
  82. package/components/Icon/Trash.tsx +27 -0
  83. package/components/Icon/Verified.tsx +20 -0
  84. package/components/Icon/index.ts +14 -0
  85. package/components/Image/Image.tsx +32 -0
  86. package/components/Image/index.ts +2 -0
  87. package/components/Input/Input.tsx +109 -0
  88. package/components/Input/Input.types.ts +17 -0
  89. package/components/Input/index.ts +2 -0
  90. package/components/Link/Link.stories.tsx +96 -0
  91. package/components/Link/Link.tsx +34 -0
  92. package/components/Link/Link.types.ts +3 -0
  93. package/components/Link/index.ts +2 -0
  94. package/components/Loader/CircularEasing.tsx +66 -0
  95. package/components/Loader/CircularEasing.types.ts +8 -0
  96. package/components/Loader/Pulse.tsx +45 -0
  97. package/components/Loader/Pulse.types.ts +5 -0
  98. package/components/Loader/index.ts +4 -0
  99. package/components/Menu/ContextMenu.tsx +83 -0
  100. package/components/Menu/Menu.tsx +143 -0
  101. package/components/Menu/Menu.types.ts +44 -0
  102. package/components/Menu/index.ts +4 -0
  103. package/components/Meter/Meter.stories.tsx +111 -0
  104. package/components/Meter/Meter.tsx +68 -0
  105. package/components/Meter/Meter.types.ts +10 -0
  106. package/components/Meter/index.ts +2 -0
  107. package/components/Modal/Modal.tsx +16 -0
  108. package/components/Modal/Modal.types.ts +6 -0
  109. package/components/Modal/index.ts +2 -0
  110. package/components/ModalOverlay/ModalOverlay.tsx +121 -0
  111. package/components/ModalOverlay/ModalOverlay.types.ts +18 -0
  112. package/components/ModalOverlay/index.ts +2 -0
  113. package/components/NumberFormat/NumberFormat.tsx +19 -0
  114. package/components/NumberFormat/NumberFormat.types.ts +8 -0
  115. package/components/NumberFormat/index.ts +2 -0
  116. package/components/NumberInput/NumberInput.tsx +164 -0
  117. package/components/NumberInput/NumberInput.types.ts +22 -0
  118. package/components/NumberInput/index.ts +2 -0
  119. package/components/NumberTicker/DigitResolver.tsx +119 -0
  120. package/components/NumberTicker/DigitResolver.types.ts +18 -0
  121. package/components/NumberTicker/NumberTicker.tsx +56 -0
  122. package/components/NumberTicker/NumberTicker.types.ts +96 -0
  123. package/components/NumberTicker/hooks/useColumnTransition.ts +36 -0
  124. package/components/NumberTicker/hooks/useNumberDelta.ts +19 -0
  125. package/components/NumberTicker/hooks/useNumberTicker.ts +36 -0
  126. package/components/NumberTicker/index.ts +10 -0
  127. package/components/Pagination/Pagination.tsx +44 -0
  128. package/components/Pagination/index.ts +2 -0
  129. package/components/PasswordCheck/PasswordCheck.tsx +59 -0
  130. package/components/PasswordCheck/PasswordCheck.types.ts +4 -0
  131. package/components/PasswordCheck/PasswordCheck.utils.ts +47 -0
  132. package/components/PasswordCheck/index.ts +2 -0
  133. package/components/PhoneInput/PhoneInput.tsx +191 -0
  134. package/components/PhoneInput/index.ts +2 -0
  135. package/components/PinInput/PinInput.tsx +314 -0
  136. package/components/PinInput/PinInput.types.ts +21 -0
  137. package/components/PinInput/index.ts +2 -0
  138. package/components/Progressbar/CircularProgressbar.tsx +71 -0
  139. package/components/Progressbar/CircularProgressbar.types.ts +10 -0
  140. package/components/Progressbar/LinearProgressbar.tsx +75 -0
  141. package/components/Progressbar/LinearProgressbar.types.ts +11 -0
  142. package/components/Progressbar/index.ts +4 -0
  143. package/components/Radio/Radio.tsx +88 -0
  144. package/components/Radio/Radio.types.ts +16 -0
  145. package/components/Radio/index.ts +2 -0
  146. package/components/RadioGroup/RadioGroup.tsx +49 -0
  147. package/components/RadioGroup/RadioGroup.types.ts +7 -0
  148. package/components/RadioGroup/index.ts +2 -0
  149. package/components/Select/Option.tsx +32 -0
  150. package/components/Select/Option.types.ts +3 -0
  151. package/components/Select/Select.tsx +253 -0
  152. package/components/Select/Select.types.ts +42 -0
  153. package/components/Select/index.ts +8 -0
  154. package/components/Skeleton/Skeleton.tsx +15 -0
  155. package/components/Skeleton/Skeleton.types.ts +3 -0
  156. package/components/Skeleton/index.ts +2 -0
  157. package/components/Slider/Slider.tsx +110 -0
  158. package/components/Slider/Slider.types.ts +11 -0
  159. package/components/Slider/index.ts +2 -0
  160. package/components/Switch/Switch.tsx +63 -0
  161. package/components/Switch/Switch.types.ts +8 -0
  162. package/components/Switch/index.ts +2 -0
  163. package/components/Table/Table.tsx +52 -0
  164. package/components/Table/Table.types.ts +22 -0
  165. package/components/Table/index.ts +2 -0
  166. package/components/Tabs/Tab.tsx +118 -0
  167. package/components/Tabs/Tab.types.ts +10 -0
  168. package/components/Tabs/TabList.tsx +51 -0
  169. package/components/Tabs/TabList.types.ts +12 -0
  170. package/components/Tabs/TabPanel.tsx +19 -0
  171. package/components/Tabs/TabPanel.types.ts +3 -0
  172. package/components/Tabs/Tabs.context.tsx +9 -0
  173. package/components/Tabs/Tabs.tsx +39 -0
  174. package/components/Tabs/Tabs.types.ts +3 -0
  175. package/components/Tabs/index.ts +9 -0
  176. package/components/TimeInput/TimeInput.stories.tsx +125 -0
  177. package/components/TimeInput/TimeInput.tsx +81 -0
  178. package/components/TimeInput/TimeInput.types.ts +15 -0
  179. package/components/TimeInput/index.ts +2 -0
  180. package/components/ToggleButton/ToggleButton.stories.tsx +89 -0
  181. package/components/ToggleButton/ToggleButton.tsx +69 -0
  182. package/components/ToggleButton/ToggleButton.types.ts +6 -0
  183. package/components/ToggleButton/index.ts +2 -0
  184. package/components/Tooltip/Tooltip.tsx +59 -0
  185. package/components/Tooltip/Tooltip.types.ts +3 -0
  186. package/components/Tooltip/index.ts +2 -0
  187. package/components/UploadImage/UploadImage.tsx +206 -0
  188. package/components/UploadImage/UploadImage.types.ts +15 -0
  189. package/components/UploadImage/index.ts +2 -0
  190. package/package.json +1 -1
  191. package/tailwind.config.js +3 -1
@@ -0,0 +1,11 @@
1
+ import type { ProgressBarProps } from 'react-aria-components';
2
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
3
+
4
+ export interface LinearProgressbarProps extends ProgressBarProps {
5
+ label?: string;
6
+ width?: string;
7
+ showValue?: boolean;
8
+ classNames?: SlotsToClasses<
9
+ 'label' | 'value' | 'labelWrapper' | 'track' | 'trackWrapper'
10
+ >;
11
+ }
@@ -0,0 +1,4 @@
1
+ export { CircularProgressbar } from './CircularProgressbar';
2
+ export { LinearProgressbar } from './LinearProgressbar';
3
+ export type { CircularProgressbarProps } from './CircularProgressbar.types';
4
+ export type { LinearProgressbarProps } from './LinearProgressbar.types';
@@ -0,0 +1,88 @@
1
+ 'use client';
2
+
3
+ import { Radio as AriaRadio } from 'react-aria-components';
4
+ import type { RadioRenderProps } from 'react-aria-components';
5
+ import { cn } from '../../utils/cn';
6
+ import type { RadioProps, RadioSlots } from './Radio.types';
7
+
8
+ function ControlSlot({
9
+ control,
10
+ classNames,
11
+ ...props
12
+ }: RadioRenderProps &
13
+ Pick<RadioSlots, 'control'> &
14
+ Pick<RadioProps, 'classNames'>) {
15
+ if (!control) {
16
+ return (
17
+ <div
18
+ data-hovered={props?.isHovered}
19
+ data-focused={props?.isFocused}
20
+ data-disabled={props?.isDisabled}
21
+ data-invalid={props?.isInvalid}
22
+ data-selected={props?.isSelected}
23
+ data-pressed={props?.isPressed}
24
+ className={cn(
25
+ 'flex items-center justify-center w-6 h-6 border-solid border border-slate-300 ransition-all duration-300 ease rounded-full',
26
+ 'data-[hovered="true"]:border-slate-400',
27
+ 'data-[focused="true"]:border-slate-400 data-[focused="true"]:outline-2 data-[focused="true"]:outline data-[focused="true"]:outline-slate-200',
28
+ 'data-[disabled="true"]:border-slate-200 data-[disabled="true"]:bg-slate-100',
29
+ 'data-[invalid="true"]:bg-red-100 data-[invalid="true"]:text-red-600 data-[invalid="true"]:border-red-500',
30
+ 'data-[invalid="true"]:data-[disabled="true"]:border-red-200 data-[invalid="true"]:data-[disabled="true"]:bg-red-100',
31
+ 'data-[invalid="true"]:data-[hovered="true"]:border-red-600',
32
+ 'data-[invalid="true"]:data-[focused="true"]:border-red-600 data-[invalid="true"]:data-[focused="true"]:outline-red-200',
33
+ 'data-[invalid="true"]:data-[selected="true"]:bg-red-100 data-[invalid="true"]:data-[selected="true"]:border-red-500',
34
+ 'data-[invalid="true"]:data-[pressed="true"]:bg-red-600 data-[invalid="true"]:data-[pressed="true"]:border-red-600',
35
+ classNames?.control
36
+ )}
37
+ // eslint-disable-next-line react/jsx-props-no-spreading
38
+ {...props}
39
+ >
40
+ <div
41
+ data-invalid={props?.isInvalid}
42
+ className={cn(
43
+ 'w-3 h-3 rounded-full bg-slate-900 opacity-0 transition-all duration-300 ease data-[invalid="true"]:bg-red-500',
44
+ {
45
+ 'opacity-100': props?.isSelected,
46
+ }
47
+ )}
48
+ />
49
+ </div>
50
+ );
51
+ }
52
+ return typeof control === 'function' ? control(props) : control;
53
+ }
54
+
55
+ export function Radio({
56
+ children,
57
+ slots,
58
+ className,
59
+ classNames,
60
+ ...props
61
+ }: RadioProps) {
62
+ return (
63
+ <AriaRadio
64
+ className={cn(
65
+ 'flex items-center gap-2 group',
66
+ 'invalid:text-red-500 invalid:disabled:text-red-300',
67
+ 'disabled:text-slate-400',
68
+ className
69
+ )}
70
+ // eslint-disable-next-line react/jsx-props-no-spreading
71
+ {...props}
72
+ >
73
+ {(renderProps) => (
74
+ <>
75
+ <ControlSlot
76
+ control={slots?.control}
77
+ classNames={classNames}
78
+ // eslint-disable-next-line react/jsx-props-no-spreading
79
+ {...renderProps}
80
+ />
81
+ {typeof children === 'function' ? children(renderProps) : children}
82
+ </>
83
+ )}
84
+ </AriaRadio>
85
+ );
86
+ }
87
+
88
+ export default Radio;
@@ -0,0 +1,16 @@
1
+ import type { ReactNode } from 'react';
2
+ import type {
3
+ RadioRenderProps,
4
+ RadioProps as UiRadioProps,
5
+ } from 'react-aria-components';
6
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
7
+
8
+ export interface RadioSlots {
9
+ control?: ReactNode | ((values: RadioRenderProps) => ReactNode);
10
+ }
11
+
12
+ export interface RadioProps extends UiRadioProps {
13
+ children?: ReactNode | ((values: RadioRenderProps) => ReactNode);
14
+ slots?: RadioSlots;
15
+ classNames?: SlotsToClasses<'control'>;
16
+ }
@@ -0,0 +1,2 @@
1
+ export { Radio } from './Radio';
2
+ export type { RadioProps, RadioSlots } from './Radio.types';
@@ -0,0 +1,49 @@
1
+ import { Label, RadioGroup as AriaRadioGroup } from 'react-aria-components';
2
+ import { cn } from '../../utils/cn';
3
+ import { RadioGroupProps } from './RadioGroup.types';
4
+
5
+ export function RadioGroup({
6
+ label,
7
+ errorMessage,
8
+ description,
9
+ orientation,
10
+ children,
11
+ className,
12
+ ...props
13
+ }: RadioGroupProps) {
14
+ return (
15
+ <AriaRadioGroup
16
+ className={cn('flex flex-col gap-4', className)}
17
+ orientation={orientation}
18
+ // eslint-disable-next-line react/jsx-props-no-spreading
19
+ {...props}
20
+ >
21
+ {typeof children === 'function' ? (
22
+ children
23
+ ) : (
24
+ <>
25
+ {label && (
26
+ <Label className="flex text-slate-500 text-xs">{label}</Label>
27
+ )}
28
+ <div
29
+ className={cn(
30
+ 'flex flex-col gap-2',
31
+ orientation === 'horizontal' && 'flex-row items-center gap-4'
32
+ )}
33
+ >
34
+ {children}
35
+ </div>
36
+ {description && (
37
+ <p className="flex mt-1 text-slate-500 text-sm">{description}</p>
38
+ )}
39
+
40
+ {errorMessage && (
41
+ <p className="flex pl-6 text-red-500 text-sm">{errorMessage}</p>
42
+ )}
43
+ </>
44
+ )}
45
+ </AriaRadioGroup>
46
+ );
47
+ }
48
+
49
+ export default RadioGroup;
@@ -0,0 +1,7 @@
1
+ import type { RadioGroupProps as AriaRadioGroupProps } from 'react-aria-components';
2
+
3
+ export interface RadioGroupProps extends AriaRadioGroupProps {
4
+ label?: string;
5
+ description?: string;
6
+ errorMessage?: string;
7
+ }
@@ -0,0 +1,2 @@
1
+ export { RadioGroup } from './RadioGroup';
2
+ export type { RadioGroupProps } from './RadioGroup.types';
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { ListBoxItem } from 'react-aria-components';
4
+ import { cn } from '../../utils/cn';
5
+ import type { OptionProps } from './Option.types';
6
+
7
+ export function Option({ className, ...props }: OptionProps) {
8
+ return (
9
+ <ListBoxItem
10
+ // eslint-disable-next-line react/jsx-props-no-spreading
11
+ {...props}
12
+ className={cn(
13
+ 'flex flex-auto items-center',
14
+ 'rounded-md',
15
+ 'p-2.5 mx-1.5',
16
+ 'text-sm',
17
+ 'cursor-default',
18
+ 'outline-none',
19
+ 'text-slate-900',
20
+ 'transition-all ease-in-out duration-300',
21
+ 'data-[hovered]:bg-slate-100 data-[hovered]:text-slate-900',
22
+ 'data-[focused]:bg-slate-100 data-[hovered]:text-slate-900',
23
+ 'data-[pressed]:bg-slate-200 data-[hovered]:text-slate-900',
24
+ 'data-[selected]:bg-none data-[selected]:text-slate-900 data-[selected]:font-semibold data-[selected]:data-[focused]:bg-slate-100',
25
+ 'data-[disabled]:bg-none data-[disabled]:text-slate-500',
26
+ className
27
+ )}
28
+ />
29
+ );
30
+ }
31
+
32
+ export default Option;
@@ -0,0 +1,3 @@
1
+ import type { ListBoxItemProps } from 'react-aria-components';
2
+
3
+ export type OptionProps = ListBoxItemProps;
@@ -0,0 +1,253 @@
1
+ 'use client';
2
+
3
+ import {
4
+ ListBox,
5
+ ComboBox,
6
+ Label,
7
+ Input,
8
+ Button,
9
+ Text,
10
+ FieldError,
11
+ Popover,
12
+ } from 'react-aria-components';
13
+ import type { ComboBoxRenderProps, ListBoxProps } from 'react-aria-components';
14
+ import { forwardRef, useState } from 'react';
15
+ import type { PropsWithChildren, Ref } from 'react';
16
+ import { motion } from 'framer-motion';
17
+ import { cn } from '../../utils/cn';
18
+ import { Pulse } from '../Loader';
19
+ import type {
20
+ ComboBoxPopoverAnimationState,
21
+ SelectProps,
22
+ SelectSlots,
23
+ } from './Select.types';
24
+
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const ForwardedPopover = forwardRef<HTMLElement, any>(
27
+ (props, ref: Ref<HTMLElement>) => (
28
+ // eslint-disable-next-line react/jsx-props-no-spreading
29
+ <Popover {...props} ref={ref} />
30
+ )
31
+ );
32
+
33
+ // Now use motion with ForwardedPopover
34
+ const MotionPopover = motion.create(ForwardedPopover);
35
+
36
+ function ButtonIconSlot<T extends object>({
37
+ buttonIcon,
38
+ ...props
39
+ }: ComboBoxRenderProps & Pick<SelectSlots<T>, 'buttonIcon'>) {
40
+ if (!buttonIcon) {
41
+ return (
42
+ <svg
43
+ width="24"
44
+ height="24"
45
+ viewBox="0 0 24 24"
46
+ fill="none"
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ className={cn(
49
+ 'transition-all ease-in-out duration-200',
50
+ props.isOpen ? 'rotate-180' : 'rotate-0'
51
+ )}
52
+ >
53
+ <path
54
+ d="M6 9L12 15L18 9"
55
+ strokeWidth="2"
56
+ strokeLinecap="round"
57
+ strokeLinejoin="round"
58
+ className={cn([
59
+ 'stroke-slate-900',
60
+ props.isDisabled && 'stroke-slate-400',
61
+ props.isInvalid && 'stroke-red-500',
62
+ ])}
63
+ />
64
+ </svg>
65
+ );
66
+ }
67
+
68
+ return typeof buttonIcon === 'function' ? buttonIcon(props) : buttonIcon;
69
+ }
70
+
71
+ function ListBoxSlot<T extends object>({
72
+ listBoxComponent,
73
+ children,
74
+ ...props
75
+ }: ListBoxProps<T> & Pick<SelectSlots<T>, 'listBoxComponent'>) {
76
+ const Component = listBoxComponent || ListBox;
77
+
78
+ return (
79
+ <Component
80
+ // eslint-disable-next-line react/jsx-props-no-spreading
81
+ {...props}
82
+ >
83
+ {children}
84
+ </Component>
85
+ );
86
+ }
87
+
88
+ export function Select<T extends object>({
89
+ label,
90
+ description,
91
+ errorMessage,
92
+ children,
93
+ placeholder,
94
+ key,
95
+ isLoading,
96
+ slots,
97
+ popoverPortalContainer,
98
+ popoverOffset,
99
+ selectedKey,
100
+ className,
101
+ classNames,
102
+ ...props
103
+ }: SelectProps<T>) {
104
+ const [animation, setAnimation] =
105
+ useState<ComboBoxPopoverAnimationState>('unmounted');
106
+ const [isComboOpen, setIsComboOpen] = useState<boolean>(false);
107
+
108
+ return (
109
+ <ComboBox
110
+ onOpenChange={() => {
111
+ setAnimation(animation === 'visible' ? 'hidden' : 'visible');
112
+ setIsComboOpen(!isComboOpen);
113
+ }}
114
+ isDisabled={props.isDisabled || isLoading}
115
+ data-has-value={!!selectedKey}
116
+ selectedKey={selectedKey}
117
+ // eslint-disable-next-line react/jsx-props-no-spreading
118
+ {...props}
119
+ className={cn('flex flex-col', 'w-full', className)}
120
+ >
121
+ {(renderProps) => (
122
+ <>
123
+ <Label
124
+ className={cn('flex', 'text-xs text-slate-500', classNames?.label)}
125
+ >
126
+ {label}
127
+ </Label>
128
+ <div
129
+ className={cn(
130
+ 'flex',
131
+ 'relative',
132
+ 'w-full',
133
+ classNames?.comboBoxContainer
134
+ )}
135
+ >
136
+ <Input
137
+ placeholder={placeholder}
138
+ className={cn(
139
+ 'border border-solid border-slate-300',
140
+ 'text-sm text-slate-900',
141
+ 'py-0 px-2',
142
+ 'h-10 w-full',
143
+ 'm-0',
144
+ 'rounded-md',
145
+ 'bg-white',
146
+ 'transition-all ease-in-out duration-200',
147
+ 'data-[hovered]:border-slate-400',
148
+ 'data-[focused]:border-slate-400 data-[focused]:outline data-[focused]:outline-2 data-[focused]:outline-slate-200',
149
+ ' data-[disabled]:bg-slate-100 data-[disabled]:border-slate-300',
150
+ isLoading
151
+ ? 'data-[disabled]:text-slate-900'
152
+ : 'data-[disabled]:text-slate-500',
153
+ 'data-[invalid]:border-red-500 data-[invalid]:bg-red-100 data-[invalid]:text-red-600',
154
+ 'data-[invalid]:data-[hovered]:border-red-600',
155
+ 'data-[invalid]:data-[focused]:border-red-600 data-[invalid]:data-[focused]:outline data-[invalid]:data-[focused]:outline-2 data-[invalid]:data-[focused]:outline-red-200',
156
+ 'data-[invalid]:placeholder:text-slate-400',
157
+ classNames?.input
158
+ )}
159
+ />
160
+ {isLoading ? (
161
+ <div
162
+ className={cn(
163
+ 'absolute top-2 right-2',
164
+ 'block',
165
+ classNames?.loader
166
+ )}
167
+ >
168
+ {slots?.loadingIcon || <Pulse />}
169
+ </div>
170
+ ) : (
171
+ <Button
172
+ className={cn(
173
+ 'absolute top-2 right-0',
174
+ 'block',
175
+ 'border-none',
176
+ 'bg-none',
177
+ classNames?.arrowButton
178
+ )}
179
+ >
180
+ <ButtonIconSlot
181
+ buttonIcon={slots?.buttonIcon}
182
+ // eslint-disable-next-line react/jsx-props-no-spreading
183
+ {...renderProps}
184
+ />
185
+ </Button>
186
+ )}
187
+ </div>
188
+ {description && (
189
+ <Text
190
+ slot="description"
191
+ className={cn(
192
+ 'flex',
193
+ 'text-xs',
194
+ 'text-slate-500',
195
+ classNames?.description
196
+ )}
197
+ >
198
+ {description}
199
+ </Text>
200
+ )}
201
+ <FieldError
202
+ className={cn(
203
+ 'flex',
204
+ 'text-xs',
205
+ 'text-red-500',
206
+ classNames?.errorMessage
207
+ )}
208
+ >
209
+ {errorMessage}
210
+ </FieldError>
211
+ <MotionPopover
212
+ key={key}
213
+ isOpen={isComboOpen}
214
+ isExiting={animation === 'hidden'}
215
+ offset={popoverOffset}
216
+ UNSTABLE_portalContainer={popoverPortalContainer}
217
+ onAnimationComplete={(completedAnimation: string) => {
218
+ setAnimation((a) =>
219
+ completedAnimation === 'hidden' && a === 'hidden'
220
+ ? 'unmounted'
221
+ : a
222
+ );
223
+ }}
224
+ variants={{
225
+ hidden: { opacity: 0, y: -10 },
226
+ visible: { opacity: 1, y: 0 },
227
+ }}
228
+ initial="hidden"
229
+ animate={animation}
230
+ className={cn(
231
+ 'px-0 py-1.5',
232
+ 'shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_-4px_rgba(0,0,0,0.1)]',
233
+ 'rounded-md',
234
+ 'w-[var(--trigger-width)]',
235
+ 'bg-white',
236
+ 'border border-solid border-slate-300',
237
+ classNames?.listContainer
238
+ )}
239
+ >
240
+ <ListBoxSlot
241
+ listBoxComponent={slots?.listBoxComponent}
242
+ className={cn('max-h-80', 'overflow-y-scroll', classNames?.list)}
243
+ >
244
+ {children}
245
+ </ListBoxSlot>
246
+ </MotionPopover>
247
+ </>
248
+ )}
249
+ </ComboBox>
250
+ );
251
+ }
252
+
253
+ export default Select;
@@ -0,0 +1,42 @@
1
+ import type {
2
+ ComboBoxRenderProps,
3
+ ComboBoxProps as AriaComboBoxProps,
4
+ ValidationResult,
5
+ ListBoxProps,
6
+ } from 'react-aria-components';
7
+ import type { ComponentType, Key, ReactNode } from 'react';
8
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
9
+
10
+ export type ComboBoxPopoverAnimationState = 'unmounted' | 'hidden' | 'visible';
11
+
12
+ export type SelectSlots<T extends object> = {
13
+ buttonIcon?: ReactNode | ((values: ComboBoxRenderProps) => ReactNode);
14
+ loadingIcon?: ReactNode;
15
+ listBoxComponent?: ComponentType<ListBoxProps<T>>;
16
+ };
17
+
18
+ export interface SelectProps<T extends object>
19
+ extends Omit<AriaComboBoxProps<T>, 'children'> {
20
+ label?: string;
21
+ description?: string | null;
22
+ errorMessage?: string | ((validation: ValidationResult) => string);
23
+ placeholder?: string;
24
+ key?: Key | null;
25
+ isLoading?: boolean;
26
+ children: ReactNode | ((item: T) => ReactNode);
27
+ slots?: SelectSlots<T>;
28
+ popoverOffset?: number;
29
+ popoverPortalContainer?: Element;
30
+ classNames?: SlotsToClasses<
31
+ | 'label'
32
+ | 'name'
33
+ | 'comboBoxContainer'
34
+ | 'input'
35
+ | 'loader'
36
+ | 'arrowButton'
37
+ | 'description'
38
+ | 'errorMessage'
39
+ | 'listContainer'
40
+ | 'list'
41
+ >;
42
+ }
@@ -0,0 +1,8 @@
1
+ export { Option } from './Option';
2
+ export { Select } from './Select';
3
+ export type { OptionProps } from './Option.types';
4
+ export type {
5
+ ComboBoxPopoverAnimationState,
6
+ SelectProps,
7
+ SelectSlots,
8
+ } from './Select.types';
@@ -0,0 +1,15 @@
1
+ import { cn } from '../../utils/cn';
2
+ import type { SkeletonProps } from './Skeleton.types';
3
+
4
+ export function Skeleton({ className }: SkeletonProps) {
5
+ return (
6
+ <div
7
+ className={cn(
8
+ 'relative rounded-md animate-pulse bg-slate-200',
9
+ className
10
+ )}
11
+ />
12
+ );
13
+ }
14
+
15
+ export default Skeleton;
@@ -0,0 +1,3 @@
1
+ export type SkeletonProps = {
2
+ className?: string;
3
+ };
@@ -0,0 +1,2 @@
1
+ export { Skeleton } from './Skeleton';
2
+ export type { SkeletonProps } from './Skeleton.types';
@@ -0,0 +1,110 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Label,
5
+ Slider as AriaSlider,
6
+ SliderOutput,
7
+ SliderTrack,
8
+ SliderThumb,
9
+ } from 'react-aria-components';
10
+ import { cn } from '../../utils/cn';
11
+ import type { SliderProps } from './Slider.types';
12
+
13
+ export function Slider({
14
+ label,
15
+ showOutput = true,
16
+ thumbLabels,
17
+ classNames,
18
+ orientation,
19
+ isDisabled,
20
+ children,
21
+ ...props
22
+ }: SliderProps) {
23
+ return (
24
+ <AriaSlider
25
+ orientation={orientation}
26
+ isDisabled={isDisabled}
27
+ className={cn(
28
+ 'data-[orientation="horizontal"]:w-full data-[orientation="horizontal"]:flex data-[orientation="horizontal"]:flex-wrap',
29
+ 'data-[orientation="vertical"]:w-8 data-[orientation="vertical"]:block data-[orientation="vertical"]:h-full',
30
+ classNames?.base
31
+ )}
32
+ // eslint-disable-next-line react/jsx-props-no-spreading
33
+ {...props}
34
+ >
35
+ {children || (
36
+ <>
37
+ {(label || showOutput) && (
38
+ <div
39
+ className={cn(
40
+ 'w-full flex justify-between items-center',
41
+ orientation === 'vertical' && 'gap-2 w-max mb-2',
42
+ classNames?.outputWrapper
43
+ )}
44
+ >
45
+ <Label
46
+ className={cn(
47
+ 'flex-1 text-sm',
48
+ isDisabled && 'opacity-50',
49
+ classNames?.label
50
+ )}
51
+ >
52
+ {label}
53
+ </Label>
54
+
55
+ {showOutput && (
56
+ <SliderOutput
57
+ className={cn(
58
+ 'flex flex-initial ml-auto justify-end text-sm',
59
+ isDisabled && 'opacity-50',
60
+ classNames?.output
61
+ )}
62
+ >
63
+ {({ state }) =>
64
+ state.values
65
+ .map((_, i) => state.getThumbValueLabel(i))
66
+ .join(' – ')
67
+ }
68
+ </SliderOutput>
69
+ )}
70
+ </div>
71
+ )}
72
+
73
+ <SliderTrack
74
+ className={cn(
75
+ 'data-[orientation="horizontal"]:w-full data-[orientation="horizontal"]:h-8',
76
+ 'data-[orientation="vertical"]:w-8 data-[orientation="vertical"]:h-full',
77
+ 'transition-all duration-200 ease',
78
+ 'before:bg-slate-300 before:block before:absolute cursor-pointer',
79
+ 'data-[orientation="horizontal"]:before:w-full data-[orientation="horizontal"]:before:h-0.5',
80
+ 'data-[orientation="horizontal"]:before:top-1/2 data-[orientation="horizontal"]:before:-translate-y-1/2',
81
+ 'data-[orientation="vertical"]:before:w-0.5 data-[orientation="vertical"]:before:h-full data-[orientation="vertical"]:before:left-1/2 data-[orientation="vertical"]:before:-translate-y-1/2 data-[orientation="vertical"]:before:-translate-x-1/2',
82
+ isDisabled && 'opacity-50 before:cursor-default',
83
+ classNames?.track
84
+ )}
85
+ >
86
+ {({ state }) =>
87
+ state.values.map((_, i) => (
88
+ <SliderThumb
89
+ // eslint-disable-next-line react/no-array-index-key
90
+ key={i}
91
+ index={i}
92
+ aria-label={thumbLabels?.[i]}
93
+ className={cn(
94
+ 'w-6 h-6 bg-slate-300 forced-color-adjust-none cursor-pointer top-1/2',
95
+ 'border-2 border-slate-400 rounded-full',
96
+ isDisabled && 'cursor-default',
97
+ orientation === 'vertical' && 'left-1/2',
98
+ classNames?.thumb
99
+ )}
100
+ />
101
+ ))
102
+ }
103
+ </SliderTrack>
104
+ </>
105
+ )}
106
+ </AriaSlider>
107
+ );
108
+ }
109
+
110
+ export default Slider;
@@ -0,0 +1,11 @@
1
+ import type { SliderProps as AriaSliderProps } from 'react-aria-components';
2
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
3
+
4
+ export type SliderProps = AriaSliderProps & {
5
+ label?: string;
6
+ showOutput?: boolean;
7
+ thumbLabels?: string[];
8
+ classNames?: SlotsToClasses<
9
+ 'base' | 'label' | 'output' | 'outputWrapper' | 'track' | 'thumb'
10
+ >;
11
+ };
@@ -0,0 +1,2 @@
1
+ export { Slider } from './Slider';
2
+ export type { SliderProps } from './Slider.types';