@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,144 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TextProps {
|
|
4
|
+
/**
|
|
5
|
+
* Text content
|
|
6
|
+
*/
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Semantic variant determines both color and meaning
|
|
11
|
+
* @default 'primary'
|
|
12
|
+
*/
|
|
13
|
+
variant?: 'primary' | 'secondary' | 'muted';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Text size
|
|
17
|
+
* @default 'base'
|
|
18
|
+
*/
|
|
19
|
+
size?: 'xs' | 'sm' | 'base' | 'lg' | 'xl';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Font weight
|
|
23
|
+
* @default 'normal'
|
|
24
|
+
*/
|
|
25
|
+
weight?: 'normal' | 'medium' | 'semibold' | 'bold';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* HTML element to render as
|
|
29
|
+
* @default 'p'
|
|
30
|
+
*/
|
|
31
|
+
as?: 'p' | 'span' | 'div' | 'label' | 'time';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Additional className for customization
|
|
35
|
+
*/
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Text Component
|
|
41
|
+
*
|
|
42
|
+
* Semantic text with automatic token-based styling.
|
|
43
|
+
* No need to manually apply CSS variable text color classes.
|
|
44
|
+
*
|
|
45
|
+
* **What it handles automatically:**
|
|
46
|
+
* - Theme-aware text colors
|
|
47
|
+
* - Semantic color variants (primary, secondary, muted)
|
|
48
|
+
* - Consistent sizing
|
|
49
|
+
* - Dark mode support
|
|
50
|
+
*
|
|
51
|
+
* **Variants:**
|
|
52
|
+
* - `primary`: Main content text (`--color-text-primary`)
|
|
53
|
+
* - `secondary`: Supporting text (`--color-text-secondary`)
|
|
54
|
+
* - `muted`: De-emphasized text (`--color-text-muted`)
|
|
55
|
+
*
|
|
56
|
+
* Usage:
|
|
57
|
+
* ```tsx
|
|
58
|
+
* // Primary content text
|
|
59
|
+
* <Text>Main paragraph content</Text>
|
|
60
|
+
*
|
|
61
|
+
* // Secondary supporting text
|
|
62
|
+
* <Text variant="secondary">Helper text or description</Text>
|
|
63
|
+
*
|
|
64
|
+
* // Muted text (least emphasis)
|
|
65
|
+
* <Text variant="muted">Footnote or metadata</Text>
|
|
66
|
+
*
|
|
67
|
+
* // Different sizes
|
|
68
|
+
* <Text size="lg">Large text</Text>
|
|
69
|
+
* <Text size="sm">Small text</Text>
|
|
70
|
+
*
|
|
71
|
+
* // As different element
|
|
72
|
+
* <Text as="span">Inline text</Text>
|
|
73
|
+
* <Text as="label">Form label</Text>
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const Text: React.FC<TextProps & { ref?: React.Ref<HTMLElement> }> = (
|
|
77
|
+
{
|
|
78
|
+
ref,
|
|
79
|
+
children,
|
|
80
|
+
variant = 'primary',
|
|
81
|
+
size = 'base',
|
|
82
|
+
weight = 'normal',
|
|
83
|
+
as: Component = 'p',
|
|
84
|
+
className = ''
|
|
85
|
+
}: TextProps & {
|
|
86
|
+
ref?: React.Ref<HTMLElement>;
|
|
87
|
+
}
|
|
88
|
+
) => {
|
|
89
|
+
const variantStyles = {
|
|
90
|
+
primary: 'text-[var(--color-text-primary)]',
|
|
91
|
+
secondary: 'text-[var(--color-text-secondary)]',
|
|
92
|
+
muted: 'text-[var(--color-text-muted)]',
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const variantInlineStyles: Record<string, React.CSSProperties> = {
|
|
96
|
+
primary: { color: 'var(--color-text-primary, #212121)' },
|
|
97
|
+
secondary: { color: 'var(--color-text-secondary, #5D5D5D)' },
|
|
98
|
+
muted: { color: 'var(--color-text-muted, #8891A7)' },
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const sizeStyles = {
|
|
102
|
+
xs: 'text-xs', // 12px
|
|
103
|
+
sm: 'text-sm', // 14px
|
|
104
|
+
base: 'text-base', // 16px
|
|
105
|
+
lg: 'text-lg', // 18px
|
|
106
|
+
xl: 'text-xl', // 20px
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const sizeInlineStyles: Record<string, React.CSSProperties> = {
|
|
110
|
+
xs: { fontSize: '0.75rem', lineHeight: String(1 / 0.75) },
|
|
111
|
+
sm: { fontSize: '0.875rem', lineHeight: String(1.25 / 0.875) },
|
|
112
|
+
base: { fontSize: '1rem', lineHeight: '1.5' },
|
|
113
|
+
lg: { fontSize: '1.125rem', lineHeight: String(1.75 / 1.125) },
|
|
114
|
+
xl: { fontSize: '1.25rem', lineHeight: String(1.75 / 1.25) },
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const weightStyles = {
|
|
118
|
+
normal: 'font-normal',
|
|
119
|
+
medium: 'font-medium',
|
|
120
|
+
semibold: 'font-semibold',
|
|
121
|
+
bold: 'font-bold',
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const weightInlineStyles: Record<string, React.CSSProperties> = {
|
|
125
|
+
normal: { fontWeight: 400 },
|
|
126
|
+
medium: { fontWeight: 500 },
|
|
127
|
+
semibold: { fontWeight: 600 },
|
|
128
|
+
bold: { fontWeight: 700 },
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return React.createElement(
|
|
132
|
+
Component,
|
|
133
|
+
{
|
|
134
|
+
ref,
|
|
135
|
+
className: `${variantStyles[variant]} ${sizeStyles[size]} ${weightStyles[weight]} ${className}`,
|
|
136
|
+
style: {
|
|
137
|
+
...variantInlineStyles[variant],
|
|
138
|
+
...sizeInlineStyles[size],
|
|
139
|
+
...weightInlineStyles[weight],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
children
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
const timelineVariants = cva("relative", {
|
|
6
|
+
variants: {
|
|
7
|
+
orientation: {
|
|
8
|
+
vertical: "flex flex-col",
|
|
9
|
+
horizontal: "flex flex-row overflow-x-auto",
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
defaultVariants: {
|
|
13
|
+
orientation: "vertical",
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const timelineItemStatusVariants = cva(
|
|
18
|
+
"flex items-center justify-center rounded-full border-2 shrink-0",
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
status: {
|
|
22
|
+
pending: "border-border bg-muted text-foreground-secondary",
|
|
23
|
+
active: "border-primary bg-primary text-primary-foreground",
|
|
24
|
+
completed: "border-primary bg-primary text-primary-foreground",
|
|
25
|
+
error: "border-destructive bg-destructive text-destructive-foreground",
|
|
26
|
+
},
|
|
27
|
+
size: {
|
|
28
|
+
sm: "h-6 w-6 [&>svg]:h-3 [&>svg]:w-3",
|
|
29
|
+
default: "h-8 w-8 [&>svg]:h-4 [&>svg]:w-4",
|
|
30
|
+
lg: "h-10 w-10 [&>svg]:h-5 [&>svg]:w-5",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
status: "pending",
|
|
35
|
+
size: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
export interface TimelineProps
|
|
41
|
+
extends React.HTMLAttributes<HTMLOListElement>,
|
|
42
|
+
VariantProps<typeof timelineVariants> {}
|
|
43
|
+
|
|
44
|
+
export interface TimelineItemProps
|
|
45
|
+
extends React.HTMLAttributes<HTMLLIElement> {
|
|
46
|
+
/** Event title */
|
|
47
|
+
title: string
|
|
48
|
+
/** Event description */
|
|
49
|
+
description?: string
|
|
50
|
+
/** Timestamp text */
|
|
51
|
+
timestamp?: string
|
|
52
|
+
/** Custom icon for the indicator */
|
|
53
|
+
icon?: React.ReactNode
|
|
54
|
+
/** Status of this event */
|
|
55
|
+
status?: "pending" | "active" | "completed" | "error"
|
|
56
|
+
/** Whether this is the last item (hides connector) */
|
|
57
|
+
isLast?: boolean
|
|
58
|
+
/** Size variant */
|
|
59
|
+
size?: "sm" | "default" | "lg"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const CheckIcon = () => (
|
|
63
|
+
<svg
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
width="14"
|
|
66
|
+
height="14"
|
|
67
|
+
viewBox="0 0 24 24"
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
strokeWidth="2.5"
|
|
71
|
+
strokeLinecap="round"
|
|
72
|
+
strokeLinejoin="round"
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
>
|
|
75
|
+
<polyline points="20 6 9 17 4 12" />
|
|
76
|
+
</svg>
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const XIcon = () => (
|
|
80
|
+
<svg
|
|
81
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
82
|
+
width="14"
|
|
83
|
+
height="14"
|
|
84
|
+
viewBox="0 0 24 24"
|
|
85
|
+
fill="none"
|
|
86
|
+
stroke="currentColor"
|
|
87
|
+
strokeWidth="2.5"
|
|
88
|
+
strokeLinecap="round"
|
|
89
|
+
strokeLinejoin="round"
|
|
90
|
+
aria-hidden="true"
|
|
91
|
+
>
|
|
92
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
93
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
94
|
+
</svg>
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const CircleIcon = () => (
|
|
98
|
+
<svg
|
|
99
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
100
|
+
width="8"
|
|
101
|
+
height="8"
|
|
102
|
+
viewBox="0 0 24 24"
|
|
103
|
+
fill="currentColor"
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
>
|
|
106
|
+
<circle cx="12" cy="12" r="6" />
|
|
107
|
+
</svg>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
function Timeline({
|
|
111
|
+
className,
|
|
112
|
+
orientation = "vertical",
|
|
113
|
+
children,
|
|
114
|
+
...props
|
|
115
|
+
}: TimelineProps) {
|
|
116
|
+
return (
|
|
117
|
+
<ol
|
|
118
|
+
data-slot="timeline"
|
|
119
|
+
className={cn(timelineVariants({ orientation }), className)}
|
|
120
|
+
{...props}
|
|
121
|
+
>
|
|
122
|
+
{children}
|
|
123
|
+
</ol>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function TimelineItem({
|
|
128
|
+
className,
|
|
129
|
+
title,
|
|
130
|
+
description,
|
|
131
|
+
timestamp,
|
|
132
|
+
icon,
|
|
133
|
+
status = "pending",
|
|
134
|
+
isLast = false,
|
|
135
|
+
size = "default",
|
|
136
|
+
...props
|
|
137
|
+
}: TimelineItemProps) {
|
|
138
|
+
const defaultIcon = (() => {
|
|
139
|
+
switch (status) {
|
|
140
|
+
case "completed":
|
|
141
|
+
return <CheckIcon />
|
|
142
|
+
case "error":
|
|
143
|
+
return <XIcon />
|
|
144
|
+
case "active":
|
|
145
|
+
return <CircleIcon />
|
|
146
|
+
default:
|
|
147
|
+
return <CircleIcon />
|
|
148
|
+
}
|
|
149
|
+
})()
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<li
|
|
153
|
+
data-slot="timeline-item"
|
|
154
|
+
className={cn("relative flex gap-4", className)}
|
|
155
|
+
aria-current={status === "active" ? "step" : undefined}
|
|
156
|
+
{...props}
|
|
157
|
+
>
|
|
158
|
+
{/* Indicator column */}
|
|
159
|
+
<div className="flex flex-col items-center">
|
|
160
|
+
<div
|
|
161
|
+
data-slot="timeline-icon"
|
|
162
|
+
className={cn(timelineItemStatusVariants({ status, size }))}
|
|
163
|
+
aria-hidden="true"
|
|
164
|
+
>
|
|
165
|
+
{icon ?? defaultIcon}
|
|
166
|
+
</div>
|
|
167
|
+
{!isLast && (
|
|
168
|
+
<div
|
|
169
|
+
data-slot="timeline-connector"
|
|
170
|
+
className={cn(
|
|
171
|
+
"w-0.5 grow bg-border",
|
|
172
|
+
size === "sm" ? "min-h-4" : size === "lg" ? "min-h-8" : "min-h-6"
|
|
173
|
+
)}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Content column */}
|
|
179
|
+
<div className={cn("pb-6", isLast && "pb-0")}>
|
|
180
|
+
<div className="flex items-center gap-2">
|
|
181
|
+
<p className="font-medium text-foreground leading-none">{title}</p>
|
|
182
|
+
{timestamp && (
|
|
183
|
+
<time className="text-xs text-foreground-secondary">{timestamp}</time>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
{description && (
|
|
187
|
+
<p className="mt-1 text-sm text-foreground-secondary">{description}</p>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</li>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { Timeline, TimelineItem, timelineVariants }
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cva } from "class-variance-authority"
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
export interface TreeNode {
|
|
8
|
+
/** Unique identifier */
|
|
9
|
+
id: string
|
|
10
|
+
/** Display label */
|
|
11
|
+
label: string
|
|
12
|
+
/** Optional icon */
|
|
13
|
+
icon?: React.ReactNode
|
|
14
|
+
/** Child nodes */
|
|
15
|
+
children?: TreeNode[]
|
|
16
|
+
/** Whether the node is disabled */
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TreeViewProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
21
|
+
/** Tree data structure */
|
|
22
|
+
nodes: TreeNode[]
|
|
23
|
+
/** Expanded node IDs (controlled) */
|
|
24
|
+
expanded?: string[]
|
|
25
|
+
/** Default expanded node IDs (uncontrolled) */
|
|
26
|
+
defaultExpanded?: string[]
|
|
27
|
+
/** Called when expanded state changes */
|
|
28
|
+
onExpandChange?: (expanded: string[]) => void
|
|
29
|
+
/** Currently selected node ID */
|
|
30
|
+
selected?: string
|
|
31
|
+
/** Called when selection changes */
|
|
32
|
+
onSelectChange?: (nodeId: string) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const treeNodeVariants = cva(
|
|
36
|
+
"flex items-center gap-2 py-1 px-2 rounded-md text-sm cursor-pointer select-none transition-colors",
|
|
37
|
+
{
|
|
38
|
+
variants: {
|
|
39
|
+
state: {
|
|
40
|
+
idle: "text-foreground hover:bg-muted",
|
|
41
|
+
selected: "text-foreground bg-primary/10 font-medium",
|
|
42
|
+
disabled: "text-foreground-secondary cursor-not-allowed opacity-60",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
defaultVariants: {
|
|
46
|
+
state: "idle",
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
|
|
52
|
+
<svg
|
|
53
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
54
|
+
width="14"
|
|
55
|
+
height="14"
|
|
56
|
+
viewBox="0 0 24 24"
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke="currentColor"
|
|
59
|
+
strokeWidth="2"
|
|
60
|
+
strokeLinecap="round"
|
|
61
|
+
strokeLinejoin="round"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
className={cn(
|
|
64
|
+
"shrink-0 transition-transform",
|
|
65
|
+
expanded ? "rotate-90" : "rotate-0"
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
<polyline points="9 18 15 12 9 6" />
|
|
69
|
+
</svg>
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
interface TreeViewContextValue {
|
|
73
|
+
expanded: Set<string>
|
|
74
|
+
selected: string | null
|
|
75
|
+
toggleExpand: (nodeId: string) => void
|
|
76
|
+
selectNode: (nodeId: string) => void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const TreeViewContext = React.createContext<TreeViewContextValue>({
|
|
80
|
+
expanded: new Set(),
|
|
81
|
+
selected: null,
|
|
82
|
+
toggleExpand: () => {},
|
|
83
|
+
selectNode: () => {},
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
function TreeView({
|
|
87
|
+
className,
|
|
88
|
+
nodes,
|
|
89
|
+
expanded: controlledExpanded,
|
|
90
|
+
defaultExpanded = [],
|
|
91
|
+
onExpandChange,
|
|
92
|
+
selected: controlledSelected,
|
|
93
|
+
onSelectChange,
|
|
94
|
+
...props
|
|
95
|
+
}: TreeViewProps) {
|
|
96
|
+
const [internalExpanded, setInternalExpanded] = React.useState<Set<string>>(
|
|
97
|
+
new Set(defaultExpanded)
|
|
98
|
+
)
|
|
99
|
+
const [internalSelected, setInternalSelected] = React.useState<string | null>(null)
|
|
100
|
+
|
|
101
|
+
const isControlled = controlledExpanded !== undefined
|
|
102
|
+
const expanded = isControlled ? new Set(controlledExpanded) : internalExpanded
|
|
103
|
+
const selected = controlledSelected ?? internalSelected
|
|
104
|
+
|
|
105
|
+
const toggleExpand = React.useCallback(
|
|
106
|
+
(nodeId: string) => {
|
|
107
|
+
const newExpanded = new Set(expanded)
|
|
108
|
+
if (newExpanded.has(nodeId)) {
|
|
109
|
+
newExpanded.delete(nodeId)
|
|
110
|
+
} else {
|
|
111
|
+
newExpanded.add(nodeId)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isControlled) {
|
|
115
|
+
setInternalExpanded(newExpanded)
|
|
116
|
+
}
|
|
117
|
+
onExpandChange?.(Array.from(newExpanded))
|
|
118
|
+
},
|
|
119
|
+
[expanded, isControlled, onExpandChange]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const selectNode = React.useCallback(
|
|
123
|
+
(nodeId: string) => {
|
|
124
|
+
if (controlledSelected === undefined) {
|
|
125
|
+
setInternalSelected(nodeId)
|
|
126
|
+
}
|
|
127
|
+
onSelectChange?.(nodeId)
|
|
128
|
+
},
|
|
129
|
+
[controlledSelected, onSelectChange]
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<TreeViewContext.Provider value={{ expanded, selected, toggleExpand, selectNode }}>
|
|
134
|
+
<div
|
|
135
|
+
data-slot="tree-view"
|
|
136
|
+
role="tree"
|
|
137
|
+
className={cn("space-y-0.5", className)}
|
|
138
|
+
{...props}
|
|
139
|
+
>
|
|
140
|
+
{nodes.map((node) => (
|
|
141
|
+
<TreeViewNodeComponent key={node.id} node={node} level={1} />
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</TreeViewContext.Provider>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
interface TreeViewNodeComponentProps {
|
|
149
|
+
node: TreeNode
|
|
150
|
+
level: number
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function TreeViewNodeComponent({ node, level }: TreeViewNodeComponentProps) {
|
|
154
|
+
const { expanded, selected, toggleExpand, selectNode } = React.useContext(TreeViewContext)
|
|
155
|
+
|
|
156
|
+
const hasChildren = node.children && node.children.length > 0
|
|
157
|
+
const isExpanded = expanded.has(node.id)
|
|
158
|
+
const isSelected = selected === node.id
|
|
159
|
+
|
|
160
|
+
const state = node.disabled ? "disabled" : isSelected ? "selected" : "idle"
|
|
161
|
+
|
|
162
|
+
const handleClick = () => {
|
|
163
|
+
if (node.disabled) return
|
|
164
|
+
selectNode(node.id)
|
|
165
|
+
if (hasChildren) {
|
|
166
|
+
toggleExpand(node.id)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
171
|
+
if (node.disabled) return
|
|
172
|
+
|
|
173
|
+
switch (e.key) {
|
|
174
|
+
case "Enter":
|
|
175
|
+
case " ":
|
|
176
|
+
e.preventDefault()
|
|
177
|
+
handleClick()
|
|
178
|
+
break
|
|
179
|
+
case "ArrowRight":
|
|
180
|
+
if (hasChildren && !isExpanded) {
|
|
181
|
+
e.preventDefault()
|
|
182
|
+
toggleExpand(node.id)
|
|
183
|
+
}
|
|
184
|
+
break
|
|
185
|
+
case "ArrowLeft":
|
|
186
|
+
if (hasChildren && isExpanded) {
|
|
187
|
+
e.preventDefault()
|
|
188
|
+
toggleExpand(node.id)
|
|
189
|
+
}
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div data-slot="tree-view-node" role="treeitem" aria-expanded={hasChildren ? isExpanded : undefined} aria-level={level} aria-selected={isSelected}>
|
|
196
|
+
<div
|
|
197
|
+
className={cn(treeNodeVariants({ state }))}
|
|
198
|
+
style={{ paddingLeft: `${(level - 1) * 20 + 8}px` }}
|
|
199
|
+
onClick={handleClick}
|
|
200
|
+
onKeyDown={handleKeyDown}
|
|
201
|
+
tabIndex={node.disabled ? -1 : 0}
|
|
202
|
+
>
|
|
203
|
+
{hasChildren ? (
|
|
204
|
+
<ChevronIcon expanded={isExpanded} />
|
|
205
|
+
) : (
|
|
206
|
+
<span className="w-3.5 shrink-0" aria-hidden="true" />
|
|
207
|
+
)}
|
|
208
|
+
{node.icon && (
|
|
209
|
+
<span className="shrink-0 text-foreground-secondary" aria-hidden="true">
|
|
210
|
+
{node.icon}
|
|
211
|
+
</span>
|
|
212
|
+
)}
|
|
213
|
+
<span className="truncate">{node.label}</span>
|
|
214
|
+
</div>
|
|
215
|
+
{hasChildren && isExpanded && (
|
|
216
|
+
<div role="group">
|
|
217
|
+
{node.children!.map((child) => (
|
|
218
|
+
<TreeViewNodeComponent key={child.id} node={child} level={level + 1} />
|
|
219
|
+
))}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
</div>
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { TreeView, treeNodeVariants }
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { motion } from 'framer-motion';
|
|
4
|
+
import { useEffect, useState } from 'react';
|
|
5
|
+
import { cn } from '../../lib/utils';
|
|
6
|
+
import { useMotionPreference } from '../../hooks/useMotionPreference';
|
|
7
|
+
|
|
8
|
+
export interface TypewriterProps {
|
|
9
|
+
text: string | string[];
|
|
10
|
+
speed?: number;
|
|
11
|
+
delay?: number;
|
|
12
|
+
loop?: boolean;
|
|
13
|
+
loopDelay?: number;
|
|
14
|
+
cursor?: string;
|
|
15
|
+
showCursor?: boolean;
|
|
16
|
+
className?: string;
|
|
17
|
+
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Typewriter({
|
|
21
|
+
text,
|
|
22
|
+
speed = 0.05,
|
|
23
|
+
delay = 0,
|
|
24
|
+
loop = false,
|
|
25
|
+
loopDelay = 2,
|
|
26
|
+
cursor = '|',
|
|
27
|
+
showCursor = true,
|
|
28
|
+
className,
|
|
29
|
+
as: Component = 'span',
|
|
30
|
+
}: TypewriterProps) {
|
|
31
|
+
const { shouldAnimate, scale } = useMotionPreference();
|
|
32
|
+
const [displayedText, setDisplayedText] = useState('');
|
|
33
|
+
|
|
34
|
+
// Flatten text to array for easier handling
|
|
35
|
+
const textArray = Array.isArray(text) ? text : [text];
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
// If reduced motion, show full text immediately
|
|
39
|
+
if (!shouldAnimate) {
|
|
40
|
+
setDisplayedText(textArray[0]);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
45
|
+
let isCancelled = false;
|
|
46
|
+
|
|
47
|
+
// State pointers
|
|
48
|
+
let stringIndex = 0;
|
|
49
|
+
let charIndex = 0;
|
|
50
|
+
let isDeleting = false;
|
|
51
|
+
|
|
52
|
+
const type = () => {
|
|
53
|
+
if (isCancelled) return;
|
|
54
|
+
|
|
55
|
+
const currentString = textArray[stringIndex];
|
|
56
|
+
const currentSpeed = (isDeleting ? speed / 2 : speed) * (scale > 0 ? (5 / scale) : 1) * 1000;
|
|
57
|
+
|
|
58
|
+
if (isDeleting) {
|
|
59
|
+
// DELETING
|
|
60
|
+
if (charIndex > 0) {
|
|
61
|
+
setDisplayedText(currentString.substring(0, charIndex - 1));
|
|
62
|
+
charIndex--;
|
|
63
|
+
timeoutId = setTimeout(type, currentSpeed);
|
|
64
|
+
} else {
|
|
65
|
+
// Start typing next string
|
|
66
|
+
isDeleting = false;
|
|
67
|
+
stringIndex = (stringIndex + 1) % textArray.length;
|
|
68
|
+
timeoutId = setTimeout(type, currentSpeed);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
// TYPING
|
|
72
|
+
if (charIndex < currentString.length) {
|
|
73
|
+
setDisplayedText(currentString.substring(0, charIndex + 1));
|
|
74
|
+
charIndex++;
|
|
75
|
+
timeoutId = setTimeout(type, currentSpeed);
|
|
76
|
+
} else {
|
|
77
|
+
// FINISHED TYPING STRING
|
|
78
|
+
if (loop) {
|
|
79
|
+
isDeleting = true;
|
|
80
|
+
timeoutId = setTimeout(type, loopDelay * 1000);
|
|
81
|
+
} else if (stringIndex < textArray.length - 1) {
|
|
82
|
+
// If we have more strings to type in sequence (and not just looping one)
|
|
83
|
+
// Note: The original logic treated array as "loop through these options".
|
|
84
|
+
// To match previous behavior: array = specific sequence to loop?
|
|
85
|
+
// Usually typewriters with arrays toggle between them.
|
|
86
|
+
isDeleting = true;
|
|
87
|
+
timeoutId = setTimeout(type, loopDelay * 1000);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Initial Start Delay
|
|
94
|
+
timeoutId = setTimeout(() => {
|
|
95
|
+
type();
|
|
96
|
+
}, delay * 1000);
|
|
97
|
+
|
|
98
|
+
return () => {
|
|
99
|
+
isCancelled = true;
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
};
|
|
102
|
+
}, [text, speed, delay, loop, loopDelay, shouldAnimate, scale]); // Re-run effect if props change
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<Component className={cn("inline", className)}>
|
|
106
|
+
<span>{displayedText}</span>
|
|
107
|
+
{showCursor && (
|
|
108
|
+
<motion.span
|
|
109
|
+
initial={{ opacity: 0 }}
|
|
110
|
+
animate={{ opacity: 1 }}
|
|
111
|
+
transition={{ duration: 0.5, repeat: Infinity, repeatType: "reverse" }}
|
|
112
|
+
className="ml-0.5 text-[var(--color-primary)] font-light"
|
|
113
|
+
>
|
|
114
|
+
{cursor}
|
|
115
|
+
</motion.span>
|
|
116
|
+
)}
|
|
117
|
+
</Component>
|
|
118
|
+
);
|
|
119
|
+
}
|