rizzo-css 0.0.62 → 0.0.63
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/README.md +9 -5
- package/bin/rizzo-css.js +247 -27
- package/dist/rizzo.min.css +5 -3
- package/package.json +14 -7
- package/scaffold/astro/Footer.astro +8 -0
- package/scaffold/astro/Settings.astro +8 -2
- package/scaffold/astro/Tabs.astro +2 -2
- package/scaffold/react/Accordion.tsx +143 -0
- package/scaffold/react/Alert.tsx +90 -0
- package/scaffold/react/AlertDialog.tsx +80 -0
- package/scaffold/react/AspectRatio.tsx +32 -0
- package/scaffold/react/Avatar.tsx +53 -0
- package/scaffold/react/BackToTop.tsx +62 -0
- package/scaffold/react/Badge.tsx +39 -0
- package/scaffold/react/Breadcrumb.tsx +81 -0
- package/scaffold/react/Button.tsx +40 -0
- package/scaffold/react/ButtonGroup.tsx +24 -0
- package/scaffold/react/Card.tsx +26 -0
- package/scaffold/react/Checkbox.tsx +40 -0
- package/scaffold/react/Collapsible.tsx +58 -0
- package/scaffold/react/ContextMenu.tsx +67 -0
- package/scaffold/react/CopyToClipboard.tsx +128 -0
- package/scaffold/react/Dashboard.tsx +23 -0
- package/scaffold/react/Divider.tsx +47 -0
- package/scaffold/react/DocsSidebar.tsx +48 -0
- package/scaffold/react/Dropdown.tsx +256 -0
- package/scaffold/react/Empty.tsx +29 -0
- package/scaffold/react/FontSwitcher.tsx +68 -0
- package/scaffold/react/Footer.tsx +55 -0
- package/scaffold/react/FormGroup.tsx +57 -0
- package/scaffold/react/HoverCard.tsx +61 -0
- package/scaffold/react/Icons.tsx +22 -0
- package/scaffold/react/Input.tsx +69 -0
- package/scaffold/react/Kbd.tsx +16 -0
- package/scaffold/react/Label.tsx +16 -0
- package/scaffold/react/Modal.tsx +149 -0
- package/scaffold/react/Navbar.tsx +72 -0
- package/scaffold/react/Pagination.tsx +155 -0
- package/scaffold/react/Popover.tsx +66 -0
- package/scaffold/react/ProgressBar.tsx +66 -0
- package/scaffold/react/Radio.tsx +38 -0
- package/scaffold/react/ResizableHandle.tsx +24 -0
- package/scaffold/react/ResizablePane.tsx +29 -0
- package/scaffold/react/ResizablePaneGroup.tsx +29 -0
- package/scaffold/react/ScrollArea.tsx +29 -0
- package/scaffold/react/Search.tsx +62 -0
- package/scaffold/react/Select.tsx +65 -0
- package/scaffold/react/Separator.tsx +33 -0
- package/scaffold/react/Settings.tsx +60 -0
- package/scaffold/react/Sheet.tsx +86 -0
- package/scaffold/react/Skeleton.tsx +32 -0
- package/scaffold/react/Slider.tsx +66 -0
- package/scaffold/react/SoundEffects.tsx +15 -0
- package/scaffold/react/Spinner.tsx +36 -0
- package/scaffold/react/Switch.tsx +52 -0
- package/scaffold/react/Table.tsx +178 -0
- package/scaffold/react/Tabs.tsx +143 -0
- package/scaffold/react/Textarea.tsx +69 -0
- package/scaffold/react/ThemeSwitcher.tsx +89 -0
- package/scaffold/react/Toast.tsx +43 -0
- package/scaffold/react/Toggle.tsx +45 -0
- package/scaffold/react/ToggleGroup.tsx +34 -0
- package/scaffold/react/Tooltip.tsx +40 -0
- package/scaffold/vanilla/README-RIZZO.md +1 -1
- package/scaffold/vanilla/components/accordion.html +30 -0
- package/scaffold/vanilla/components/alert-dialog.html +30 -0
- package/scaffold/vanilla/components/alert.html +30 -0
- package/scaffold/vanilla/components/aspect-ratio.html +30 -0
- package/scaffold/vanilla/components/avatar.html +30 -0
- package/scaffold/vanilla/components/back-to-top.html +30 -0
- package/scaffold/vanilla/components/badge.html +30 -0
- package/scaffold/vanilla/components/breadcrumb.html +30 -0
- package/scaffold/vanilla/components/button-group.html +30 -0
- package/scaffold/vanilla/components/button.html +30 -0
- package/scaffold/vanilla/components/cards.html +30 -0
- package/scaffold/vanilla/components/collapsible.html +30 -0
- package/scaffold/vanilla/components/context-menu.html +30 -0
- package/scaffold/vanilla/components/copy-to-clipboard.html +30 -0
- package/scaffold/vanilla/components/dashboard.html +30 -0
- package/scaffold/vanilla/components/divider.html +30 -0
- package/scaffold/vanilla/components/docs-sidebar.html +30 -0
- package/scaffold/vanilla/components/dropdown.html +30 -0
- package/scaffold/vanilla/components/empty.html +30 -0
- package/scaffold/vanilla/components/font-switcher.html +30 -0
- package/scaffold/vanilla/components/footer.html +30 -0
- package/scaffold/vanilla/components/forms.html +30 -0
- package/scaffold/vanilla/components/hover-card.html +30 -0
- package/scaffold/vanilla/components/icons.html +30 -0
- package/scaffold/vanilla/components/index.html +30 -0
- package/scaffold/vanilla/components/kbd.html +30 -0
- package/scaffold/vanilla/components/label.html +30 -0
- package/scaffold/vanilla/components/modal.html +30 -0
- package/scaffold/vanilla/components/navbar.html +30 -0
- package/scaffold/vanilla/components/pagination.html +30 -0
- package/scaffold/vanilla/components/popover.html +30 -0
- package/scaffold/vanilla/components/progress-bar.html +30 -0
- package/scaffold/vanilla/components/resizable.html +30 -0
- package/scaffold/vanilla/components/scroll-area.html +30 -0
- package/scaffold/vanilla/components/search.html +30 -0
- package/scaffold/vanilla/components/separator.html +30 -0
- package/scaffold/vanilla/components/settings.html +30 -0
- package/scaffold/vanilla/components/sheet.html +30 -0
- package/scaffold/vanilla/components/skeleton.html +30 -0
- package/scaffold/vanilla/components/slider.html +30 -0
- package/scaffold/vanilla/components/sound-effects.html +30 -0
- package/scaffold/vanilla/components/spinner.html +30 -0
- package/scaffold/vanilla/components/switch.html +30 -0
- package/scaffold/vanilla/components/table.html +30 -0
- package/scaffold/vanilla/components/tabs.html +30 -0
- package/scaffold/vanilla/components/theme-switcher.html +30 -0
- package/scaffold/vanilla/components/toast.html +30 -0
- package/scaffold/vanilla/components/toggle-group.html +30 -0
- package/scaffold/vanilla/components/toggle.html +30 -0
- package/scaffold/vanilla/components/tooltip.html +30 -0
- package/scaffold/vanilla/index.html +30 -0
- package/scaffold/vue/Accordion.vue +9 -0
- package/scaffold/vue/Alert.vue +9 -0
- package/scaffold/vue/AlertDialog.vue +9 -0
- package/scaffold/vue/AspectRatio.vue +9 -0
- package/scaffold/vue/Avatar.vue +9 -0
- package/scaffold/vue/BackToTop.vue +9 -0
- package/scaffold/vue/Badge.vue +28 -0
- package/scaffold/vue/Breadcrumb.vue +9 -0
- package/scaffold/vue/Button.vue +23 -0
- package/scaffold/vue/ButtonGroup.vue +9 -0
- package/scaffold/vue/Card.vue +21 -0
- package/scaffold/vue/Checkbox.vue +31 -0
- package/scaffold/vue/Collapsible.vue +9 -0
- package/scaffold/vue/ContextMenu.vue +9 -0
- package/scaffold/vue/CopyToClipboard.vue +9 -0
- package/scaffold/vue/Dashboard.vue +9 -0
- package/scaffold/vue/Divider.vue +23 -0
- package/scaffold/vue/DocsSidebar.vue +9 -0
- package/scaffold/vue/Dropdown.vue +9 -0
- package/scaffold/vue/Empty.vue +9 -0
- package/scaffold/vue/FontSwitcher.vue +9 -0
- package/scaffold/vue/Footer.vue +9 -0
- package/scaffold/vue/FormGroup.vue +45 -0
- package/scaffold/vue/HoverCard.vue +9 -0
- package/scaffold/vue/Icons.vue +9 -0
- package/scaffold/vue/Input.vue +59 -0
- package/scaffold/vue/Kbd.vue +9 -0
- package/scaffold/vue/Label.vue +23 -0
- package/scaffold/vue/Modal.vue +9 -0
- package/scaffold/vue/Navbar.vue +9 -0
- package/scaffold/vue/Pagination.vue +9 -0
- package/scaffold/vue/Popover.vue +9 -0
- package/scaffold/vue/ProgressBar.vue +9 -0
- package/scaffold/vue/Radio.vue +29 -0
- package/scaffold/vue/ResizableHandle.vue +9 -0
- package/scaffold/vue/ResizablePane.vue +9 -0
- package/scaffold/vue/ResizablePaneGroup.vue +9 -0
- package/scaffold/vue/ScrollArea.vue +9 -0
- package/scaffold/vue/Search.vue +9 -0
- package/scaffold/vue/Select.vue +52 -0
- package/scaffold/vue/Separator.vue +9 -0
- package/scaffold/vue/Settings.vue +9 -0
- package/scaffold/vue/Sheet.vue +9 -0
- package/scaffold/vue/Skeleton.vue +9 -0
- package/scaffold/vue/Slider.vue +9 -0
- package/scaffold/vue/SoundEffects.vue +9 -0
- package/scaffold/vue/Spinner.vue +21 -0
- package/scaffold/vue/Switch.vue +9 -0
- package/scaffold/vue/Table.vue +9 -0
- package/scaffold/vue/Tabs.vue +9 -0
- package/scaffold/vue/Textarea.vue +60 -0
- package/scaffold/vue/ThemeSwitcher.vue +9 -0
- package/scaffold/vue/Toast.vue +9 -0
- package/scaffold/vue/Toggle.vue +9 -0
- package/scaffold/vue/ToggleGroup.vue +9 -0
- package/scaffold/vue/Tooltip.vue +9 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface BackToTopProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
threshold?: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function BackToTop({
|
|
11
|
+
threshold = 400,
|
|
12
|
+
label = 'Back to top',
|
|
13
|
+
className = '',
|
|
14
|
+
...rest
|
|
15
|
+
}: BackToTopProps) {
|
|
16
|
+
const [visible, setVisible] = useState(false);
|
|
17
|
+
|
|
18
|
+
const scrollToTop = () => {
|
|
19
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const handler = () => setVisible(window.scrollY > threshold);
|
|
24
|
+
window.addEventListener('scroll', handler, { passive: true });
|
|
25
|
+
handler();
|
|
26
|
+
return () => window.removeEventListener('scroll', handler);
|
|
27
|
+
}, [threshold]);
|
|
28
|
+
|
|
29
|
+
const wrapperClass = ['back-to-top', className].filter(Boolean).join(' ').trim();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={wrapperClass}
|
|
34
|
+
data-back-to-top
|
|
35
|
+
data-visible={visible ? 'true' : 'false'}
|
|
36
|
+
aria-hidden={visible ? 'false' : 'true'}
|
|
37
|
+
{...rest}
|
|
38
|
+
>
|
|
39
|
+
<button type="button" className="back-to-top__btn" aria-label={label} onClick={scrollToTop}>
|
|
40
|
+
<span className="back-to-top__icon" aria-hidden="true">
|
|
41
|
+
<svg
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
width="24"
|
|
44
|
+
height="24"
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
fill="none"
|
|
47
|
+
stroke="currentColor"
|
|
48
|
+
strokeWidth="2"
|
|
49
|
+
strokeLinecap="round"
|
|
50
|
+
strokeLinejoin="round"
|
|
51
|
+
className="icon"
|
|
52
|
+
aria-hidden="true"
|
|
53
|
+
>
|
|
54
|
+
<path d="m18 15-6-6-6 6" />
|
|
55
|
+
</svg>
|
|
56
|
+
</span>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default BackToTop;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type BadgeVariant = 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
4
|
+
export type BadgeSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
7
|
+
variant?: BadgeVariant;
|
|
8
|
+
size?: BadgeSize;
|
|
9
|
+
pill?: boolean;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Badge({
|
|
15
|
+
variant = 'primary',
|
|
16
|
+
size = 'md',
|
|
17
|
+
pill = false,
|
|
18
|
+
className = '',
|
|
19
|
+
children,
|
|
20
|
+
...rest
|
|
21
|
+
}: BadgeProps) {
|
|
22
|
+
const classes = [
|
|
23
|
+
'badge',
|
|
24
|
+
`badge--${variant}`,
|
|
25
|
+
`badge--${size}`,
|
|
26
|
+
pill ? 'badge--pill' : '',
|
|
27
|
+
className,
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ')
|
|
31
|
+
.trim();
|
|
32
|
+
return (
|
|
33
|
+
<span className={classes} {...rest}>
|
|
34
|
+
{children}
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default Badge;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type BreadcrumbSeparator = 'chevron' | 'slash' | 'arrow' | string;
|
|
9
|
+
|
|
10
|
+
export interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {
|
|
11
|
+
items: BreadcrumbItem[];
|
|
12
|
+
separator?: BreadcrumbSeparator;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Breadcrumb({
|
|
17
|
+
items,
|
|
18
|
+
separator = 'chevron',
|
|
19
|
+
className = '',
|
|
20
|
+
...rest
|
|
21
|
+
}: BreadcrumbProps) {
|
|
22
|
+
const separatorVariant =
|
|
23
|
+
separator === 'slash'
|
|
24
|
+
? 'breadcrumb--slash'
|
|
25
|
+
: separator === 'arrow'
|
|
26
|
+
? 'breadcrumb--arrow'
|
|
27
|
+
: 'breadcrumb--chevron';
|
|
28
|
+
const classes = ['breadcrumb', separatorVariant, className].filter(Boolean).join(' ').trim();
|
|
29
|
+
const separatorChar =
|
|
30
|
+
separator === 'slash' ? '/' : separator === 'arrow' ? '›' : typeof separator === 'string' ? separator : '›';
|
|
31
|
+
const useIcon = separator === 'chevron';
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<nav className={classes} aria-label="Breadcrumb" {...rest}>
|
|
35
|
+
<ol className="breadcrumb__list">
|
|
36
|
+
{items.map((item, index) => {
|
|
37
|
+
const isLast = index === items.length - 1;
|
|
38
|
+
const isCurrent = isLast || !item.href;
|
|
39
|
+
return (
|
|
40
|
+
<li
|
|
41
|
+
key={index}
|
|
42
|
+
className={`breadcrumb__item ${isCurrent ? 'breadcrumb__item--current' : ''}`.trim()}
|
|
43
|
+
>
|
|
44
|
+
{isCurrent ? (
|
|
45
|
+
<span className="breadcrumb__current" aria-current="page">
|
|
46
|
+
{item.label}
|
|
47
|
+
</span>
|
|
48
|
+
) : (
|
|
49
|
+
<a className="breadcrumb__link" href={item.href}>
|
|
50
|
+
{item.label}
|
|
51
|
+
</a>
|
|
52
|
+
)}
|
|
53
|
+
{!isLast && (
|
|
54
|
+
<span className="breadcrumb__separator" aria-hidden="true">
|
|
55
|
+
{useIcon ? (
|
|
56
|
+
<svg
|
|
57
|
+
className="breadcrumb__separator-icon"
|
|
58
|
+
width="14"
|
|
59
|
+
height="14"
|
|
60
|
+
viewBox="0 0 24 24"
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
strokeWidth="2"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
>
|
|
66
|
+
<path d="M6 9l6 6 6-6" />
|
|
67
|
+
</svg>
|
|
68
|
+
) : (
|
|
69
|
+
separatorChar
|
|
70
|
+
)}
|
|
71
|
+
</span>
|
|
72
|
+
)}
|
|
73
|
+
</li>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</ol>
|
|
77
|
+
</nav>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default Breadcrumb;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ButtonVariant =
|
|
4
|
+
| 'default'
|
|
5
|
+
| 'primary'
|
|
6
|
+
| 'success'
|
|
7
|
+
| 'warning'
|
|
8
|
+
| 'error'
|
|
9
|
+
| 'info'
|
|
10
|
+
| 'outline';
|
|
11
|
+
|
|
12
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
13
|
+
variant?: ButtonVariant;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Button component using Rizzo CSS BEM classes (btn, btn-primary, etc.).
|
|
20
|
+
* Same API and markup as Astro/Svelte Button for design system parity.
|
|
21
|
+
*/
|
|
22
|
+
export function Button({
|
|
23
|
+
variant = 'default',
|
|
24
|
+
type = 'button',
|
|
25
|
+
disabled = false,
|
|
26
|
+
className = '',
|
|
27
|
+
children,
|
|
28
|
+
...rest
|
|
29
|
+
}: ButtonProps) {
|
|
30
|
+
const variantClass = variant === 'default' ? '' : `btn-${variant}`;
|
|
31
|
+
const classes = ['btn', variantClass, className].filter(Boolean).join(' ');
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button type={type} disabled={disabled} className={classes} {...rest}>
|
|
35
|
+
{children}
|
|
36
|
+
</button>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default Button;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ButtonGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
orientation?: 'horizontal' | 'vertical';
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ButtonGroup({
|
|
10
|
+
orientation = 'horizontal',
|
|
11
|
+
className = '',
|
|
12
|
+
children,
|
|
13
|
+
...rest
|
|
14
|
+
}: ButtonGroupProps) {
|
|
15
|
+
const orientationClass = orientation === 'vertical' ? 'button-group--vertical' : '';
|
|
16
|
+
const classes = ['button-group', orientationClass, className].filter(Boolean).join(' ').trim();
|
|
17
|
+
return (
|
|
18
|
+
<div className={classes} role="group" {...rest}>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default ButtonGroup;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type CardVariant = 'default' | 'elevated' | 'outlined' | 'filled';
|
|
4
|
+
|
|
5
|
+
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: CardVariant;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Card({
|
|
12
|
+
variant = 'default',
|
|
13
|
+
className = '',
|
|
14
|
+
children,
|
|
15
|
+
...rest
|
|
16
|
+
}: CardProps) {
|
|
17
|
+
const variantClass = variant !== 'default' ? `card--${variant}` : '';
|
|
18
|
+
const classes = ['card', variantClass, className].filter(Boolean).join(' ').trim();
|
|
19
|
+
return (
|
|
20
|
+
<div className={classes} {...rest}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default Card;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type'> {
|
|
4
|
+
ariaDescribedby?: string;
|
|
5
|
+
ariaLabel?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Checkbox({
|
|
10
|
+
id,
|
|
11
|
+
name,
|
|
12
|
+
value,
|
|
13
|
+
checked = false,
|
|
14
|
+
required = false,
|
|
15
|
+
disabled = false,
|
|
16
|
+
className = '',
|
|
17
|
+
ariaDescribedby,
|
|
18
|
+
ariaLabel,
|
|
19
|
+
onChange,
|
|
20
|
+
...rest
|
|
21
|
+
}: CheckboxProps) {
|
|
22
|
+
return (
|
|
23
|
+
<input
|
|
24
|
+
type="checkbox"
|
|
25
|
+
id={id}
|
|
26
|
+
name={name}
|
|
27
|
+
value={value}
|
|
28
|
+
checked={checked}
|
|
29
|
+
required={required}
|
|
30
|
+
disabled={disabled}
|
|
31
|
+
className={className}
|
|
32
|
+
aria-describedby={ariaDescribedby}
|
|
33
|
+
aria-label={ariaLabel}
|
|
34
|
+
onChange={onChange}
|
|
35
|
+
{...rest}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default Checkbox;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface CollapsibleProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
id?: string;
|
|
6
|
+
defaultOpen?: boolean;
|
|
7
|
+
triggerLabel?: string;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Collapsible({
|
|
13
|
+
id: idProp,
|
|
14
|
+
defaultOpen = false,
|
|
15
|
+
triggerLabel = 'Toggle',
|
|
16
|
+
children,
|
|
17
|
+
className = '',
|
|
18
|
+
...rest
|
|
19
|
+
}: CollapsibleProps) {
|
|
20
|
+
const id = idProp ?? `collapsible-${Math.random().toString(36).slice(2, 9)}`;
|
|
21
|
+
const triggerId = `${id}-trigger`;
|
|
22
|
+
const panelId = `${id}-panel`;
|
|
23
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
24
|
+
|
|
25
|
+
const triggerClasses = ['collapsible__trigger', open ? 'collapsible__trigger--open' : ''].filter(Boolean).join(' ');
|
|
26
|
+
const panelClasses = ['collapsible__panel', open ? 'collapsible__panel--open' : ''].filter(Boolean).join(' ');
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className={`collapsible ${className}`.trim()} data-collapsible id={id} {...rest}>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
className={triggerClasses}
|
|
33
|
+
id={triggerId}
|
|
34
|
+
aria-expanded={open}
|
|
35
|
+
aria-controls={panelId}
|
|
36
|
+
onClick={() => setOpen((o) => !o)}
|
|
37
|
+
>
|
|
38
|
+
<span className="collapsible__trigger-label">{triggerLabel}</span>
|
|
39
|
+
<span className="collapsible__icon" aria-hidden="true">
|
|
40
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
41
|
+
<path d="m6 9 6 6 6-6" />
|
|
42
|
+
</svg>
|
|
43
|
+
</span>
|
|
44
|
+
</button>
|
|
45
|
+
<div
|
|
46
|
+
className={panelClasses}
|
|
47
|
+
id={panelId}
|
|
48
|
+
role="region"
|
|
49
|
+
aria-labelledby={triggerId}
|
|
50
|
+
hidden={!open}
|
|
51
|
+
>
|
|
52
|
+
<div className="collapsible__panel-inner">{children}</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default Collapsible;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface ContextMenuProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
id?: string;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
trigger?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ContextMenu({
|
|
12
|
+
id: idProp,
|
|
13
|
+
children,
|
|
14
|
+
trigger,
|
|
15
|
+
className = '',
|
|
16
|
+
...rest
|
|
17
|
+
}: ContextMenuProps) {
|
|
18
|
+
const id = idProp ?? `context-menu-${Math.random().toString(36).slice(2, 9)}`;
|
|
19
|
+
const [open, setOpen] = useState(false);
|
|
20
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
21
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
22
|
+
|
|
23
|
+
const handleContextMenu = (e: React.MouseEvent) => {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
setPosition({ x: e.clientX, y: e.clientY });
|
|
26
|
+
setOpen(true);
|
|
27
|
+
requestAnimationFrame(() => document.addEventListener('click', closeMenu));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const closeMenu = () => {
|
|
31
|
+
setOpen(false);
|
|
32
|
+
document.removeEventListener('click', closeMenu);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleKeydown = (e: React.KeyboardEvent) => {
|
|
36
|
+
if (e.key === 'Escape') closeMenu();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (open && contentRef.current) {
|
|
41
|
+
contentRef.current.style.left = `${position.x}px`;
|
|
42
|
+
contentRef.current.style.top = `${position.y}px`;
|
|
43
|
+
}
|
|
44
|
+
}, [open, position]);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={`context-menu ${className}`.trim()} data-context-menu id={id} {...rest}>
|
|
48
|
+
<div data-context-menu-trigger onContextMenu={handleContextMenu}>
|
|
49
|
+
{trigger}
|
|
50
|
+
</div>
|
|
51
|
+
<div
|
|
52
|
+
ref={contentRef}
|
|
53
|
+
className={`context-menu__content ${open ? 'context-menu__content--open' : ''}`.trim()}
|
|
54
|
+
role="menu"
|
|
55
|
+
aria-hidden={!open}
|
|
56
|
+
hidden={!open}
|
|
57
|
+
data-context-menu-content
|
|
58
|
+
id={`${id}-content`}
|
|
59
|
+
onKeyDown={handleKeydown}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface CopyToClipboardProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
|
5
|
+
value: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
format?: string;
|
|
8
|
+
iconOnly?: boolean;
|
|
9
|
+
buttonLabel?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CopyToClipboard({
|
|
15
|
+
value,
|
|
16
|
+
label,
|
|
17
|
+
format,
|
|
18
|
+
iconOnly = false,
|
|
19
|
+
buttonLabel,
|
|
20
|
+
className = '',
|
|
21
|
+
id: idProp,
|
|
22
|
+
onClick,
|
|
23
|
+
onKeyDown,
|
|
24
|
+
...rest
|
|
25
|
+
}: CopyToClipboardProps) {
|
|
26
|
+
const [copied, setCopied] = useState(false);
|
|
27
|
+
const [feedbackText, setFeedbackText] = useState('');
|
|
28
|
+
const buttonId = idProp ?? `copy-btn-${Math.random().toString(36).slice(2, 11)}`;
|
|
29
|
+
const displayText = iconOnly ? (buttonLabel ?? 'Copy') : value;
|
|
30
|
+
const classes = [
|
|
31
|
+
'copy-to-clipboard',
|
|
32
|
+
iconOnly ? 'copy-to-clipboard--icon-only' : '',
|
|
33
|
+
className,
|
|
34
|
+
]
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.join(' ')
|
|
37
|
+
.trim();
|
|
38
|
+
const ariaLabel = copied
|
|
39
|
+
? format ? `Copied ${format}!` : 'Copied!'
|
|
40
|
+
: label ?? (iconOnly ? (buttonLabel ?? 'Copy to clipboard') : `Copy ${value} to clipboard`);
|
|
41
|
+
const tooltipText = copied
|
|
42
|
+
? format ? `Copied ${format}!` : 'Copied!'
|
|
43
|
+
: label ?? (iconOnly ? (buttonLabel ?? 'Copy') : 'Copy to clipboard');
|
|
44
|
+
|
|
45
|
+
const copy = async () => {
|
|
46
|
+
if (!value) return;
|
|
47
|
+
try {
|
|
48
|
+
await navigator.clipboard.writeText(value);
|
|
49
|
+
setCopied(true);
|
|
50
|
+
setFeedbackText(format ? `Copied ${format}!` : 'Copied!');
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
setCopied(false);
|
|
53
|
+
setFeedbackText('');
|
|
54
|
+
}, 2000);
|
|
55
|
+
} catch {
|
|
56
|
+
const textArea = document.createElement('textarea');
|
|
57
|
+
textArea.value = value;
|
|
58
|
+
textArea.style.position = 'fixed';
|
|
59
|
+
textArea.style.left = '-999999px';
|
|
60
|
+
document.body.appendChild(textArea);
|
|
61
|
+
textArea.focus();
|
|
62
|
+
textArea.select();
|
|
63
|
+
try {
|
|
64
|
+
document.execCommand('copy');
|
|
65
|
+
setCopied(true);
|
|
66
|
+
setFeedbackText(format ? `Copied ${format}!` : 'Copied!');
|
|
67
|
+
setTimeout(() => {
|
|
68
|
+
setCopied(false);
|
|
69
|
+
setFeedbackText('');
|
|
70
|
+
}, 2000);
|
|
71
|
+
} catch {
|
|
72
|
+
setFeedbackText('Failed to copy');
|
|
73
|
+
}
|
|
74
|
+
document.body.removeChild(textArea);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
79
|
+
onClick?.(e);
|
|
80
|
+
copy();
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
84
|
+
onKeyDown?.(e);
|
|
85
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
copy();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<span className="tooltip-host" data-tooltip={tooltipText}>
|
|
93
|
+
<button
|
|
94
|
+
type="button"
|
|
95
|
+
className={classes}
|
|
96
|
+
aria-label={ariaLabel}
|
|
97
|
+
id={buttonId}
|
|
98
|
+
onClick={handleClick}
|
|
99
|
+
onKeyDown={handleKeyDown}
|
|
100
|
+
{...rest}
|
|
101
|
+
>
|
|
102
|
+
<span className="copy-to-clipboard__text">{displayText}</span>
|
|
103
|
+
<span
|
|
104
|
+
className={`copy-to-clipboard__icon copy-to-clipboard__icon--copy ${copied ? 'copy-to-clipboard__icon--hidden' : ''}`.trim()}
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
>
|
|
107
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
|
108
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
109
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
110
|
+
</svg>
|
|
111
|
+
</span>
|
|
112
|
+
<span
|
|
113
|
+
className={`copy-to-clipboard__icon copy-to-clipboard__icon--check ${!copied ? 'copy-to-clipboard__icon--hidden' : ''}`.trim()}
|
|
114
|
+
aria-hidden="true"
|
|
115
|
+
>
|
|
116
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
|
|
117
|
+
<polyline points="20 6 9 17 4 12" />
|
|
118
|
+
</svg>
|
|
119
|
+
</span>
|
|
120
|
+
<span className="copy-to-clipboard__feedback" aria-live="polite">
|
|
121
|
+
{feedbackText}
|
|
122
|
+
</span>
|
|
123
|
+
</button>
|
|
124
|
+
</span>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default CopyToClipboard;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DashboardProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
sidebar?: ReactNode;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Dashboard({
|
|
10
|
+
sidebar,
|
|
11
|
+
children,
|
|
12
|
+
className = '',
|
|
13
|
+
...rest
|
|
14
|
+
}: DashboardProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div className={`dashboard ${className}`.trim()} data-dashboard {...rest}>
|
|
17
|
+
{sidebar && <div className="dashboard__sidebar">{sidebar}</div>}
|
|
18
|
+
<main className="dashboard__main">{children}</main>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default Dashboard;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DividerProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
orientation?: 'horizontal' | 'vertical';
|
|
5
|
+
label?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Divider({
|
|
10
|
+
orientation = 'horizontal',
|
|
11
|
+
label,
|
|
12
|
+
className = '',
|
|
13
|
+
...rest
|
|
14
|
+
}: DividerProps) {
|
|
15
|
+
const hasLabel = typeof label === 'string' && label.trim().length > 0;
|
|
16
|
+
const labelText = label?.trim() ?? '';
|
|
17
|
+
const classes = [
|
|
18
|
+
'divider',
|
|
19
|
+
`divider--${orientation}`,
|
|
20
|
+
hasLabel ? 'divider--labeled' : '',
|
|
21
|
+
className,
|
|
22
|
+
]
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join(' ')
|
|
25
|
+
.trim();
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={classes}
|
|
29
|
+
role="separator"
|
|
30
|
+
aria-orientation={orientation}
|
|
31
|
+
aria-label={labelText || undefined}
|
|
32
|
+
{...rest}
|
|
33
|
+
>
|
|
34
|
+
{hasLabel && orientation === 'horizontal' ? (
|
|
35
|
+
<>
|
|
36
|
+
<span className="divider__line" aria-hidden="true" />
|
|
37
|
+
<span className="divider__label">{labelText}</span>
|
|
38
|
+
<span className="divider__line" aria-hidden="true" />
|
|
39
|
+
</>
|
|
40
|
+
) : (
|
|
41
|
+
<span className="divider__line" aria-hidden="true" />
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default Divider;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DocsSidebarLink {
|
|
4
|
+
href: string;
|
|
5
|
+
label: string;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface DocsSidebarProps extends HTMLAttributes<HTMLElement> {
|
|
10
|
+
links?: DocsSidebarLink[];
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_LINKS: DocsSidebarLink[] = [
|
|
16
|
+
{ href: '/docs', label: 'Overview' },
|
|
17
|
+
{ href: '/docs/getting-started', label: 'Getting started' },
|
|
18
|
+
{ href: '/docs/components', label: 'Components', active: true },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function DocsSidebar({
|
|
22
|
+
links = DEFAULT_LINKS,
|
|
23
|
+
children,
|
|
24
|
+
className = '',
|
|
25
|
+
...rest
|
|
26
|
+
}: DocsSidebarProps) {
|
|
27
|
+
return (
|
|
28
|
+
<aside className={`docs-sidebar ${className}`.trim()} {...rest}>
|
|
29
|
+
<nav className="docs-sidebar__nav" aria-label="Docs">
|
|
30
|
+
<ul className="docs-sidebar__list">
|
|
31
|
+
{children ??
|
|
32
|
+
links.map((link, i) => (
|
|
33
|
+
<li key={i} className="docs-sidebar__item">
|
|
34
|
+
<a
|
|
35
|
+
href={link.href}
|
|
36
|
+
className={`docs-sidebar__link ${link.active ? 'docs-sidebar__link--active' : ''}`.trim()}
|
|
37
|
+
>
|
|
38
|
+
{link.label}
|
|
39
|
+
</a>
|
|
40
|
+
</li>
|
|
41
|
+
))}
|
|
42
|
+
</ul>
|
|
43
|
+
</nav>
|
|
44
|
+
</aside>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default DocsSidebar;
|