@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,44 @@
1
+ import { Variant } from 'framer-motion';
2
+ import type { ReactElement } from 'react';
3
+ import type {
4
+ MiddlewareState,
5
+ Placement,
6
+ FloatingArrowProps,
7
+ UseRoleProps,
8
+ UseFloatingOptions,
9
+ } from '@floating-ui/react';
10
+
11
+ type Trigger = 'click' | 'hover' | 'focus';
12
+
13
+ /** from floating-ui docs https://floating-ui.com/docs/offset */
14
+ interface AxesOffsets {
15
+ mainAxis?: number;
16
+ crossAxis?: number;
17
+ alignmentAxis?: number | null;
18
+ }
19
+ type Options =
20
+ | number
21
+ | AxesOffsets
22
+ | ((state: MiddlewareState) => number | AxesOffsets);
23
+
24
+ export interface MenuProps {
25
+ closeDelay?: number;
26
+ offset?: Options;
27
+ openDelay?: number;
28
+ placement?: Placement;
29
+ slots?: {
30
+ content?: ReactElement;
31
+ contentRoot?: ReactElement;
32
+ submenuRoot?: ReactElement;
33
+ submenu?: ReactElement;
34
+ };
35
+ trigger?: Trigger[];
36
+ withArrow?: boolean;
37
+ arrow?: Omit<FloatingArrowProps, 'context'>;
38
+ role?: UseRoleProps['role'];
39
+ visible?: Variant;
40
+ hidden?: Variant;
41
+ floatingOptions?: UseFloatingOptions;
42
+ isOpen?: boolean;
43
+ onOpenChange?: (isOpen: boolean) => void;
44
+ }
@@ -0,0 +1,4 @@
1
+ export { ContextMenu } from './ContextMenu';
2
+ export { Menu } from './Menu';
3
+
4
+ export type { MenuProps } from './Menu.types';
@@ -0,0 +1,111 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState, useEffect } from 'react';
3
+ import { Meter } from './Meter';
4
+ import { Button } from '../Button/Button';
5
+ import { MeterProps } from './Meter.types';
6
+
7
+ type Story = StoryObj<typeof Meter>;
8
+
9
+ function Component({ children, value: v, ...props }: MeterProps) {
10
+ const [value, setValue] = useState(v || 0);
11
+
12
+ const increase = () =>
13
+ setValue(value === props?.maxValue ? props?.maxValue : value + 10);
14
+
15
+ const decrease = () =>
16
+ setValue(value === props?.minValue ? props?.minValue : value - 10);
17
+
18
+ useEffect(() => {
19
+ if (v) setValue(v);
20
+ }, [v]);
21
+
22
+ return (
23
+ <div
24
+ style={{
25
+ display: 'flex',
26
+ flexDirection: 'column',
27
+ gap: '2rem',
28
+ margin: '2rem 0',
29
+ }}
30
+ >
31
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
32
+ <Meter value={value} {...props} />
33
+
34
+ <div
35
+ style={{
36
+ display: 'flex',
37
+ gap: '2rem',
38
+ justifyContent: 'center',
39
+ }}
40
+ >
41
+ <Button size="sm" onPress={decrease}>
42
+ -
43
+ </Button>
44
+ <Button size="sm" onPress={increase}>
45
+ +
46
+ </Button>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
51
+
52
+ const meta: Meta<typeof Meter> = {
53
+ component: Component,
54
+ };
55
+
56
+ export default meta;
57
+
58
+ export const Base: Story = {
59
+ argTypes: {
60
+ label: {
61
+ control: 'text',
62
+ description: 'The content to display as label',
63
+ },
64
+ showValue: {
65
+ control: 'boolean',
66
+ description: 'Whether the value show',
67
+ },
68
+ formatOptions: {
69
+ control: 'object',
70
+ description: 'The display format of the value label.',
71
+ },
72
+ valueLabel: {
73
+ control: 'text',
74
+ description:
75
+ 'The content to display as the value&apos;s label (e.g. 1 of 4)',
76
+ },
77
+ value: {
78
+ control: 'number',
79
+ description: 'The current value (controlled).',
80
+ },
81
+ minValue: {
82
+ control: 'number',
83
+ description: 'The smallest value allowed for the input.',
84
+ },
85
+ maxValue: {
86
+ control: 'number',
87
+ description: 'The largest value allowed for the input.',
88
+ },
89
+ style: {
90
+ control: 'object',
91
+ description:
92
+ 'The inline style for the element. A function may be provided to compute the style based on component state.',
93
+ },
94
+ className: {
95
+ control: 'text',
96
+ description:
97
+ 'The CSS className for the element. A function may be provided to compute the class based on component state.',
98
+ },
99
+ },
100
+ args: {
101
+ label: '',
102
+ showValue: true,
103
+ formatOptions: { style: 'percent' },
104
+ valueLabel: '',
105
+ value: 0,
106
+ minValue: 0,
107
+ maxValue: 100,
108
+ style: {},
109
+ className: '',
110
+ },
111
+ };
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { Meter as AriaMeter, Label } from 'react-aria-components';
4
+ import { motion } from 'framer-motion';
5
+ import { cn } from '../../utils/cn';
6
+ import type { MeterProps } from './Meter.types';
7
+
8
+ export function Meter({
9
+ label,
10
+ showValue = true,
11
+ value = 0,
12
+ minValue = 0,
13
+ maxValue = 100,
14
+ formatOptions = { style: 'percent' },
15
+ className,
16
+ classNames,
17
+ ...props
18
+ }: MeterProps) {
19
+ return (
20
+ <AriaMeter
21
+ className={cn('flex flex-col gap-2', className)}
22
+ value={value}
23
+ minValue={minValue}
24
+ maxValue={maxValue}
25
+ formatOptions={formatOptions}
26
+ // eslint-disable-next-line react/jsx-props-no-spreading
27
+ {...props}
28
+ >
29
+ {({ percentage, valueText }) => (
30
+ <div
31
+ className={cn(
32
+ 'flex justify-between items-center gap-4',
33
+ classNames?.labelWrapper
34
+ )}
35
+ >
36
+ {label && (
37
+ <Label className={cn('text-sm', classNames?.label)}>{label}</Label>
38
+ )}
39
+ <div
40
+ className={cn(
41
+ 'flex-auto h-2.5 rounded-md bg-slate-300 forced-color-adjust-none overflow-hidden',
42
+ classNames?.trackWrapepr
43
+ )}
44
+ >
45
+ <motion.div
46
+ className={cn('h-full bg-slate-800', classNames?.track)}
47
+ initial={{ width: `${percentage}%` }}
48
+ animate={{ width: `${percentage}%` }}
49
+ transition={{
50
+ type: 'spring',
51
+ bounce: 0,
52
+ }}
53
+ />
54
+ </div>
55
+ {showValue && (
56
+ <span
57
+ className={cn('text-sm tabular-nums ml-auto', classNames?.value)}
58
+ >
59
+ {valueText}
60
+ </span>
61
+ )}
62
+ </div>
63
+ )}
64
+ </AriaMeter>
65
+ );
66
+ }
67
+
68
+ export default Meter;
@@ -0,0 +1,10 @@
1
+ import type { MeterProps as AriaMeterProps } from 'react-aria-components';
2
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
3
+
4
+ export type MeterProps = AriaMeterProps & {
5
+ label?: string;
6
+ showValue?: boolean;
7
+ classNames?: SlotsToClasses<
8
+ 'label' | 'value' | 'labelWrapper' | 'track' | 'trackWrapepr'
9
+ >;
10
+ };
@@ -0,0 +1,2 @@
1
+ export { Meter } from './Meter';
2
+ export type { MeterProps } from './Meter.types';
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+
3
+ import { Modal as UIModal } from 'react-aria-components';
4
+ import { cn } from '../../utils/cn';
5
+ import type { ModalProps } from './Modal.types';
6
+
7
+ export function Modal({ children, className, ...props }: ModalProps) {
8
+ return (
9
+ // eslint-disable-next-line react/jsx-props-no-spreading
10
+ <UIModal {...props} className={cn('absolute inset-1/2', className)}>
11
+ {children}
12
+ </UIModal>
13
+ );
14
+ }
15
+
16
+ export default Modal;
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ModalOverlayProps } from 'react-aria-components';
3
+
4
+ export interface ModalProps extends Omit<ModalOverlayProps, 'children'> {
5
+ children: ReactNode;
6
+ }
@@ -0,0 +1,2 @@
1
+ export { Modal } from './Modal';
2
+ export type { ModalProps } from './Modal.types';
@@ -0,0 +1,121 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, useId } from 'react';
4
+ import type { Dispatch, ReactNode, Ref, SetStateAction } from 'react';
5
+ import { ModalOverlay as UIModalOverlay } from 'react-aria-components';
6
+ import { AnimatePresence, motion } from 'framer-motion';
7
+ import { useDialogTrigger } from '../DialogTrigger/DialogTrigger';
8
+ /** @todo this probably belongs in Modal instead of DialogTrigger */
9
+ import type { DriverAnimationState } from '../DialogTrigger/DialogTrigger.types';
10
+ import { cn } from '../../utils/cn';
11
+ import type { ModalOverlayProps } from './ModalOverlay.types';
12
+
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ const ForwardedModalOverlay = forwardRef<HTMLElement, any>(
15
+ ({ style, ...props }, ref: Ref<HTMLElement>) => {
16
+ // Separate the dynamic style logic
17
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
18
+ const ariaStyle = typeof style === 'function' ? style(props) : style;
19
+
20
+ return (
21
+ // Pass only static styles to framer-motion
22
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, react/jsx-props-no-spreading
23
+ <UIModalOverlay {...props} ref={ref} style={ariaStyle} />
24
+ );
25
+ }
26
+ );
27
+
28
+ const MotionModalOverlay = motion.create(ForwardedModalOverlay);
29
+
30
+ function InnerModalOverlay({
31
+ animate,
32
+ animation,
33
+ setAnimation,
34
+ className,
35
+ animationVariants,
36
+ children,
37
+ ...props
38
+ }: ModalOverlayProps & {
39
+ animate: string;
40
+ animation: string;
41
+ setAnimation: Dispatch<SetStateAction<DriverAnimationState>>;
42
+ }) {
43
+ const id = useId();
44
+
45
+ // extract key from props to avoid spreading it
46
+ const { key, ...restProps } = props;
47
+
48
+ return (
49
+ <MotionModalOverlay
50
+ key={key || id}
51
+ isExiting={animation === 'hidden'}
52
+ onAnimationComplete={(currentAnimation: DriverAnimationState) => {
53
+ setAnimation((a) =>
54
+ currentAnimation === 'hidden' && a === 'hidden' ? 'unmounted' : a
55
+ );
56
+ }}
57
+ variants={
58
+ animationVariants || {
59
+ hidden: {
60
+ opacity: 0,
61
+ backdropFilter: 'blur(0px)',
62
+ transition: {
63
+ delay: 0.08,
64
+ },
65
+ },
66
+ visible: {
67
+ opacity: 1,
68
+ backdropFilter: 'blur(8px)',
69
+ },
70
+ }
71
+ }
72
+ initial="hidden"
73
+ animate={animate}
74
+ exit="hidden"
75
+ className={cn(
76
+ 'bg-black/30',
77
+ 'fixed top-0 left-0',
78
+ 'z-50',
79
+ 'w-screen h-[var(--visual-viewport-height)]',
80
+ className
81
+ )}
82
+ // eslint-disable-next-line react/jsx-props-no-spreading
83
+ {...restProps}
84
+ >
85
+ {children as ReactNode}
86
+ </MotionModalOverlay>
87
+ );
88
+ }
89
+
90
+ export function ModalOverlay(props: ModalOverlayProps) {
91
+ const { isOpen } = props;
92
+ const { animation, setAnimation } = useDialogTrigger();
93
+
94
+ if (isOpen !== undefined) {
95
+ return (
96
+ <AnimatePresence>
97
+ {isOpen && (
98
+ <InnerModalOverlay
99
+ // eslint-disable-next-line react/jsx-props-no-spreading
100
+ {...props}
101
+ animate="visible"
102
+ animation={animation}
103
+ setAnimation={setAnimation}
104
+ />
105
+ )}
106
+ </AnimatePresence>
107
+ );
108
+ }
109
+
110
+ return (
111
+ <InnerModalOverlay
112
+ // eslint-disable-next-line react/jsx-props-no-spreading
113
+ {...props}
114
+ animate={animation}
115
+ animation={animation}
116
+ setAnimation={setAnimation}
117
+ />
118
+ );
119
+ }
120
+
121
+ export default ModalOverlay;
@@ -0,0 +1,18 @@
1
+ import type { Key, ReactNode } from 'react';
2
+ import type {
3
+ ModalOverlayProps as UiModalOverlayProps,
4
+ ModalRenderProps,
5
+ } from 'react-aria-components';
6
+ import type { MotionStyle, Variant } from 'framer-motion';
7
+
8
+ export interface ModalOverlayProps
9
+ extends Omit<UiModalOverlayProps, 'children' | 'style'> {
10
+ key?: Key;
11
+ style?: MotionStyle;
12
+ animationVariants?: { visible: Variant; hidden: Variant };
13
+ children:
14
+ | ReactNode
15
+ | ((
16
+ values: ModalRenderProps & { defaultChildren: ReactNode }
17
+ ) => ReactNode);
18
+ }
@@ -0,0 +1,2 @@
1
+ export { ModalOverlay } from './ModalOverlay';
2
+ export type { ModalOverlayProps } from './ModalOverlay.types';
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+
3
+ import { NumberFormatProps } from './NumberFormat.types';
4
+
5
+ export function NumberFormat({
6
+ locale = 'en-US',
7
+ style = 'decimal',
8
+ fallback = '-',
9
+ children,
10
+ ...options
11
+ }: NumberFormatProps) {
12
+ return children
13
+ ? new Intl.NumberFormat(locale, { ...options, style }).format(
14
+ Number(children)
15
+ )
16
+ : fallback;
17
+ }
18
+
19
+ export default NumberFormat;
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type NumberFormatProps = Omit<Intl.NumberFormatOptions, 'style'> & {
4
+ locale?: string;
5
+ style?: 'decimal' | 'currency' | 'percent' | 'unit';
6
+ fallback?: ReactNode;
7
+ children?: string | number;
8
+ };
@@ -0,0 +1,2 @@
1
+ export { NumberFormat } from './NumberFormat';
2
+ export type { NumberFormatProps } from './NumberFormat.types';
@@ -0,0 +1,164 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Group,
5
+ NumberField,
6
+ Button,
7
+ Input,
8
+ Text,
9
+ FieldError,
10
+ Label,
11
+ } from 'react-aria-components';
12
+ import { cn } from '../../utils/cn';
13
+ import type { NumberInputProps } from './NumberInput.types';
14
+
15
+ const DecrementIcon = (
16
+ <svg
17
+ width="16"
18
+ height="16"
19
+ viewBox="0 0 24 24"
20
+ fill="none"
21
+ xmlns="http://www.w3.org/2000/svg"
22
+ >
23
+ <path
24
+ d="M5 12H19"
25
+ stroke="currentColor"
26
+ strokeWidth="2"
27
+ strokeLinecap="round"
28
+ strokeLinejoin="round"
29
+ />
30
+ </svg>
31
+ );
32
+
33
+ const IncrementIcon = (
34
+ <svg
35
+ width="16"
36
+ height="16"
37
+ viewBox="0 0 24 24"
38
+ fill="none"
39
+ xmlns="http://www.w3.org/2000/svg"
40
+ >
41
+ <path
42
+ d="M12 5V19M5 12H19"
43
+ stroke="currentColor"
44
+ strokeWidth="2"
45
+ strokeLinecap="round"
46
+ strokeLinejoin="round"
47
+ />
48
+ </svg>
49
+ );
50
+
51
+ export function NumberInput({
52
+ label,
53
+ description,
54
+ errorMessage,
55
+ isInvalid,
56
+ isReadOnly,
57
+ slots,
58
+ className,
59
+ classNames,
60
+ ...props
61
+ }: NumberInputProps) {
62
+ const groupClasses = [
63
+ 'group-hover:border-slate-400',
64
+ 'group-disabled:border-slate-200',
65
+ 'group-invalid:border-red-500 group-invalid:bg-red-100 group-invalid:text-red-600',
66
+ 'group-invalid:group-hover:border-red-600',
67
+ 'group-invalid:group-focus:border-red-600',
68
+ ];
69
+ const buttonClasses = [
70
+ 'flex items-center justify-center shrink-0',
71
+ 'rounded-md border-solid border',
72
+ 'font-semibold text-sm',
73
+ 'w-10 h-10 py-0 px-0 cursor-pointer disabled:cursor-default',
74
+ 'transition-all duration-200 ease-in-out',
75
+ 'focus:outline-2 focus:outline focus:outline-slate-200 pressed:scale-95',
76
+ 'bg-transparent hover:bg-transparent focus:bg-transparent disabled:bg-transparent',
77
+ 'border-slate-300 hover:border-slate-400 focus:border-slate-400 disabled:border-slate-200',
78
+ 'text-slate-900 disabled:text-slate-500',
79
+ 'group-aria-readonly:bg-slate-100 group-aria-readonly:text-slate-500 group-aria-readonly:border-slate-200 group-aria-readonly:cursor-default',
80
+ ];
81
+
82
+ return (
83
+ <NumberField
84
+ isInvalid={isInvalid}
85
+ isReadOnly={isReadOnly}
86
+ className={cn('flex flex-col w-full', className)}
87
+ // eslint-disable-next-line react/jsx-props-no-spreading
88
+ {...props}
89
+ >
90
+ <Label className={cn('flex text-slate-500 text-sm', classNames?.label)}>
91
+ {label}
92
+ </Label>
93
+
94
+ <Group
95
+ isInvalid={isInvalid}
96
+ aria-readonly={isReadOnly}
97
+ className={cn(
98
+ 'flex rounded-md group',
99
+ 'focus-within:outline-2 focus-within:outline focus-within:outline-slate-200',
100
+ 'focus-within:invalid:outline-2 focus-within:invalid:outline focus-within:invalid:outline-slate-200',
101
+ 'disabled:bg-slate-100',
102
+ classNames?.group
103
+ )}
104
+ >
105
+ <Button
106
+ slot="decrement"
107
+ className={cn(
108
+ buttonClasses,
109
+ groupClasses,
110
+ 'border-r-0 rounded-r-none',
111
+ classNames?.decrementBtn
112
+ )}
113
+ >
114
+ {slots?.decrementIcon || DecrementIcon}
115
+ </Button>
116
+
117
+ <Input
118
+ className={cn(
119
+ 'border-solid border border-slate-300 text-center',
120
+ 'text-sm text-slate-900 placeholder-slate-400',
121
+ 'h-10 px-2 py-0 m-0 w-full',
122
+ 'bg-white',
123
+ 'transition-all duration-200 ease-in-out',
124
+ 'hover:border-slate-400',
125
+ 'focus:outline-0 focus:border-slate-400',
126
+ 'disabled:border-slate-200 disabled:bg-slate-100',
127
+ 'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
128
+ 'invalid:hover:border-red-600 invalid:focus:border-red-600',
129
+ groupClasses,
130
+ classNames?.input
131
+ )}
132
+ />
133
+
134
+ <Button
135
+ slot="increment"
136
+ className={cn(
137
+ buttonClasses,
138
+ groupClasses,
139
+ 'border-l-0 rounded-l-none',
140
+ classNames?.incrementBtn
141
+ )}
142
+ >
143
+ {slots?.incrementIcon || IncrementIcon}
144
+ </Button>
145
+ </Group>
146
+
147
+ {description && (
148
+ <Text
149
+ className={cn('flex text-slate-500 text-sm', classNames?.description)}
150
+ slot="description"
151
+ >
152
+ {description}
153
+ </Text>
154
+ )}
155
+ <FieldError
156
+ className={cn('flex text-red-500 text-sm', classNames?.error)}
157
+ >
158
+ {errorMessage}
159
+ </FieldError>
160
+ </NumberField>
161
+ );
162
+ }
163
+
164
+ export default NumberInput;
@@ -0,0 +1,22 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { NumberFieldProps, ValidationResult } from 'react-aria-components';
3
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
4
+
5
+ export interface NumberInputProps extends NumberFieldProps {
6
+ label?: string;
7
+ description?: string;
8
+ errorMessage?: string | ((validation: ValidationResult) => string);
9
+ slots?: {
10
+ decrementIcon?: ReactNode;
11
+ incrementIcon?: ReactNode;
12
+ };
13
+ classNames?: SlotsToClasses<
14
+ | 'label'
15
+ | 'input'
16
+ | 'description'
17
+ | 'error'
18
+ | 'incrementBtn'
19
+ | 'decrementBtn'
20
+ | 'group'
21
+ >;
22
+ }
@@ -0,0 +1,2 @@
1
+ export { NumberInput } from './NumberInput';
2
+ export type { NumberInputProps } from './NumberInput.types';