@opencosmos/ui 1.3.1

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 (260) hide show
  1. package/.claude/CLAUDE.md +239 -0
  2. package/README.md +161 -0
  3. package/dist/cli.mjs +151 -0
  4. package/dist/dates.d.mts +20 -0
  5. package/dist/dates.d.ts +20 -0
  6. package/dist/dates.js +240 -0
  7. package/dist/dates.js.map +1 -0
  8. package/dist/dates.mjs +203 -0
  9. package/dist/dates.mjs.map +1 -0
  10. package/dist/dnd.d.mts +126 -0
  11. package/dist/dnd.d.ts +126 -0
  12. package/dist/dnd.js +274 -0
  13. package/dist/dnd.js.map +1 -0
  14. package/dist/dnd.mjs +250 -0
  15. package/dist/dnd.mjs.map +1 -0
  16. package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
  17. package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
  18. package/dist/forms.d.mts +38 -0
  19. package/dist/forms.d.ts +38 -0
  20. package/dist/forms.js +198 -0
  21. package/dist/forms.js.map +1 -0
  22. package/dist/forms.mjs +159 -0
  23. package/dist/forms.mjs.map +1 -0
  24. package/dist/hooks-1b8WaQf1.d.mts +225 -0
  25. package/dist/hooks-CKW8vE9H.d.ts +225 -0
  26. package/dist/hooks.d.mts +3 -0
  27. package/dist/hooks.d.ts +3 -0
  28. package/dist/hooks.js +971 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/hooks.mjs +943 -0
  31. package/dist/hooks.mjs.map +1 -0
  32. package/dist/index-DscTIrZ2.d.mts +29 -0
  33. package/dist/index-DscTIrZ2.d.ts +29 -0
  34. package/dist/index.d.mts +3382 -0
  35. package/dist/index.d.ts +3382 -0
  36. package/dist/index.js +15146 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +14802 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/providers-CXPDMsl7.d.mts +30 -0
  41. package/dist/providers-Dn_Msjvz.d.ts +30 -0
  42. package/dist/providers.d.mts +3 -0
  43. package/dist/providers.d.ts +3 -0
  44. package/dist/providers.js +1885 -0
  45. package/dist/providers.js.map +1 -0
  46. package/dist/providers.mjs +1859 -0
  47. package/dist/providers.mjs.map +1 -0
  48. package/dist/tables.d.mts +10 -0
  49. package/dist/tables.d.ts +10 -0
  50. package/dist/tables.js +248 -0
  51. package/dist/tables.js.map +1 -0
  52. package/dist/tables.mjs +218 -0
  53. package/dist/tables.mjs.map +1 -0
  54. package/dist/tokens.d.mts +1065 -0
  55. package/dist/tokens.d.ts +1065 -0
  56. package/dist/tokens.js +2637 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/dist/tokens.mjs +2555 -0
  59. package/dist/tokens.mjs.map +1 -0
  60. package/dist/utils-CIIM7dAC.d.ts +986 -0
  61. package/dist/utils-Cs04sxth.d.mts +986 -0
  62. package/dist/utils.d.mts +4 -0
  63. package/dist/utils.d.ts +4 -0
  64. package/dist/utils.js +874 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/utils.mjs +806 -0
  67. package/dist/utils.mjs.map +1 -0
  68. package/dist/validation-Bj1ye-v_.d.mts +114 -0
  69. package/dist/validation-Bj1ye-v_.d.ts +114 -0
  70. package/dist/webgl.d.mts +104 -0
  71. package/dist/webgl.d.ts +104 -0
  72. package/dist/webgl.js +226 -0
  73. package/dist/webgl.js.map +1 -0
  74. package/dist/webgl.mjs +195 -0
  75. package/dist/webgl.mjs.map +1 -0
  76. package/package.json +267 -0
  77. package/src/cli.ts +206 -0
  78. package/src/component-registry.ts +183 -0
  79. package/src/components/actions/Button.test.tsx +61 -0
  80. package/src/components/actions/Button.tsx +70 -0
  81. package/src/components/actions/Link.tsx +78 -0
  82. package/src/components/actions/Magnetic.tsx +68 -0
  83. package/src/components/actions/Toggle.test.tsx +40 -0
  84. package/src/components/actions/Toggle.tsx +47 -0
  85. package/src/components/actions/ToggleGroup.tsx +70 -0
  86. package/src/components/actions/index.ts +5 -0
  87. package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
  88. package/src/components/backgrounds/OrbBackground.tsx +424 -0
  89. package/src/components/backgrounds/WarpBackground.tsx +358 -0
  90. package/src/components/backgrounds/index.ts +3 -0
  91. package/src/components/blocks/Hero.tsx +142 -0
  92. package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
  93. package/src/components/cursor/SplashCursor.tsx +1315 -0
  94. package/src/components/cursor/TargetCursor.tsx +187 -0
  95. package/src/components/cursor/index.ts +2 -0
  96. package/src/components/data-display/AspectImage.tsx +73 -0
  97. package/src/components/data-display/Avatar.test.tsx +35 -0
  98. package/src/components/data-display/Avatar.tsx +55 -0
  99. package/src/components/data-display/Badge.test.tsx +43 -0
  100. package/src/components/data-display/Badge.tsx +84 -0
  101. package/src/components/data-display/Brand.tsx +123 -0
  102. package/src/components/data-display/Calendar.tsx +70 -0
  103. package/src/components/data-display/Card.test.tsx +92 -0
  104. package/src/components/data-display/Card.tsx +115 -0
  105. package/src/components/data-display/Code.tsx +210 -0
  106. package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
  107. package/src/components/data-display/DataTable.tsx +119 -0
  108. package/src/components/data-display/DescriptionList.tsx +41 -0
  109. package/src/components/data-display/GitHubIcon.tsx +44 -0
  110. package/src/components/data-display/Heading.test.tsx +36 -0
  111. package/src/components/data-display/Heading.tsx +83 -0
  112. package/src/components/data-display/StatCard.tsx +195 -0
  113. package/src/components/data-display/Table.tsx +133 -0
  114. package/src/components/data-display/Text.test.tsx +48 -0
  115. package/src/components/data-display/Text.tsx +144 -0
  116. package/src/components/data-display/Timeline.tsx +194 -0
  117. package/src/components/data-display/TreeView.tsx +226 -0
  118. package/src/components/data-display/Typewriter.tsx +119 -0
  119. package/src/components/data-display/VariableWeightText.tsx +130 -0
  120. package/src/components/data-display/index.ts +19 -0
  121. package/src/components/feedback/Alert.test.tsx +44 -0
  122. package/src/components/feedback/Alert.tsx +65 -0
  123. package/src/components/feedback/EmptyState.tsx +113 -0
  124. package/src/components/feedback/Progress.test.tsx +60 -0
  125. package/src/components/feedback/Progress.tsx +30 -0
  126. package/src/components/feedback/ProgressBar.tsx +158 -0
  127. package/src/components/feedback/Skeleton.test.tsx +39 -0
  128. package/src/components/feedback/Skeleton.tsx +45 -0
  129. package/src/components/feedback/Sonner.tsx +28 -0
  130. package/src/components/feedback/Spinner.test.tsx +33 -0
  131. package/src/components/feedback/Spinner.tsx +99 -0
  132. package/src/components/feedback/Stepper.tsx +307 -0
  133. package/src/components/feedback/Toast/Toast.tsx +243 -0
  134. package/src/components/feedback/Toast/index.ts +2 -0
  135. package/src/components/feedback/index.ts +9 -0
  136. package/src/components/forms/Checkbox.test.tsx +40 -0
  137. package/src/components/forms/Checkbox.tsx +31 -0
  138. package/src/components/forms/ColorPicker.tsx +118 -0
  139. package/src/components/forms/Combobox.tsx +96 -0
  140. package/src/components/forms/DragDrop.tsx +440 -0
  141. package/src/components/forms/FileUpload.tsx +252 -0
  142. package/src/components/forms/FilterButton.tsx +65 -0
  143. package/src/components/forms/Form.tsx +197 -0
  144. package/src/components/forms/Input.test.tsx +46 -0
  145. package/src/components/forms/Input.tsx +43 -0
  146. package/src/components/forms/InputOTP.tsx +81 -0
  147. package/src/components/forms/Label.test.tsx +20 -0
  148. package/src/components/forms/Label.tsx +25 -0
  149. package/src/components/forms/RadioGroup.tsx +51 -0
  150. package/src/components/forms/SearchBar.tsx +215 -0
  151. package/src/components/forms/Select.test.tsx +118 -0
  152. package/src/components/forms/Select.tsx +274 -0
  153. package/src/components/forms/Slider.tsx +29 -0
  154. package/src/components/forms/Switch.test.tsx +76 -0
  155. package/src/components/forms/Switch.tsx +30 -0
  156. package/src/components/forms/TextField.tsx +152 -0
  157. package/src/components/forms/Textarea.test.tsx +41 -0
  158. package/src/components/forms/Textarea.tsx +29 -0
  159. package/src/components/forms/ThemeSwitcher.tsx +290 -0
  160. package/src/components/forms/ThemeToggle.tsx +151 -0
  161. package/src/components/forms/index.ts +19 -0
  162. package/src/components/layout/Accordion.test.tsx +66 -0
  163. package/src/components/layout/Accordion.tsx +64 -0
  164. package/src/components/layout/AspectRatio.tsx +7 -0
  165. package/src/components/layout/Carousel.tsx +277 -0
  166. package/src/components/layout/Collapsible.test.tsx +40 -0
  167. package/src/components/layout/Collapsible.tsx +31 -0
  168. package/src/components/layout/Container.test.tsx +45 -0
  169. package/src/components/layout/Container.tsx +99 -0
  170. package/src/components/layout/CustomizerPanel.tsx +400 -0
  171. package/src/components/layout/DatePicker.tsx +57 -0
  172. package/src/components/layout/Footer/Footer.tsx +175 -0
  173. package/src/components/layout/Footer/index.ts +2 -0
  174. package/src/components/layout/GlassSurface.tsx +82 -0
  175. package/src/components/layout/Grid.test.tsx +31 -0
  176. package/src/components/layout/Grid.tsx +130 -0
  177. package/src/components/layout/Header/Header.tsx +450 -0
  178. package/src/components/layout/Header/index.ts +2 -0
  179. package/src/components/layout/PageLayout.tsx +180 -0
  180. package/src/components/layout/PageTemplate.tsx +158 -0
  181. package/src/components/layout/Resizable.tsx +48 -0
  182. package/src/components/layout/ScrollArea.tsx +53 -0
  183. package/src/components/layout/Separator.test.tsx +28 -0
  184. package/src/components/layout/Separator.tsx +29 -0
  185. package/src/components/layout/Sidebar.tsx +171 -0
  186. package/src/components/layout/Stack.test.tsx +41 -0
  187. package/src/components/layout/Stack.tsx +89 -0
  188. package/src/components/layout/glass-surface.css +60 -0
  189. package/src/components/layout/index.ts +18 -0
  190. package/src/components/motion/AnimatedBeam.tsx +159 -0
  191. package/src/components/navigation/Breadcrumb.test.tsx +57 -0
  192. package/src/components/navigation/Breadcrumb.tsx +119 -0
  193. package/src/components/navigation/Breadcrumbs.tsx +221 -0
  194. package/src/components/navigation/Command.tsx +159 -0
  195. package/src/components/navigation/Menubar.tsx +115 -0
  196. package/src/components/navigation/NavLink.tsx +55 -0
  197. package/src/components/navigation/NavigationMenu.tsx +125 -0
  198. package/src/components/navigation/Pagination.tsx +121 -0
  199. package/src/components/navigation/SecondaryNav.tsx +100 -0
  200. package/src/components/navigation/Tabs.test.tsx +47 -0
  201. package/src/components/navigation/Tabs.tsx +60 -0
  202. package/src/components/navigation/TertiaryNav.tsx +90 -0
  203. package/src/components/navigation/index.ts +10 -0
  204. package/src/components/overlays/AlertDialog.test.tsx +69 -0
  205. package/src/components/overlays/AlertDialog.tsx +166 -0
  206. package/src/components/overlays/ContextMenu.tsx +243 -0
  207. package/src/components/overlays/Dialog.test.tsx +79 -0
  208. package/src/components/overlays/Dialog.tsx +158 -0
  209. package/src/components/overlays/Drawer.tsx +128 -0
  210. package/src/components/overlays/Dropdown.tsx +253 -0
  211. package/src/components/overlays/DropdownMenu.tsx +242 -0
  212. package/src/components/overlays/HoverCard.tsx +32 -0
  213. package/src/components/overlays/Modal.tsx +250 -0
  214. package/src/components/overlays/NotificationCenter.tsx +364 -0
  215. package/src/components/overlays/Popover.test.tsx +40 -0
  216. package/src/components/overlays/Popover.tsx +46 -0
  217. package/src/components/overlays/Sheet.tsx +163 -0
  218. package/src/components/overlays/Tooltip.test.tsx +33 -0
  219. package/src/components/overlays/Tooltip.tsx +32 -0
  220. package/src/components/overlays/index.ts +12 -0
  221. package/src/dates.ts +2 -0
  222. package/src/dnd.ts +1 -0
  223. package/src/forms.ts +1 -0
  224. package/src/globals.css +187 -0
  225. package/src/hooks/index.ts +6 -0
  226. package/src/hooks/useForm.ts +247 -0
  227. package/src/hooks/useMotionPreference.test.ts +102 -0
  228. package/src/hooks/useMotionPreference.ts +78 -0
  229. package/src/hooks/useTheme.ts +58 -0
  230. package/src/hooks.ts +9 -0
  231. package/src/index.ts +168 -0
  232. package/src/lib/animations.ts +356 -0
  233. package/src/lib/breadcrumbs.ts +94 -0
  234. package/src/lib/colors.ts +493 -0
  235. package/src/lib/store/customizer.ts +482 -0
  236. package/src/lib/store/index.ts +3 -0
  237. package/src/lib/store/theme.ts +55 -0
  238. package/src/lib/syntax-parser/index.ts +50 -0
  239. package/src/lib/syntax-parser/patterns.ts +64 -0
  240. package/src/lib/syntax-parser/tokenizer.ts +117 -0
  241. package/src/lib/syntax-parser/types.ts +27 -0
  242. package/src/lib/utils.ts +6 -0
  243. package/src/lib/validation.ts +204 -0
  244. package/src/lib/webgl/Color.ts +11 -0
  245. package/src/lib/webgl/Mesh.ts +41 -0
  246. package/src/lib/webgl/Program.ts +118 -0
  247. package/src/lib/webgl/Renderer.ts +51 -0
  248. package/src/lib/webgl/Triangle.ts +27 -0
  249. package/src/lib/webgl/Vec3.ts +18 -0
  250. package/src/lib/webgl/index.ts +13 -0
  251. package/src/nativewind-env.d.ts +1 -0
  252. package/src/providers/ThemeProvider.tsx +461 -0
  253. package/src/providers/index.ts +1 -0
  254. package/src/providers.ts +7 -0
  255. package/src/tables.ts +1 -0
  256. package/src/test/setup.ts +39 -0
  257. package/src/theme.css +158 -0
  258. package/src/tokens.ts +7 -0
  259. package/src/utils.ts +12 -0
  260. package/src/webgl.ts +1 -0
