@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,157 @@
1
+ import React, { forwardRef, createContext, useContext } from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../utils/cn';
4
+
5
+ export const toggleVariants = cva(
6
+ 'inline-flex items-center justify-center text-sm font-medium transition-colors hover:bg-[var(--ui-bg-tertiary)] hover:text-[var(--ui-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:pointer-events-none disabled:opacity-50 select-none cursor-pointer',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-transparent text-[var(--ui-text-secondary)]',
11
+ outline:
12
+ 'border border-[var(--ui-border-default)] bg-transparent hover:bg-[var(--ui-bg-tertiary)]',
13
+ },
14
+ size: {
15
+ sm: 'h-8 px-2.5 text-xs rounded-[var(--ui-radius-sm)]',
16
+ md: 'h-9 px-3 text-sm rounded-[var(--ui-radius-md)]',
17
+ lg: 'h-10 px-4 text-base rounded-[var(--ui-radius-md)]',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ size: 'md',
23
+ },
24
+ }
25
+ );
26
+
27
+ interface ToggleGroupContextType {
28
+ value?: string | string[];
29
+ onChange?: (val: string) => void;
30
+ variant?: 'default' | 'outline';
31
+ size?: 'sm' | 'md' | 'lg';
32
+ }
33
+
34
+ const ToggleGroupContext = createContext<ToggleGroupContextType>({});
35
+
36
+ export interface ToggleButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof toggleVariants> {
39
+ pressed?: boolean;
40
+ value?: string;
41
+ }
42
+
43
+ export const ToggleButton = forwardRef<HTMLButtonElement, ToggleButtonProps>(
44
+ ({ className, pressed: customPressed, variant, size, value, onClick, children, ...props }, ref) => {
45
+ const group = useContext(ToggleGroupContext);
46
+
47
+ const isPressed =
48
+ customPressed !== undefined
49
+ ? customPressed
50
+ : group.value !== undefined && value !== undefined
51
+ ? Array.isArray(group.value)
52
+ ? group.value.includes(value)
53
+ : group.value === value
54
+ : false;
55
+
56
+ const currentVariant = variant || group.variant || 'default';
57
+ const currentSize = size || group.size || 'md';
58
+
59
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
60
+ if (value && group.onChange) {
61
+ group.onChange(value);
62
+ }
63
+ onClick?.(e);
64
+ };
65
+
66
+ return (
67
+ <button
68
+ ref={ref}
69
+ type="button"
70
+ aria-pressed={isPressed}
71
+ data-state={isPressed ? 'on' : 'off'}
72
+ onClick={handleClick}
73
+ className={cn(
74
+ toggleVariants({ variant: currentVariant, size: currentSize }),
75
+ isPressed &&
76
+ 'bg-[var(--ui-bg-tertiary)] text-[var(--ui-text-primary)] font-semibold shadow-inner border-[var(--ui-border-strong)]',
77
+ className
78
+ )}
79
+ {...props}
80
+ >
81
+ {children}
82
+ </button>
83
+ );
84
+ }
85
+ );
86
+ ToggleButton.displayName = 'ToggleButton';
87
+
88
+ export interface ToggleGroupProps extends React.HTMLAttributes<HTMLDivElement> {
89
+ type?: 'single' | 'multiple';
90
+ value?: string | string[];
91
+ defaultValue?: string | string[];
92
+ onValueChange?: (value: any) => void;
93
+ variant?: 'default' | 'outline';
94
+ size?: 'sm' | 'md' | 'lg';
95
+ }
96
+
97
+ export const ToggleGroup = forwardRef<HTMLDivElement, ToggleGroupProps>(
98
+ (
99
+ {
100
+ className,
101
+ type = 'single',
102
+ value: controlledValue,
103
+ defaultValue,
104
+ onValueChange,
105
+ variant = 'outline',
106
+ size = 'md',
107
+ children,
108
+ ...props
109
+ },
110
+ ref
111
+ ) => {
112
+ const [stateValue, setStateValue] = React.useState<string | string[]>(
113
+ defaultValue || (type === 'multiple' ? [] : '')
114
+ );
115
+
116
+ const currentValue = controlledValue !== undefined ? controlledValue : stateValue;
117
+
118
+ const handleItemChange = (itemValue: string) => {
119
+ if (type === 'single') {
120
+ const next = currentValue === itemValue ? '' : itemValue;
121
+ setStateValue(next);
122
+ onValueChange?.(next);
123
+ } else {
124
+ const arr = Array.isArray(currentValue) ? currentValue : [];
125
+ const next = arr.includes(itemValue)
126
+ ? arr.filter((v) => v !== itemValue)
127
+ : [...arr, itemValue];
128
+ setStateValue(next);
129
+ onValueChange?.(next);
130
+ }
131
+ };
132
+
133
+ return (
134
+ <ToggleGroupContext.Provider
135
+ value={{
136
+ value: currentValue,
137
+ onChange: handleItemChange,
138
+ variant,
139
+ size,
140
+ }}
141
+ >
142
+ <div
143
+ ref={ref}
144
+ role="group"
145
+ className={cn(
146
+ 'inline-flex items-center gap-1 rounded-[var(--ui-radius-md)] p-1 bg-[var(--ui-bg-secondary)] border border-[var(--ui-border-subtle)]',
147
+ className
148
+ )}
149
+ {...props}
150
+ >
151
+ {children}
152
+ </div>
153
+ </ToggleGroupContext.Provider>
154
+ );
155
+ }
156
+ );
157
+ ToggleGroup.displayName = 'ToggleGroup';
@@ -0,0 +1,5 @@
1
+ export * from './Button';
2
+ export * from './IconButton';
3
+ export * from './ButtonGroup';
4
+ export * from './ToggleButton';
5
+ export * from './SplitButton';
@@ -0,0 +1,6 @@
1
+ /* Button Tokens */
2
+ :root {
3
+ --ui-btn-height-sm: 2rem;
4
+ --ui-btn-height-md: 2.5rem;
5
+ --ui-btn-height-lg: 3rem;
6
+ }
@@ -0,0 +1,123 @@
1
+ import React, { createContext, useContext, useEffect, useState, useMemo } from 'react';
2
+
3
+ export type Theme = 'dark' | 'light' | 'system';
4
+
5
+ export interface ThemeProviderProps {
6
+ children: React.ReactNode;
7
+ defaultTheme?: Theme;
8
+ storageKey?: string;
9
+ attribute?: string;
10
+ enableSystem?: boolean;
11
+ }
12
+
13
+ interface ThemeContextType {
14
+ theme: Theme;
15
+ resolvedTheme: 'dark' | 'light';
16
+ setTheme: (theme: Theme) => void;
17
+ toggleTheme: () => void;
18
+ }
19
+
20
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
21
+
22
+ export function ThemeProvider({
23
+ children,
24
+ defaultTheme = 'dark',
25
+ storageKey = 'ui-theme',
26
+ attribute = 'data-theme',
27
+ enableSystem = true,
28
+ }: ThemeProviderProps) {
29
+ const [theme, setThemeState] = useState<Theme>(() => {
30
+ if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
31
+ try {
32
+ const stored = window.localStorage.getItem(storageKey) as Theme | null;
33
+ if (stored) return stored;
34
+ } catch {
35
+ // Ignore storage access errors in test/restricted environments
36
+ }
37
+ }
38
+ return defaultTheme;
39
+ });
40
+
41
+ const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('dark');
42
+
43
+ useEffect(() => {
44
+ const root = document.documentElement;
45
+
46
+ const computeResolved = (t: Theme): 'dark' | 'light' => {
47
+ if (t === 'system' && enableSystem && typeof window !== 'undefined' && window.matchMedia) {
48
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
49
+ }
50
+ return t === 'dark' ? 'dark' : 'light';
51
+ };
52
+
53
+ const currentResolved = computeResolved(theme);
54
+ setResolvedTheme(currentResolved);
55
+
56
+ // Apply attributes and classes
57
+ root.setAttribute(attribute, currentResolved);
58
+ if (currentResolved === 'dark') {
59
+ root.classList.add('dark');
60
+ root.classList.remove('light');
61
+ } else {
62
+ root.classList.add('light');
63
+ root.classList.remove('dark');
64
+ }
65
+
66
+ if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
67
+ try {
68
+ window.localStorage.setItem(storageKey, theme);
69
+ } catch {
70
+ // Ignore storage access errors
71
+ }
72
+ }
73
+ }, [theme, attribute, enableSystem, storageKey]);
74
+
75
+ // System theme listener
76
+ useEffect(() => {
77
+ if (theme !== 'system' || !enableSystem) return;
78
+
79
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
80
+ const handleChange = () => {
81
+ const newResolved = mediaQuery.matches ? 'dark' : 'light';
82
+ setResolvedTheme(newResolved);
83
+ document.documentElement.setAttribute(attribute, newResolved);
84
+ if (newResolved === 'dark') {
85
+ document.documentElement.classList.add('dark');
86
+ document.documentElement.classList.remove('light');
87
+ } else {
88
+ document.documentElement.classList.add('light');
89
+ document.documentElement.classList.remove('dark');
90
+ }
91
+ };
92
+
93
+ mediaQuery.addEventListener('change', handleChange);
94
+ return () => mediaQuery.removeEventListener('change', handleChange);
95
+ }, [theme, enableSystem, attribute]);
96
+
97
+ const toggleTheme = () => {
98
+ setThemeState((prev) => {
99
+ if (prev === 'dark') return 'light';
100
+ return 'dark';
101
+ });
102
+ };
103
+
104
+ const value = useMemo(
105
+ () => ({
106
+ theme,
107
+ resolvedTheme,
108
+ setTheme: setThemeState,
109
+ toggleTheme,
110
+ }),
111
+ [theme, resolvedTheme]
112
+ );
113
+
114
+ return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
115
+ }
116
+
117
+ export function useTheme(): ThemeContextType {
118
+ const context = useContext(ThemeContext);
119
+ if (!context) {
120
+ throw new Error('useTheme must be used within a ThemeProvider');
121
+ }
122
+ return context;
123
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ThemeProvider';
2
+ export * from '../utils/cn';
@@ -0,0 +1,128 @@
1
+ :root {
2
+ /* ==========================================================================
3
+ APPLE DESIGN SYSTEM TOKENS — LIGHT MODE (ALABASTER & FROSTED GLASS)
4
+ ========================================================================== */
5
+ --ui-bg-primary: #f5f5f7;
6
+ --ui-bg-secondary: #ffffff;
7
+ --ui-bg-tertiary: #e8e8ed;
8
+ --ui-bg-surface: rgba(255, 255, 255, 0.75);
9
+ --ui-bg-overlay: rgba(0, 0, 0, 0.28);
10
+
11
+ --ui-border-subtle: rgba(0, 0, 0, 0.06);
12
+ --ui-border-default: rgba(0, 0, 0, 0.10);
13
+ --ui-border-strong: rgba(0, 0, 0, 0.20);
14
+
15
+ --ui-text-primary: #1d1d1f;
16
+ --ui-text-secondary: #515154;
17
+ --ui-text-muted: #86868b;
18
+ --ui-text-inverse: #ffffff;
19
+
20
+ /* Apple System Colors */
21
+ --ui-primary: #0071e3;
22
+ --ui-primary-hover: #0077ed;
23
+ --ui-primary-fg: #ffffff;
24
+ --ui-primary-glow: rgba(0, 113, 227, 0.25);
25
+
26
+ --ui-secondary: #06c;
27
+ --ui-secondary-hover: #0071e3;
28
+ --ui-secondary-fg: #ffffff;
29
+ --ui-secondary-glow: rgba(0, 102, 204, 0.25);
30
+
31
+ --ui-accent: #af52de;
32
+ --ui-accent-hover: #bf5af2;
33
+ --ui-accent-fg: #ffffff;
34
+
35
+ /* Apple Semantic Feedback */
36
+ --ui-success: #34c759;
37
+ --ui-success-subtle: rgba(52, 199, 89, 0.12);
38
+ --ui-success-fg: #ffffff;
39
+
40
+ --ui-warning: #ff9500;
41
+ --ui-warning-subtle: rgba(255, 149, 0, 0.12);
42
+ --ui-warning-fg: #ffffff;
43
+
44
+ --ui-error: #ff3b30;
45
+ --ui-error-subtle: rgba(255, 59, 48, 0.12);
46
+ --ui-error-fg: #ffffff;
47
+
48
+ --ui-info: #0071e3;
49
+ --ui-info-subtle: rgba(0, 113, 227, 0.12);
50
+ --ui-info-fg: #ffffff;
51
+
52
+ /* Apple Shadows & Translucency */
53
+ --ui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
54
+ --ui-shadow-md: 0 4px 14px rgba(0, 0, 0, 0.06), 0 0 1px rgba(0, 0, 0, 0.12);
55
+ --ui-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.16);
56
+ --ui-shadow-xl: 0 24px 64px rgba(0, 0, 0, 0.12), 0 0 1px rgba(0, 0, 0, 0.2);
57
+ --ui-focus-ring: 0 0 0 4px rgba(0, 113, 227, 0.35);
58
+
59
+ /* Radii (Apple Continuous Corners) */
60
+ --ui-radius-sm: 0.5rem;
61
+ --ui-radius-md: 0.75rem;
62
+ --ui-radius-lg: 1.125rem;
63
+ --ui-radius-xl: 1.5rem;
64
+ --ui-radius-2xl: 2rem;
65
+ --ui-radius-full: 9999px;
66
+
67
+ /* Transitions (Apple Spring Curves) */
68
+ --ui-transition-fast: 160ms cubic-bezier(0.25, 1, 0.5, 1);
69
+ --ui-transition-normal: 280ms cubic-bezier(0.25, 1, 0.5, 1);
70
+ --ui-transition-slow: 420ms cubic-bezier(0.25, 1, 0.5, 1);
71
+ }
72
+
73
+ .dark, [data-theme="dark"] {
74
+ /* ==========================================================================
75
+ APPLE DESIGN SYSTEM TOKENS — DARK MODE (VISIONOS OBSIDIAN & LIQUID GLASS)
76
+ ========================================================================== */
77
+ --ui-bg-primary: #000000;
78
+ --ui-bg-secondary: #161618;
79
+ --ui-bg-tertiary: #242426;
80
+ --ui-bg-surface: rgba(30, 30, 34, 0.72);
81
+ --ui-bg-overlay: rgba(0, 0, 0, 0.75);
82
+
83
+ --ui-border-subtle: rgba(255, 255, 255, 0.08);
84
+ --ui-border-default: rgba(255, 255, 255, 0.14);
85
+ --ui-border-strong: rgba(255, 255, 255, 0.25);
86
+
87
+ --ui-text-primary: #f5f5f7;
88
+ --ui-text-secondary: #a1a1a6;
89
+ --ui-text-muted: #6e6e73;
90
+ --ui-text-inverse: #000000;
91
+
92
+ /* Apple Vision Dark System Colors */
93
+ --ui-primary: #2997ff;
94
+ --ui-primary-hover: #47a6ff;
95
+ --ui-primary-fg: #ffffff;
96
+ --ui-primary-glow: rgba(41, 151, 255, 0.35);
97
+
98
+ --ui-secondary: #64d2ff;
99
+ --ui-secondary-hover: #7dd9ff;
100
+ --ui-secondary-fg: #000000;
101
+ --ui-secondary-glow: rgba(100, 210, 255, 0.35);
102
+
103
+ --ui-accent: #bf5af2;
104
+ --ui-accent-hover: #d27fff;
105
+ --ui-accent-fg: #ffffff;
106
+
107
+ --ui-success: #30d158;
108
+ --ui-success-subtle: rgba(48, 209, 88, 0.15);
109
+ --ui-success-fg: #ffffff;
110
+
111
+ --ui-warning: #ffd60a;
112
+ --ui-warning-subtle: rgba(255, 214, 10, 0.15);
113
+ --ui-warning-fg: #000000;
114
+
115
+ --ui-error: #ff453a;
116
+ --ui-error-subtle: rgba(255, 69, 58, 0.15);
117
+ --ui-error-fg: #ffffff;
118
+
119
+ --ui-info: #0a84ff;
120
+ --ui-info-subtle: rgba(10, 132, 255, 0.15);
121
+ --ui-info-fg: #ffffff;
122
+
123
+ --ui-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.6);
124
+ --ui-shadow-md: 0 8px 24px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.12);
125
+ --ui-shadow-lg: 0 16px 40px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.16);
126
+ --ui-shadow-xl: 0 28px 72px rgba(0, 0, 0, 0.85), inset 0 1px 0 rgba(255, 255, 255, 0.22);
127
+ --ui-focus-ring: 0 0 0 4px rgba(41, 151, 255, 0.4);
128
+ }
@@ -0,0 +1,147 @@
1
+ import React, { createContext, useContext, useState, forwardRef } from 'react';
2
+ import { ChevronDown } from 'lucide-react';
3
+ import { cn } from '../utils/cn';
4
+
5
+ interface AccordionContextType {
6
+ openItems: string[];
7
+ toggleItem: (id: string) => void;
8
+ }
9
+
10
+ const AccordionContext = createContext<AccordionContextType | null>(null);
11
+
12
+ export function useAccordion() {
13
+ const context = useContext(AccordionContext);
14
+ if (!context) throw new Error('AccordionItem components must be used within <Accordion />');
15
+ return context;
16
+ }
17
+
18
+ export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
19
+ type?: 'single' | 'multiple';
20
+ defaultValue?: string | string[];
21
+ value?: string | string[];
22
+ onValueChange?: (val: any) => void;
23
+ }
24
+
25
+ export const Accordion = forwardRef<HTMLDivElement, AccordionProps>(
26
+ (
27
+ {
28
+ className,
29
+ type = 'single',
30
+ defaultValue,
31
+ value: controlledValue,
32
+ onValueChange,
33
+ children,
34
+ ...props
35
+ },
36
+ ref
37
+ ) => {
38
+ const [internalOpen, setInternalOpen] = useState<string[]>(() => {
39
+ if (Array.isArray(defaultValue)) return defaultValue;
40
+ if (typeof defaultValue === 'string') return [defaultValue];
41
+ return [];
42
+ });
43
+
44
+ const openItems =
45
+ controlledValue !== undefined
46
+ ? Array.isArray(controlledValue)
47
+ ? controlledValue
48
+ : [controlledValue]
49
+ : internalOpen;
50
+
51
+ const toggleItem = (id: string) => {
52
+ let next: string[];
53
+ if (type === 'single') {
54
+ next = openItems.includes(id) ? [] : [id];
55
+ } else {
56
+ next = openItems.includes(id) ? openItems.filter((i) => i !== id) : [...openItems, id];
57
+ }
58
+
59
+ setInternalOpen(next);
60
+ onValueChange?.(type === 'single' ? next[0] || '' : next);
61
+ };
62
+
63
+ return (
64
+ <AccordionContext.Provider value={{ openItems, toggleItem }}>
65
+ <div ref={ref} className={cn('divide-y divide-[var(--ui-border-subtle)] border-y border-[var(--ui-border-subtle)]', className)} {...props}>
66
+ {children}
67
+ </div>
68
+ </AccordionContext.Provider>
69
+ );
70
+ }
71
+ );
72
+ Accordion.displayName = 'Accordion';
73
+
74
+ interface AccordionItemContextType {
75
+ id: string;
76
+ }
77
+
78
+ const AccordionItemContext = createContext<AccordionItemContextType | null>(null);
79
+
80
+ export interface AccordionItemProps extends React.HTMLAttributes<HTMLDivElement> {
81
+ value: string;
82
+ }
83
+
84
+ export const AccordionItem = forwardRef<HTMLDivElement, AccordionItemProps>(
85
+ ({ className, value, children, ...props }, ref) => {
86
+ return (
87
+ <AccordionItemContext.Provider value={{ id: value }}>
88
+ <div ref={ref} className={cn('w-full', className)} {...props}>
89
+ {children}
90
+ </div>
91
+ </AccordionItemContext.Provider>
92
+ );
93
+ }
94
+ );
95
+ AccordionItem.displayName = 'AccordionItem';
96
+
97
+ export const AccordionTrigger = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
98
+ ({ className, children, ...props }, ref) => {
99
+ const { openItems, toggleItem } = useAccordion();
100
+ const item = useContext(AccordionItemContext);
101
+ const isOpen = item ? openItems.includes(item.id) : false;
102
+
103
+ return (
104
+ <button
105
+ ref={ref}
106
+ type="button"
107
+ aria-expanded={isOpen}
108
+ onClick={() => item && toggleItem(item.id)}
109
+ className={cn(
110
+ 'flex w-full items-center justify-between py-4 text-sm font-semibold text-[var(--ui-text-primary)] transition-all hover:text-[var(--ui-primary)] text-left',
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ {children}
116
+ <ChevronDown
117
+ className={cn(
118
+ 'h-4 w-4 shrink-0 text-[var(--ui-text-muted)] transition-transform duration-200',
119
+ isOpen && 'rotate-180 text-[var(--ui-primary)]'
120
+ )}
121
+ />
122
+ </button>
123
+ );
124
+ }
125
+ );
126
+ AccordionTrigger.displayName = 'AccordionTrigger';
127
+
128
+ export const AccordionContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
129
+ ({ className, children, ...props }, ref) => {
130
+ const { openItems } = useAccordion();
131
+ const item = useContext(AccordionItemContext);
132
+ const isOpen = item ? openItems.includes(item.id) : false;
133
+
134
+ if (!isOpen) return null;
135
+
136
+ return (
137
+ <div
138
+ ref={ref}
139
+ className={cn('pb-4 pt-0 text-sm text-[var(--ui-text-secondary)] animate-in fade-in duration-200', className)}
140
+ {...props}
141
+ >
142
+ {children}
143
+ </div>
144
+ );
145
+ }
146
+ );
147
+ AccordionContent.displayName = 'AccordionContent';