@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.
- package/.claude/CLAUDE.md +239 -0
- package/README.md +161 -0
- package/dist/cli.mjs +151 -0
- package/dist/dates.d.mts +20 -0
- package/dist/dates.d.ts +20 -0
- package/dist/dates.js +240 -0
- package/dist/dates.js.map +1 -0
- package/dist/dates.mjs +203 -0
- package/dist/dates.mjs.map +1 -0
- package/dist/dnd.d.mts +126 -0
- package/dist/dnd.d.ts +126 -0
- package/dist/dnd.js +274 -0
- package/dist/dnd.js.map +1 -0
- package/dist/dnd.mjs +250 -0
- package/dist/dnd.mjs.map +1 -0
- package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
- package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
- package/dist/forms.d.mts +38 -0
- package/dist/forms.d.ts +38 -0
- package/dist/forms.js +198 -0
- package/dist/forms.js.map +1 -0
- package/dist/forms.mjs +159 -0
- package/dist/forms.mjs.map +1 -0
- package/dist/hooks-1b8WaQf1.d.mts +225 -0
- package/dist/hooks-CKW8vE9H.d.ts +225 -0
- package/dist/hooks.d.mts +3 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +971 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hooks.mjs +943 -0
- package/dist/hooks.mjs.map +1 -0
- package/dist/index-DscTIrZ2.d.mts +29 -0
- package/dist/index-DscTIrZ2.d.ts +29 -0
- package/dist/index.d.mts +3382 -0
- package/dist/index.d.ts +3382 -0
- package/dist/index.js +15146 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14802 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers-CXPDMsl7.d.mts +30 -0
- package/dist/providers-Dn_Msjvz.d.ts +30 -0
- package/dist/providers.d.mts +3 -0
- package/dist/providers.d.ts +3 -0
- package/dist/providers.js +1885 -0
- package/dist/providers.js.map +1 -0
- package/dist/providers.mjs +1859 -0
- package/dist/providers.mjs.map +1 -0
- package/dist/tables.d.mts +10 -0
- package/dist/tables.d.ts +10 -0
- package/dist/tables.js +248 -0
- package/dist/tables.js.map +1 -0
- package/dist/tables.mjs +218 -0
- package/dist/tables.mjs.map +1 -0
- package/dist/tokens.d.mts +1065 -0
- package/dist/tokens.d.ts +1065 -0
- package/dist/tokens.js +2637 -0
- package/dist/tokens.js.map +1 -0
- package/dist/tokens.mjs +2555 -0
- package/dist/tokens.mjs.map +1 -0
- package/dist/utils-CIIM7dAC.d.ts +986 -0
- package/dist/utils-Cs04sxth.d.mts +986 -0
- package/dist/utils.d.mts +4 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +874 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +806 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/validation-Bj1ye-v_.d.mts +114 -0
- package/dist/validation-Bj1ye-v_.d.ts +114 -0
- package/dist/webgl.d.mts +104 -0
- package/dist/webgl.d.ts +104 -0
- package/dist/webgl.js +226 -0
- package/dist/webgl.js.map +1 -0
- package/dist/webgl.mjs +195 -0
- package/dist/webgl.mjs.map +1 -0
- package/package.json +267 -0
- package/src/cli.ts +206 -0
- package/src/component-registry.ts +183 -0
- package/src/components/actions/Button.test.tsx +61 -0
- package/src/components/actions/Button.tsx +70 -0
- package/src/components/actions/Link.tsx +78 -0
- package/src/components/actions/Magnetic.tsx +68 -0
- package/src/components/actions/Toggle.test.tsx +40 -0
- package/src/components/actions/Toggle.tsx +47 -0
- package/src/components/actions/ToggleGroup.tsx +70 -0
- package/src/components/actions/index.ts +5 -0
- package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
- package/src/components/backgrounds/OrbBackground.tsx +424 -0
- package/src/components/backgrounds/WarpBackground.tsx +358 -0
- package/src/components/backgrounds/index.ts +3 -0
- package/src/components/blocks/Hero.tsx +142 -0
- package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
- package/src/components/cursor/SplashCursor.tsx +1315 -0
- package/src/components/cursor/TargetCursor.tsx +187 -0
- package/src/components/cursor/index.ts +2 -0
- package/src/components/data-display/AspectImage.tsx +73 -0
- package/src/components/data-display/Avatar.test.tsx +35 -0
- package/src/components/data-display/Avatar.tsx +55 -0
- package/src/components/data-display/Badge.test.tsx +43 -0
- package/src/components/data-display/Badge.tsx +84 -0
- package/src/components/data-display/Brand.tsx +123 -0
- package/src/components/data-display/Calendar.tsx +70 -0
- package/src/components/data-display/Card.test.tsx +92 -0
- package/src/components/data-display/Card.tsx +115 -0
- package/src/components/data-display/Code.tsx +210 -0
- package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
- package/src/components/data-display/DataTable.tsx +119 -0
- package/src/components/data-display/DescriptionList.tsx +41 -0
- package/src/components/data-display/GitHubIcon.tsx +44 -0
- package/src/components/data-display/Heading.test.tsx +36 -0
- package/src/components/data-display/Heading.tsx +83 -0
- package/src/components/data-display/StatCard.tsx +195 -0
- package/src/components/data-display/Table.tsx +133 -0
- package/src/components/data-display/Text.test.tsx +48 -0
- package/src/components/data-display/Text.tsx +144 -0
- package/src/components/data-display/Timeline.tsx +194 -0
- package/src/components/data-display/TreeView.tsx +226 -0
- package/src/components/data-display/Typewriter.tsx +119 -0
- package/src/components/data-display/VariableWeightText.tsx +130 -0
- package/src/components/data-display/index.ts +19 -0
- package/src/components/feedback/Alert.test.tsx +44 -0
- package/src/components/feedback/Alert.tsx +65 -0
- package/src/components/feedback/EmptyState.tsx +113 -0
- package/src/components/feedback/Progress.test.tsx +60 -0
- package/src/components/feedback/Progress.tsx +30 -0
- package/src/components/feedback/ProgressBar.tsx +158 -0
- package/src/components/feedback/Skeleton.test.tsx +39 -0
- package/src/components/feedback/Skeleton.tsx +45 -0
- package/src/components/feedback/Sonner.tsx +28 -0
- package/src/components/feedback/Spinner.test.tsx +33 -0
- package/src/components/feedback/Spinner.tsx +99 -0
- package/src/components/feedback/Stepper.tsx +307 -0
- package/src/components/feedback/Toast/Toast.tsx +243 -0
- package/src/components/feedback/Toast/index.ts +2 -0
- package/src/components/feedback/index.ts +9 -0
- package/src/components/forms/Checkbox.test.tsx +40 -0
- package/src/components/forms/Checkbox.tsx +31 -0
- package/src/components/forms/ColorPicker.tsx +118 -0
- package/src/components/forms/Combobox.tsx +96 -0
- package/src/components/forms/DragDrop.tsx +440 -0
- package/src/components/forms/FileUpload.tsx +252 -0
- package/src/components/forms/FilterButton.tsx +65 -0
- package/src/components/forms/Form.tsx +197 -0
- package/src/components/forms/Input.test.tsx +46 -0
- package/src/components/forms/Input.tsx +43 -0
- package/src/components/forms/InputOTP.tsx +81 -0
- package/src/components/forms/Label.test.tsx +20 -0
- package/src/components/forms/Label.tsx +25 -0
- package/src/components/forms/RadioGroup.tsx +51 -0
- package/src/components/forms/SearchBar.tsx +215 -0
- package/src/components/forms/Select.test.tsx +118 -0
- package/src/components/forms/Select.tsx +274 -0
- package/src/components/forms/Slider.tsx +29 -0
- package/src/components/forms/Switch.test.tsx +76 -0
- package/src/components/forms/Switch.tsx +30 -0
- package/src/components/forms/TextField.tsx +152 -0
- package/src/components/forms/Textarea.test.tsx +41 -0
- package/src/components/forms/Textarea.tsx +29 -0
- package/src/components/forms/ThemeSwitcher.tsx +290 -0
- package/src/components/forms/ThemeToggle.tsx +151 -0
- package/src/components/forms/index.ts +19 -0
- package/src/components/layout/Accordion.test.tsx +66 -0
- package/src/components/layout/Accordion.tsx +64 -0
- package/src/components/layout/AspectRatio.tsx +7 -0
- package/src/components/layout/Carousel.tsx +277 -0
- package/src/components/layout/Collapsible.test.tsx +40 -0
- package/src/components/layout/Collapsible.tsx +31 -0
- package/src/components/layout/Container.test.tsx +45 -0
- package/src/components/layout/Container.tsx +99 -0
- package/src/components/layout/CustomizerPanel.tsx +400 -0
- package/src/components/layout/DatePicker.tsx +57 -0
- package/src/components/layout/Footer/Footer.tsx +175 -0
- package/src/components/layout/Footer/index.ts +2 -0
- package/src/components/layout/GlassSurface.tsx +82 -0
- package/src/components/layout/Grid.test.tsx +31 -0
- package/src/components/layout/Grid.tsx +130 -0
- package/src/components/layout/Header/Header.tsx +450 -0
- package/src/components/layout/Header/index.ts +2 -0
- package/src/components/layout/PageLayout.tsx +180 -0
- package/src/components/layout/PageTemplate.tsx +158 -0
- package/src/components/layout/Resizable.tsx +48 -0
- package/src/components/layout/ScrollArea.tsx +53 -0
- package/src/components/layout/Separator.test.tsx +28 -0
- package/src/components/layout/Separator.tsx +29 -0
- package/src/components/layout/Sidebar.tsx +171 -0
- package/src/components/layout/Stack.test.tsx +41 -0
- package/src/components/layout/Stack.tsx +89 -0
- package/src/components/layout/glass-surface.css +60 -0
- package/src/components/layout/index.ts +18 -0
- package/src/components/motion/AnimatedBeam.tsx +159 -0
- package/src/components/navigation/Breadcrumb.test.tsx +57 -0
- package/src/components/navigation/Breadcrumb.tsx +119 -0
- package/src/components/navigation/Breadcrumbs.tsx +221 -0
- package/src/components/navigation/Command.tsx +159 -0
- package/src/components/navigation/Menubar.tsx +115 -0
- package/src/components/navigation/NavLink.tsx +55 -0
- package/src/components/navigation/NavigationMenu.tsx +125 -0
- package/src/components/navigation/Pagination.tsx +121 -0
- package/src/components/navigation/SecondaryNav.tsx +100 -0
- package/src/components/navigation/Tabs.test.tsx +47 -0
- package/src/components/navigation/Tabs.tsx +60 -0
- package/src/components/navigation/TertiaryNav.tsx +90 -0
- package/src/components/navigation/index.ts +10 -0
- package/src/components/overlays/AlertDialog.test.tsx +69 -0
- package/src/components/overlays/AlertDialog.tsx +166 -0
- package/src/components/overlays/ContextMenu.tsx +243 -0
- package/src/components/overlays/Dialog.test.tsx +79 -0
- package/src/components/overlays/Dialog.tsx +158 -0
- package/src/components/overlays/Drawer.tsx +128 -0
- package/src/components/overlays/Dropdown.tsx +253 -0
- package/src/components/overlays/DropdownMenu.tsx +242 -0
- package/src/components/overlays/HoverCard.tsx +32 -0
- package/src/components/overlays/Modal.tsx +250 -0
- package/src/components/overlays/NotificationCenter.tsx +364 -0
- package/src/components/overlays/Popover.test.tsx +40 -0
- package/src/components/overlays/Popover.tsx +46 -0
- package/src/components/overlays/Sheet.tsx +163 -0
- package/src/components/overlays/Tooltip.test.tsx +33 -0
- package/src/components/overlays/Tooltip.tsx +32 -0
- package/src/components/overlays/index.ts +12 -0
- package/src/dates.ts +2 -0
- package/src/dnd.ts +1 -0
- package/src/forms.ts +1 -0
- package/src/globals.css +187 -0
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useForm.ts +247 -0
- package/src/hooks/useMotionPreference.test.ts +102 -0
- package/src/hooks/useMotionPreference.ts +78 -0
- package/src/hooks/useTheme.ts +58 -0
- package/src/hooks.ts +9 -0
- package/src/index.ts +168 -0
- package/src/lib/animations.ts +356 -0
- package/src/lib/breadcrumbs.ts +94 -0
- package/src/lib/colors.ts +493 -0
- package/src/lib/store/customizer.ts +482 -0
- package/src/lib/store/index.ts +3 -0
- package/src/lib/store/theme.ts +55 -0
- package/src/lib/syntax-parser/index.ts +50 -0
- package/src/lib/syntax-parser/patterns.ts +64 -0
- package/src/lib/syntax-parser/tokenizer.ts +117 -0
- package/src/lib/syntax-parser/types.ts +27 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/validation.ts +204 -0
- package/src/lib/webgl/Color.ts +11 -0
- package/src/lib/webgl/Mesh.ts +41 -0
- package/src/lib/webgl/Program.ts +118 -0
- package/src/lib/webgl/Renderer.ts +51 -0
- package/src/lib/webgl/Triangle.ts +27 -0
- package/src/lib/webgl/Vec3.ts +18 -0
- package/src/lib/webgl/index.ts +13 -0
- package/src/nativewind-env.d.ts +1 -0
- package/src/providers/ThemeProvider.tsx +461 -0
- package/src/providers/index.ts +1 -0
- package/src/providers.ts +7 -0
- package/src/tables.ts +1 -0
- package/src/test/setup.ts +39 -0
- package/src/theme.css +158 -0
- package/src/tokens.ts +7 -0
- package/src/utils.ts +12 -0
- 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
|
+
})
|