@@ -0,0 +1,119 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import {
5
+ ColumnDef,
6
+ flexRender,
7
+ getCoreRowModel,
8
+ getPaginationRowModel,
9
+ getSortedRowModel,
10
+ SortingState,
11
+ useReactTable,
12
+ } from "@tanstack/react-table"
13
+
14
+ import {
15
+ Table,
16
+ TableBody,
17
+ TableCell,
18
+ TableHead,
19
+ TableHeader,
20
+ TableRow,
21
+ } from "./Table"
22
+ import { Button } from "../actions/Button"
23
+
24
+ interface DataTableProps<TData, TValue> {
25
+ columns: ColumnDef<TData, TValue>[]
26
+ data: TData[]
27
+ }
28
+
29
+ export function DataTable<TData, TValue>({
30
+ columns,
31
+ data,
32
+ }: DataTableProps<TData, TValue>) {
33
+ const [sorting, setSorting] = React.useState<SortingState>([])
34
+
35
+ const table = useReactTable({
36
+ data,
37
+ columns,
38
+ getCoreRowModel: getCoreRowModel(),
39
+ getPaginationRowModel: getPaginationRowModel(),
40
+ onSortingChange: setSorting,
41
+ getSortedRowModel: getSortedRowModel(),
42
+ state: {
43
+ sorting,
44
+ },
45
+ })
46
+
47
+ return (
48
+ <div className="space-y-4">
49
+ <div className="rounded-md border">
50
+ <Table>
51
+ <TableHeader>
52
+ {table.getHeaderGroups().map((headerGroup) => (
53
+ <TableRow key={headerGroup.id}>
54
+ {headerGroup.headers.map((header) => {
55
+ return (
56
+ <TableHead key={header.id}>
57
+ {header.isPlaceholder
58
+ ? null
59
+ : flexRender(
60
+ header.column.columnDef.header,
61
+ header.getContext()
62
+ )}
63
+ </TableHead>
64
+ )
65
+ })}
66
+ </TableRow>
67
+ ))}
68
+ </TableHeader>
69
+ <TableBody>
70
+ {table.getRowModel().rows?.length ? (
71
+ table.getRowModel().rows.map((row) => (
72
+ <TableRow
73
+ key={row.id}
74
+ data-state={row.getIsSelected() && "selected"}
75
+ >
76
+ {row.getVisibleCells().map((cell) => (
77
+ <TableCell key={cell.id}>
78
+ {flexRender(
79
+ cell.column.columnDef.cell,
80
+ cell.getContext()
81
+ )}
82
+ </TableCell>
83
+ ))}
84
+ </TableRow>
85
+ ))
86
+ ) : (
87
+ <TableRow>
88
+ <TableCell
89
+ colSpan={columns.length}
90
+ className="h-24 text-center"
91
+ >
92
+ No results.
93
+ </TableCell>
94
+ </TableRow>
95
+ )}
96
+ </TableBody>
97
+ </Table>
98
+ </div>
99
+ <div className="flex items-center justify-end space-x-2">
100
+ <Button
101
+ variant="outline"
102
+ size="sm"
103
+ onClick={() => table.previousPage()}
104
+ disabled={!table.getCanPreviousPage()}
105
+ >
106
+ Previous
107
+ </Button>
108
+ <Button
109
+ variant="outline"
110
+ size="sm"
111
+ onClick={() => table.nextPage()}
112
+ disabled={!table.getCanNextPage()}
113
+ >
114
+ Next
115
+ </Button>
116
+ </div>
117
+ </div>
118
+ )
119
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ export interface DescriptionListItem {
4
+ label: string | React.ReactNode;
5
+ value: string | React.ReactNode;
6
+ }
7
+
8
+ export interface DescriptionListProps {
9
+ items: DescriptionListItem[];
10
+ /**
11
+ * Layout direction
12
+ * @default 'row' (side-by-side labels/values in a grid)
13
+ */
14
+ direction?: 'row' | 'column';
15
+ className?: string;
16
+ }
17
+
18
+ export const DescriptionList: React.FC<DescriptionListProps> = ({
19
+ items,
20
+ direction = 'row',
21
+ className = ''
22
+ }) => {
23
+ return (
24
+ <dl className={`
25
+ grid gap-x-4 gap-y-4
26
+ ${direction === 'row' ? 'grid-cols-2 sm:grid-cols-[auto_1fr]' : 'grid-cols-1'}
27
+ ${className}
28
+ `}>
29
+ {items.map((item, index) => (
30
+ <div key={index} className="contents">
31
+ <dt className="text-sm font-medium text-[var(--color-text-secondary)]">
32
+ {item.label}
33
+ </dt>
34
+ <dd className="text-sm text-[var(--color-text-primary)] font-medium">
35
+ {item.value}
36
+ </dd>
37
+ </div>
38
+ ))}
39
+ </dl>
40
+ );
41
+ };
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+
3
+ export interface GitHubIconProps {
4
+ /**
5
+ * Size of the icon in pixels
6
+ * @default 20
7
+ */
8
+ size?: number;
9
+ /**
10
+ * Additional className for customization
11
+ */
12
+ className?: string;
13
+ }
14
+
15
+ /**
16
+ * GitHub Icon Component
17
+ *
18
+ * Uses currentColor so it inherits the text color from its parent.
19
+ * Works automatically with light/dark modes through color inheritance.
20
+ */
21
+ export const GitHubIcon = (
22
+ {
23
+ ref,
24
+ size = 20,
25
+ className = ''
26
+ }: GitHubIconProps & {
27
+ ref?: React.Ref<SVGSVGElement>;
28
+ }
29
+ ) => {
30
+ return (
31
+ <svg
32
+ ref={ref}
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ width={size}
35
+ height={size}
36
+ fill="currentColor"
37
+ viewBox="0 0 16 16"
38
+ className={className}
39
+ aria-hidden="true"
40
+ >
41
+ <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8"/>
42
+ </svg>
43
+ );
44
+ };
@@ -0,0 +1,36 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Heading } from './Heading'
4
+
5
+ describe('Heading', () => {
6
+ it('renders h1', () => {
7
+ render(<Heading level={1}>Page Title</Heading>)
8
+ expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Page Title')
9
+ })
10
+
11
+ it('renders h2', () => {
12
+ render(<Heading level={2}>Section</Heading>)
13
+ expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('Section')
14
+ })
15
+
16
+ it('renders h3', () => {
17
+ render(<Heading level={3}>Subsection</Heading>)
18
+ expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Subsection')
19
+ })
20
+
21
+ it('renders all levels (4, 5, 6)', () => {
22
+ const { rerender } = render(<Heading level={4}>H4</Heading>)
23
+ expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument()
24
+
25
+ rerender(<Heading level={5}>H5</Heading>)
26
+ expect(screen.getByRole('heading', { level: 5 })).toBeInTheDocument()
27
+
28
+ rerender(<Heading level={6}>H6</Heading>)
29
+ expect(screen.getByRole('heading', { level: 6 })).toBeInTheDocument()
30
+ })
31
+
32
+ it('applies custom className', () => {
33
+ render(<Heading level={1} className="custom-heading">Title</Heading>)
34
+ expect(screen.getByRole('heading')).toHaveClass('custom-heading')
35
+ })
36
+ })
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+
3
+ export interface HeadingProps {
4
+ /**
5
+ * Heading content
6
+ */
7
+ children: React.ReactNode;
8
+
9
+ /**
10
+ * Heading level (affects both semantics and styling)
11
+ */
12
+ level: 1 | 2 | 3 | 4 | 5 | 6;
13
+
14
+ /**
15
+ * Additional className for customization
16
+ */
17
+ className?: string;
18
+ }
19
+
20
+ /**
21
+ * Heading Component
22
+ *
23
+ * Semantic heading with automatic token-based styling.
24
+ * No need to manually apply text colors, sizes, or weights.
25
+ *
26
+ * **What it handles automatically:**
27
+ * - Theme-aware text color (`--color-text-primary`)
28
+ * - Appropriate font size for each level
29
+ * - Font weight (bold for all levels)
30
+ * - Line height for readability
31
+ * - Dark mode support
32
+ *
33
+ * **Swiss Grid Typography:**
34
+ * - H1: 48px (3xl) on mobile, 60px (5xl) on desktop
35
+ * - H2: 36px (3xl) on mobile, 48px (4xl) on desktop
36
+ * - H3: 30px (3xl)
37
+ * - H4: 24px (2xl)
38
+ * - H5: 20px (xl)
39
+ * - H6: 18px (lg)
40
+ *
41
+ * Usage:
42
+ * ```tsx
43
+ * <Heading level={1}>Page Title</Heading>
44
+ * <Heading level={2}>Section Heading</Heading>
45
+ * <Heading level={3}>Subsection</Heading>
46
+ * ```
47
+ */
48
+ export const Heading = (
49
+ {
50
+ ref,
51
+ children,
52
+ level,
53
+ className = ''
54
+ }: HeadingProps & {
55
+ ref?: React.Ref<HTMLHeadingElement>;
56
+ }
57
+ ) => {
58
+ const levelStyles = {
59
+ 1: 'text-4xl lg:text-5xl', // 36px → 48px
60
+ 2: 'text-3xl lg:text-4xl', // 30px → 36px
61
+ 3: 'text-2xl lg:text-3xl', // 24px → 30px
62
+ 4: 'text-xl lg:text-2xl', // 20px → 24px
63
+ 5: 'text-lg lg:text-xl', // 18px → 20px
64
+ 6: 'text-base lg:text-lg', // 16px → 18px
65
+ };
66
+
67
+ const baseStyles = `
68
+ font-bold
69
+ text-[var(--color-text-primary)]
70
+ ${levelStyles[level]}
71
+ `;
72
+
73
+ const Component = `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
74
+
75
+ return React.createElement(
76
+ Component,
77
+ {
78
+ ref,
79
+ className: `${baseStyles} ${className}`,
80
+ },
81
+ children
82
+ );
83
+ };
@@ -0,0 +1,195 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const statCardVariants = cva(
6
+ "rounded-2xl border bg-surface text-foreground shadow-xs p-6",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-surface border-border",
11
+ glass: "bg-glass border-glass-border backdrop-blur-md",
12
+ outline: "bg-transparent border-border",
13
+ },
14
+ size: {
15
+ sm: "p-4 rounded-xl",
16
+ default: "p-6 rounded-2xl",
17
+ lg: "p-8 rounded-2xl",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ size: "default",
23
+ },
24
+ }
25
+ )
26
+
27
+ const statCardValueVariants = cva("font-bold font-heading tracking-tight", {
28
+ variants: {
29
+ size: {
30
+ sm: "text-2xl",
31
+ default: "text-3xl",
32
+ lg: "text-4xl",
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ size: "default",
37
+ },
38
+ })
39
+
40
+ const statCardChangeVariants = cva(
41
+ "inline-flex items-center gap-1 text-sm font-medium rounded-full px-2 py-0.5",
42
+ {
43
+ variants: {
44
+ trend: {
45
+ up: "text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-950/50",
46
+ down: "text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-950/50",
47
+ flat: "text-foreground-secondary bg-muted",
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ trend: "flat",
52
+ },
53
+ }
54
+ )
55
+
56
+ export interface StatCardProps
57
+ extends React.HTMLAttributes<HTMLDivElement>,
58
+ VariantProps<typeof statCardVariants> {
59
+ /** The metric label (e.g. "Revenue", "Active Users") */
60
+ label: string
61
+ /** The metric value (e.g. "$1.2M", "12,345") */
62
+ value: string | number
63
+ /** Percentage change (e.g. 5.2 for +5.2%, -3.1 for -3.1%) */
64
+ change?: number
65
+ /** Direction of the trend */
66
+ trend?: "up" | "down" | "flat"
67
+ /** Optional icon displayed in the top-right */
68
+ icon?: React.ReactNode
69
+ /** Additional description text below the value */
70
+ description?: string
71
+ }
72
+
73
+ const TrendUpIcon = () => (
74
+ <svg
75
+ xmlns="http://www.w3.org/2000/svg"
76
+ width="14"
77
+ height="14"
78
+ viewBox="0 0 24 24"
79
+ fill="none"
80
+ stroke="currentColor"
81
+ strokeWidth="2"
82
+ strokeLinecap="round"
83
+ strokeLinejoin="round"
84
+ aria-hidden="true"
85
+ >
86
+ <polyline points="22 7 13.5 15.5 8.5 10.5 2 17" />
87
+ <polyline points="16 7 22 7 22 13" />
88
+ </svg>
89
+ )
90
+
91
+ const TrendDownIcon = () => (
92
+ <svg
93
+ xmlns="http://www.w3.org/2000/svg"
94
+ width="14"
95
+ height="14"
96
+ viewBox="0 0 24 24"
97
+ fill="none"
98
+ stroke="currentColor"
99
+ strokeWidth="2"
100
+ strokeLinecap="round"
101
+ strokeLinejoin="round"
102
+ aria-hidden="true"
103
+ >
104
+ <polyline points="22 17 13.5 8.5 8.5 13.5 2 7" />
105
+ <polyline points="16 17 22 17 22 11" />
106
+ </svg>
107
+ )
108
+
109
+ const MinusIcon = () => (
110
+ <svg
111
+ xmlns="http://www.w3.org/2000/svg"
112
+ width="14"
113
+ height="14"
114
+ viewBox="0 0 24 24"
115
+ fill="none"
116
+ stroke="currentColor"
117
+ strokeWidth="2"
118
+ strokeLinecap="round"
119
+ strokeLinejoin="round"
120
+ aria-hidden="true"
121
+ >
122
+ <line x1="5" y1="12" x2="19" y2="12" />
123
+ </svg>
124
+ )
125
+
126
+ function StatCard({
127
+ className,
128
+ variant,
129
+ size,
130
+ label,
131
+ value,
132
+ change,
133
+ trend,
134
+ icon,
135
+ description,
136
+ ...props
137
+ }: StatCardProps) {
138
+ const resolvedTrend = trend ?? (change !== undefined ? (change > 0 ? "up" : change < 0 ? "down" : "flat") : undefined)
139
+
140
+ return (
141
+ <div
142
+ data-slot="stat-card"
143
+ role="article"
144
+ className={cn(statCardVariants({ variant, size }), className)}
145
+ {...props}
146
+ >
147
+ <dl className="space-y-1">
148
+ <div className="flex items-center justify-between">
149
+ <dt className="text-sm font-medium text-foreground-secondary">{label}</dt>
150
+ {icon && (
151
+ <div className="text-foreground-secondary" aria-hidden="true">
152
+ {icon}
153
+ </div>
154
+ )}
155
+ </div>
156
+ <dd className={cn(statCardValueVariants({ size }))}>
157
+ {value}
158
+ </dd>
159
+ {(change !== undefined || description) && (
160
+ <dd className="flex items-center gap-2 pt-1">
161
+ {change !== undefined && resolvedTrend && (
162
+ <span className={cn(statCardChangeVariants({ trend: resolvedTrend }))}>
163
+ {resolvedTrend === "up" && <TrendUpIcon />}
164
+ {resolvedTrend === "down" && <TrendDownIcon />}
165
+ {resolvedTrend === "flat" && <MinusIcon />}
166
+ <span>
167
+ {change > 0 ? "+" : ""}
168
+ {change}%
169
+ </span>
170
+ </span>
171
+ )}
172
+ {description && (
173
+ <span className="text-sm text-foreground-secondary">{description}</span>
174
+ )}
175
+ </dd>
176
+ )}
177
+ </dl>
178
+ </div>
179
+ )
180
+ }
181
+
182
+ function StatCardGroup({
183
+ className,
184
+ ...props
185
+ }: React.HTMLAttributes<HTMLDivElement>) {
186
+ return (
187
+ <div
188
+ data-slot="stat-card-group"
189
+ className={cn("grid gap-4 sm:grid-cols-2 lg:grid-cols-4", className)}
190
+ {...props}
191
+ />
192
+ )
193
+ }
194
+
195
+ export { StatCard, statCardVariants, StatCardGroup }
@@ -0,0 +1,133 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Table = (
6
+ {
7
+ ref,
8
+ className,
9
+ ...props
10
+ }: React.HTMLAttributes<HTMLTableElement> & {
11
+ ref?: React.Ref<HTMLTableElement>;
12
+ }
13
+ ) => (<div className="relative w-full overflow-auto">
14
+ <table
15
+ ref={ref}
16
+ className={cn("w-full caption-bottom text-sm", className)}
17
+ {...props}
18
+ />
19
+ </div>)
20
+
21
+ const TableHeader = (
22
+ {
23
+ ref,
24
+ className,
25
+ ...props
26
+ }: React.HTMLAttributes<HTMLTableSectionElement> & {
27
+ ref?: React.Ref<HTMLTableSectionElement>;
28
+ }
29
+ ) => (<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />)
30
+
31
+ const TableBody = (
32
+ {
33
+ ref,
34
+ className,
35
+ ...props
36
+ }: React.HTMLAttributes<HTMLTableSectionElement> & {
37
+ ref?: React.Ref<HTMLTableSectionElement>;
38
+ }
39
+ ) => (<tbody
40
+ ref={ref}
41
+ className={cn("[&_tr:last-child]:border-0", className)}
42
+ {...props}
43
+ />)
44
+
45
+ const TableFooter = (
46
+ {
47
+ ref,
48
+ className,
49
+ ...props
50
+ }: React.HTMLAttributes<HTMLTableSectionElement> & {
51
+ ref?: React.Ref<HTMLTableSectionElement>;
52
+ }
53
+ ) => (<tfoot
54
+ ref={ref}
55
+ className={cn(
56
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
57
+ className
58
+ )}
59
+ {...props}
60
+ />)
61
+
62
+ const TableRow = (
63
+ {
64
+ ref,
65
+ className,
66
+ ...props
67
+ }: React.HTMLAttributes<HTMLTableRowElement> & {
68
+ ref?: React.Ref<HTMLTableRowElement>;
69
+ }
70
+ ) => (<tr
71
+ ref={ref}
72
+ className={cn(
73
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
74
+ className
75
+ )}
76
+ {...props}
77
+ />)
78
+
79
+ const TableHead = (
80
+ {
81
+ ref,
82
+ className,
83
+ ...props
84
+ }: React.ThHTMLAttributes<HTMLTableCellElement> & {
85
+ ref?: React.Ref<HTMLTableCellElement>;
86
+ }
87
+ ) => (<th
88
+ ref={ref}
89
+ className={cn(
90
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
91
+ className
92
+ )}
93
+ {...props}
94
+ />)
95
+
96
+ const TableCell = (
97
+ {
98
+ ref,
99
+ className,
100
+ ...props
101
+ }: React.TdHTMLAttributes<HTMLTableCellElement> & {
102
+ ref?: React.Ref<HTMLTableCellElement>;
103
+ }
104
+ ) => (<td
105
+ ref={ref}
106
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
107
+ {...props}
108
+ />)
109
+
110
+ const TableCaption = (
111
+ {
112
+ ref,
113
+ className,
114
+ ...props
115
+ }: React.HTMLAttributes<HTMLTableCaptionElement> & {
116
+ ref?: React.Ref<HTMLTableCaptionElement>;
117
+ }
118
+ ) => (<caption
119
+ ref={ref}
120
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
121
+ {...props}
122
+ />)
123
+
124
+ export {
125
+ Table,
126
+ TableHeader,
127
+ TableBody,
128
+ TableFooter,
129
+ TableHead,
130
+ TableRow,
131
+ TableCell,
132
+ TableCaption,
133
+ }
@@ -0,0 +1,48 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { describe, it, expect } from 'vitest'
3
+ import { Text } from './Text'
4
+
5
+ describe('Text', () => {
6
+ it('renders as paragraph by default', () => {
7
+ render(<Text>Hello world</Text>)
8
+ const el = screen.getByText('Hello world')
9
+ expect(el).toBeInTheDocument()
10
+ expect(el.tagName).toBe('P')
11
+ })
12
+
13
+ it('renders as span', () => {
14
+ render(<Text as="span">Inline</Text>)
15
+ expect(screen.getByText('Inline').tagName).toBe('SPAN')
16
+ })
17
+
18
+ it('renders as div', () => {
19
+ render(<Text as="div">Block</Text>)
20
+ expect(screen.getByText('Block').tagName).toBe('DIV')
21
+ })
22
+
23
+ it('applies variant styles', () => {
24
+ const { rerender } = render(<Text variant="primary">Primary</Text>)
25
+ expect(screen.getByText('Primary')).toHaveClass('text-[var(--color-text-primary)]')
26
+
27
+ rerender(<Text variant="secondary">Secondary</Text>)
28
+ expect(screen.getByText('Secondary')).toHaveClass('text-[var(--color-text-secondary)]')
29
+
30
+ rerender(<Text variant="muted">Muted</Text>)
31
+ expect(screen.getByText('Muted')).toHaveClass('text-[var(--color-text-muted)]')
32
+ })
33
+
34
+ it('applies size styles', () => {
35
+ render(<Text size="lg">Large</Text>)
36
+ expect(screen.getByText('Large')).toHaveClass('text-lg')
37
+ })
38
+
39
+ it('applies weight styles', () => {
40
+ render(<Text weight="bold">Bold</Text>)
41
+ expect(screen.getByText('Bold')).toHaveClass('font-bold')
42
+ })
43
+
44
+ it('applies custom className', () => {
45
+ render(<Text className="custom-text">Styled</Text>)
46
+ expect(screen.getByText('Styled')).toHaveClass('custom-text')
47
+ })
48
+ })