@thangnqdev/ui 0.1.0

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 (253) hide show
  1. package/README.md +196 -0
  2. package/dist/Label-DWOlgSNW.d.cts +14 -0
  3. package/dist/Label-DWOlgSNW.d.ts +14 -0
  4. package/dist/bento/index.cjs +140 -0
  5. package/dist/bento/index.cjs.map +1 -0
  6. package/dist/bento/index.d.cts +4 -0
  7. package/dist/bento/index.d.ts +4 -0
  8. package/dist/bento/index.js +4 -0
  9. package/dist/bento/index.js.map +1 -0
  10. package/dist/bento/styles.css +46 -0
  11. package/dist/buttons/index.cjs +365 -0
  12. package/dist/buttons/index.cjs.map +1 -0
  13. package/dist/buttons/index.d.cts +4 -0
  14. package/dist/buttons/index.d.ts +4 -0
  15. package/dist/buttons/index.js +5 -0
  16. package/dist/buttons/index.js.map +1 -0
  17. package/dist/buttons/styles.css +6 -0
  18. package/dist/chunk-2OXZK6IP.js +514 -0
  19. package/dist/chunk-2OXZK6IP.js.map +1 -0
  20. package/dist/chunk-4S6L5V7X.js +15 -0
  21. package/dist/chunk-4S6L5V7X.js.map +1 -0
  22. package/dist/chunk-5FZ6ZFII.js +728 -0
  23. package/dist/chunk-5FZ6ZFII.js.map +1 -0
  24. package/dist/chunk-66VWUJEI.js +86 -0
  25. package/dist/chunk-66VWUJEI.js.map +1 -0
  26. package/dist/chunk-A7P3O5Z5.js +163 -0
  27. package/dist/chunk-A7P3O5Z5.js.map +1 -0
  28. package/dist/chunk-ATYGDTMK.js +257 -0
  29. package/dist/chunk-ATYGDTMK.js.map +1 -0
  30. package/dist/chunk-C4SUXMI3.js +138 -0
  31. package/dist/chunk-C4SUXMI3.js.map +1 -0
  32. package/dist/chunk-EU2LPUYP.js +25 -0
  33. package/dist/chunk-EU2LPUYP.js.map +1 -0
  34. package/dist/chunk-F5Q7BTMH.js +1991 -0
  35. package/dist/chunk-F5Q7BTMH.js.map +1 -0
  36. package/dist/chunk-GXI2H5XA.js +46 -0
  37. package/dist/chunk-GXI2H5XA.js.map +1 -0
  38. package/dist/chunk-H3LCNPMC.js +101 -0
  39. package/dist/chunk-H3LCNPMC.js.map +1 -0
  40. package/dist/chunk-IABPUCAQ.js +19 -0
  41. package/dist/chunk-IABPUCAQ.js.map +1 -0
  42. package/dist/chunk-KJRBRTQE.js +101 -0
  43. package/dist/chunk-KJRBRTQE.js.map +1 -0
  44. package/dist/chunk-LBUBXUYQ.js +40 -0
  45. package/dist/chunk-LBUBXUYQ.js.map +1 -0
  46. package/dist/chunk-LWFRUJRU.js +537 -0
  47. package/dist/chunk-LWFRUJRU.js.map +1 -0
  48. package/dist/chunk-NFVONJUK.js +882 -0
  49. package/dist/chunk-NFVONJUK.js.map +1 -0
  50. package/dist/chunk-PF4ZHGVW.js +284 -0
  51. package/dist/chunk-PF4ZHGVW.js.map +1 -0
  52. package/dist/chunk-WAFHC4DQ.js +366 -0
  53. package/dist/chunk-WAFHC4DQ.js.map +1 -0
  54. package/dist/chunk-X76KBH4Y.js +71 -0
  55. package/dist/chunk-X76KBH4Y.js.map +1 -0
  56. package/dist/core/index.cjs +103 -0
  57. package/dist/core/index.cjs.map +1 -0
  58. package/dist/core/index.d.cts +3 -0
  59. package/dist/core/index.d.ts +3 -0
  60. package/dist/core/index.js +4 -0
  61. package/dist/core/index.js.map +1 -0
  62. package/dist/core/styles.css +128 -0
  63. package/dist/data-display/index.cjs +1023 -0
  64. package/dist/data-display/index.cjs.map +1 -0
  65. package/dist/data-display/index.d.cts +4 -0
  66. package/dist/data-display/index.d.ts +4 -0
  67. package/dist/data-display/index.js +6 -0
  68. package/dist/data-display/index.js.map +1 -0
  69. package/dist/data-display/styles.css +4 -0
  70. package/dist/feedback/index.cjs +368 -0
  71. package/dist/feedback/index.cjs.map +1 -0
  72. package/dist/feedback/index.d.cts +4 -0
  73. package/dist/feedback/index.d.ts +4 -0
  74. package/dist/feedback/index.js +4 -0
  75. package/dist/feedback/index.js.map +1 -0
  76. package/dist/feedback/styles.css +4 -0
  77. package/dist/forms/index.cjs +2287 -0
  78. package/dist/forms/index.cjs.map +1 -0
  79. package/dist/forms/index.d.cts +5 -0
  80. package/dist/forms/index.d.ts +5 -0
  81. package/dist/forms/index.js +10 -0
  82. package/dist/forms/index.js.map +1 -0
  83. package/dist/forms/styles.css +6 -0
  84. package/dist/glass/index.cjs +165 -0
  85. package/dist/glass/index.cjs.map +1 -0
  86. package/dist/glass/index.d.cts +4 -0
  87. package/dist/glass/index.d.ts +4 -0
  88. package/dist/glass/index.js +4 -0
  89. package/dist/glass/index.js.map +1 -0
  90. package/dist/glass/styles.css +81 -0
  91. package/dist/index-BTvkbCIj.d.cts +146 -0
  92. package/dist/index-BTvkbCIj.d.ts +146 -0
  93. package/dist/index-BkRqGNG1.d.cts +201 -0
  94. package/dist/index-BkRqGNG1.d.ts +201 -0
  95. package/dist/index-BlPcGeW-.d.cts +45 -0
  96. package/dist/index-BlPcGeW-.d.ts +45 -0
  97. package/dist/index-BzOCqvC6.d.cts +32 -0
  98. package/dist/index-BzOCqvC6.d.ts +32 -0
  99. package/dist/index-CCeWYAvn.d.cts +83 -0
  100. package/dist/index-COQ7uwt9.d.cts +44 -0
  101. package/dist/index-COQ7uwt9.d.ts +44 -0
  102. package/dist/index-CenuF_P2.d.cts +221 -0
  103. package/dist/index-CenuF_P2.d.ts +221 -0
  104. package/dist/index-Cle6QzQl.d.ts +371 -0
  105. package/dist/index-D6fE7w-b.d.cts +90 -0
  106. package/dist/index-D6fE7w-b.d.ts +90 -0
  107. package/dist/index-DR8cfJUG.d.ts +83 -0
  108. package/dist/index-DzcWh1Hs.d.cts +131 -0
  109. package/dist/index-DzcWh1Hs.d.ts +131 -0
  110. package/dist/index-TyK-Ldpy.d.cts +371 -0
  111. package/dist/index-ugj5s6a3.d.cts +95 -0
  112. package/dist/index-ugj5s6a3.d.ts +95 -0
  113. package/dist/index.cjs +6400 -0
  114. package/dist/index.cjs.map +1 -0
  115. package/dist/index.d.cts +16 -0
  116. package/dist/index.d.ts +16 -0
  117. package/dist/index.js +21 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/layout/index.cjs +516 -0
  120. package/dist/layout/index.cjs.map +1 -0
  121. package/dist/layout/index.d.cts +4 -0
  122. package/dist/layout/index.d.ts +4 -0
  123. package/dist/layout/index.js +4 -0
  124. package/dist/layout/index.js.map +1 -0
  125. package/dist/layout/styles.css +8 -0
  126. package/dist/navigation/index.cjs +673 -0
  127. package/dist/navigation/index.cjs.map +1 -0
  128. package/dist/navigation/index.d.cts +2 -0
  129. package/dist/navigation/index.d.ts +2 -0
  130. package/dist/navigation/index.js +8 -0
  131. package/dist/navigation/index.js.map +1 -0
  132. package/dist/navigation/styles.css +4 -0
  133. package/dist/overlays/index.cjs +954 -0
  134. package/dist/overlays/index.cjs.map +1 -0
  135. package/dist/overlays/index.d.cts +4 -0
  136. package/dist/overlays/index.d.ts +4 -0
  137. package/dist/overlays/index.js +9 -0
  138. package/dist/overlays/index.js.map +1 -0
  139. package/dist/overlays/styles.css +8 -0
  140. package/dist/styles.css +333 -0
  141. package/dist/typography/index.cjs +311 -0
  142. package/dist/typography/index.cjs.map +1 -0
  143. package/dist/typography/index.d.cts +5 -0
  144. package/dist/typography/index.d.ts +5 -0
  145. package/dist/typography/index.js +6 -0
  146. package/dist/typography/index.js.map +1 -0
  147. package/dist/typography/styles.css +5 -0
  148. package/llms.txt +113 -0
  149. package/package.json +116 -0
  150. package/src/bento/BentoCard.tsx +140 -0
  151. package/src/bento/BentoGrid.tsx +50 -0
  152. package/src/bento/index.ts +2 -0
  153. package/src/bento/tokens.css +46 -0
  154. package/src/buttons/Button.tsx +104 -0
  155. package/src/buttons/ButtonGroup.tsx +43 -0
  156. package/src/buttons/IconButton.tsx +79 -0
  157. package/src/buttons/SplitButton.tsx +86 -0
  158. package/src/buttons/ToggleButton.tsx +157 -0
  159. package/src/buttons/index.ts +5 -0
  160. package/src/buttons/tokens.css +6 -0
  161. package/src/core/ThemeProvider.tsx +123 -0
  162. package/src/core/index.ts +2 -0
  163. package/src/core/tokens.css +128 -0
  164. package/src/data-display/Accordion.tsx +147 -0
  165. package/src/data-display/Avatar.tsx +134 -0
  166. package/src/data-display/Card.tsx +112 -0
  167. package/src/data-display/Carousel.tsx +130 -0
  168. package/src/data-display/Chip.tsx +74 -0
  169. package/src/data-display/DataTablePro.tsx +356 -0
  170. package/src/data-display/StatCard.tsx +60 -0
  171. package/src/data-display/Table.tsx +74 -0
  172. package/src/data-display/Timeline.tsx +62 -0
  173. package/src/data-display/TreeView.tsx +140 -0
  174. package/src/data-display/index.ts +10 -0
  175. package/src/data-display/tokens.css +4 -0
  176. package/src/feedback/Alert.tsx +80 -0
  177. package/src/feedback/EmptyState.tsx +45 -0
  178. package/src/feedback/ProgressBar.tsx +174 -0
  179. package/src/feedback/Skeleton.tsx +66 -0
  180. package/src/feedback/Spinner.tsx +65 -0
  181. package/src/feedback/index.ts +5 -0
  182. package/src/feedback/tokens.css +4 -0
  183. package/src/forms/Checkbox.tsx +69 -0
  184. package/src/forms/CheckboxGroup.tsx +29 -0
  185. package/src/forms/Combobox.tsx +368 -0
  186. package/src/forms/DatePicker.tsx +480 -0
  187. package/src/forms/FileUpload.tsx +222 -0
  188. package/src/forms/FormField.tsx +115 -0
  189. package/src/forms/Input.tsx +151 -0
  190. package/src/forms/MaskedInput.tsx +97 -0
  191. package/src/forms/NumberInput.tsx +248 -0
  192. package/src/forms/PasswordInput.tsx +35 -0
  193. package/src/forms/PinInput.tsx +125 -0
  194. package/src/forms/Radio.tsx +142 -0
  195. package/src/forms/RangeSlider.tsx +111 -0
  196. package/src/forms/SearchInput.tsx +34 -0
  197. package/src/forms/Select.tsx +103 -0
  198. package/src/forms/Slider.tsx +85 -0
  199. package/src/forms/Switch.tsx +95 -0
  200. package/src/forms/Textarea.tsx +114 -0
  201. package/src/forms/index.ts +18 -0
  202. package/src/forms/tokens.css +6 -0
  203. package/src/glass/GlassBadge.tsx +61 -0
  204. package/src/glass/GlassButton.tsx +59 -0
  205. package/src/glass/GlassCard.tsx +71 -0
  206. package/src/glass/index.ts +3 -0
  207. package/src/glass/tokens.css +81 -0
  208. package/src/index.ts +26 -0
  209. package/src/layout/AspectRatio.tsx +39 -0
  210. package/src/layout/Container.tsx +53 -0
  211. package/src/layout/Divider.tsx +61 -0
  212. package/src/layout/Flex.tsx +79 -0
  213. package/src/layout/Grid.tsx +124 -0
  214. package/src/layout/PageLayout.tsx +59 -0
  215. package/src/layout/ScrollArea.tsx +43 -0
  216. package/src/layout/Section.tsx +47 -0
  217. package/src/layout/Spacer.tsx +30 -0
  218. package/src/layout/Stack.tsx +88 -0
  219. package/src/layout/index.ts +10 -0
  220. package/src/layout/tokens.css +8 -0
  221. package/src/navigation/Breadcrumb.tsx +73 -0
  222. package/src/navigation/CommandPalette.tsx +142 -0
  223. package/src/navigation/DropdownMenu.tsx +160 -0
  224. package/src/navigation/Pagination.tsx +114 -0
  225. package/src/navigation/Stepper.tsx +93 -0
  226. package/src/navigation/Tabs.tsx +133 -0
  227. package/src/navigation/index.ts +6 -0
  228. package/src/navigation/tokens.css +4 -0
  229. package/src/overlays/AlertDialog.tsx +54 -0
  230. package/src/overlays/BottomSheet.tsx +123 -0
  231. package/src/overlays/ContextMenu.tsx +119 -0
  232. package/src/overlays/Drawer.tsx +145 -0
  233. package/src/overlays/FocusTrap.tsx +49 -0
  234. package/src/overlays/HoverCard.tsx +72 -0
  235. package/src/overlays/Modal.tsx +219 -0
  236. package/src/overlays/Popover.tsx +126 -0
  237. package/src/overlays/Portal.tsx +23 -0
  238. package/src/overlays/Toast.tsx +137 -0
  239. package/src/overlays/Tooltip.tsx +100 -0
  240. package/src/overlays/index.ts +11 -0
  241. package/src/overlays/tokens.css +8 -0
  242. package/src/styles/tokens.css +2 -0
  243. package/src/typography/Blockquote.tsx +32 -0
  244. package/src/typography/Code.tsx +89 -0
  245. package/src/typography/Heading.tsx +56 -0
  246. package/src/typography/Kbd.tsx +25 -0
  247. package/src/typography/Label.tsx +47 -0
  248. package/src/typography/Link.tsx +56 -0
  249. package/src/typography/Text.tsx +62 -0
  250. package/src/typography/index.ts +7 -0
  251. package/src/typography/tokens.css +5 -0
  252. package/src/utils/cn.ts +6 -0
  253. package/ui-contract.json +62 -0
