@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,10 @@
1
+ export * from './Card';
2
+ export * from './Table';
3
+ export * from './Avatar';
4
+ export * from './Accordion';
5
+ export * from './Chip';
6
+ export * from './StatCard';
7
+ export * from './Timeline';
8
+ export * from './DataTablePro';
9
+ export * from './TreeView';
10
+ export * from './Carousel';
@@ -0,0 +1,4 @@
1
+ /* Data Display Tokens */
2
+ :root {
3
+ --ui-avatar-ring-width: 2px;
4
+ }
@@ -0,0 +1,80 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { CheckCircle2, AlertTriangle, AlertCircle, Info, X } from 'lucide-react';
4
+ import { cn } from '../utils/cn';
5
+
6
+ export const alertVariants = cva(
7
+ 'relative w-full rounded-[var(--ui-radius-lg)] border p-4 text-sm flex items-start gap-3.5 shadow-sm',
8
+ {
9
+ variants: {
10
+ status: {
11
+ info: 'bg-[var(--ui-info-subtle)] border-[var(--ui-info)]/30 text-[var(--ui-text-primary)] [&>svg]:text-[var(--ui-info)]',
12
+ success: 'bg-[var(--ui-success-subtle)] border-[var(--ui-success)]/30 text-[var(--ui-text-primary)] [&>svg]:text-[var(--ui-success)]',
13
+ warning: 'bg-[var(--ui-warning-subtle)] border-[var(--ui-warning)]/30 text-[var(--ui-text-primary)] [&>svg]:text-[var(--ui-warning)]',
14
+ error: 'bg-[var(--ui-error-subtle)] border-[var(--ui-error)]/30 text-[var(--ui-text-primary)] [&>svg]:text-[var(--ui-error)]',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ status: 'info',
19
+ },
20
+ }
21
+ );
22
+
23
+ const statusIcons = {
24
+ info: <Info className="w-5 h-5 flex-shrink-0" />,
25
+ success: <CheckCircle2 className="w-5 h-5 flex-shrink-0" />,
26
+ warning: <AlertTriangle className="w-5 h-5 flex-shrink-0" />,
27
+ error: <AlertCircle className="w-5 h-5 flex-shrink-0" />,
28
+ };
29
+
30
+ export interface AlertProps
31
+ extends React.HTMLAttributes<HTMLDivElement>,
32
+ VariantProps<typeof alertVariants> {
33
+ icon?: React.ReactNode;
34
+ onClose?: () => void;
35
+ }
36
+
37
+ export const Alert = forwardRef<HTMLDivElement, AlertProps>(
38
+ ({ className, status = 'info', icon, onClose, children, ...props }, ref) => {
39
+ return (
40
+ <div
41
+ ref={ref}
42
+ role="alert"
43
+ className={cn(alertVariants({ status }), className)}
44
+ {...props}
45
+ >
46
+ {icon !== undefined ? icon : statusIcons[status || 'info']}
47
+ <div className="flex-1 space-y-1">{children}</div>
48
+ {onClose && (
49
+ <button
50
+ type="button"
51
+ onClick={onClose}
52
+ aria-label="Dismiss alert"
53
+ className="p-1 rounded text-[var(--ui-text-muted)] hover:text-[var(--ui-text-primary)] hover:bg-black/5 dark:hover:bg-white/5 transition-colors -mr-1 -mt-1"
54
+ >
55
+ <X className="w-4 h-4" />
56
+ </button>
57
+ )}
58
+ </div>
59
+ );
60
+ }
61
+ );
62
+ Alert.displayName = 'Alert';
63
+
64
+ export const AlertTitle = forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
65
+ ({ className, children, ...props }, ref) => (
66
+ <h5 ref={ref} className={cn('font-semibold leading-none tracking-tight text-[var(--ui-text-primary)]', className)} {...props}>
67
+ {children}
68
+ </h5>
69
+ )
70
+ );
71
+ AlertTitle.displayName = 'AlertTitle';
72
+
73
+ export const AlertDescription = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
74
+ ({ className, children, ...props }, ref) => (
75
+ <p ref={ref} className={cn('text-xs text-[var(--ui-text-secondary)] leading-relaxed', className)} {...props}>
76
+ {children}
77
+ </p>
78
+ )
79
+ );
80
+ AlertDescription.displayName = 'AlertDescription';
@@ -0,0 +1,45 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { Inbox } from 'lucide-react';
3
+ import { cn } from '../utils/cn';
4
+
5
+ export interface EmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
6
+ icon?: React.ReactNode;
7
+ title: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ action?: React.ReactNode;
10
+ }
11
+
12
+ export const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(
13
+ ({ className, icon, title, description, action, children, ...props }, ref) => {
14
+ return (
15
+ <div
16
+ ref={ref}
17
+ className={cn(
18
+ 'flex flex-col items-center justify-center p-8 sm:p-12 text-center rounded-[var(--ui-radius-xl)] border border-dashed border-[var(--ui-border-default)] bg-[var(--ui-bg-secondary)]/50',
19
+ className
20
+ )}
21
+ {...props}
22
+ >
23
+ <div className="flex items-center justify-center w-14 h-14 rounded-full bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-muted)] mb-4">
24
+ {icon || <Inbox className="w-7 h-7 stroke-[1.5]" />}
25
+ </div>
26
+
27
+ <h3 className="text-base sm:text-lg font-bold text-[var(--ui-text-primary)] mb-1">
28
+ {title}
29
+ </h3>
30
+
31
+ {description && (
32
+ <p className="text-xs sm:text-sm text-[var(--ui-text-muted)] max-w-sm mb-6 leading-relaxed">
33
+ {description}
34
+ </p>
35
+ )}
36
+
37
+ {action && <div className="inline-flex items-center gap-3">{action}</div>}
38
+
39
+ {children}
40
+ </div>
41
+ );
42
+ }
43
+ );
44
+
45
+ EmptyState.displayName = 'EmptyState';
@@ -0,0 +1,174 @@
1
+ import React, { forwardRef, useId } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ export const progressVariants = cva('w-full overflow-hidden rounded-full bg-[var(--ui-bg-tertiary)]', {
6
+ variants: {
7
+ size: {
8
+ sm: 'h-1.5',
9
+ md: 'h-2.5',
10
+ lg: 'h-4',
11
+ },
12
+ variant: {
13
+ primary: '[&>div]:bg-[var(--ui-primary)]',
14
+ secondary: '[&>div]:bg-[var(--ui-secondary)]',
15
+ success: '[&>div]:bg-[var(--ui-success)]',
16
+ warning: '[&>div]:bg-[var(--ui-warning)]',
17
+ error: '[&>div]:bg-[var(--ui-error)]',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ size: 'md',
22
+ variant: 'primary',
23
+ },
24
+ });
25
+
26
+ export interface ProgressBarProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof progressVariants> {
29
+ value: number;
30
+ max?: number;
31
+ showValue?: boolean;
32
+ striped?: boolean;
33
+ }
34
+
35
+ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
36
+ ({ className, size, variant, value, max = 100, showValue = false, striped = false, ...props }, ref) => {
37
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
38
+
39
+ return (
40
+ <div className="w-full space-y-1">
41
+ {showValue && (
42
+ <div className="flex justify-between text-xs font-semibold text-[var(--ui-text-muted)] font-mono">
43
+ <span>Progress</span>
44
+ <span>{Math.round(percentage)}%</span>
45
+ </div>
46
+ )}
47
+ <div
48
+ ref={ref}
49
+ role="progressbar"
50
+ aria-valuenow={value}
51
+ aria-valuemin={0}
52
+ aria-valuemax={max}
53
+ className={cn(progressVariants({ size, variant }), className)}
54
+ {...props}
55
+ >
56
+ <div
57
+ className={cn(
58
+ 'h-full transition-all duration-300 ease-out rounded-full',
59
+ striped && 'bg-[length:1rem_1rem] bg-gradient-to-r from-transparent via-white/20 to-transparent animate-pulse'
60
+ )}
61
+ style={{ width: `${percentage}%` }}
62
+ />
63
+ </div>
64
+ </div>
65
+ );
66
+ }
67
+ );
68
+ ProgressBar.displayName = 'ProgressBar';
69
+
70
+ export interface CircularProgressProps extends React.SVGAttributes<SVGSVGElement> {
71
+ value: number;
72
+ max?: number;
73
+ size?: number;
74
+ strokeWidth?: number;
75
+ showValue?: boolean;
76
+ variant?: 'primary' | 'success' | 'warning' | 'error' | 'purple' | 'cyan';
77
+ trackColor?: string;
78
+ glow?: boolean;
79
+ }
80
+
81
+ export const CircularProgress = forwardRef<SVGSVGElement, CircularProgressProps>(
82
+ (
83
+ {
84
+ className,
85
+ value,
86
+ max = 100,
87
+ size = 64,
88
+ strokeWidth = 6,
89
+ showValue = true,
90
+ variant = 'primary',
91
+ trackColor,
92
+ glow = false,
93
+ ...props
94
+ },
95
+ ref
96
+ ) => {
97
+ const gradId = useId().replace(/:/g, '');
98
+ const radius = (size - strokeWidth) / 2;
99
+ const circumference = radius * 2 * Math.PI;
100
+ const percentage = Math.min(100, Math.max(0, (value / max) * 100));
101
+ const strokeDashoffset = circumference - (percentage / 100) * circumference;
102
+
103
+ const gradients: Record<string, [string, string]> = {
104
+ primary: ['#0071e3', '#2997ff'],
105
+ success: ['#34c759', '#30d158'],
106
+ warning: ['#ff9f0a', '#ffd60a'],
107
+ error: ['#ff3b30', '#ff453a'],
108
+ purple: ['#af52de', '#bf5af2'],
109
+ cyan: ['#00c7be', '#30b0c7'],
110
+ };
111
+
112
+ const [startColor, endColor] = gradients[variant] || gradients.primary;
113
+
114
+ return (
115
+ <div
116
+ className={cn('relative inline-flex items-center justify-center select-none', glow && 'drop-shadow-md')}
117
+ style={{ width: size, height: size }}
118
+ >
119
+ <svg
120
+ ref={ref}
121
+ width={size}
122
+ height={size}
123
+ viewBox={`0 0 ${size} ${size}`}
124
+ className={cn('-rotate-90', className)}
125
+ {...props}
126
+ >
127
+ <defs>
128
+ <linearGradient id={`circle-grad-${gradId}`} x1="0%" y1="0%" x2="100%" y2="100%">
129
+ <stop offset="0%" stopColor={startColor} />
130
+ <stop offset="100%" stopColor={endColor} />
131
+ </linearGradient>
132
+ </defs>
133
+
134
+ {/* Apple Background track */}
135
+ <circle
136
+ cx={size / 2}
137
+ cy={size / 2}
138
+ r={radius}
139
+ stroke={trackColor || 'currentColor'}
140
+ strokeWidth={strokeWidth}
141
+ fill="none"
142
+ className="text-[var(--ui-border-default)]/40 dark:text-white/10 opacity-70"
143
+ />
144
+
145
+ {/* Apple Progress gradient stroke */}
146
+ <circle
147
+ cx={size / 2}
148
+ cy={size / 2}
149
+ r={radius}
150
+ stroke={`url(#circle-grad-${gradId})`}
151
+ strokeWidth={strokeWidth}
152
+ strokeDasharray={circumference}
153
+ strokeDashoffset={strokeDashoffset}
154
+ strokeLinecap="round"
155
+ fill="none"
156
+ className="transition-all duration-500 ease-out"
157
+ />
158
+ </svg>
159
+
160
+ {showValue && (
161
+ <div className="absolute inset-0 flex items-center justify-center leading-none">
162
+ <span
163
+ className="font-sans font-extrabold tracking-tight text-[var(--ui-text-primary)]"
164
+ style={{ fontSize: `${Math.max(10, Math.round(size * 0.23))}px` }}
165
+ >
166
+ {Math.round(percentage)}%
167
+ </span>
168
+ </div>
169
+ )}
170
+ </div>
171
+ );
172
+ }
173
+ );
174
+ CircularProgress.displayName = 'CircularProgress';
@@ -0,0 +1,66 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ circle?: boolean;
6
+ }
7
+
8
+ export const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(
9
+ ({ className, circle = false, ...props }, ref) => {
10
+ return (
11
+ <div
12
+ ref={ref}
13
+ aria-hidden="true"
14
+ className={cn(
15
+ 'animate-pulse bg-[var(--ui-bg-tertiary)]',
16
+ circle ? 'rounded-full' : 'rounded-[var(--ui-radius-md)]',
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ );
22
+ }
23
+ );
24
+ Skeleton.displayName = 'Skeleton';
25
+
26
+ export interface SkeletonTextProps extends React.HTMLAttributes<HTMLDivElement> {
27
+ lines?: number;
28
+ gap?: 'sm' | 'md' | 'lg';
29
+ }
30
+
31
+ export const SkeletonText = forwardRef<HTMLDivElement, SkeletonTextProps>(
32
+ ({ className, lines = 3, gap = 'md', ...props }, ref) => {
33
+ const gapClass = gap === 'sm' ? 'space-y-1.5' : gap === 'lg' ? 'space-y-3' : 'space-y-2';
34
+
35
+ return (
36
+ <div ref={ref} aria-hidden="true" className={cn('w-full', gapClass, className)} {...props}>
37
+ {Array.from({ length: lines }).map((_, i) => (
38
+ <Skeleton
39
+ key={i}
40
+ className={cn('h-4 w-full', i === lines - 1 && 'w-4/5')}
41
+ />
42
+ ))}
43
+ </div>
44
+ );
45
+ }
46
+ );
47
+ SkeletonText.displayName = 'SkeletonText';
48
+
49
+ export interface SkeletonCircleProps extends React.HTMLAttributes<HTMLDivElement> {
50
+ size?: number | string;
51
+ }
52
+
53
+ export const SkeletonCircle = forwardRef<HTMLDivElement, SkeletonCircleProps>(
54
+ ({ className, size = '2.5rem', style, ...props }, ref) => {
55
+ return (
56
+ <Skeleton
57
+ ref={ref}
58
+ circle
59
+ style={{ width: size, height: size, ...style }}
60
+ className={className}
61
+ {...props}
62
+ />
63
+ );
64
+ }
65
+ );
66
+ SkeletonCircle.displayName = 'SkeletonCircle';
@@ -0,0 +1,65 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ export const spinnerVariants = cva('animate-spin inline-block', {
6
+ variants: {
7
+ size: {
8
+ xs: 'w-3 h-3',
9
+ sm: 'w-4 h-4',
10
+ md: 'w-6 h-6',
11
+ lg: 'w-8 h-8',
12
+ xl: 'w-12 h-12',
13
+ },
14
+ variant: {
15
+ primary: 'text-[var(--ui-primary)]',
16
+ secondary: 'text-[var(--ui-secondary)]',
17
+ current: 'text-current',
18
+ muted: 'text-[var(--ui-text-muted)]',
19
+ white: 'text-white',
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ size: 'md',
24
+ variant: 'primary',
25
+ },
26
+ });
27
+
28
+ export interface SpinnerProps
29
+ extends React.SVGAttributes<SVGSVGElement>,
30
+ VariantProps<typeof spinnerVariants> {
31
+ label?: string;
32
+ }
33
+
34
+ export const Spinner = forwardRef<SVGSVGElement, SpinnerProps>(
35
+ ({ className, size, variant, label = 'Loading...', ...props }, ref) => {
36
+ return (
37
+ <svg
38
+ ref={ref}
39
+ role="status"
40
+ aria-label={label}
41
+ viewBox="0 0 24 24"
42
+ fill="none"
43
+ xmlns="http://www.w3.org/2000/svg"
44
+ className={cn(spinnerVariants({ size, variant }), className)}
45
+ {...props}
46
+ >
47
+ <circle
48
+ className="opacity-25"
49
+ cx="12"
50
+ cy="12"
51
+ r="10"
52
+ stroke="currentColor"
53
+ strokeWidth="4"
54
+ />
55
+ <path
56
+ className="opacity-75"
57
+ fill="currentColor"
58
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
59
+ />
60
+ </svg>
61
+ );
62
+ }
63
+ );
64
+
65
+ Spinner.displayName = 'Spinner';
@@ -0,0 +1,5 @@
1
+ export * from './Spinner';
2
+ export * from './Skeleton';
3
+ export * from './ProgressBar';
4
+ export * from './Alert';
5
+ export * from './EmptyState';
@@ -0,0 +1,4 @@
1
+ /* Feedback Tokens */
2
+ :root {
3
+ --ui-spinner-speed: 750ms;
4
+ }
@@ -0,0 +1,69 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { Check, Minus } from 'lucide-react';
3
+ import { cva, type VariantProps } from 'class-variance-authority';
4
+ import { useFormField } from './FormField';
5
+ import { cn } from '../utils/cn';
6
+
7
+ export const checkboxVariants = cva(
8
+ 'peer shrink-0 rounded-[var(--ui-radius-sm)] border border-[var(--ui-border-default)] bg-[var(--ui-bg-surface)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 transition-all cursor-pointer flex items-center justify-center text-[var(--ui-primary-fg)]',
9
+ {
10
+ variants: {
11
+ size: {
12
+ sm: 'w-4 h-4 text-xs',
13
+ md: 'w-5 h-5 text-sm',
14
+ lg: 'w-6 h-6 text-base',
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ size: 'md',
19
+ },
20
+ }
21
+ );
22
+
23
+ export interface CheckboxProps
24
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
25
+ VariantProps<typeof checkboxVariants> {
26
+ indeterminate?: boolean;
27
+ label?: React.ReactNode;
28
+ }
29
+
30
+ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
31
+ ({ className, size, indeterminate = false, label, checked, disabled, id, onChange, ...props }, ref) => {
32
+ const formField = useFormField();
33
+ const isDisabled = disabled ?? formField?.isDisabled ?? false;
34
+ const checkboxId = id || formField?.id;
35
+
36
+ return (
37
+ <label className={cn('inline-flex items-center gap-2 select-none cursor-pointer', isDisabled && 'cursor-not-allowed opacity-50')}>
38
+ <div className="relative flex items-center justify-center">
39
+ <input
40
+ ref={ref}
41
+ type="checkbox"
42
+ id={checkboxId}
43
+ disabled={isDisabled}
44
+ checked={checked}
45
+ onChange={onChange}
46
+ className="sr-only peer"
47
+ {...props}
48
+ />
49
+ <div
50
+ className={cn(
51
+ checkboxVariants({ size }),
52
+ (checked || indeterminate) && 'bg-[var(--ui-primary)] border-[var(--ui-primary)]',
53
+ className
54
+ )}
55
+ >
56
+ {indeterminate ? (
57
+ <Minus className="w-3.5 h-3.5 stroke-[3]" />
58
+ ) : (
59
+ checked && <Check className="w-3.5 h-3.5 stroke-[3]" />
60
+ )}
61
+ </div>
62
+ </div>
63
+ {label && <span className="text-sm font-medium text-[var(--ui-text-primary)]">{label}</span>}
64
+ </label>
65
+ );
66
+ }
67
+ );
68
+
69
+ Checkbox.displayName = 'Checkbox';
@@ -0,0 +1,29 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { cn } from '../utils/cn';
3
+
4
+ export interface CheckboxGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
5
+ orientation?: 'horizontal' | 'vertical';
6
+ value?: string[];
7
+ onChange?: (value: string[]) => void;
8
+ }
9
+
10
+ export const CheckboxGroup = forwardRef<HTMLDivElement, CheckboxGroupProps>(
11
+ ({ className, orientation = 'vertical', children, value, onChange, ...props }, ref) => {
12
+ return (
13
+ <div
14
+ ref={ref}
15
+ role="group"
16
+ className={cn(
17
+ 'flex gap-2',
18
+ orientation === 'vertical' ? 'flex-col' : 'flex-row flex-wrap items-center',
19
+ className
20
+ )}
21
+ {...props}
22
+ >
23
+ {children}
24
+ </div>
25
+ );
26
+ }
27
+ );
28
+
29
+ CheckboxGroup.displayName = 'CheckboxGroup';