rizzo-css 0.0.61 → 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.
Files changed (171) hide show
  1. package/README.md +9 -5
  2. package/bin/rizzo-css.js +568 -79
  3. package/dist/rizzo.min.css +5 -3
  4. package/package.json +14 -7
  5. package/scaffold/astro/Footer.astro +8 -0
  6. package/scaffold/astro/Settings.astro +8 -2
  7. package/scaffold/astro/Tabs.astro +2 -2
  8. package/scaffold/react/Accordion.tsx +143 -0
  9. package/scaffold/react/Alert.tsx +90 -0
  10. package/scaffold/react/AlertDialog.tsx +80 -0
  11. package/scaffold/react/AspectRatio.tsx +32 -0
  12. package/scaffold/react/Avatar.tsx +53 -0
  13. package/scaffold/react/BackToTop.tsx +62 -0
  14. package/scaffold/react/Badge.tsx +39 -0
  15. package/scaffold/react/Breadcrumb.tsx +81 -0
  16. package/scaffold/react/Button.tsx +40 -0
  17. package/scaffold/react/ButtonGroup.tsx +24 -0
  18. package/scaffold/react/Card.tsx +26 -0
  19. package/scaffold/react/Checkbox.tsx +40 -0
  20. package/scaffold/react/Collapsible.tsx +58 -0
  21. package/scaffold/react/ContextMenu.tsx +67 -0
  22. package/scaffold/react/CopyToClipboard.tsx +128 -0
  23. package/scaffold/react/Dashboard.tsx +23 -0
  24. package/scaffold/react/Divider.tsx +47 -0
  25. package/scaffold/react/DocsSidebar.tsx +48 -0
  26. package/scaffold/react/Dropdown.tsx +256 -0
  27. package/scaffold/react/Empty.tsx +29 -0
  28. package/scaffold/react/FontSwitcher.tsx +68 -0
  29. package/scaffold/react/Footer.tsx +55 -0
  30. package/scaffold/react/FormGroup.tsx +57 -0
  31. package/scaffold/react/HoverCard.tsx +61 -0
  32. package/scaffold/react/Icons.tsx +22 -0
  33. package/scaffold/react/Input.tsx +69 -0
  34. package/scaffold/react/Kbd.tsx +16 -0
  35. package/scaffold/react/Label.tsx +16 -0
  36. package/scaffold/react/Modal.tsx +149 -0
  37. package/scaffold/react/Navbar.tsx +72 -0
  38. package/scaffold/react/Pagination.tsx +155 -0
  39. package/scaffold/react/Popover.tsx +66 -0
  40. package/scaffold/react/ProgressBar.tsx +66 -0
  41. package/scaffold/react/Radio.tsx +38 -0
  42. package/scaffold/react/ResizableHandle.tsx +24 -0
  43. package/scaffold/react/ResizablePane.tsx +29 -0
  44. package/scaffold/react/ResizablePaneGroup.tsx +29 -0
  45. package/scaffold/react/ScrollArea.tsx +29 -0
  46. package/scaffold/react/Search.tsx +62 -0
  47. package/scaffold/react/Select.tsx +65 -0
  48. package/scaffold/react/Separator.tsx +33 -0
  49. package/scaffold/react/Settings.tsx +60 -0
  50. package/scaffold/react/Sheet.tsx +86 -0
  51. package/scaffold/react/Skeleton.tsx +32 -0
  52. package/scaffold/react/Slider.tsx +66 -0
  53. package/scaffold/react/SoundEffects.tsx +15 -0
  54. package/scaffold/react/Spinner.tsx +36 -0
  55. package/scaffold/react/Switch.tsx +52 -0
  56. package/scaffold/react/Table.tsx +178 -0
  57. package/scaffold/react/Tabs.tsx +143 -0
  58. package/scaffold/react/Textarea.tsx +69 -0
  59. package/scaffold/react/ThemeSwitcher.tsx +89 -0
  60. package/scaffold/react/Toast.tsx +43 -0
  61. package/scaffold/react/Toggle.tsx +45 -0
  62. package/scaffold/react/ToggleGroup.tsx +34 -0
  63. package/scaffold/react/Tooltip.tsx +40 -0
  64. package/scaffold/vanilla/README-RIZZO.md +1 -1
  65. package/scaffold/vanilla/components/accordion.html +40 -0
  66. package/scaffold/vanilla/components/alert-dialog.html +40 -0
  67. package/scaffold/vanilla/components/alert.html +40 -0
  68. package/scaffold/vanilla/components/aspect-ratio.html +40 -0
  69. package/scaffold/vanilla/components/avatar.html +40 -0
  70. package/scaffold/vanilla/components/back-to-top.html +40 -0
  71. package/scaffold/vanilla/components/badge.html +40 -0
  72. package/scaffold/vanilla/components/breadcrumb.html +40 -0
  73. package/scaffold/vanilla/components/button-group.html +40 -0
  74. package/scaffold/vanilla/components/button.html +40 -0
  75. package/scaffold/vanilla/components/cards.html +40 -0
  76. package/scaffold/vanilla/components/collapsible.html +40 -0
  77. package/scaffold/vanilla/components/context-menu.html +40 -0
  78. package/scaffold/vanilla/components/copy-to-clipboard.html +40 -0
  79. package/scaffold/vanilla/components/dashboard.html +40 -0
  80. package/scaffold/vanilla/components/divider.html +40 -0
  81. package/scaffold/vanilla/components/docs-sidebar.html +40 -0
  82. package/scaffold/vanilla/components/dropdown.html +40 -0
  83. package/scaffold/vanilla/components/empty.html +40 -0
  84. package/scaffold/vanilla/components/font-switcher.html +40 -0
  85. package/scaffold/vanilla/components/footer.html +40 -0
  86. package/scaffold/vanilla/components/forms.html +40 -0
  87. package/scaffold/vanilla/components/hover-card.html +40 -0
  88. package/scaffold/vanilla/components/icons.html +40 -0
  89. package/scaffold/vanilla/components/index.html +40 -0
  90. package/scaffold/vanilla/components/kbd.html +40 -0
  91. package/scaffold/vanilla/components/label.html +40 -0
  92. package/scaffold/vanilla/components/modal.html +40 -0
  93. package/scaffold/vanilla/components/navbar.html +40 -0
  94. package/scaffold/vanilla/components/pagination.html +40 -0
  95. package/scaffold/vanilla/components/popover.html +40 -0
  96. package/scaffold/vanilla/components/progress-bar.html +40 -0
  97. package/scaffold/vanilla/components/resizable.html +40 -0
  98. package/scaffold/vanilla/components/scroll-area.html +40 -0
  99. package/scaffold/vanilla/components/search.html +40 -0
  100. package/scaffold/vanilla/components/separator.html +40 -0
  101. package/scaffold/vanilla/components/settings.html +40 -0
  102. package/scaffold/vanilla/components/sheet.html +40 -0
  103. package/scaffold/vanilla/components/skeleton.html +40 -0
  104. package/scaffold/vanilla/components/slider.html +40 -0
  105. package/scaffold/vanilla/components/sound-effects.html +40 -0
  106. package/scaffold/vanilla/components/spinner.html +40 -0
  107. package/scaffold/vanilla/components/switch.html +40 -0
  108. package/scaffold/vanilla/components/table.html +40 -0
  109. package/scaffold/vanilla/components/tabs.html +40 -0
  110. package/scaffold/vanilla/components/theme-switcher.html +40 -0
  111. package/scaffold/vanilla/components/toast.html +40 -0
  112. package/scaffold/vanilla/components/toggle-group.html +40 -0
  113. package/scaffold/vanilla/components/toggle.html +40 -0
  114. package/scaffold/vanilla/components/tooltip.html +40 -0
  115. package/scaffold/vanilla/index.html +40 -0
  116. package/scaffold/vue/Accordion.vue +9 -0
  117. package/scaffold/vue/Alert.vue +9 -0
  118. package/scaffold/vue/AlertDialog.vue +9 -0
  119. package/scaffold/vue/AspectRatio.vue +9 -0
  120. package/scaffold/vue/Avatar.vue +9 -0
  121. package/scaffold/vue/BackToTop.vue +9 -0
  122. package/scaffold/vue/Badge.vue +28 -0
  123. package/scaffold/vue/Breadcrumb.vue +9 -0
  124. package/scaffold/vue/Button.vue +23 -0
  125. package/scaffold/vue/ButtonGroup.vue +9 -0
  126. package/scaffold/vue/Card.vue +21 -0
  127. package/scaffold/vue/Checkbox.vue +31 -0
  128. package/scaffold/vue/Collapsible.vue +9 -0
  129. package/scaffold/vue/ContextMenu.vue +9 -0
  130. package/scaffold/vue/CopyToClipboard.vue +9 -0
  131. package/scaffold/vue/Dashboard.vue +9 -0
  132. package/scaffold/vue/Divider.vue +23 -0
  133. package/scaffold/vue/DocsSidebar.vue +9 -0
  134. package/scaffold/vue/Dropdown.vue +9 -0
  135. package/scaffold/vue/Empty.vue +9 -0
  136. package/scaffold/vue/FontSwitcher.vue +9 -0
  137. package/scaffold/vue/Footer.vue +9 -0
  138. package/scaffold/vue/FormGroup.vue +45 -0
  139. package/scaffold/vue/HoverCard.vue +9 -0
  140. package/scaffold/vue/Icons.vue +9 -0
  141. package/scaffold/vue/Input.vue +59 -0
  142. package/scaffold/vue/Kbd.vue +9 -0
  143. package/scaffold/vue/Label.vue +23 -0
  144. package/scaffold/vue/Modal.vue +9 -0
  145. package/scaffold/vue/Navbar.vue +9 -0
  146. package/scaffold/vue/Pagination.vue +9 -0
  147. package/scaffold/vue/Popover.vue +9 -0
  148. package/scaffold/vue/ProgressBar.vue +9 -0
  149. package/scaffold/vue/Radio.vue +29 -0
  150. package/scaffold/vue/ResizableHandle.vue +9 -0
  151. package/scaffold/vue/ResizablePane.vue +9 -0
  152. package/scaffold/vue/ResizablePaneGroup.vue +9 -0
  153. package/scaffold/vue/ScrollArea.vue +9 -0
  154. package/scaffold/vue/Search.vue +9 -0
  155. package/scaffold/vue/Select.vue +52 -0
  156. package/scaffold/vue/Separator.vue +9 -0
  157. package/scaffold/vue/Settings.vue +9 -0
  158. package/scaffold/vue/Sheet.vue +9 -0
  159. package/scaffold/vue/Skeleton.vue +9 -0
  160. package/scaffold/vue/Slider.vue +9 -0
  161. package/scaffold/vue/SoundEffects.vue +9 -0
  162. package/scaffold/vue/Spinner.vue +21 -0
  163. package/scaffold/vue/Switch.vue +9 -0
  164. package/scaffold/vue/Table.vue +9 -0
  165. package/scaffold/vue/Tabs.vue +9 -0
  166. package/scaffold/vue/Textarea.vue +60 -0
  167. package/scaffold/vue/ThemeSwitcher.vue +9 -0
  168. package/scaffold/vue/Toast.vue +9 -0
  169. package/scaffold/vue/Toggle.vue +9 -0
  170. package/scaffold/vue/ToggleGroup.vue +9 -0
  171. package/scaffold/vue/Tooltip.vue +9 -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;
@@ -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.61`, in production.)
31
+ (Replace `@latest` with a specific version, e.g. `@0.0.63`, 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