@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,115 @@
1
+ 'use client';
2
+
3
+ import { NumberFormatBase, NumberFormatValues } from 'react-number-format';
4
+ import type { InputAttributes } from 'react-number-format';
5
+ import {
6
+ FieldError,
7
+ Input,
8
+ Label,
9
+ Text,
10
+ TextField,
11
+ } from 'react-aria-components';
12
+ import { useEffect, useState } from 'react';
13
+ import { cn } from '../../utils/cn';
14
+ import type { CreditCardExpirationInputProps } from './CreditCardExpirationInput.types';
15
+
16
+ function AriaInput({ className, ...props }: InputAttributes) {
17
+ return (
18
+ <Input
19
+ className={cn(
20
+ 'border-solid border border-slate-300 rounded-md',
21
+ 'text-sm text-slate-900 placeholder-slate-400',
22
+ 'h-10 px-2 py-0 m-0 w-full',
23
+ 'bg-white',
24
+ 'transition-all duration-200 ease-in-out',
25
+ 'hover:border-slate-400',
26
+ 'focus:outline-2 focus:outline focus:outline-slate-200 focus:border-slate-400',
27
+ 'disabled:border-slate-200 disabled:bg-slate-100',
28
+ 'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
29
+ 'invalid:hover:border-red-600 invalid:focus:border-red-600 invalid:focus:outline-red-200',
30
+ className
31
+ )}
32
+ // eslint-disable-next-line react/jsx-props-no-spreading
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+
38
+ export function CreditCardExpirationInput({
39
+ label,
40
+ description,
41
+ errorMessage,
42
+ placeholder,
43
+ value,
44
+ className,
45
+ classNames,
46
+ onChange,
47
+ ...props
48
+ }: CreditCardExpirationInputProps) {
49
+ const [inputValue, setInputValue] = useState(value || '');
50
+
51
+ useEffect(() => {
52
+ setInputValue(value || '');
53
+ }, [value]);
54
+
55
+ const format = (val: string) => {
56
+ if (val === '') return '';
57
+ let month = val.substring(0, 2);
58
+ const year = val.substring(2, 4);
59
+
60
+ if (month.length === 1 && Number(month[0]) > 1) {
61
+ month = `0${month[0]}`;
62
+ } else if (month.length === 2) {
63
+ // set the lower and upper boundary
64
+ if (Number(month) === 0) {
65
+ month = `01`;
66
+ } else if (Number(month) > 12) {
67
+ month = '12';
68
+ }
69
+ }
70
+
71
+ return `${month}/${year}`;
72
+ };
73
+
74
+ const handleValueChange = (values: NumberFormatValues) => {
75
+ setInputValue(values.formattedValue);
76
+ if (onChange) {
77
+ onChange(values.formattedValue);
78
+ }
79
+ };
80
+
81
+ return (
82
+ <TextField
83
+ className={cn('flex flex-col w-full text-slate-900', className)}
84
+ // eslint-disable-next-line react/jsx-props-no-spreading
85
+ {...props}
86
+ >
87
+ <Label className={cn('flex text-slate-500 text-xs', classNames?.label)}>
88
+ {label}
89
+ </Label>
90
+ <NumberFormatBase
91
+ format={format}
92
+ customInput={AriaInput}
93
+ className={cn(classNames?.input)}
94
+ placeholder={placeholder}
95
+ value={inputValue}
96
+ onValueChange={handleValueChange}
97
+ />
98
+ {description && (
99
+ <Text
100
+ className={cn('flex text-slate-500 text-sm', classNames?.description)}
101
+ slot="description"
102
+ >
103
+ {description}
104
+ </Text>
105
+ )}
106
+ <FieldError
107
+ className={cn('flex text-red-500 text-sm', classNames?.error)}
108
+ >
109
+ {errorMessage}
110
+ </FieldError>
111
+ </TextField>
112
+ );
113
+ }
114
+
115
+ export default CreditCardExpirationInput;
@@ -0,0 +1,10 @@
1
+ import type { TextFieldProps, ValidationResult } from 'react-aria-components';
2
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
3
+
4
+ export interface CreditCardExpirationInputProps extends TextFieldProps {
5
+ label?: string;
6
+ description?: string;
7
+ errorMessage?: string | ((validation: ValidationResult) => string);
8
+ placeholder?: string;
9
+ classNames?: SlotsToClasses<'label' | 'input' | 'description' | 'error'>;
10
+ }
@@ -0,0 +1,2 @@
1
+ export { CreditCardExpirationInput } from './CreditCardExpirationInput';
2
+ export type { CreditCardExpirationInputProps } from './CreditCardExpirationInput.types';
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { NumberFormatBase } from 'react-number-format';
5
+ import type { InputAttributes, NumberFormatValues } from 'react-number-format';
6
+ import {
7
+ FieldError,
8
+ Input,
9
+ Label,
10
+ Text,
11
+ TextField,
12
+ } from 'react-aria-components';
13
+ import { cn } from '../../utils/cn';
14
+ import type { CreditCardInputProps } from './CreditCardInput.types';
15
+
16
+ const getCardFormat = (cardNumber: string) => {
17
+ const cardPatterns = [
18
+ { regex: /^4[0-9]{0,15}/, format: '#### #### #### ####' }, // Visa
19
+ { regex: /^5[1-5][0-9]{0,14}/, format: '#### #### #### ####' }, // MasterCard
20
+ { regex: /^3[47][0-9]{0,13}/, format: '#### ###### #####' }, // American Express
21
+ { regex: /^6(?:011|5[0-9]{2})[0-9]{0,12}/, format: '#### #### #### ####' }, // Discover
22
+ { regex: /^3(?:0[0-5]|[68][0-9])[0-9]{0,11}/, format: '#### ###### ####' }, // Diners Club
23
+ { regex: /^(?:2131|1800|35\d{3})\d{0,11}/, format: '#### #### #### ####' }, // JCB
24
+ ];
25
+
26
+ const matchingPattern = cardPatterns.find((cardPattern) =>
27
+ cardPattern.regex.test(cardNumber)
28
+ );
29
+ if (matchingPattern) {
30
+ return matchingPattern.format;
31
+ }
32
+
33
+ // Default format if no match found
34
+ return '#### #### #### #### ###';
35
+ };
36
+
37
+ function AriaInput({ className, ...props }: InputAttributes) {
38
+ return (
39
+ <Input
40
+ className={cn(
41
+ 'border-solid border border-slate-300 rounded-md',
42
+ 'text-sm text-slate-900 placeholder-slate-400',
43
+ 'h-10 px-2 py-0 m-0 w-full',
44
+ 'bg-white',
45
+ 'transition-all duration-200 ease-in-out',
46
+ 'hover:border-slate-400',
47
+ 'focus:outline-2 focus:outline focus:outline-slate-200 focus:border-slate-400',
48
+ 'disabled:border-slate-200 disabled:bg-slate-100',
49
+ 'invalid:border-red-500 invalid:bg-red-100 invalid:text-red-600',
50
+ 'invalid:hover:border-red-600 invalid:focus:border-red-600 invalid:focus:outline-red-200',
51
+ className
52
+ )}
53
+ // eslint-disable-next-line react/jsx-props-no-spreading
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ export function CreditCardInput({
60
+ label,
61
+ description,
62
+ errorMessage,
63
+ placeholder,
64
+ value,
65
+ onChange,
66
+ className,
67
+ classNames,
68
+ ...props
69
+ }: CreditCardInputProps) {
70
+ const [inputValue, setInputValue] = useState(value || '');
71
+
72
+ useEffect(() => {
73
+ setInputValue(value || '');
74
+ }, [value]);
75
+
76
+ const format = (val: string) => {
77
+ if (val === '') return '';
78
+ const cleanedVal = val.replace(/\s+/g, '');
79
+ const cardFormat = getCardFormat(cleanedVal);
80
+
81
+ // Add spaces according to the format
82
+ let formattedVal = '';
83
+ let position = 0;
84
+
85
+ for (let i = 0; i < cardFormat.length; i += 1) {
86
+ if (cardFormat[i] === '#') {
87
+ if (position < cleanedVal.length) {
88
+ formattedVal += cleanedVal[position];
89
+ position += 1;
90
+ } else {
91
+ break;
92
+ }
93
+ } else {
94
+ formattedVal += cardFormat[i];
95
+ }
96
+ }
97
+
98
+ return formattedVal;
99
+ };
100
+
101
+ const handleValueChange = (values: NumberFormatValues) => {
102
+ const cleanedVal = values.value.replace(/\s+/g, '');
103
+ const cardFormat = getCardFormat(cleanedVal);
104
+ const maxLength = cardFormat.replace(/[^#]/g, '').length;
105
+ const truncatedVal = cleanedVal.slice(0, maxLength);
106
+
107
+ setInputValue(truncatedVal);
108
+ if (onChange) {
109
+ onChange(truncatedVal);
110
+ }
111
+ };
112
+
113
+ return (
114
+ <TextField
115
+ className={cn('flex flex-col w-full text-slate-900', className)}
116
+ // eslint-disable-next-line react/jsx-props-no-spreading
117
+ {...props}
118
+ >
119
+ <Label className={cn('flex text-slate-500 text-sm', classNames?.label)}>
120
+ {label}
121
+ </Label>
122
+ <NumberFormatBase
123
+ value={inputValue}
124
+ format={format}
125
+ customInput={AriaInput}
126
+ className={cn(classNames?.input)}
127
+ placeholder={placeholder}
128
+ onValueChange={handleValueChange}
129
+ />
130
+ {description && (
131
+ <Text
132
+ className={cn('flex text-slate-500 text-sm', classNames?.description)}
133
+ slot="description"
134
+ >
135
+ {description}
136
+ </Text>
137
+ )}
138
+ <FieldError
139
+ className={cn('flex text-red-500 text-sm', classNames?.error)}
140
+ >
141
+ {errorMessage}
142
+ </FieldError>
143
+ </TextField>
144
+ );
145
+ }
146
+
147
+ export default CreditCardInput;
@@ -0,0 +1,12 @@
1
+ import type { TextFieldProps, ValidationResult } from 'react-aria-components';
2
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
3
+
4
+ export interface CreditCardInputProps extends TextFieldProps {
5
+ label?: string;
6
+ description?: string;
7
+ errorMessage?: string | ((validation: ValidationResult) => string);
8
+ placeholder?: string;
9
+ value?: string;
10
+ onChange?: (value: string) => void;
11
+ classNames?: SlotsToClasses<'label' | 'input' | 'description' | 'error'>;
12
+ }
@@ -0,0 +1,2 @@
1
+ export { CreditCardInput } from './CreditCardInput';
2
+ export type { CreditCardInputProps } from './CreditCardInput.types';
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+
3
+ import {
4
+ DateField,
5
+ DateInput as AriaDateInput,
6
+ DateSegment,
7
+ FieldError,
8
+ Label,
9
+ Text,
10
+ } from 'react-aria-components';
11
+ import { cn } from '../../utils/cn';
12
+ import type { DateInputProps } from './DateInput.types';
13
+
14
+ export function DateInput({
15
+ label,
16
+ description,
17
+ errorMessage,
18
+ className,
19
+ classNames,
20
+ ...props
21
+ }: DateInputProps) {
22
+ return (
23
+ <DateField
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-xs', classNames?.label)}>
29
+ {label}
30
+ </Label>
31
+ <AriaDateInput
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',
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
+ </AriaDateInput>
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
+ </DateField>
78
+ );
79
+ }
80
+
81
+ export default DateInput;
@@ -0,0 +1,15 @@
1
+ import type {
2
+ DateFieldProps,
3
+ DateValue,
4
+ ValidationResult,
5
+ } from 'react-aria-components';
6
+ import type { SlotsToClasses } from '../../types/SlotsToClasses';
7
+
8
+ export interface DateInputProps extends DateFieldProps<DateValue> {
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 { DateInput } from './DateInput';
2
+ export type { DateInputProps } from './DateInput.types';
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+
3
+ import type { DateTimeFormatProps } from './DateTimeFormat.types';
4
+
5
+ export function DateTimeFormat({
6
+ locale = 'en-US',
7
+ fallback = '-',
8
+ children,
9
+ ...options
10
+ }: DateTimeFormatProps) {
11
+ return children && !Number.isNaN(new Date(children).valueOf())
12
+ ? new Intl.DateTimeFormat(locale, options).format(new Date(children))
13
+ : fallback;
14
+ }
15
+
16
+ export default DateTimeFormat;
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type DateTimeFormatProps = Intl.DateTimeFormatOptions & {
4
+ locale?: string;
5
+ fallback?: ReactNode;
6
+ children?: string | Date;
7
+ };
@@ -0,0 +1,2 @@
1
+ export { DateTimeFormat } from './DateTimeFormat';
2
+ export type { DateTimeFormatProps } from './DateTimeFormat.types';
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import { forwardRef } from 'react';
4
+ import type { Ref, ReactNode } from 'react';
5
+ import { Dialog as AriaDialog } from 'react-aria-components';
6
+ import { motion } from 'framer-motion';
7
+ import { cn } from '../../utils/cn';
8
+ import type { DialogProps } from './Dialog.types';
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const ForwardedDialog = forwardRef<HTMLElement, any>(
12
+ ({ style, animationVariants, ...props }, ref: Ref<HTMLElement>) => {
13
+ // Separate the dynamic style logic
14
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
15
+ const ariaStyle = typeof style === 'function' ? style(props) : style;
16
+
17
+ return (
18
+ // Pass only static styles to framer-motion
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, react/jsx-props-no-spreading
20
+ <AriaDialog {...props} ref={ref} style={ariaStyle} />
21
+ );
22
+ }
23
+ );
24
+
25
+ const MotionDialog = motion.create(ForwardedDialog);
26
+
27
+ export function Dialog({
28
+ className,
29
+ children,
30
+ animationVariants,
31
+ ...props
32
+ }: DialogProps) {
33
+ return (
34
+ <MotionDialog
35
+ className={cn(
36
+ 'p-8 outline-0 max-w-max w-screen absolute top-2/4 left-2/4',
37
+ 'shadow-[0_8px_24px_rgba(0,0,0,0.1)] rounded-lg bg-white border border-solid border-slate-400',
38
+ 'transform -translate-x-1/2 -translate-y-1/2',
39
+ className
40
+ )}
41
+ // eslint-disable-next-line react/jsx-props-no-spreading
42
+ {...props}
43
+ variants={
44
+ animationVariants || {
45
+ hidden: {
46
+ transform: 'translate(-50%, -50%) scale(0.8)',
47
+ transition: {
48
+ ease: 'backIn',
49
+ },
50
+ },
51
+ visible: {
52
+ transform: 'translate(-50%, -50%) scale(1)',
53
+ transition: {
54
+ ease: 'backOut',
55
+ },
56
+ },
57
+ }
58
+ }
59
+ >
60
+ {children as ReactNode}
61
+ </MotionDialog>
62
+ );
63
+ }
64
+
65
+ export default Dialog;
@@ -0,0 +1,9 @@
1
+ import type { Key } from 'react';
2
+ import type { DialogProps as UIDialogProps } from 'react-aria-components';
3
+ import { MotionStyle, Variants } from 'framer-motion';
4
+
5
+ export type DialogProps = UIDialogProps & {
6
+ key?: Key;
7
+ style?: MotionStyle;
8
+ animationVariants?: Variants;
9
+ };
@@ -0,0 +1,2 @@
1
+ export { Dialog } from './Dialog';
2
+ export type { DialogProps } from './Dialog.types';
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import { createContext, useState, useMemo, useContext } from 'react';
4
+ import type { Dispatch, SetStateAction } from 'react';
5
+ import { DialogTrigger as UIDialogTrigger } from 'react-aria-components';
6
+ import type { DialogTriggerProps } from 'react-aria-components';
7
+ import type { DriverAnimationState } from './DialogTrigger.types';
8
+
9
+ const DialogTriggerContext = createContext<{
10
+ animation: DriverAnimationState;
11
+ setAnimation: Dispatch<SetStateAction<DriverAnimationState>>;
12
+ onOpenChange: (isOpen: boolean) => void;
13
+ }>({
14
+ animation: 'unmounted',
15
+ setAnimation: () => {},
16
+ onOpenChange: () => {},
17
+ });
18
+
19
+ export const useDialogTrigger = () => useContext(DialogTriggerContext);
20
+
21
+ export function DialogTrigger(props: DialogTriggerProps) {
22
+ const [animation, setAnimation] = useState<DriverAnimationState>('unmounted');
23
+
24
+ const onOpenChange = (isOpen: boolean) => {
25
+ setAnimation(isOpen ? 'visible' : 'hidden');
26
+ };
27
+
28
+ const value = useMemo(
29
+ () => ({
30
+ animation,
31
+ setAnimation,
32
+ onOpenChange,
33
+ }),
34
+ [animation]
35
+ );
36
+
37
+ return (
38
+ <DialogTriggerContext.Provider value={value}>
39
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
40
+ <UIDialogTrigger {...props} onOpenChange={onOpenChange} />
41
+ </DialogTriggerContext.Provider>
42
+ );
43
+ }
44
+
45
+ export default DialogTrigger;
@@ -0,0 +1,6 @@
1
+ import type { DialogTriggerProps as UIDialogTriggerProps } from 'react-aria-components';
2
+
3
+ /** @todo this feels like it should be renamed, maybe moved to modal directly */
4
+ export type DriverAnimationState = 'unmounted' | 'visible' | 'hidden';
5
+
6
+ export type DialogTriggerProps = UIDialogTriggerProps;
@@ -0,0 +1,5 @@
1
+ export { DialogTrigger, useDialogTrigger } from './DialogTrigger';
2
+ export type {
3
+ DriverAnimationState,
4
+ DialogTriggerProps,
5
+ } from './DialogTrigger.types';
@@ -0,0 +1,37 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { SeparatorProps } from 'react-aria';
3
+ import { Divider } from './Divider';
4
+
5
+ type Story = StoryObj<typeof Divider>;
6
+
7
+ function Component(props: SeparatorProps) {
8
+ const { orientation } = props;
9
+ const display = orientation === 'vertical' ? 'flex' : 'block';
10
+
11
+ return (
12
+ <div style={{ display }}>
13
+ <div>Lorem ipsum dolor sit amet consectetur, adipisicing elit.</div>
14
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
15
+ <Divider {...props} />
16
+ <div>Lorem ipsum dolor sit amet consectetur, adipisicing elit.</div>
17
+ </div>
18
+ );
19
+ }
20
+
21
+ const meta: Meta<typeof Divider> = {
22
+ component: Component,
23
+ };
24
+
25
+ export default meta;
26
+
27
+ export const Example: Story = {
28
+ argTypes: {
29
+ orientation: {
30
+ options: ['horizontal', 'vertical'],
31
+ control: 'select',
32
+ },
33
+ },
34
+ args: {
35
+ orientation: 'horizontal',
36
+ },
37
+ };
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useSeparator } from 'react-aria';
4
+ import { cn } from '../../utils/cn';
5
+ import type { DividerProps } from './Divider.types';
6
+
7
+ export function Divider({
8
+ orientation = 'horizontal',
9
+ className,
10
+ ...props
11
+ }: DividerProps) {
12
+ const { separatorProps } = useSeparator({ ...props, orientation });
13
+ const combinedProps = { ...props, ...separatorProps };
14
+
15
+ return (
16
+ <div
17
+ // eslint-disable-next-line react/jsx-props-no-spreading
18
+ {...combinedProps}
19
+ className={cn(
20
+ 'bg-slate-200',
21
+ ['w-full', 'h-px', 'my-4', 'mx-0'],
22
+ [
23
+ 'aria-[orientation=vertical]:w-px',
24
+ 'aria-[orientation=vertical]:h-auto',
25
+ 'aria-[orientation=vertical]:my-0',
26
+ 'aria-[orientation=vertical]:mx-4',
27
+ ],
28
+ className
29
+ )}
30
+ />
31
+ );
32
+ }
33
+
34
+ export default Divider;
@@ -0,0 +1,5 @@
1
+ import { SeparatorProps } from 'react-aria';
2
+
3
+ export type DividerProps = SeparatorProps & {
4
+ className?: string;
5
+ };
@@ -0,0 +1,2 @@
1
+ export { Divider } from './Divider';
2
+ export type { DividerProps } from './Divider.types';