rizzo-css 0.0.62 → 0.0.64
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 +350 -31
- 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/react/base/README-RIZZO.md +50 -0
- package/scaffold/react/base/gitignore +8 -0
- package/scaffold/react/base/index.html +16 -0
- package/scaffold/react/base/package.json +22 -0
- package/scaffold/react/base/public/.gitkeep +0 -0
- package/scaffold/react/base/src/App.tsx +11 -0
- package/scaffold/react/base/src/index.css +1 -0
- package/scaffold/react/base/src/main.tsx +10 -0
- package/scaffold/react/base/src/vite-env.d.ts +1 -0
- package/scaffold/react/base/tsconfig.json +21 -0
- package/scaffold/react/base/tsconfig.node.json +9 -0
- package/scaffold/react/base/vite.config.ts +6 -0
- package/scaffold/vanilla/README-RIZZO.md +1 -1
- package/scaffold/vanilla/components/accordion.html +36 -0
- package/scaffold/vanilla/components/alert-dialog.html +36 -0
- package/scaffold/vanilla/components/alert.html +36 -0
- package/scaffold/vanilla/components/aspect-ratio.html +36 -0
- package/scaffold/vanilla/components/avatar.html +36 -0
- package/scaffold/vanilla/components/back-to-top.html +36 -0
- package/scaffold/vanilla/components/badge.html +36 -0
- package/scaffold/vanilla/components/breadcrumb.html +36 -0
- package/scaffold/vanilla/components/button-group.html +36 -0
- package/scaffold/vanilla/components/button.html +36 -0
- package/scaffold/vanilla/components/cards.html +36 -0
- package/scaffold/vanilla/components/collapsible.html +36 -0
- package/scaffold/vanilla/components/context-menu.html +36 -0
- package/scaffold/vanilla/components/copy-to-clipboard.html +36 -0
- package/scaffold/vanilla/components/dashboard.html +36 -0
- package/scaffold/vanilla/components/divider.html +36 -0
- package/scaffold/vanilla/components/docs-sidebar.html +36 -0
- package/scaffold/vanilla/components/dropdown.html +36 -0
- package/scaffold/vanilla/components/empty.html +36 -0
- package/scaffold/vanilla/components/font-switcher.html +36 -0
- package/scaffold/vanilla/components/footer.html +36 -0
- package/scaffold/vanilla/components/forms.html +36 -0
- package/scaffold/vanilla/components/hover-card.html +36 -0
- package/scaffold/vanilla/components/icons.html +36 -0
- package/scaffold/vanilla/components/index.html +36 -0
- package/scaffold/vanilla/components/kbd.html +36 -0
- package/scaffold/vanilla/components/label.html +36 -0
- package/scaffold/vanilla/components/modal.html +36 -0
- package/scaffold/vanilla/components/navbar.html +36 -0
- package/scaffold/vanilla/components/pagination.html +36 -0
- package/scaffold/vanilla/components/popover.html +36 -0
- package/scaffold/vanilla/components/progress-bar.html +36 -0
- package/scaffold/vanilla/components/resizable.html +36 -0
- package/scaffold/vanilla/components/scroll-area.html +36 -0
- package/scaffold/vanilla/components/search.html +36 -0
- package/scaffold/vanilla/components/separator.html +36 -0
- package/scaffold/vanilla/components/settings.html +36 -0
- package/scaffold/vanilla/components/sheet.html +36 -0
- package/scaffold/vanilla/components/skeleton.html +36 -0
- package/scaffold/vanilla/components/slider.html +36 -0
- package/scaffold/vanilla/components/sound-effects.html +36 -0
- package/scaffold/vanilla/components/spinner.html +36 -0
- package/scaffold/vanilla/components/switch.html +36 -0
- package/scaffold/vanilla/components/table.html +36 -0
- package/scaffold/vanilla/components/tabs.html +36 -0
- package/scaffold/vanilla/components/theme-switcher.html +36 -0
- package/scaffold/vanilla/components/toast.html +36 -0
- package/scaffold/vanilla/components/toggle-group.html +36 -0
- package/scaffold/vanilla/components/toggle.html +36 -0
- package/scaffold/vanilla/components/tooltip.html +36 -0
- package/scaffold/vanilla/index.html +36 -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
- package/scaffold/vue/base/README-RIZZO.md +50 -0
- package/scaffold/vue/base/gitignore +8 -0
- package/scaffold/vue/base/index.html +16 -0
- package/scaffold/vue/base/package.json +20 -0
- package/scaffold/vue/base/public/.gitkeep +0 -0
- package/scaffold/vue/base/src/App.vue +13 -0
- package/scaffold/vue/base/src/index.css +1 -0
- package/scaffold/vue/base/src/main.ts +5 -0
- package/scaffold/vue/base/src/vite-env.d.ts +7 -0
- package/scaffold/vue/base/tsconfig.json +22 -0
- package/scaffold/vue/base/tsconfig.node.json +10 -0
- package/scaffold/vue/base/vite.config.ts +6 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { useState, useCallback, useRef } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface Tab {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
icon?: string;
|
|
8
|
+
content?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TabsProps extends HTMLAttributes<HTMLDivElement> {
|
|
12
|
+
tabs: Tab[];
|
|
13
|
+
id?: string;
|
|
14
|
+
defaultTab?: string;
|
|
15
|
+
variant?: 'default' | 'pills' | 'underline';
|
|
16
|
+
children?: (activeTabId: string) => ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Tabs({
|
|
21
|
+
tabs,
|
|
22
|
+
id: idProp,
|
|
23
|
+
defaultTab,
|
|
24
|
+
variant = 'default',
|
|
25
|
+
children,
|
|
26
|
+
className = '',
|
|
27
|
+
...rest
|
|
28
|
+
}: TabsProps) {
|
|
29
|
+
const defaultActiveId = defaultTab ?? tabs[0]?.id ?? '';
|
|
30
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
31
|
+
const activeTabId = selectedId ?? defaultActiveId;
|
|
32
|
+
const tabsId = idProp ?? `tabs-${Math.random().toString(36).slice(2, 11)}`;
|
|
33
|
+
const variantClass = variant !== 'default' ? `tabs--${variant}` : '';
|
|
34
|
+
const classes = ['tabs', variantClass, className].filter(Boolean).join(' ').trim();
|
|
35
|
+
const tabListRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
|
|
37
|
+
const activateTab = useCallback(
|
|
38
|
+
(index: number) => {
|
|
39
|
+
if (index < 0 || index >= tabs.length) return;
|
|
40
|
+
setSelectedId(tabs[index].id);
|
|
41
|
+
},
|
|
42
|
+
[tabs]
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const handleKeydown = useCallback(
|
|
46
|
+
(e: React.KeyboardEvent, index: number) => {
|
|
47
|
+
let targetIndex = index;
|
|
48
|
+
switch (e.key) {
|
|
49
|
+
case 'ArrowRight':
|
|
50
|
+
case 'ArrowDown':
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
targetIndex = (index + 1) % tabs.length;
|
|
53
|
+
break;
|
|
54
|
+
case 'ArrowLeft':
|
|
55
|
+
case 'ArrowUp':
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
targetIndex = index === 0 ? tabs.length - 1 : index - 1;
|
|
58
|
+
break;
|
|
59
|
+
case 'Home':
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
targetIndex = 0;
|
|
62
|
+
break;
|
|
63
|
+
case 'End':
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
targetIndex = tabs.length - 1;
|
|
66
|
+
break;
|
|
67
|
+
case 'Enter':
|
|
68
|
+
case ' ':
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
activateTab(index);
|
|
71
|
+
return;
|
|
72
|
+
default:
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
activateTab(targetIndex);
|
|
76
|
+
const buttons = tabListRef.current?.querySelectorAll('[role="tab"]');
|
|
77
|
+
if (buttons && buttons[targetIndex]) (buttons[targetIndex] as HTMLElement).focus();
|
|
78
|
+
},
|
|
79
|
+
[tabs.length, activateTab]
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className={classes} data-tabs={tabsId} {...rest}>
|
|
84
|
+
<div className="tabs__list" role="tablist" aria-label="Tabs" ref={tabListRef}>
|
|
85
|
+
{tabs.map((tab, index) => {
|
|
86
|
+
const isActive = tab.id === activeTabId;
|
|
87
|
+
return (
|
|
88
|
+
<span
|
|
89
|
+
key={tab.id}
|
|
90
|
+
className={`tabs__tab ${isActive ? 'tabs__tab--active' : ''}`.trim()}
|
|
91
|
+
id={`${tabsId}-tab-${tab.id}`}
|
|
92
|
+
role="tab"
|
|
93
|
+
tabIndex={isActive ? 0 : -1}
|
|
94
|
+
aria-selected={isActive ? 'true' : 'false'}
|
|
95
|
+
aria-controls={`${tabsId}-panel-${tab.id}`}
|
|
96
|
+
data-tab-id={tab.id}
|
|
97
|
+
data-tab-index={index}
|
|
98
|
+
onClick={() => activateTab(index)}
|
|
99
|
+
onKeyDown={(e) => handleKeydown(e, index)}
|
|
100
|
+
>
|
|
101
|
+
{tab.icon && (
|
|
102
|
+
<img
|
|
103
|
+
src={tab.icon}
|
|
104
|
+
alt=""
|
|
105
|
+
className="tabs__tab-icon"
|
|
106
|
+
width={20}
|
|
107
|
+
height={20}
|
|
108
|
+
loading="lazy"
|
|
109
|
+
aria-hidden="true"
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
112
|
+
{tab.label}
|
|
113
|
+
</span>
|
|
114
|
+
);
|
|
115
|
+
})}
|
|
116
|
+
</div>
|
|
117
|
+
<div className="tabs__panels-wrapper">
|
|
118
|
+
{tabs.map((tab) => {
|
|
119
|
+
const isActive = tab.id === activeTabId;
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
key={tab.id}
|
|
123
|
+
className={`tabs__panel ${isActive ? 'tabs__panel--active' : ''}`.trim()}
|
|
124
|
+
id={`${tabsId}-panel-${tab.id}`}
|
|
125
|
+
role="tabpanel"
|
|
126
|
+
aria-labelledby={`${tabsId}-tab-${tab.id}`}
|
|
127
|
+
aria-hidden={isActive ? 'false' : 'true'}
|
|
128
|
+
data-panel-id={tab.id}
|
|
129
|
+
>
|
|
130
|
+
{tab.content ? (
|
|
131
|
+
<div className="tabs__panel-content" dangerouslySetInnerHTML={{ __html: tab.content }} />
|
|
132
|
+
) : isActive && children ? (
|
|
133
|
+
children(activeTabId)
|
|
134
|
+
) : null}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default Tabs;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { TextareaHTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export type TextareaSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
|
|
5
|
+
export interface TextareaProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> {
|
|
6
|
+
size?: TextareaSize;
|
|
7
|
+
error?: boolean;
|
|
8
|
+
success?: boolean;
|
|
9
|
+
ariaDescribedby?: string;
|
|
10
|
+
ariaInvalid?: boolean | 'true' | 'false';
|
|
11
|
+
onValueChange?: (value: string) => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function Textarea({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
value = '',
|
|
19
|
+
placeholder,
|
|
20
|
+
required = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
readOnly = false,
|
|
23
|
+
rows = 4,
|
|
24
|
+
cols,
|
|
25
|
+
size = 'md',
|
|
26
|
+
error = false,
|
|
27
|
+
success = false,
|
|
28
|
+
className = '',
|
|
29
|
+
ariaDescribedby,
|
|
30
|
+
ariaInvalid,
|
|
31
|
+
onChange,
|
|
32
|
+
onValueChange,
|
|
33
|
+
...rest
|
|
34
|
+
}: TextareaProps) {
|
|
35
|
+
const sizeClass = size !== 'md' ? `form-input--${size}` : '';
|
|
36
|
+
const errorClass = error ? 'form-input--error' : '';
|
|
37
|
+
const successClass = success ? 'form-input--success' : '';
|
|
38
|
+
const classes = ['form-input', sizeClass, errorClass, successClass, className]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(' ')
|
|
41
|
+
.trim();
|
|
42
|
+
const invalid = error || ariaInvalid === true || ariaInvalid === 'true';
|
|
43
|
+
|
|
44
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
45
|
+
onChange?.(e);
|
|
46
|
+
onValueChange?.(e.target.value);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<textarea
|
|
51
|
+
id={id}
|
|
52
|
+
name={name}
|
|
53
|
+
value={value}
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
required={required}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
readOnly={readOnly}
|
|
58
|
+
rows={rows}
|
|
59
|
+
cols={cols}
|
|
60
|
+
className={classes}
|
|
61
|
+
aria-invalid={invalid ? 'true' : 'false'}
|
|
62
|
+
aria-describedby={ariaDescribedby}
|
|
63
|
+
onChange={handleChange}
|
|
64
|
+
{...rest}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Textarea;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface ThemeOption {
|
|
5
|
+
value: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ThemeSwitcherProps extends HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
idPrefix?: string;
|
|
11
|
+
themes?: ThemeOption[];
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const DEFAULT_THEMES: ThemeOption[] = [
|
|
16
|
+
{ value: 'github-dark-classic', label: 'GitHub Dark' },
|
|
17
|
+
{ value: 'github-light', label: 'GitHub Light' },
|
|
18
|
+
{ value: 'system', label: 'System' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function ThemeSwitcher({
|
|
22
|
+
idPrefix = '',
|
|
23
|
+
themes = DEFAULT_THEMES,
|
|
24
|
+
className = '',
|
|
25
|
+
...rest
|
|
26
|
+
}: ThemeSwitcherProps) {
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [current, setCurrent] = useState(themes[0]?.value ?? 'system');
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const stored = typeof localStorage !== 'undefined' ? localStorage.getItem('theme') : null;
|
|
32
|
+
if (stored) setCurrent(stored);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const menuId = idPrefix ? `theme-switcher-${idPrefix}-menu` : 'theme-switcher-menu';
|
|
36
|
+
const triggerId = idPrefix ? `theme-switcher-${idPrefix}-trigger` : 'theme-switcher-trigger';
|
|
37
|
+
|
|
38
|
+
const apply = (value: string) => {
|
|
39
|
+
if (value === 'system') {
|
|
40
|
+
document.documentElement.removeAttribute('data-theme');
|
|
41
|
+
localStorage.removeItem('theme');
|
|
42
|
+
} else {
|
|
43
|
+
document.documentElement.setAttribute('data-theme', value);
|
|
44
|
+
localStorage.setItem('theme', value);
|
|
45
|
+
}
|
|
46
|
+
setCurrent(value);
|
|
47
|
+
setOpen(false);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`theme-switcher ${className}`.trim()} data-theme-switcher {...rest}>
|
|
52
|
+
<button
|
|
53
|
+
type="button"
|
|
54
|
+
id={triggerId}
|
|
55
|
+
className="theme-switcher__trigger"
|
|
56
|
+
aria-expanded={open}
|
|
57
|
+
aria-haspopup="true"
|
|
58
|
+
aria-controls={menuId}
|
|
59
|
+
aria-label="Theme"
|
|
60
|
+
onClick={() => setOpen((o) => !o)}
|
|
61
|
+
>
|
|
62
|
+
<span className="theme-switcher__trigger-icon" aria-hidden="true">◐</span>
|
|
63
|
+
<span className="theme-switcher__trigger-label">{themes.find((t) => t.value === current)?.label ?? 'Theme'}</span>
|
|
64
|
+
</button>
|
|
65
|
+
<div
|
|
66
|
+
className={`theme-switcher__menu ${open ? 'theme-switcher__menu--open' : ''}`.trim()}
|
|
67
|
+
id={menuId}
|
|
68
|
+
role="menu"
|
|
69
|
+
aria-labelledby={triggerId}
|
|
70
|
+
aria-hidden={!open}
|
|
71
|
+
hidden={!open}
|
|
72
|
+
>
|
|
73
|
+
{themes.map((t) => (
|
|
74
|
+
<button
|
|
75
|
+
key={t.value}
|
|
76
|
+
type="button"
|
|
77
|
+
role="menuitem"
|
|
78
|
+
className={`theme-switcher__item ${current === t.value ? 'theme-switcher__item--active' : ''}`.trim()}
|
|
79
|
+
onClick={() => apply(t.value)}
|
|
80
|
+
>
|
|
81
|
+
{t.label}
|
|
82
|
+
</button>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default ThemeSwitcher;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { Alert } from './Alert';
|
|
3
|
+
|
|
4
|
+
export type ToastPosition =
|
|
5
|
+
| 'top-right'
|
|
6
|
+
| 'top-left'
|
|
7
|
+
| 'bottom-right'
|
|
8
|
+
| 'bottom-left'
|
|
9
|
+
| 'top-center'
|
|
10
|
+
| 'bottom-center';
|
|
11
|
+
|
|
12
|
+
export interface ToastProps extends HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
variant?: 'success' | 'error' | 'warning' | 'info';
|
|
14
|
+
dismissible?: boolean;
|
|
15
|
+
autoDismiss?: number;
|
|
16
|
+
position?: ToastPosition;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
id?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function Toast({
|
|
23
|
+
variant = 'info',
|
|
24
|
+
dismissible = true,
|
|
25
|
+
autoDismiss = 5000,
|
|
26
|
+
position = 'top-right',
|
|
27
|
+
children,
|
|
28
|
+
className = '',
|
|
29
|
+
id,
|
|
30
|
+
...rest
|
|
31
|
+
}: ToastProps) {
|
|
32
|
+
const positionClass = `toast--${position}`;
|
|
33
|
+
const classes = ['toast', positionClass, className].filter(Boolean).join(' ').trim();
|
|
34
|
+
return (
|
|
35
|
+
<div className={classes} data-toast-container {...rest}>
|
|
36
|
+
<Alert variant={variant} dismissible={dismissible} autoDismiss={autoDismiss} id={id}>
|
|
37
|
+
{children}
|
|
38
|
+
</Alert>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default Toast;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ToggleProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
|
4
|
+
pressed?: boolean;
|
|
5
|
+
onPressedChange?: (pressed: boolean) => void;
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Toggle({
|
|
11
|
+
pressed = false,
|
|
12
|
+
onPressedChange,
|
|
13
|
+
disabled = false,
|
|
14
|
+
type = 'button',
|
|
15
|
+
value,
|
|
16
|
+
'aria-label': ariaLabel,
|
|
17
|
+
className = '',
|
|
18
|
+
children,
|
|
19
|
+
onClick,
|
|
20
|
+
...rest
|
|
21
|
+
}: ToggleProps) {
|
|
22
|
+
const pressedClass = pressed ? 'toggle--pressed' : '';
|
|
23
|
+
const classes = ['toggle', pressedClass, className].filter(Boolean).join(' ').trim();
|
|
24
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
25
|
+
onClick?.(e);
|
|
26
|
+
if (!disabled) onPressedChange?.(!pressed);
|
|
27
|
+
};
|
|
28
|
+
return (
|
|
29
|
+
<button
|
|
30
|
+
type={type}
|
|
31
|
+
className={classes}
|
|
32
|
+
aria-pressed={pressed}
|
|
33
|
+
disabled={disabled}
|
|
34
|
+
value={value}
|
|
35
|
+
aria-label={ariaLabel}
|
|
36
|
+
data-toggle
|
|
37
|
+
onClick={handleClick}
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
</button>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default Toggle;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ToggleGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
type?: 'single' | 'multiple';
|
|
5
|
+
orientation?: 'horizontal' | 'vertical';
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ToggleGroup({
|
|
11
|
+
type = 'single',
|
|
12
|
+
orientation = 'horizontal',
|
|
13
|
+
className = '',
|
|
14
|
+
children,
|
|
15
|
+
...rest
|
|
16
|
+
}: ToggleGroupProps) {
|
|
17
|
+
const orientationClass = orientation === 'vertical' ? 'toggle-group--vertical' : '';
|
|
18
|
+
const role = type === 'single' ? 'radiogroup' : 'group';
|
|
19
|
+
const classes = ['toggle-group', orientationClass, className].filter(Boolean).join(' ').trim();
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={classes}
|
|
23
|
+
role={role}
|
|
24
|
+
aria-label="Toggle group"
|
|
25
|
+
data-toggle-group
|
|
26
|
+
data-toggle-type={type}
|
|
27
|
+
{...rest}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default ToggleGroup;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
|
|
5
|
+
export interface TooltipProps extends HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
text: string;
|
|
7
|
+
position?: TooltipPosition;
|
|
8
|
+
delay?: number;
|
|
9
|
+
className?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Tooltip({
|
|
14
|
+
text,
|
|
15
|
+
position = 'top',
|
|
16
|
+
delay = 0,
|
|
17
|
+
className = '',
|
|
18
|
+
id: idProp,
|
|
19
|
+
style,
|
|
20
|
+
...rest
|
|
21
|
+
}: TooltipProps) {
|
|
22
|
+
const tooltipId = idProp ?? `tooltip-${Math.random().toString(36).slice(2, 11)}`;
|
|
23
|
+
const positionClass = `tooltip--${position}`;
|
|
24
|
+
const classes = ['tooltip', positionClass, className].filter(Boolean).join(' ').trim();
|
|
25
|
+
const delayStyle = delay > 0 ? { ['--tooltip-delay' as string]: `${delay}ms`, ...(style as object) } : style;
|
|
26
|
+
return (
|
|
27
|
+
<span
|
|
28
|
+
className={classes}
|
|
29
|
+
role="tooltip"
|
|
30
|
+
id={tooltipId}
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
style={delayStyle}
|
|
33
|
+
{...rest}
|
|
34
|
+
>
|
|
35
|
+
{text}
|
|
36
|
+
</span>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default Tooltip;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# React + Rizzo CSS
|
|
2
|
+
|
|
3
|
+
<pre style="font-family: ui-monospace, monospace; font-size: 0.85em; line-height: 1.2;">
|
|
4
|
+
/\___/\
|
|
5
|
+
__( o o )__
|
|
6
|
+
( =^= )
|
|
7
|
+
_/ ~ \_
|
|
8
|
+
/ \_____/ \
|
|
9
|
+
<span style="color:#c44536"> ____ </span><span style="color:#e07c3e"> ___ _</span><span style="color:#d4a800">______</span><span style="color:#2d9d78">______</span><span style="color:#0052bd"> _</span><span style="color:#7c3aed">___ __</span><span style="color:#d946ef">__ ____</span>
|
|
10
|
+
<span style="color:#c44536">| _ \</span><span style="color:#e07c3e">|_ _|_</span><span style="color:#d4a800">_ /__</span><span style="color:#2d9d78"> / _ </span><span style="color:#0052bd">\ / </span><span style="color:#7c3aed">___/ _</span><span style="color:#d946ef">__/ ___|</span>
|
|
11
|
+
<span style="color:#c44536">| |_) </span><span style="color:#e07c3e">|| | </span><span style="color:#d4a800">/ / /</span><span style="color:#2d9d78"> / | |</span><span style="color:#0052bd"> | | |</span><span style="color:#7c3aed"> \__</span><span style="color:#d946ef">_ \___ \</span>
|
|
12
|
+
<span style="color:#c44536">| _ <</span><span style="color:#e07c3e"> | | /</span><span style="color:#d4a800"> /_ / </span><span style="color:#2d9d78">/| |_|</span><span style="color:#0052bd"> | | |</span><span style="color:#7c3aed">___ __</span><span style="color:#d946ef">_) |__) |</span>
|
|
13
|
+
<span style="color:#c44536">|_| \_</span><span style="color:#e07c3e">\___/_</span><span style="color:#d4a800">___/__</span><span style="color:#2d9d78">__\___</span><span style="color:#0052bd">/ \_</span><span style="color:#7c3aed">___|__</span><span style="color:#d946ef">__/____/</span>
|
|
14
|
+
|
|
15
|
+
Design system · Vanilla · Astro · Svelte · React · Vue
|
|
16
|
+
</pre>
|
|
17
|
+
|
|
18
|
+
Vite + React project with Rizzo CSS. Scaffolded with `npx rizzo-css init` when you chose **Create new** and React, then **Landing**, **Docs**, **Dashboard**, or **Full**. Same template choice for **Add to existing** (`npx rizzo-css add`).
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
1. Install dependencies:
|
|
23
|
+
```bash
|
|
24
|
+
pnpm install
|
|
25
|
+
# or: npm install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
2. Start the dev server:
|
|
29
|
+
```bash
|
|
30
|
+
pnpm dev
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Project structure
|
|
34
|
+
|
|
35
|
+
- `vite.config.ts` — Vite configuration
|
|
36
|
+
- `index.html` — HTML shell with Rizzo CSS and theme (edit `data-theme` for default)
|
|
37
|
+
- `src/main.tsx` — Entry point
|
|
38
|
+
- `src/App.tsx` — Root component
|
|
39
|
+
- `public/css/rizzo.min.css` — Rizzo CSS bundle (added by CLI; font URLs point to `/assets/fonts/`)
|
|
40
|
+
- `public/assets/fonts/` — Rizzo font files (added by CLI)
|
|
41
|
+
- `README-RIZZO.md` — This file (scaffold docs; does not replace your project README)
|
|
42
|
+
- `LICENSE-RIZZO` — Rizzo CSS license (does not replace your project LICENSE)
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
- `pnpm dev` — Start dev server
|
|
47
|
+
- `pnpm build` — Build for production
|
|
48
|
+
- `pnpm preview` — Preview production build
|
|
49
|
+
|
|
50
|
+
Docs: [rizzo-css.vercel.app](https://rizzo-css.vercel.app)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-theme="{{DATA_THEME}}"><!-- {{THEME_LIST_COMMENT}} -->
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<script>
|
|
7
|
+
(function(){try{var s=localStorage.getItem('theme');var d='{{DEFAULT_DARK}}';var l='{{DEFAULT_LIGHT}}';var initial=document.documentElement.getAttribute('data-theme');var r=!s?((initial&&initial!=='system')?initial:(matchMedia('(prefers-color-scheme: dark)').matches?d:l)):s==='system'?(matchMedia('(prefers-color-scheme: dark)').matches?d:l):s;document.documentElement.setAttribute('data-theme',r);}catch(e){}})();
|
|
8
|
+
</script>
|
|
9
|
+
<link rel="stylesheet" href="/css/rizzo.min.css" />
|
|
10
|
+
<title>{{TITLE}}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.3.1",
|
|
13
|
+
"react-dom": "^18.3.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.3.12",
|
|
17
|
+
"@types/react-dom": "^18.3.1",
|
|
18
|
+
"@vitejs/plugin-react": "^4.3.3",
|
|
19
|
+
"typescript": "~5.6.2",
|
|
20
|
+
"vite": "^6.0.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function App() {
|
|
2
|
+
return (
|
|
3
|
+
<>
|
|
4
|
+
<a href="#main-content" className="skip-link">Skip to main content</a>
|
|
5
|
+
<main id="main-content" className="container">
|
|
6
|
+
<h1>Hello, Rizzo CSS</h1>
|
|
7
|
+
<p>Edit <code>src/App.tsx</code> and add components. Docs: <a href="https://rizzo-css.vercel.app">rizzo-css.vercel.app</a></p>
|
|
8
|
+
</main>
|
|
9
|
+
</>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* App styles; Rizzo is loaded from /css/rizzo.min.css in index.html */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"noUncheckedSideEffectImports": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src"]
|
|
21
|
+
}
|
|
@@ -28,7 +28,7 @@ If you prefer to load CSS from a CDN instead of the local file, replace the `<li
|
|
|
28
28
|
- `<link rel="stylesheet" href="https://unpkg.com/rizzo-css@latest/dist/rizzo.min.css" />`
|
|
29
29
|
- Or jsDelivr: `https://cdn.jsdelivr.net/npm/rizzo-css@latest/dist/rizzo.min.css`
|
|
30
30
|
|
|
31
|
-
(Replace `@latest` with a specific version, e.g. `@0.0.
|
|
31
|
+
(Replace `@latest` with a specific version, e.g. `@0.0.64`, in production.)
|
|
32
32
|
|
|
33
33
|
The CLI replaces placeholders in `index.html` (e.g. `{{DATA_THEME}}`, `{{TITLE}}`) when you run `rizzo-css init`. The theme selected during init is used on first load when you have no saved preference in the browser.
|
|
34
34
|
|