@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,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,8 @@
1
+ import type {
2
+ SwitchProps as UISwitchProps,
3
+ SwitchRenderProps as UISwitchRenderProps,
4
+ } from 'react-aria-components';
5
+
6
+ export type SwitchProps = UISwitchProps;
7
+
8
+ export type SwitchRenderProps = UISwitchRenderProps;
@@ -0,0 +1,2 @@
1
+ export { Switch } from './Switch';
2
+ export type { SwitchProps, SwitchRenderProps } from './Switch.types';
@@ -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,2 @@
1
+ export { Table } from './Table';
2
+ export type { TableProps } from './Table.types';
@@ -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,3 @@
1
+ import { TabPanelProps as AriaTabPanelProps } from 'react-aria-components';
2
+
3
+ export type TabPanelProps = AriaTabPanelProps;
@@ -0,0 +1,9 @@
1
+ 'use client';
2
+
3
+ import { createContext } from 'react';
4
+ import type { TabsProps } from './Tabs.types';
5
+
6
+ export const TabsOrientationContext =
7
+ createContext<TabsProps['orientation']>('horizontal');
8
+
9
+ export default TabsOrientationContext;
@@ -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,3 @@
1
+ import type { TabsProps as AriaTabsProps } from 'react-aria-components';
2
+
3
+ export type TabsProps = AriaTabsProps;
@@ -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&apos;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
+ }
@@ -0,0 +1,2 @@
1
+ export { TimeInput } from './TimeInput';
2
+ export type { TimeInputProps } from './TimeInput.types';