@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,53 @@
|
|
|
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 InterferenceProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Static intensity: `light`, default, or `heavy`. */
|
|
8
|
+
intensity?: 'light' | 'heavy';
|
|
9
|
+
/** Extra-aggressive noise (combine with `heavy`). */
|
|
10
|
+
intense?: boolean;
|
|
11
|
+
/** Enable the text/data corruption effect (uses `corruptText` as the clipped layer text). */
|
|
12
|
+
glitch?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Text driving the glitch corruption layers (sets `data-text` on the content wrapper).
|
|
15
|
+
* Required for the corruption effect to render; should match the visible heading text.
|
|
16
|
+
*/
|
|
17
|
+
corruptText?: string;
|
|
18
|
+
/** Content shown beneath the noise overlay. */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Animated static / data-corruption overlay — adds a `__noise` layer over wrapped content. */
|
|
23
|
+
export function Interference({
|
|
24
|
+
intensity,
|
|
25
|
+
intense = false,
|
|
26
|
+
glitch = false,
|
|
27
|
+
corruptText,
|
|
28
|
+
className = '',
|
|
29
|
+
children,
|
|
30
|
+
...rest
|
|
31
|
+
}: InterferenceProps) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
className={cx(
|
|
35
|
+
'cd-interference',
|
|
36
|
+
intensity && `cd-interference--${intensity}`,
|
|
37
|
+
intense && 'cd-interference--intense',
|
|
38
|
+
glitch && 'cd-interference--glitch',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...rest}
|
|
42
|
+
>
|
|
43
|
+
<div className="cd-interference__noise" />
|
|
44
|
+
{glitch ? (
|
|
45
|
+
<div className="cd-interference__content" data-text={corruptText}>
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
) : (
|
|
49
|
+
children
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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 KbdProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
/** `lg` renders the larger standalone key. */
|
|
8
|
+
size?: 'md' | 'lg';
|
|
9
|
+
/** Accent-highlighted key with a neon glow. */
|
|
10
|
+
accent?: boolean;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Keyboard key — boxed glyph with size and accent variants. */
|
|
15
|
+
export function Kbd({
|
|
16
|
+
size = 'md',
|
|
17
|
+
accent = false,
|
|
18
|
+
className = '',
|
|
19
|
+
children,
|
|
20
|
+
...rest
|
|
21
|
+
}: KbdProps) {
|
|
22
|
+
return (
|
|
23
|
+
<kbd
|
|
24
|
+
className={cx(
|
|
25
|
+
'cd-kbd',
|
|
26
|
+
size === 'lg' && 'cd-kbd--lg',
|
|
27
|
+
accent && 'cd-kbd--accent',
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...rest}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</kbd>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KbdGroupProps extends HTMLAttributes<HTMLSpanElement> {
|
|
38
|
+
/** Separator rendered between keys (default `+`). */
|
|
39
|
+
separator?: ReactNode;
|
|
40
|
+
/** `Kbd` keys to combine. */
|
|
41
|
+
children?: ReactNode;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Key combination group — joins multiple `Kbd` keys with a separator (e.g. ⌘ + K). */
|
|
45
|
+
export function KbdGroup({
|
|
46
|
+
separator = '+',
|
|
47
|
+
className = '',
|
|
48
|
+
children,
|
|
49
|
+
...rest
|
|
50
|
+
}: KbdGroupProps) {
|
|
51
|
+
const keys = Array.isArray(children) ? children : [children];
|
|
52
|
+
return (
|
|
53
|
+
<span className={cx('cd-kbd-group', className)} {...rest}>
|
|
54
|
+
{keys.map((key, i) => (
|
|
55
|
+
<span key={i} style={{ display: 'contents' }}>
|
|
56
|
+
{i > 0 && <span className="cd-kbd-group__sep">{separator}</span>}
|
|
57
|
+
{key}
|
|
58
|
+
</span>
|
|
59
|
+
))}
|
|
60
|
+
</span>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { LabelHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
|
|
7
|
+
/** Append a red required asterisk. */
|
|
8
|
+
required?: boolean;
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Uppercase form label — optional required asterisk. */
|
|
13
|
+
export function Label({
|
|
14
|
+
required = false,
|
|
15
|
+
className = '',
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}: LabelProps) {
|
|
19
|
+
return (
|
|
20
|
+
<label
|
|
21
|
+
className={cx(
|
|
22
|
+
'cd-form-label',
|
|
23
|
+
required && 'cd-form-label--required',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...rest}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</label>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -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 ModalProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
7
|
+
/** Controls visibility — adds `is-open` to animate the overlay/panel in. */
|
|
8
|
+
open: boolean;
|
|
9
|
+
/** Fired by the backdrop click, the ✕ close button, and Escape handlers you wire up. */
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
/** Uppercase accent heading rendered in the header. Omit to hide the header. */
|
|
12
|
+
title?: ReactNode;
|
|
13
|
+
/** Footer content (typically action buttons). Omit to hide the footer. */
|
|
14
|
+
footer?: ReactNode;
|
|
15
|
+
/** Panel width. `default` is 480px; `sm` 340px, `lg` 680px, `xl` 900px. */
|
|
16
|
+
size?: 'default' | 'sm' | 'lg' | 'xl';
|
|
17
|
+
/** Hide the header ✕ close button. */
|
|
18
|
+
hideClose?: boolean;
|
|
19
|
+
/** Dialog body content. */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Centered modal dialog over a dimmed backdrop — controlled via `open` / `onClose`. */
|
|
24
|
+
export function Modal({
|
|
25
|
+
open,
|
|
26
|
+
onClose,
|
|
27
|
+
title,
|
|
28
|
+
footer,
|
|
29
|
+
size = 'default',
|
|
30
|
+
hideClose = false,
|
|
31
|
+
className = '',
|
|
32
|
+
children,
|
|
33
|
+
...rest
|
|
34
|
+
}: ModalProps) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={cx('cd-modal-overlay', open && 'is-open')}
|
|
38
|
+
onClick={(e) => {
|
|
39
|
+
if (e.target === e.currentTarget) onClose?.();
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<div
|
|
43
|
+
className={cx('cd-modal', size !== 'default' && `cd-modal--${size}`, className)}
|
|
44
|
+
role="dialog"
|
|
45
|
+
aria-modal="true"
|
|
46
|
+
{...rest}
|
|
47
|
+
>
|
|
48
|
+
{(title != null || !hideClose) && (
|
|
49
|
+
<div className="cd-modal__header">
|
|
50
|
+
{title != null && <span className="cd-modal__title">{title}</span>}
|
|
51
|
+
{!hideClose && (
|
|
52
|
+
<button className="cd-modal__close" onClick={onClose} aria-label="Close">
|
|
53
|
+
✕
|
|
54
|
+
</button>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
<div className="cd-modal__body">{children}</div>
|
|
59
|
+
{footer != null && <div className="cd-modal__footer">{footer}</div>}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
7
|
+
/** Current page (1-based). */
|
|
8
|
+
page: number;
|
|
9
|
+
/** Total number of pages. */
|
|
10
|
+
pageCount: number;
|
|
11
|
+
/** Fired with the target page when a control is activated. */
|
|
12
|
+
onPageChange?: (page: number) => void;
|
|
13
|
+
/** How many sibling pages to show on each side of the current page. */
|
|
14
|
+
siblingCount?: number;
|
|
15
|
+
/** Size variant. */
|
|
16
|
+
size?: 'default' | 'sm' | 'lg';
|
|
17
|
+
/** Collapse gaps so items share borders. */
|
|
18
|
+
compact?: boolean;
|
|
19
|
+
/** Show "Page X of Y" info text after the controls. */
|
|
20
|
+
showInfo?: boolean;
|
|
21
|
+
/** Prev button label. */
|
|
22
|
+
prevLabel?: React.ReactNode;
|
|
23
|
+
/** Next button label. */
|
|
24
|
+
nextLabel?: React.ReactNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Build the windowed list of page numbers with `'…'` gaps. */
|
|
28
|
+
function buildPages(page: number, pageCount: number, siblingCount: number): Array<number | '…'> {
|
|
29
|
+
const total = pageCount;
|
|
30
|
+
// pages always shown: first, last, current ± siblingCount
|
|
31
|
+
const left = Math.max(2, page - siblingCount);
|
|
32
|
+
const right = Math.min(total - 1, page + siblingCount);
|
|
33
|
+
const pages: Array<number | '…'> = [1];
|
|
34
|
+
if (left > 2) pages.push('…');
|
|
35
|
+
for (let p = left; p <= right; p++) pages.push(p);
|
|
36
|
+
if (right < total - 1) pages.push('…');
|
|
37
|
+
if (total > 1) pages.push(total);
|
|
38
|
+
return pages;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Page navigation — windowed page list with prev/next, ellipsis and optional info. */
|
|
42
|
+
export function Pagination({
|
|
43
|
+
page,
|
|
44
|
+
pageCount,
|
|
45
|
+
onPageChange,
|
|
46
|
+
siblingCount = 1,
|
|
47
|
+
size = 'default',
|
|
48
|
+
compact = false,
|
|
49
|
+
showInfo = false,
|
|
50
|
+
prevLabel = '‹ Prev',
|
|
51
|
+
nextLabel = 'Next ›',
|
|
52
|
+
className = '',
|
|
53
|
+
...rest
|
|
54
|
+
}: PaginationProps) {
|
|
55
|
+
const go = (p: number) => {
|
|
56
|
+
if (p < 1 || p > pageCount || p === page) return;
|
|
57
|
+
onPageChange?.(p);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<nav
|
|
62
|
+
className={cx(
|
|
63
|
+
'cd-pagination',
|
|
64
|
+
size !== 'default' && `cd-pagination--${size}`,
|
|
65
|
+
compact && 'cd-pagination--compact',
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
aria-label="Pagination"
|
|
69
|
+
{...rest}
|
|
70
|
+
>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
className="cd-pagination__prev cd-pagination__item"
|
|
74
|
+
disabled={page <= 1}
|
|
75
|
+
aria-disabled={page <= 1 || undefined}
|
|
76
|
+
onClick={() => go(page - 1)}
|
|
77
|
+
>
|
|
78
|
+
{prevLabel}
|
|
79
|
+
</button>
|
|
80
|
+
{buildPages(page, pageCount, siblingCount).map((p, i) =>
|
|
81
|
+
p === '…' ? (
|
|
82
|
+
<span key={`e${i}`} className="cd-pagination__ellipsis">
|
|
83
|
+
···
|
|
84
|
+
</span>
|
|
85
|
+
) : (
|
|
86
|
+
<button
|
|
87
|
+
key={p}
|
|
88
|
+
type="button"
|
|
89
|
+
className={cx('cd-pagination__item', p === page && 'is-active')}
|
|
90
|
+
aria-current={p === page ? 'page' : undefined}
|
|
91
|
+
onClick={() => go(p)}
|
|
92
|
+
>
|
|
93
|
+
{p}
|
|
94
|
+
</button>
|
|
95
|
+
),
|
|
96
|
+
)}
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
className="cd-pagination__next cd-pagination__item"
|
|
100
|
+
disabled={page >= pageCount}
|
|
101
|
+
aria-disabled={page >= pageCount || undefined}
|
|
102
|
+
onClick={() => go(page + 1)}
|
|
103
|
+
>
|
|
104
|
+
{nextLabel}
|
|
105
|
+
</button>
|
|
106
|
+
{showInfo && (
|
|
107
|
+
<span className="cd-pagination__info">
|
|
108
|
+
Page {page} of {pageCount}
|
|
109
|
+
</span>
|
|
110
|
+
)}
|
|
111
|
+
</nav>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -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
|
+
export interface PanelHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Trailing actions slot (right-aligned), e.g. a count or status indicator. */
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
/** Header label / title content. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Panel header bar — uppercase neon title with an animated sweep and a right-aligned actions slot. */
|
|
14
|
+
export function PanelHeader({
|
|
15
|
+
actions,
|
|
16
|
+
className = '',
|
|
17
|
+
children,
|
|
18
|
+
...rest
|
|
19
|
+
}: PanelHeaderProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className={cx('cd-panel-header', className)} {...rest}>
|
|
22
|
+
{children}
|
|
23
|
+
{actions != null && (
|
|
24
|
+
<div className="cd-panel-header__actions">{actions}</div>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface ProgressProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Fill percentage (0–100). Ignored when `indeterminate`. */
|
|
8
|
+
value?: number;
|
|
9
|
+
/** Color variant. Default uses the theme accent. */
|
|
10
|
+
variant?: 'default' | 'danger' | 'success' | 'warning';
|
|
11
|
+
/** Taller 8px bar. */
|
|
12
|
+
thick?: boolean;
|
|
13
|
+
/** Scanning/indeterminate animation (no fixed value). */
|
|
14
|
+
indeterminate?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Progress bar — accent fill driven by `value`%, with color, thick, and indeterminate variants. */
|
|
18
|
+
export function Progress({
|
|
19
|
+
value = 0,
|
|
20
|
+
variant = 'default',
|
|
21
|
+
thick = false,
|
|
22
|
+
indeterminate = false,
|
|
23
|
+
className = '',
|
|
24
|
+
...rest
|
|
25
|
+
}: ProgressProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={cx(
|
|
29
|
+
'cd-progress',
|
|
30
|
+
variant !== 'default' && `cd-progress--${variant}`,
|
|
31
|
+
thick && 'cd-progress--thick',
|
|
32
|
+
indeterminate && 'cd-progress--indeterminate',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
role="progressbar"
|
|
36
|
+
aria-valuenow={indeterminate ? undefined : value}
|
|
37
|
+
aria-valuemin={0}
|
|
38
|
+
aria-valuemax={100}
|
|
39
|
+
{...rest}
|
|
40
|
+
>
|
|
41
|
+
<div
|
|
42
|
+
className="cd-progress__bar"
|
|
43
|
+
style={indeterminate ? undefined : { width: `${value}%` }}
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -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 RadioProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
8
|
+
/** Text shown beside the dot. 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 radio — neon filled dot; renders inside a `cd-control` label when labelled. */
|
|
15
|
+
export function Radio({ label, description, className = '', ...rest }: RadioProps) {
|
|
16
|
+
const input = (
|
|
17
|
+
<input type="radio" className={cx('cd-radio', !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="radio" className="cd-radio" {...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,37 @@
|
|
|
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 ScannerProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Sweep top-to-bottom instead of left-to-right. */
|
|
8
|
+
vertical?: boolean;
|
|
9
|
+
/** Fainter return-sweep look. */
|
|
10
|
+
ping?: boolean;
|
|
11
|
+
/** Content scanned by the sweep line. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Animated scan/sweep-line overlay — wraps content with a glowing lidar/radar sweep. */
|
|
16
|
+
export function Scanner({
|
|
17
|
+
vertical = false,
|
|
18
|
+
ping = false,
|
|
19
|
+
className = '',
|
|
20
|
+
children,
|
|
21
|
+
...rest
|
|
22
|
+
}: ScannerProps) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className={cx(
|
|
26
|
+
'cd-scanner',
|
|
27
|
+
vertical && 'cd-scanner--vertical',
|
|
28
|
+
ping && 'cd-scanner--ping',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...rest}
|
|
32
|
+
>
|
|
33
|
+
<div className="cd-scanner__line" />
|
|
34
|
+
{children}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReactNode, SelectHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface SelectProps
|
|
7
|
+
extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
|
|
8
|
+
/** `sm` is the compact height; default is the standard height. */
|
|
9
|
+
size?: 'sm' | 'md';
|
|
10
|
+
/** Uppercase tracked label styling (viz-controls pattern). */
|
|
11
|
+
label?: boolean;
|
|
12
|
+
/** Error state — red border. */
|
|
13
|
+
error?: boolean;
|
|
14
|
+
/** `<option>` elements. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Native select with custom neon chevron — wraps the element for the arrow. */
|
|
19
|
+
export function Select({
|
|
20
|
+
size = 'md',
|
|
21
|
+
label = false,
|
|
22
|
+
error = false,
|
|
23
|
+
className = '',
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}: SelectProps) {
|
|
27
|
+
return (
|
|
28
|
+
<div className="cd-select-wrap">
|
|
29
|
+
<select
|
|
30
|
+
className={cx(
|
|
31
|
+
'cd-select',
|
|
32
|
+
size === 'sm' && 'cd-select--sm',
|
|
33
|
+
label && 'cd-select--label',
|
|
34
|
+
error && 'cd-select--error',
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...rest}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</select>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 SeparatorProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Orientation. `vertical` draws a vertical divider. */
|
|
8
|
+
orientation?: 'horizontal' | 'vertical';
|
|
9
|
+
/** Neon gradient line instead of a plain rule. */
|
|
10
|
+
neon?: boolean;
|
|
11
|
+
/** Optional inline label (e.g. "OR"); centers it between the rules. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Divider rule — horizontal/vertical, optional neon gradient and centered label. */
|
|
16
|
+
export function Separator({
|
|
17
|
+
orientation = 'horizontal',
|
|
18
|
+
neon = false,
|
|
19
|
+
className = '',
|
|
20
|
+
children,
|
|
21
|
+
...rest
|
|
22
|
+
}: SeparatorProps) {
|
|
23
|
+
const hasLabel = children != null;
|
|
24
|
+
return (
|
|
25
|
+
<div
|
|
26
|
+
className={cx(
|
|
27
|
+
'cd-separator',
|
|
28
|
+
orientation === 'vertical' && 'cd-separator--vertical',
|
|
29
|
+
neon && 'cd-separator--neon',
|
|
30
|
+
hasLabel && 'cd-separator--label',
|
|
31
|
+
!hasLabel && orientation === 'horizontal' && 'cd-separator--plain',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
role="separator"
|
|
35
|
+
{...rest}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface SkeletonProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Pre-sized shape. `block`/`card` render the compound containers; the rest are line/avatar/button placeholders. */
|
|
8
|
+
shape?: 'plain' | 'text' | 'heading' | 'avatar' | 'btn' | 'block' | 'card';
|
|
9
|
+
/** Fractional width helper (full / 3/4 / 1/2 / 1/3). Ignored for `block`/`card`. */
|
|
10
|
+
width?: 'full' | '3/4' | '1/2' | '1/3';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Shimmering loading placeholder — text/heading/avatar/btn shapes plus block & card containers. */
|
|
14
|
+
export function Skeleton({
|
|
15
|
+
shape = 'plain',
|
|
16
|
+
width,
|
|
17
|
+
className = '',
|
|
18
|
+
children,
|
|
19
|
+
...rest
|
|
20
|
+
}: SkeletonProps) {
|
|
21
|
+
if (shape === 'card' || shape === 'block') {
|
|
22
|
+
return (
|
|
23
|
+
<div className={cx(`cd-skeleton-${shape}`, className)} {...rest}>
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={cx(
|
|
31
|
+
'cd-skeleton',
|
|
32
|
+
shape !== 'plain' && `cd-skeleton--${shape}`,
|
|
33
|
+
width && `cd-skeleton--w-${width}`,
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...rest}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Inline row inside a `cd-skeleton-block` (e.g. avatar + text lines). */
|
|
44
|
+
export function SkeletonRow({ className = '', children, ...rest }: HTMLAttributes<HTMLDivElement>) {
|
|
45
|
+
return (
|
|
46
|
+
<div className={cx('cd-skeleton-block__row', className)} {...rest}>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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 SliderProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
8
|
+
/** When provided, renders the value label beside the track inside a `cd-slider-wrap`. */
|
|
9
|
+
value?: InputHTMLAttributes<HTMLInputElement>['value'];
|
|
10
|
+
/** Node shown in the trailing `cd-slider-value` label. When set, wraps in `cd-slider-wrap`. */
|
|
11
|
+
valueLabel?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Range slider — neon track with 3D thumb; optional trailing value label. */
|
|
15
|
+
export function Slider({ valueLabel, className = '', ...rest }: SliderProps) {
|
|
16
|
+
const input = (
|
|
17
|
+
<input type="range" className={cx('cd-slider', !valueLabel && className)} {...rest} />
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (valueLabel == null) return input;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className={cx('cd-slider-wrap', className)}>
|
|
24
|
+
<input type="range" className="cd-slider" {...rest} />
|
|
25
|
+
<span className="cd-slider-value">{valueLabel}</span>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
const cx = (...parts: Array<string | false | null | undefined>) =>
|
|
4
|
+
parts.filter(Boolean).join(' ');
|
|
5
|
+
|
|
6
|
+
export interface SpinnerProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** Diameter preset. `md` is the default (no modifier). */
|
|
8
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
/** `ring` (default), `dual` counter-rotating rings, or `dots` pulse. */
|
|
10
|
+
variant?: 'ring' | 'dual' | 'dots';
|
|
11
|
+
/** Accessible label announced to screen readers. */
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Loading spinner — ring / dual-ring / dot-pulse variants in five sizes. */
|
|
16
|
+
export function Spinner({
|
|
17
|
+
size = 'md',
|
|
18
|
+
variant = 'ring',
|
|
19
|
+
label = 'Loading',
|
|
20
|
+
className = '',
|
|
21
|
+
...rest
|
|
22
|
+
}: SpinnerProps) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
role="status"
|
|
26
|
+
aria-label={label}
|
|
27
|
+
className={cx(
|
|
28
|
+
'cd-spinner',
|
|
29
|
+
size !== 'md' && `cd-spinner--${size}`,
|
|
30
|
+
variant === 'dual' && 'cd-spinner--dual',
|
|
31
|
+
variant === 'dots' && 'cd-spinner--dots',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...rest}
|
|
35
|
+
>
|
|
36
|
+
{variant === 'dots' && <span />}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|