@@ -0,0 +1,103 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { useFormField } from './FormField';
5
+ import { cn } from '../utils/cn';
6
+
7
+ export const selectVariants = cva(
8
+ 'w-full rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-[var(--ui-text-primary)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 appearance-none pr-10',
9
+ {
10
+ variants: {
11
+ size: {
12
+ sm: 'h-8 px-2.5 text-xs',
13
+ md: 'h-10 px-3.5 text-sm',
14
+ lg: 'h-12 px-4 text-base',
15
+ },
16
+ variant: {
17
+ default: 'border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]',
18
+ filled: 'bg-[var(--ui-bg-secondary)] border-transparent hover:bg-[var(--ui-bg-tertiary)]',
19
+ },
20
+ isInvalid: {
21
+ true: 'border-[var(--ui-error)] focus-visible:ring-[var(--ui-error)]',
22
+ false: '',
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ size: 'md',
27
+ variant: 'default',
28
+ isInvalid: false,
29
+ },
30
+ }
31
+ );
32
+
33
+ export interface SelectOption {
34
+ value: string | number;
35
+ label: string;
36
+ disabled?: boolean;
37
+ }
38
+
39
+ export interface SelectProps
40
+ extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'>,
41
+ VariantProps<typeof selectVariants> {
42
+ options?: SelectOption[];
43
+ placeholder?: string;
44
+ }
45
+
46
+ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
47
+ (
48
+ {
49
+ className,
50
+ size,
51
+ variant,
52
+ isInvalid: customInvalid,
53
+ options,
54
+ placeholder,
55
+ disabled,
56
+ children,
57
+ id,
58
+ ...props
59
+ },
60
+ ref
61
+ ) => {
62
+ const formField = useFormField();
63
+ const isInvalid = customInvalid ?? formField?.isInvalid ?? false;
64
+ const isDisabled = disabled ?? formField?.isDisabled ?? false;
65
+ const inputId = id || formField?.id;
66
+
67
+ return (
68
+ <div className="relative w-full flex items-center">
69
+ <select
70
+ ref={ref}
71
+ id={inputId}
72
+ disabled={isDisabled}
73
+ aria-invalid={isInvalid}
74
+ aria-describedby={
75
+ isInvalid
76
+ ? formField?.errorId
77
+ : formField?.descriptionId || undefined
78
+ }
79
+ className={cn(selectVariants({ size, variant, isInvalid }), className)}
80
+ {...props}
81
+ >
82
+ {placeholder && (
83
+ <option value="" disabled hidden>
84
+ {placeholder}
85
+ </option>
86
+ )}
87
+ {options
88
+ ? options.map((opt) => (
89
+ <option key={opt.value} value={opt.value} disabled={opt.disabled}>
90
+ {opt.label}
91
+ </option>
92
+ ))
93
+ : children}
94
+ </select>
95
+ <div className="absolute right-3 flex items-center pointer-events-none text-[var(--ui-text-muted)]">
96
+ <ChevronDown className="w-4 h-4" />
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+ );
102
+
103
+ Select.displayName = 'Select';
@@ -0,0 +1,85 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue'> {
5
+ value?: number;
6
+ defaultValue?: number;
7
+ min?: number;
8
+ max?: number;
9
+ step?: number;
10
+ showValue?: boolean;
11
+ formatValue?: (val: number) => string;
12
+ onChange?: (val: number) => void;
13
+ }
14
+
15
+ export const Slider = forwardRef<HTMLInputElement, SliderProps>(
16
+ (
17
+ {
18
+ className,
19
+ value: controlledValue,
20
+ defaultValue = 0,
21
+ min = 0,
22
+ max = 100,
23
+ step = 1,
24
+ showValue = false,
25
+ formatValue = (v) => `${v}`,
26
+ onChange,
27
+ disabled,
28
+ ...props
29
+ },
30
+ ref
31
+ ) => {
32
+ const [val, setVal] = React.useState<number>(defaultValue);
33
+ const currentValue = controlledValue !== undefined ? controlledValue : val;
34
+
35
+ const percentage = Math.min(100, Math.max(0, ((currentValue - min) / (max - min)) * 100));
36
+
37
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
38
+ const num = Number(e.target.value);
39
+ setVal(num);
40
+ onChange?.(num);
41
+ };
42
+
43
+ return (
44
+ <div className={cn('w-full flex items-center gap-4', className)}>
45
+ <div className="relative w-full flex items-center h-6 select-none touch-none">
46
+ {/* Rail */}
47
+ <div className="w-full h-2 rounded-full bg-[var(--ui-border-default)] overflow-hidden relative">
48
+ {/* Fill Track */}
49
+ <div
50
+ className="h-full bg-[var(--ui-primary)] transition-all duration-75"
51
+ style={{ width: `${percentage}%` }}
52
+ />
53
+ </div>
54
+
55
+ <input
56
+ ref={ref}
57
+ type="range"
58
+ min={min}
59
+ max={max}
60
+ step={step}
61
+ value={currentValue}
62
+ disabled={disabled}
63
+ onChange={handleChange}
64
+ className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed"
65
+ {...props}
66
+ />
67
+
68
+ {/* Thumb visual */}
69
+ <div
70
+ className="absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md pointer-events-none transition-transform duration-75 -translate-x-1/2"
71
+ style={{ left: `${percentage}%` }}
72
+ />
73
+ </div>
74
+
75
+ {showValue && (
76
+ <span className="text-xs font-semibold font-mono text-[var(--ui-text-secondary)] min-w-[2.5rem] text-right">
77
+ {formatValue(currentValue)}
78
+ </span>
79
+ )}
80
+ </div>
81
+ );
82
+ }
83
+ );
84
+
85
+ Slider.displayName = 'Slider';
@@ -0,0 +1,95 @@
1
+ import React, { forwardRef, useState } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { useFormField } from './FormField';
4
+ import { cn } from '../utils/cn';
5
+
6
+ export const switchVariants = cva(
7
+ 'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-all duration-200 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[#34c759]/30 disabled:cursor-not-allowed disabled:opacity-50 bg-[var(--ui-border-strong)] data-[state=checked]:bg-[#34c759] dark:data-[state=checked]:bg-[#30d158] shadow-inner',
8
+ {
9
+ variants: {
10
+ size: {
11
+ sm: 'h-5 w-9 p-0.5',
12
+ md: 'h-6.5 w-12 p-0.5',
13
+ lg: 'h-8 w-15 p-1',
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ size: 'md',
18
+ },
19
+ }
20
+ );
21
+
22
+ export const switchThumbVariants = cva(
23
+ 'pointer-events-none block rounded-full bg-white shadow-[0_2px_4px_rgba(0,0,0,0.25)] ring-0 transition-transform duration-200 ease-out data-[state=unchecked]:translate-x-0',
24
+ {
25
+ variants: {
26
+ size: {
27
+ sm: 'h-4 w-4 data-[state=checked]:translate-x-4',
28
+ md: 'h-5.5 w-5.5 data-[state=checked]:translate-x-5.5',
29
+ lg: 'h-6 w-6 data-[state=checked]:translate-x-7',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ size: 'md',
34
+ },
35
+ }
36
+ );
37
+
38
+ export interface SwitchProps
39
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
40
+ VariantProps<typeof switchVariants> {
41
+ label?: React.ReactNode;
42
+ checked?: boolean;
43
+ defaultChecked?: boolean;
44
+ onCheckedChange?: (checked: boolean) => void;
45
+ }
46
+
47
+ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
48
+ ({ className, size, label, checked, defaultChecked, onCheckedChange, disabled, id, onChange, ...props }, ref) => {
49
+ const isControlled = checked !== undefined;
50
+ const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
51
+ const isChecked = isControlled ? checked : internalChecked;
52
+
53
+ const formField = useFormField();
54
+ const isDisabled = disabled ?? formField?.isDisabled ?? false;
55
+ const switchId = id || formField?.id;
56
+
57
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
58
+ if (!isControlled) {
59
+ setInternalChecked(e.target.checked);
60
+ }
61
+ onChange?.(e);
62
+ onCheckedChange?.(e.target.checked);
63
+ };
64
+
65
+ return (
66
+ <label className={cn('inline-flex items-center gap-3 select-none cursor-pointer', isDisabled && 'cursor-not-allowed opacity-50')}>
67
+ <div className="relative flex items-center">
68
+ <input
69
+ ref={ref}
70
+ type="checkbox"
71
+ role="switch"
72
+ id={switchId}
73
+ disabled={isDisabled}
74
+ checked={isChecked}
75
+ onChange={handleChange}
76
+ className="sr-only peer"
77
+ {...props}
78
+ />
79
+ <div
80
+ data-state={isChecked ? 'checked' : 'unchecked'}
81
+ className={cn(switchVariants({ size }), className)}
82
+ >
83
+ <span
84
+ data-state={isChecked ? 'checked' : 'unchecked'}
85
+ className={cn(switchThumbVariants({ size }))}
86
+ />
87
+ </div>
88
+ </div>
89
+ {label && <span className="text-sm font-medium text-[var(--ui-text-primary)]">{label}</span>}
90
+ </label>
91
+ );
92
+ }
93
+ );
94
+
95
+ Switch.displayName = 'Switch';
@@ -0,0 +1,114 @@
1
+ import React, { forwardRef, useEffect, useRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { useFormField } from './FormField';
4
+ import { cn } from '../utils/cn';
5
+
6
+ export const textareaVariants = cva(
7
+ 'w-full rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] p-3 text-sm text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 min-h-[80px]',
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: 'border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]',
12
+ filled: 'bg-[var(--ui-bg-secondary)] border-transparent hover:bg-[var(--ui-bg-tertiary)]',
13
+ },
14
+ isInvalid: {
15
+ true: 'border-[var(--ui-error)] focus-visible:ring-[var(--ui-error)]',
16
+ false: '',
17
+ },
18
+ resize: {
19
+ none: 'resize-none',
20
+ vertical: 'resize-y',
21
+ horizontal: 'resize-x',
22
+ both: 'resize',
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ variant: 'default',
27
+ isInvalid: false,
28
+ resize: 'vertical',
29
+ },
30
+ }
31
+ );
32
+
33
+ export interface TextareaProps
34
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
35
+ VariantProps<typeof textareaVariants> {
36
+ autoResize?: boolean;
37
+ }
38
+
39
+ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
40
+ (
41
+ {
42
+ className,
43
+ variant,
44
+ isInvalid: customInvalid,
45
+ resize,
46
+ autoResize = false,
47
+ disabled,
48
+ value,
49
+ onChange,
50
+ id,
51
+ ...props
52
+ },
53
+ ref
54
+ ) => {
55
+ const formField = useFormField();
56
+ const isInvalid = customInvalid ?? formField?.isInvalid ?? false;
57
+ const isDisabled = disabled ?? formField?.isDisabled ?? false;
58
+ const inputId = id || formField?.id;
59
+
60
+ const innerRef = useRef<HTMLTextAreaElement | null>(null);
61
+
62
+ const setMergedRef = (element: HTMLTextAreaElement | null) => {
63
+ innerRef.current = element;
64
+ if (typeof ref === 'function') {
65
+ ref(element);
66
+ } else if (ref) {
67
+ (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = element;
68
+ }
69
+ };
70
+
71
+ const handleInput = () => {
72
+ if (autoResize && innerRef.current) {
73
+ innerRef.current.style.height = 'auto';
74
+ innerRef.current.style.height = `${innerRef.current.scrollHeight}px`;
75
+ }
76
+ };
77
+
78
+ useEffect(() => {
79
+ if (autoResize && innerRef.current) {
80
+ handleInput();
81
+ }
82
+ }, [value, autoResize]);
83
+
84
+ return (
85
+ <textarea
86
+ ref={setMergedRef}
87
+ id={inputId}
88
+ disabled={isDisabled}
89
+ value={value}
90
+ onChange={(e) => {
91
+ handleInput();
92
+ onChange?.(e);
93
+ }}
94
+ aria-invalid={isInvalid}
95
+ aria-describedby={
96
+ isInvalid
97
+ ? formField?.errorId
98
+ : formField?.descriptionId || undefined
99
+ }
100
+ className={cn(
101
+ textareaVariants({
102
+ variant,
103
+ isInvalid,
104
+ resize: autoResize ? 'none' : resize,
105
+ }),
106
+ className
107
+ )}
108
+ {...props}
109
+ />
110
+ );
111
+ }
112
+ );
113
+
114
+ Textarea.displayName = 'Textarea';
@@ -0,0 +1,18 @@
1
+ export * from './FormField';
2
+ export * from './Input';
3
+ export * from './SearchInput';
4
+ export * from './PasswordInput';
5
+ export * from './Textarea';
6
+ export * from './Select';
7
+ export * from './Checkbox';
8
+ export * from './CheckboxGroup';
9
+ export * from './Radio';
10
+ export * from './Switch';
11
+ export * from './Slider';
12
+ export * from './RangeSlider';
13
+ export * from './PinInput';
14
+ export * from './DatePicker';
15
+ export * from './Combobox';
16
+ export * from './FileUpload';
17
+ export * from './NumberInput';
18
+ export * from './MaskedInput';
@@ -0,0 +1,6 @@
1
+ /* Forms Tokens */
2
+ :root {
3
+ --ui-input-height-sm: 2rem;
4
+ --ui-input-height-md: 2.5rem;
5
+ --ui-input-height-lg: 3rem;
6
+ }
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ const glassBadgeVariants = cva(
6
+ 'inline-flex items-center font-sans font-semibold tracking-tight rounded-full backdrop-blur-xl border transition-all select-none',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ 'bg-[var(--ui-bg-secondary)]/80 text-[var(--ui-text-primary)] border-[var(--ui-border-default)] shadow-xs',
12
+ emerald:
13
+ 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/25 shadow-xs shadow-emerald-500/10',
14
+ cyan:
15
+ 'bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/25 shadow-xs shadow-blue-500/10',
16
+ purple:
17
+ 'bg-purple-500/15 text-purple-600 dark:text-purple-400 border-purple-500/25 shadow-xs shadow-purple-500/10',
18
+ amber:
19
+ 'bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/25 shadow-xs shadow-amber-500/10',
20
+ rose:
21
+ 'bg-rose-500/15 text-rose-600 dark:text-rose-400 border-rose-500/25 shadow-xs shadow-rose-500/10',
22
+ },
23
+ size: {
24
+ sm: 'text-[10px] px-2 py-0.5 gap-1',
25
+ md: 'text-[11px] px-2.5 py-0.5 gap-1.5',
26
+ lg: 'text-xs px-3 py-1 gap-1.5',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'default',
31
+ size: 'md',
32
+ },
33
+ }
34
+ );
35
+
36
+ export interface GlassBadgeProps
37
+ extends React.HTMLAttributes<HTMLSpanElement>,
38
+ VariantProps<typeof glassBadgeVariants> {
39
+ dot?: boolean;
40
+ }
41
+
42
+ export const GlassBadge: React.FC<GlassBadgeProps> = ({
43
+ className,
44
+ variant,
45
+ size,
46
+ dot = false,
47
+ children,
48
+ ...props
49
+ }) => {
50
+ return (
51
+ <span className={cn(glassBadgeVariants({ variant, size, className }))} {...props}>
52
+ {dot && (
53
+ <span
54
+ className="h-1.5 w-1.5 rounded-full bg-current motion-safe:animate-pulse shrink-0"
55
+ aria-hidden="true"
56
+ />
57
+ )}
58
+ <span>{children}</span>
59
+ </span>
60
+ );
61
+ };
@@ -0,0 +1,59 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ const glassButtonVariants = cva(
6
+ 'group relative inline-flex items-center justify-center font-medium tracking-tight transition-all duration-300 active:scale-[0.97] disabled:pointer-events-none disabled:opacity-40 overflow-hidden cursor-pointer select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 dark:focus-visible:ring-white/90 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-focus-ring-offset)]',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ 'bg-[var(--ui-glass-btn-default-bg)] hover:bg-[var(--ui-glass-btn-default-hover)] backdrop-blur-md border border-[var(--ui-glass-btn-default-border)] text-[var(--ui-glass-btn-default-text)] shadow-sm dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] hover:border-black/20 dark:hover:border-white/35',
12
+ primary:
13
+ 'bg-slate-900 text-white dark:bg-white dark:text-slate-950 font-semibold shadow-md hover:bg-slate-800 dark:hover:bg-slate-100 border border-slate-700 dark:border-white shadow-[0_4px_20px_rgba(0,0,0,0.2)] dark:shadow-[0_0_24px_rgba(255,255,255,0.25)]',
14
+ luminescent:
15
+ 'bg-gradient-to-r from-slate-800/80 to-slate-900/80 text-white dark:from-white/15 dark:to-white/5 dark:text-white backdrop-blur-xl border border-white/20 dark:border-white/30 shadow-[0_0_20px_-4px_rgba(255,255,255,0.15)] hover:border-white/50 hover:shadow-[0_0_30px_-4px_rgba(255,255,255,0.3)]',
16
+ ghost:
17
+ 'bg-transparent hover:bg-black/5 dark:hover:bg-white/10 backdrop-blur-sm border border-transparent hover:border-black/10 dark:hover:border-white/15 text-[var(--ui-glass-text-secondary)] hover:text-[var(--ui-glass-text-primary)]',
18
+ },
19
+ size: {
20
+ sm: 'h-8 px-3.5 text-xs rounded-xl gap-1.5',
21
+ md: 'h-10 px-5 text-sm rounded-2xl gap-2',
22
+ lg: 'h-12 px-7 text-base rounded-2xl gap-2.5',
23
+ icon: 'h-10 w-10 rounded-2xl',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ size: 'md',
29
+ },
30
+ }
31
+ );
32
+
33
+ export interface GlassButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof glassButtonVariants> {
36
+ shimmer?: boolean;
37
+ }
38
+
39
+ export const GlassButton = forwardRef<HTMLButtonElement, GlassButtonProps>(
40
+ ({ className, variant, size, shimmer = true, children, ...props }, ref) => {
41
+ return (
42
+ <button
43
+ ref={ref}
44
+ className={cn(glassButtonVariants({ variant, size, className }))}
45
+ {...props}
46
+ >
47
+ {shimmer && (
48
+ <span
49
+ className="pointer-events-none absolute -inset-full top-0 block -rotate-45 bg-gradient-to-r from-transparent via-white/25 dark:via-white/15 to-transparent transition-all duration-700 motion-safe:group-hover:translate-x-full"
50
+ aria-hidden="true"
51
+ />
52
+ )}
53
+ <span className="relative z-10 inline-flex items-center gap-2">{children}</span>
54
+ </button>
55
+ );
56
+ }
57
+ );
58
+
59
+ GlassButton.displayName = 'GlassButton';
@@ -0,0 +1,71 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ const glassCardVariants = cva(
6
+ 'relative overflow-hidden transition-all duration-300 text-[var(--ui-glass-text-primary)]',
7
+ {
8
+ variants: {
9
+ intensity: {
10
+ subtle:
11
+ 'bg-[var(--ui-glass-subtle-bg)] backdrop-blur-[var(--ui-glass-subtle-blur)] border border-[var(--ui-glass-subtle-border)]',
12
+ standard:
13
+ 'bg-[var(--ui-glass-standard-bg)] backdrop-blur-[var(--ui-glass-standard-blur)] border border-[var(--ui-glass-standard-border)] shadow-[var(--ui-glass-standard-shadow)]',
14
+ frosted:
15
+ 'bg-[var(--ui-glass-frosted-bg)] backdrop-blur-[var(--ui-glass-frosted-blur)] border border-[var(--ui-glass-frosted-border)] shadow-[var(--ui-glass-frosted-shadow)]',
16
+ acrylic:
17
+ 'bg-[image:var(--ui-glass-acrylic-bg)] backdrop-blur-[var(--ui-glass-acrylic-blur)] border border-[var(--ui-glass-acrylic-border)] shadow-[var(--ui-glass-acrylic-shadow)]',
18
+ },
19
+ glow: {
20
+ none: '',
21
+ cyan: 'hover:shadow-[0_0_40px_-8px_rgba(56,189,248,0.3)] hover:border-sky-400/40',
22
+ purple: 'hover:shadow-[0_0_40px_-8px_rgba(192,132,252,0.3)] hover:border-purple-400/40',
23
+ white: 'hover:shadow-[0_0_35px_-8px_rgba(255,255,255,0.25)] hover:border-white/30',
24
+ },
25
+ radius: {
26
+ md: 'rounded-xl',
27
+ lg: 'rounded-2xl',
28
+ xl: 'rounded-3xl',
29
+ },
30
+ padding: {
31
+ sm: 'p-4',
32
+ md: 'p-6',
33
+ lg: 'p-8',
34
+ },
35
+ },
36
+ defaultVariants: {
37
+ intensity: 'standard',
38
+ glow: 'none',
39
+ radius: 'lg',
40
+ padding: 'md',
41
+ },
42
+ }
43
+ );
44
+
45
+ export interface GlassCardProps
46
+ extends React.HTMLAttributes<HTMLDivElement>,
47
+ VariantProps<typeof glassCardVariants> {
48
+ specularLight?: boolean;
49
+ }
50
+
51
+ export const GlassCard = forwardRef<HTMLDivElement, GlassCardProps>(
52
+ ({ className, intensity, glow, radius, padding, specularLight = true, children, ...props }, ref) => {
53
+ return (
54
+ <div
55
+ ref={ref}
56
+ className={cn(glassCardVariants({ intensity, glow, radius, padding, className }))}
57
+ {...props}
58
+ >
59
+ {specularLight && (
60
+ <div
61
+ className="pointer-events-none absolute inset-x-0 top-0 h-px bg-[image:var(--ui-glass-specular-light)] opacity-80"
62
+ aria-hidden="true"
63
+ />
64
+ )}
65
+ {children}
66
+ </div>
67
+ );
68
+ }
69
+ );
70
+
71
+ GlassCard.displayName = 'GlassCard';
@@ -0,0 +1,3 @@
1
+ export * from './GlassCard';
2
+ export * from './GlassButton';
3
+ export * from './GlassBadge';