@nukleas/cyberdesign 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/build.ts +286 -0
- package/dist/cyberdesign-vars-only.css +116 -0
- package/dist/cyberdesign.css +8377 -0
- package/dist/fonts/JetBrainsMono-Variable.woff2 +0 -0
- package/dist/fonts/OFL.txt +93 -0
- package/dist/react/Accordion.d.ts +34 -0
- package/dist/react/Accordion.js +13 -0
- package/dist/react/Alert.d.ts +19 -0
- package/dist/react/Alert.js +6 -0
- package/dist/react/AugButton.d.ts +12 -0
- package/dist/react/AugButton.js +6 -0
- package/dist/react/AugPanel.d.ts +10 -0
- package/dist/react/AugPanel.js +6 -0
- package/dist/react/Avatar.d.ts +28 -0
- package/dist/react/Avatar.js +11 -0
- package/dist/react/Badge.d.ts +10 -0
- package/dist/react/Badge.js +6 -0
- package/dist/react/Breadcrumb.d.ts +19 -0
- package/dist/react/Breadcrumb.js +10 -0
- package/dist/react/Button.d.ts +12 -0
- package/dist/react/Button.js +6 -0
- package/dist/react/Checkbox.d.ts +9 -0
- package/dist/react/Checkbox.js +9 -0
- package/dist/react/Dropdown.d.ts +35 -0
- package/dist/react/Dropdown.js +20 -0
- package/dist/react/Field.d.ts +13 -0
- package/dist/react/Field.js +6 -0
- package/dist/react/Gauge.d.ts +19 -0
- package/dist/react/Gauge.js +6 -0
- package/dist/react/GlowCard.d.ts +12 -0
- package/dist/react/GlowCard.js +4 -0
- package/dist/react/HudBar.d.ts +34 -0
- package/dist/react/HudBar.js +10 -0
- package/dist/react/HudFrame.d.ts +19 -0
- package/dist/react/HudFrame.js +6 -0
- package/dist/react/Input.d.ts +11 -0
- package/dist/react/Input.js +6 -0
- package/dist/react/Interference.d.ts +18 -0
- package/dist/react/Interference.js +6 -0
- package/dist/react/Kbd.d.ts +18 -0
- package/dist/react/Kbd.js +11 -0
- package/dist/react/Label.d.ts +8 -0
- package/dist/react/Label.js +6 -0
- package/dist/react/Modal.d.ts +19 -0
- package/dist/react/Modal.js +9 -0
- package/dist/react/Pagination.d.ts +23 -0
- package/dist/react/Pagination.js +28 -0
- package/dist/react/PanelHeader.d.ts +9 -0
- package/dist/react/PanelHeader.js +6 -0
- package/dist/react/Progress.d.ts +13 -0
- package/dist/react/Progress.js +6 -0
- package/dist/react/Radio.d.ts +9 -0
- package/dist/react/Radio.js +9 -0
- package/dist/react/Scanner.d.ts +11 -0
- package/dist/react/Scanner.js +6 -0
- package/dist/react/Select.d.ts +13 -0
- package/dist/react/Select.js +6 -0
- package/dist/react/Separator.d.ts +11 -0
- package/dist/react/Separator.js +7 -0
- package/dist/react/Skeleton.d.ts +11 -0
- package/dist/react/Skeleton.js +13 -0
- package/dist/react/Slider.d.ts +9 -0
- package/dist/react/Slider.js +9 -0
- package/dist/react/Spinner.d.ts +11 -0
- package/dist/react/Spinner.js +6 -0
- package/dist/react/Stat.d.ts +20 -0
- package/dist/react/Stat.js +11 -0
- package/dist/react/Switch.d.ts +9 -0
- package/dist/react/Switch.js +6 -0
- package/dist/react/Table.d.ts +55 -0
- package/dist/react/Table.js +36 -0
- package/dist/react/Tabs.d.ts +28 -0
- package/dist/react/Tabs.js +16 -0
- package/dist/react/Terminal.d.ts +33 -0
- package/dist/react/Terminal.js +6 -0
- package/dist/react/Textarea.d.ts +7 -0
- package/dist/react/Textarea.js +6 -0
- package/dist/react/Ticker.d.ts +13 -0
- package/dist/react/Ticker.js +7 -0
- package/dist/react/Toast.d.ts +15 -0
- package/dist/react/Toast.js +6 -0
- package/dist/react/Tooltip.d.ts +11 -0
- package/dist/react/Tooltip.js +6 -0
- package/dist/react/index.d.ts +39 -0
- package/dist/react/index.js +46 -0
- package/dist/tailwind-theme.css +171 -0
- package/dist/tokens/base.d.ts +33 -0
- package/dist/tokens/base.js +41 -0
- package/dist/tokens/index.d.ts +7 -0
- package/dist/tokens/index.js +7 -0
- package/dist/tokens/semantic.d.ts +1 -0
- package/dist/tokens/semantic.js +54 -0
- package/dist/tokens/themes/cyan.d.ts +1 -0
- package/dist/tokens/themes/cyan.js +15 -0
- package/dist/tokens/themes/green.d.ts +1 -0
- package/dist/tokens/themes/green.js +15 -0
- package/dist/tokens/themes/orange.d.ts +1 -0
- package/dist/tokens/themes/orange.js +15 -0
- package/dist/tokens/themes/red.d.ts +1 -0
- package/dist/tokens/themes/red.js +15 -0
- package/dist/tokens/themes/steel.d.ts +1 -0
- package/dist/tokens/themes/steel.js +15 -0
- package/package.json +73 -0
- package/src/css/a11y.css +67 -0
- package/src/css/animations.css +111 -0
- package/src/css/base.css +34 -0
- package/src/css/components/accordion.css +148 -0
- package/src/css/components/alert.css +116 -0
- package/src/css/components/app-patterns.css +894 -0
- package/src/css/components/augmented.css +474 -0
- package/src/css/components/avatar.css +105 -0
- package/src/css/components/badge.css +46 -0
- package/src/css/components/breadcrumb.css +75 -0
- package/src/css/components/button.css +130 -0
- package/src/css/components/calendar.css +135 -0
- package/src/css/components/card.css +68 -0
- package/src/css/components/carousel.css +69 -0
- package/src/css/components/charts.css +464 -0
- package/src/css/components/checkbox.css +136 -0
- package/src/css/components/command.css +162 -0
- package/src/css/components/dropdown.css +187 -0
- package/src/css/components/form.css +280 -0
- package/src/css/components/gauge.css +156 -0
- package/src/css/components/glow-card.css +32 -0
- package/src/css/components/hud.css +165 -0
- package/src/css/components/image.css +164 -0
- package/src/css/components/input-otp.css +53 -0
- package/src/css/components/input.css +182 -0
- package/src/css/components/interference.css +217 -0
- package/src/css/components/kbd.css +54 -0
- package/src/css/components/layout-extras.css +256 -0
- package/src/css/components/modal.css +146 -0
- package/src/css/components/navigation-extras.css +280 -0
- package/src/css/components/pagination.css +122 -0
- package/src/css/components/panel-header.css +55 -0
- package/src/css/components/popover.css +112 -0
- package/src/css/components/scanner.css +125 -0
- package/src/css/components/select.css +99 -0
- package/src/css/components/sheet.css +149 -0
- package/src/css/components/skeleton.css +77 -0
- package/src/css/components/slider.css +116 -0
- package/src/css/components/spinner.css +101 -0
- package/src/css/components/spreadsheet.css +314 -0
- package/src/css/components/svg.css +104 -0
- package/src/css/components/switch.css +114 -0
- package/src/css/components/table.css +115 -0
- package/src/css/components/tabs.css +159 -0
- package/src/css/components/terminal.css +259 -0
- package/src/css/components/ticker.css +100 -0
- package/src/css/components/toast.css +137 -0
- package/src/css/components/toaster.css +48 -0
- package/src/css/components/toggle.css +99 -0
- package/src/css/components/typography.css +122 -0
- package/src/css/components/vectorbg.css +57 -0
- package/src/css/utilities.css +165 -0
- package/src/fonts/JetBrainsMono-Variable.woff2 +0 -0
- package/src/fonts/OFL.txt +93 -0
- package/src/js/charts.js +533 -0
- package/src/react/Accordion.tsx +85 -0
- package/src/react/Alert.tsx +58 -0
- package/src/react/AugButton.tsx +40 -0
- package/src/react/AugPanel.tsx +36 -0
- package/src/react/Avatar.tsx +69 -0
- package/src/react/Badge.tsx +28 -0
- package/src/react/Breadcrumb.tsx +63 -0
- package/src/react/Button.tsx +39 -0
- package/src/react/Checkbox.tsx +31 -0
- package/src/react/Dropdown.tsx +104 -0
- package/src/react/Field.tsx +36 -0
- package/src/react/Gauge.tsx +57 -0
- package/src/react/GlowCard.tsx +25 -0
- package/src/react/HudBar.tsx +86 -0
- package/src/react/HudFrame.tsx +52 -0
- package/src/react/Input.tsx +38 -0
- package/src/react/Interference.tsx +53 -0
- package/src/react/Kbd.tsx +62 -0
- package/src/react/Label.tsx +31 -0
- package/src/react/Modal.tsx +63 -0
- package/src/react/Pagination.tsx +113 -0
- package/src/react/PanelHeader.tsx +28 -0
- package/src/react/Progress.tsx +47 -0
- package/src/react/Radio.tsx +31 -0
- package/src/react/Scanner.tsx +37 -0
- package/src/react/Select.tsx +43 -0
- package/src/react/Separator.tsx +40 -0
- package/src/react/Skeleton.tsx +50 -0
- package/src/react/Slider.tsx +28 -0
- package/src/react/Spinner.tsx +39 -0
- package/src/react/Stat.tsx +52 -0
- package/src/react/Switch.tsx +25 -0
- package/src/react/Table.tsx +144 -0
- package/src/react/Tabs.tsx +97 -0
- package/src/react/Terminal.tsx +95 -0
- package/src/react/Textarea.tsx +20 -0
- package/src/react/Ticker.tsx +46 -0
- package/src/react/Toast.tsx +43 -0
- package/src/react/Tooltip.tsx +32 -0
- package/src/react/index.ts +52 -0
- package/src/tokens/base.ts +48 -0
- package/src/tokens/index.ts +7 -0
- package/src/tokens/semantic.ts +63 -0
- package/src/tokens/themes/cyan.ts +16 -0
- package/src/tokens/themes/green.ts +16 -0
- package/src/tokens/themes/orange.ts +16 -0
- package/src/tokens/themes/red.ts +16 -0
- package/src/tokens/themes/steel.ts +16 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface AugPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Clip/padding size: `sm` (6px clip), `md` (default, 20px clip) or `lg` (32px clip). */
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
/** Clip all four corners instead of the default diagonal TL+BR pair. */
|
|
10
|
+
corners4?: boolean;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Augmented clip-path panel — neon drop-shadow border with clipped corners (cd-aug-panel). */
|
|
15
|
+
export function AugPanel({
|
|
16
|
+
size = 'md',
|
|
17
|
+
corners4 = false,
|
|
18
|
+
className = '',
|
|
19
|
+
children,
|
|
20
|
+
...rest
|
|
21
|
+
}: AugPanelProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={cx(
|
|
25
|
+
'cd-aug-panel',
|
|
26
|
+
size === 'sm' && 'cd-aug-panel--sm',
|
|
27
|
+
size === 'lg' && 'cd-aug-panel--lg',
|
|
28
|
+
corners4 && 'cd-aug-panel--4',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...rest}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface AvatarProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Image source. When set, renders `<img>`; otherwise shows `fallback` initials. */
|
|
8
|
+
src?: string;
|
|
9
|
+
/** Alt text for the image. */
|
|
10
|
+
alt?: string;
|
|
11
|
+
/** Initials shown when no `src` (e.g. "NH"). */
|
|
12
|
+
fallback?: ReactNode;
|
|
13
|
+
/** Diameter preset. `md` is the default (no modifier). */
|
|
14
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
15
|
+
/** Neon ring around the avatar. */
|
|
16
|
+
glow?: boolean;
|
|
17
|
+
/** Presence dot rendered via `data-status`. */
|
|
18
|
+
status?: 'online' | 'away' | 'busy' | 'offline';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Circular avatar — image or fallback initials, sized presets, neon glow ring and presence dot. */
|
|
22
|
+
export function Avatar({
|
|
23
|
+
src,
|
|
24
|
+
alt = '',
|
|
25
|
+
fallback,
|
|
26
|
+
size = 'md',
|
|
27
|
+
glow = false,
|
|
28
|
+
status,
|
|
29
|
+
className = '',
|
|
30
|
+
...rest
|
|
31
|
+
}: AvatarProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cx(
|
|
35
|
+
'cd-avatar',
|
|
36
|
+
size !== 'md' && `cd-avatar--${size}`,
|
|
37
|
+
glow && 'cd-avatar--glow',
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-status={status}
|
|
41
|
+
{...rest}
|
|
42
|
+
>
|
|
43
|
+
{src != null ? (
|
|
44
|
+
<img className="cd-avatar__image" src={src} alt={alt} />
|
|
45
|
+
) : (
|
|
46
|
+
<span className="cd-avatar__fallback">{fallback}</span>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
53
|
+
/** Overflow count rendered as a trailing `+N` badge. */
|
|
54
|
+
more?: number;
|
|
55
|
+
/** Avatars to stack, in order. */
|
|
56
|
+
children?: ReactNode;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Overlapping stack of avatars with an optional `+N` overflow badge. */
|
|
60
|
+
export function AvatarGroup({ more, className = '', children, ...rest }: AvatarGroupProps) {
|
|
61
|
+
return (
|
|
62
|
+
<div className={cx('cd-avatar-group', className)} {...rest}>
|
|
63
|
+
{children}
|
|
64
|
+
{more != null && more > 0 && <span className="cd-avatar-group__more">+{more}</span>}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Avatar.Group = AvatarGroup;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
type CdColor =
|
|
7
|
+
| 'orange' | 'amber' | 'gold' | 'cyan' | 'magenta'
|
|
8
|
+
| 'green' | 'red' | 'blue' | 'purple' | 'yellow';
|
|
9
|
+
|
|
10
|
+
export interface BadgeProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {
|
|
11
|
+
/** Color variant. Defaults to `orange`. */
|
|
12
|
+
color?: CdColor;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Inline status badge — uppercase, pixel-corner, with a neon color variant. */
|
|
17
|
+
export function Badge({
|
|
18
|
+
color = 'orange',
|
|
19
|
+
className = '',
|
|
20
|
+
children,
|
|
21
|
+
...rest
|
|
22
|
+
}: BadgeProps) {
|
|
23
|
+
return (
|
|
24
|
+
<span className={cx('cd-badge', `cd-badge--${color}`, className)} {...rest}>
|
|
25
|
+
{children}
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface BreadcrumbItem {
|
|
7
|
+
/** Crumb text. */
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
/** Link target. Omitted (or on the active crumb) renders plain text. */
|
|
10
|
+
href?: string;
|
|
11
|
+
/** Marks this crumb as the current page (plain, non-interactive). */
|
|
12
|
+
active?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BreadcrumbProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
|
|
16
|
+
/** Crumbs in order. The last item is treated as active if none is flagged. */
|
|
17
|
+
items: BreadcrumbItem[];
|
|
18
|
+
/** Separator style: `/` (default), `›` arrow, or `·` dot. */
|
|
19
|
+
variant?: 'default' | 'arrow' | 'dot';
|
|
20
|
+
/** Accessible label for the `<nav>`. */
|
|
21
|
+
'aria-label'?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Breadcrumb trail — data-driven crumbs with slash / arrow / dot separators; renders `<nav><ol>`. */
|
|
25
|
+
export function Breadcrumb({
|
|
26
|
+
items,
|
|
27
|
+
variant = 'default',
|
|
28
|
+
className = '',
|
|
29
|
+
'aria-label': ariaLabel = 'breadcrumb',
|
|
30
|
+
...rest
|
|
31
|
+
}: BreadcrumbProps) {
|
|
32
|
+
const lastIndex = items.length - 1;
|
|
33
|
+
return (
|
|
34
|
+
<nav aria-label={ariaLabel} {...rest}>
|
|
35
|
+
<ol
|
|
36
|
+
className={cx(
|
|
37
|
+
'cd-breadcrumb',
|
|
38
|
+
variant === 'arrow' && 'cd-breadcrumb--arrow',
|
|
39
|
+
variant === 'dot' && 'cd-breadcrumb--dot',
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
43
|
+
{items.map((item, i) => {
|
|
44
|
+
const isActive = item.active ?? i === lastIndex;
|
|
45
|
+
return (
|
|
46
|
+
<li
|
|
47
|
+
key={i}
|
|
48
|
+
className={cx('cd-breadcrumb__item', isActive && 'cd-breadcrumb__item--active')}
|
|
49
|
+
>
|
|
50
|
+
{isActive || item.href == null ? (
|
|
51
|
+
<span>{item.label}</span>
|
|
52
|
+
) : (
|
|
53
|
+
<a className="cd-breadcrumb__link" href={item.href}>
|
|
54
|
+
{item.label}
|
|
55
|
+
</a>
|
|
56
|
+
)}
|
|
57
|
+
</li>
|
|
58
|
+
);
|
|
59
|
+
})}
|
|
60
|
+
</ol>
|
|
61
|
+
</nav>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
+
/** Visual emphasis. `primary` uses the theme accent; `danger` is destructive. */
|
|
8
|
+
variant?: 'default' | 'primary' | 'danger';
|
|
9
|
+
/** `sm` is the compact height; `md` is the default. */
|
|
10
|
+
size?: 'sm' | 'md';
|
|
11
|
+
/** Square icon-only button (no horizontal padding). */
|
|
12
|
+
icon?: boolean;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Cyber terminal button — uppercase, pixel-corner accent, neon hover glow. */
|
|
17
|
+
export function Button({
|
|
18
|
+
variant = 'default',
|
|
19
|
+
size = 'md',
|
|
20
|
+
icon = false,
|
|
21
|
+
className = '',
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}: ButtonProps) {
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
className={cx(
|
|
28
|
+
'cd-btn',
|
|
29
|
+
variant !== 'default' && `cd-btn--${variant}`,
|
|
30
|
+
size === 'sm' && 'cd-btn--sm',
|
|
31
|
+
icon && 'cd-btn--icon',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...rest}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface CheckboxProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
8
|
+
/** Text shown beside the box. When set, wraps the input in a `cd-control` label. */
|
|
9
|
+
label?: ReactNode;
|
|
10
|
+
/** Smaller helper line below the label. */
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Cyber checkbox — neon fill + checkmark; renders inside a `cd-control` label when labelled. */
|
|
15
|
+
export function Checkbox({ label, description, className = '', ...rest }: CheckboxProps) {
|
|
16
|
+
const input = (
|
|
17
|
+
<input type="checkbox" className={cx('cd-checkbox', !label && className)} {...rest} />
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (label == null && description == null) return input;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<label className={cx('cd-control', className)}>
|
|
24
|
+
<input type="checkbox" className="cd-checkbox" {...rest} />
|
|
25
|
+
<span className="cd-control__label">
|
|
26
|
+
{label}
|
|
27
|
+
{description != null && <span className="cd-control__desc">{description}</span>}
|
|
28
|
+
</span>
|
|
29
|
+
</label>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
5
|
+
parts.filter(Boolean).join(' ');
|
|
6
|
+
|
|
7
|
+
export interface DropdownItem {
|
|
8
|
+
/** Item label text. */
|
|
9
|
+
label: ReactNode;
|
|
10
|
+
/** Leading icon glyph rendered in the `__item-icon` slot. */
|
|
11
|
+
icon?: ReactNode;
|
|
12
|
+
/** Right-aligned keyboard shortcut hint (`__item-kbd`). */
|
|
13
|
+
kbd?: ReactNode;
|
|
14
|
+
/** Destructive styling (red on hover). */
|
|
15
|
+
danger?: boolean;
|
|
16
|
+
/** Disable selection. */
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
/** Fired when the item is clicked (closes the menu). */
|
|
19
|
+
onSelect?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DropdownEntry =
|
|
23
|
+
| DropdownItem
|
|
24
|
+
| { sep: true }
|
|
25
|
+
| { label: ReactNode; heading: true };
|
|
26
|
+
|
|
27
|
+
export interface DropdownProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
28
|
+
/** Trigger node — rendered as the `cd-dropdown__trigger` button content. */
|
|
29
|
+
trigger: ReactNode;
|
|
30
|
+
/** Menu entries: items, `{ sep: true }` separators, or `{ heading: true }` labels. */
|
|
31
|
+
items: DropdownEntry[];
|
|
32
|
+
/** Right-align the menu panel under the trigger. */
|
|
33
|
+
align?: 'left' | 'right';
|
|
34
|
+
/** Extra classes for the trigger button (e.g. `cd-btn cd-btn--primary`). */
|
|
35
|
+
triggerClassName?: string;
|
|
36
|
+
/** Initial open state (uncontrolled). */
|
|
37
|
+
defaultOpen?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Dropdown menu — a trigger button toggling a `cd-dropdown__menu` of data-driven items. */
|
|
41
|
+
export function Dropdown({
|
|
42
|
+
trigger,
|
|
43
|
+
items,
|
|
44
|
+
align = 'left',
|
|
45
|
+
triggerClassName = 'cd-btn',
|
|
46
|
+
defaultOpen = false,
|
|
47
|
+
className = '',
|
|
48
|
+
...rest
|
|
49
|
+
}: DropdownProps) {
|
|
50
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className={cx('cd-dropdown', open && 'is-open', className)} {...rest}>
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
className={cx(triggerClassName, 'cd-dropdown__trigger')}
|
|
57
|
+
aria-expanded={open}
|
|
58
|
+
onClick={() => setOpen((o) => !o)}
|
|
59
|
+
>
|
|
60
|
+
{trigger}
|
|
61
|
+
</button>
|
|
62
|
+
<div
|
|
63
|
+
className={cx('cd-dropdown__menu', align === 'right' && 'cd-dropdown__menu--right')}
|
|
64
|
+
role="menu"
|
|
65
|
+
>
|
|
66
|
+
{items.map((entry, i) => {
|
|
67
|
+
if ('sep' in entry && entry.sep) {
|
|
68
|
+
return <div key={i} className="cd-dropdown__sep" />;
|
|
69
|
+
}
|
|
70
|
+
if ('heading' in entry && entry.heading) {
|
|
71
|
+
return (
|
|
72
|
+
<div key={i} className="cd-dropdown__label">
|
|
73
|
+
{entry.label}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const item = entry as DropdownItem;
|
|
78
|
+
return (
|
|
79
|
+
<button
|
|
80
|
+
key={i}
|
|
81
|
+
type="button"
|
|
82
|
+
role="menuitem"
|
|
83
|
+
className={cx('cd-dropdown__item', item.danger && 'cd-dropdown__item--danger')}
|
|
84
|
+
disabled={item.disabled}
|
|
85
|
+
aria-disabled={item.disabled || undefined}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
item.onSelect?.();
|
|
88
|
+
setOpen(false);
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{item.icon != null && (
|
|
92
|
+
<span className="cd-dropdown__item-icon">{item.icon}</span>
|
|
93
|
+
)}
|
|
94
|
+
{item.label}
|
|
95
|
+
{item.kbd != null && (
|
|
96
|
+
<span className="cd-dropdown__item-kbd">{item.kbd}</span>
|
|
97
|
+
)}
|
|
98
|
+
</button>
|
|
99
|
+
);
|
|
100
|
+
})}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface FieldProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Inline layout: label left, control right. */
|
|
8
|
+
inline?: boolean;
|
|
9
|
+
/** Helper / description text below the control. */
|
|
10
|
+
description?: ReactNode;
|
|
11
|
+
/** Error message below the control (rendered with a ⚠ marker). */
|
|
12
|
+
error?: ReactNode;
|
|
13
|
+
/** Label + control (e.g. a `Label` followed by an input). */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Form field wrapper — stacks a label, control, and optional helper/error text. */
|
|
18
|
+
export function Field({
|
|
19
|
+
inline = false,
|
|
20
|
+
description,
|
|
21
|
+
error,
|
|
22
|
+
className = '',
|
|
23
|
+
children,
|
|
24
|
+
...rest
|
|
25
|
+
}: FieldProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cx('cd-field', inline && 'cd-field--inline', className)}
|
|
29
|
+
{...rest}
|
|
30
|
+
>
|
|
31
|
+
{children}
|
|
32
|
+
{description != null && <p className="cd-field-desc">{description}</p>}
|
|
33
|
+
{error != null && <p className="cd-field-error">{error}</p>}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface GaugeProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Fill amount, 0–100. Drives the conic `--value` CSS variable. */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Text shown in the center; defaults to `value`. */
|
|
10
|
+
display?: ReactNode;
|
|
11
|
+
/** Uppercase caption under the value. */
|
|
12
|
+
label?: ReactNode;
|
|
13
|
+
/** `small` is the compact radial. */
|
|
14
|
+
size?: 'small';
|
|
15
|
+
/** Color theme: accent (default), or a semantic override. */
|
|
16
|
+
variant?: 'danger' | 'success' | 'warn';
|
|
17
|
+
/** Concentric-ring radar style with sweep fill. */
|
|
18
|
+
radar?: boolean;
|
|
19
|
+
/** Extra center content (rendered after value/label). */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Radial neon gauge — conic fill driven by a 0–100 `value`, with center readout. */
|
|
24
|
+
export function Gauge({
|
|
25
|
+
value,
|
|
26
|
+
display,
|
|
27
|
+
label,
|
|
28
|
+
size,
|
|
29
|
+
variant,
|
|
30
|
+
radar = false,
|
|
31
|
+
className = '',
|
|
32
|
+
style,
|
|
33
|
+
children,
|
|
34
|
+
...rest
|
|
35
|
+
}: GaugeProps) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className={cx(
|
|
39
|
+
'cd-gauge',
|
|
40
|
+
size === 'small' && 'cd-gauge--small',
|
|
41
|
+
variant && `cd-gauge--${variant}`,
|
|
42
|
+
radar && 'cd-gauge--radar',
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
style={{ ['--value']: value, ...style } as CSSProperties}
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
<div className="cd-gauge__track" />
|
|
49
|
+
<div className="cd-gauge__fill" />
|
|
50
|
+
<div className="cd-gauge__center">
|
|
51
|
+
<div className="cd-gauge__value">{display ?? value}</div>
|
|
52
|
+
{label != null && <div className="cd-gauge__label">{label}</div>}
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ReactNode, MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
type GlowCardColor = 'orange' | 'amber' | 'gold' | 'cyan' | 'magenta' | 'green' | 'red' | 'blue' | 'purple';
|
|
4
|
+
|
|
5
|
+
interface GlowCardProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
color?: GlowCardColor;
|
|
8
|
+
active?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
onClick?: (e: MouseEvent<HTMLDivElement>) => void;
|
|
11
|
+
style?: React.CSSProperties;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function GlowCard({ color = 'orange', active, className = '', onClick, style, children }: GlowCardProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className={`cd-glow-card cd-glow-card--${color}${className ? ` ${className}` : ''}`}
|
|
18
|
+
data-active={active ? 'true' : undefined}
|
|
19
|
+
onClick={onClick}
|
|
20
|
+
style={style}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
/** Status color for a segment value — maps to `cd-hud-segment__value--*`. */
|
|
7
|
+
export type HudSegmentStatus = 'ok' | 'warn' | 'err';
|
|
8
|
+
|
|
9
|
+
export interface HudSegmentProps extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
/** Small uppercase caption. */
|
|
11
|
+
label?: ReactNode;
|
|
12
|
+
/** Accent-colored value. */
|
|
13
|
+
value?: ReactNode;
|
|
14
|
+
/** Color the value by status. */
|
|
15
|
+
status?: HudSegmentStatus;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** One label/value cell inside a `HudBar`. */
|
|
20
|
+
export function HudSegment({
|
|
21
|
+
label,
|
|
22
|
+
value,
|
|
23
|
+
status,
|
|
24
|
+
className = '',
|
|
25
|
+
children,
|
|
26
|
+
...rest
|
|
27
|
+
}: HudSegmentProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div className={cx('cd-hud-segment', className)} {...rest}>
|
|
30
|
+
{label != null && <span className="cd-hud-segment__label">{label}</span>}
|
|
31
|
+
{value != null && (
|
|
32
|
+
<span className={cx('cd-hud-segment__value', status && `cd-hud-segment__value--${status}`)}>
|
|
33
|
+
{value}
|
|
34
|
+
</span>
|
|
35
|
+
)}
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface HudBarSegment {
|
|
42
|
+
/** Small uppercase caption. */
|
|
43
|
+
label?: ReactNode;
|
|
44
|
+
/** Accent-colored value. */
|
|
45
|
+
value?: ReactNode;
|
|
46
|
+
/** Color the value by status. */
|
|
47
|
+
status?: HudSegmentStatus;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface HudBarProps extends HTMLAttributes<HTMLDivElement> {
|
|
51
|
+
/** Data-driven segments rendered left-to-right. */
|
|
52
|
+
segments?: HudBarSegment[];
|
|
53
|
+
/** Glowing accent edge: `top` or `bottom` bar, or `side` for vertical. */
|
|
54
|
+
position?: 'top' | 'bottom' | 'side';
|
|
55
|
+
/** Tighter segment padding/type. */
|
|
56
|
+
compact?: boolean;
|
|
57
|
+
/** Custom segment markup (e.g. `HudSegment` children) instead of `segments`. */
|
|
58
|
+
children?: ReactNode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Persistent HUD status bar — a row of label/value segments with a glowing accent edge. */
|
|
62
|
+
export function HudBar({
|
|
63
|
+
segments,
|
|
64
|
+
position,
|
|
65
|
+
compact = false,
|
|
66
|
+
className = '',
|
|
67
|
+
children,
|
|
68
|
+
...rest
|
|
69
|
+
}: HudBarProps) {
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
className={cx(
|
|
73
|
+
'cd-hud-bar',
|
|
74
|
+
position && `cd-hud-bar--${position}`,
|
|
75
|
+
compact && 'cd-hud-bar--compact',
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
{...rest}
|
|
79
|
+
>
|
|
80
|
+
{segments?.map((seg, i) => (
|
|
81
|
+
<HudSegment key={i} label={seg.label} value={seg.value} status={seg.status} />
|
|
82
|
+
))}
|
|
83
|
+
{children}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
/** Status color for a HUD value readout — maps to `cd-hud__value--*`. */
|
|
7
|
+
export type HudValueStatus = 'ok' | 'warn' | 'err' | 'dim';
|
|
8
|
+
|
|
9
|
+
export interface HudFrameProps extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
/** Uppercase label line above the value. */
|
|
11
|
+
label?: ReactNode;
|
|
12
|
+
/** Primary readout value. */
|
|
13
|
+
value?: ReactNode;
|
|
14
|
+
/** Color the value by status. */
|
|
15
|
+
status?: HudValueStatus;
|
|
16
|
+
/** Tighter padding for dense UIs. */
|
|
17
|
+
compact?: boolean;
|
|
18
|
+
/** Frosted / AR-style translucent backdrop. */
|
|
19
|
+
floating?: boolean;
|
|
20
|
+
/** Extra content rendered after the label/value (e.g. nested gauges). */
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Framed HUD stat panel — uppercase label over a glowing accent value readout. */
|
|
25
|
+
export function HudFrame({
|
|
26
|
+
label,
|
|
27
|
+
value,
|
|
28
|
+
status,
|
|
29
|
+
compact = false,
|
|
30
|
+
floating = false,
|
|
31
|
+
className = '',
|
|
32
|
+
children,
|
|
33
|
+
...rest
|
|
34
|
+
}: HudFrameProps) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cx(
|
|
38
|
+
'cd-hud-frame',
|
|
39
|
+
compact && 'cd-hud--compact',
|
|
40
|
+
floating && 'cd-hud--floating',
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
{...rest}
|
|
44
|
+
>
|
|
45
|
+
{label != null && <div className="cd-hud__label">{label}</div>}
|
|
46
|
+
{value != null && (
|
|
47
|
+
<div className={cx('cd-hud__value', status && `cd-hud__value--${status}`)}>{value}</div>
|
|
48
|
+
)}
|
|
49
|
+
{children}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
7
|
+
/** `sm` is compact, `lg` is taller; default is the standard height. */
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
/** Adds a left accent border (corpus-search pattern). */
|
|
10
|
+
accent?: boolean;
|
|
11
|
+
/** Error state — red border and glow. */
|
|
12
|
+
error?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Cyber text input — neon focus ring, optional left-accent border and error state. */
|
|
16
|
+
export function Input({
|
|
17
|
+
size = 'md',
|
|
18
|
+
accent = false,
|
|
19
|
+
error = false,
|
|
20
|
+
className = '',
|
|
21
|
+
type = 'text',
|
|
22
|
+
...rest
|
|
23
|
+
}: InputProps) {
|
|
24
|
+
return (
|
|
25
|
+
<input
|
|
26
|
+
type={type}
|
|
27
|
+
className={cx(
|
|
28
|
+
'cd-input',
|
|
29
|
+
size === 'sm' && 'cd-input--sm',
|
|
30
|
+
size === 'lg' && 'cd-input--lg',
|
|
31
|
+
accent && 'cd-input--accent',
|
|
32
|
+
error && 'cd-input--error',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...rest}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|