rizzo-css 0.0.30 → 0.0.32
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 +1 -1
- package/bin/rizzo-css.js +11 -10
- package/package.json +1 -1
- package/scaffold/astro/Navbar.astro +43 -2
- package/scaffold/astro/Search.astro +78 -0
- package/scaffold/svelte/Navbar.svelte +19 -0
- package/scaffold/svelte/Search.svelte +51 -1
- package/scaffold/svelte/Settings.svelte +31 -4
- package/scaffold/vanilla/README-RIZZO.md +4 -2
- package/scaffold/vanilla/components/accordion.html +24 -0
- package/scaffold/vanilla/components/alert.html +24 -0
- package/scaffold/vanilla/components/avatar.html +24 -0
- package/scaffold/vanilla/components/badge.html +24 -0
- package/scaffold/vanilla/components/breadcrumb.html +24 -0
- package/scaffold/vanilla/components/button.html +24 -0
- package/scaffold/vanilla/components/cards.html +24 -0
- package/scaffold/vanilla/components/copy-to-clipboard.html +24 -0
- package/scaffold/vanilla/components/divider.html +24 -0
- package/scaffold/vanilla/components/dropdown.html +24 -0
- package/scaffold/vanilla/components/forms.html +24 -0
- package/scaffold/vanilla/components/icons.html +24 -0
- package/scaffold/vanilla/components/index.html +24 -0
- package/scaffold/vanilla/components/modal.html +24 -0
- package/scaffold/vanilla/components/navbar.html +24 -0
- package/scaffold/vanilla/components/pagination.html +24 -0
- package/scaffold/vanilla/components/progress-bar.html +24 -0
- package/scaffold/vanilla/components/search.html +24 -0
- package/scaffold/vanilla/components/settings.html +24 -0
- package/scaffold/vanilla/components/spinner.html +24 -0
- package/scaffold/vanilla/components/table.html +24 -0
- package/scaffold/vanilla/components/tabs.html +24 -0
- package/scaffold/vanilla/components/theme-switcher.html +24 -0
- package/scaffold/vanilla/components/toast.html +24 -0
- package/scaffold/vanilla/components/tooltip.html +24 -0
- package/scaffold/vanilla/index.html +24 -0
- package/scaffold/vanilla/js/main.js +118 -14
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ import 'rizzo-css';
|
|
|
61
61
|
**Without a bundler (plain HTML):** Use a CDN. Both unpkg and jsDelivr resolve the package root to the built CSS (via the `unpkg` / `jsdelivr` fields in this package). For reliability or to pin a version, use the explicit path:
|
|
62
62
|
|
|
63
63
|
```html
|
|
64
|
-
<!-- unpkg (pin version: replace @latest with @0.0.
|
|
64
|
+
<!-- unpkg (pin version: replace @latest with @0.0.32 or any version) -->
|
|
65
65
|
<link rel="stylesheet" href="https://unpkg.com/rizzo-css@latest/dist/rizzo.min.css" />
|
|
66
66
|
|
|
67
67
|
<!-- or jsDelivr -->
|
package/bin/rizzo-css.js
CHANGED
|
@@ -42,7 +42,7 @@ const VANILLA_MINIMAL_README = `# Vanilla + Rizzo CSS (minimal)
|
|
|
42
42
|
Minimal starter: HTML + CSS + js/main.js + recommended component pages. Scaffolded with \`npx rizzo-css init --framework vanilla --template minimal\`.
|
|
43
43
|
|
|
44
44
|
- Open \`index.html\` in a browser or serve the folder. Edit \`index.html\` and add your content. CSS: \`css/rizzo.min.css\`. Script: \`js/main.js\` (already linked).
|
|
45
|
-
- \`components/\` contains HTML pages for the recommended set (Button, Badge, Card, Modal, Tabs, ThemeSwitcher, FormGroup, Alert, Toast, Dropdown). Open \`components/index.html\` to browse them.
|
|
45
|
+
- \`components/\` contains HTML pages for the recommended set (Button, Badge, Card, Modal, Tabs, ThemeSwitcher, FormGroup, Alert, Toast, Dropdown, Navbar, Search, Settings, Accordion, CopyToClipboard). Open \`components/index.html\` to browse them.
|
|
46
46
|
- Set a theme: \`<html data-theme="github-dark-classic">\` (see \`npx rizzo-css theme\` for all themes).
|
|
47
47
|
- For the full component showcase and icons, use template **Full** or copy from a Full scaffold.
|
|
48
48
|
|
|
@@ -58,7 +58,7 @@ Manual setup: HTML + CSS, plus any component pages you chose. Scaffolded with \`
|
|
|
58
58
|
- If you picked components, \`components/\` has their HTML pages and \`js/main.js\` is included (open \`components/index.html\` to browse).
|
|
59
59
|
- Set a theme: \`<html data-theme="github-dark-classic">\` (see \`npx rizzo-css theme\` for all themes).
|
|
60
60
|
|
|
61
|
-
**If you chose no components:** To add component JavaScript (modal, dropdown, tabs, toast, search, navbar, theme switcher, etc.), use the [Vanilla component docs](https://rizzo-css.vercel.app/docs/vanilla/components) or run \`npx rizzo-css init\` with Vanilla → **Full** in a temp folder and copy \`js/main.js\` and \`icons/\` into this project.
|
|
61
|
+
**If you chose no components:** To add component JavaScript (modal, dropdown, tabs, toast, search, navbar, copy-to-clipboard, theme switcher, etc.), use the [Vanilla component docs](https://rizzo-css.vercel.app/docs/vanilla/components) or run \`npx rizzo-css init\` with Vanilla → **Full** in a temp folder and copy \`js/main.js\` and \`icons/\` into this project.
|
|
62
62
|
|
|
63
63
|
Docs: [rizzo-css.vercel.app](https://rizzo-css.vercel.app)
|
|
64
64
|
`;
|
|
@@ -109,13 +109,14 @@ const ASTRO_COMPONENTS = [
|
|
|
109
109
|
'Navbar', 'Settings', 'Search', 'Icons',
|
|
110
110
|
];
|
|
111
111
|
|
|
112
|
-
// Recommended subset for Full/Minimal (same for Astro, Svelte, Vanilla)
|
|
112
|
+
// Recommended subset for Full/Minimal (same for Astro, Svelte, Vanilla). Includes all interactive components.
|
|
113
113
|
const RECOMMENDED_COMPONENTS = [
|
|
114
114
|
'Button', 'Badge', 'Card', 'Modal', 'Tabs', 'ThemeSwitcher', 'FormGroup', 'Alert', 'Toast', 'Dropdown',
|
|
115
|
+
'Navbar', 'Search', 'Settings', 'Accordion', 'CopyToClipboard',
|
|
115
116
|
];
|
|
116
117
|
|
|
117
|
-
// Vanilla components that need js/main.js for interactivity
|
|
118
|
-
const VANILLA_JS_COMPONENTS = ['Modal', 'Dropdown', 'Tabs', 'Toast', 'ThemeSwitcher'];
|
|
118
|
+
// Vanilla components that need js/main.js for interactivity.
|
|
119
|
+
const VANILLA_JS_COMPONENTS = ['Modal', 'Dropdown', 'Tabs', 'Toast', 'ThemeSwitcher', 'Search', 'Accordion', 'CopyToClipboard', 'Navbar', 'Settings'];
|
|
119
120
|
|
|
120
121
|
// Component dependencies per framework: when user selects a component, these are copied automatically so it works.
|
|
121
122
|
// Manual users can run: npx rizzo-css help components
|
|
@@ -465,7 +466,7 @@ async function confirmRunInstall(pm) {
|
|
|
465
466
|
|
|
466
467
|
/** Ask user to copy js/main.js for vanilla interactive components. */
|
|
467
468
|
async function confirmCopyVanillaJs() {
|
|
468
|
-
const answer = await question('\nCopy js/main.js for modal, dropdown, tabs, toast, search, navbar
|
|
469
|
+
const answer = await question('\nCopy js/main.js for modal, dropdown, tabs, toast, search, navbar, copy-to-clipboard, theme switcher? (Y/n) ');
|
|
469
470
|
return answer === '' || /^y(es)?$/i.test(answer);
|
|
470
471
|
}
|
|
471
472
|
|
|
@@ -811,7 +812,7 @@ Options (add):
|
|
|
811
812
|
--no-snippet Do not write RIZZO-SNIPPET.txt (link + theme copy-paste)
|
|
812
813
|
--readme Write README-RIZZO.md into the project
|
|
813
814
|
--force Overwrite existing rizzo.min.css without prompting
|
|
814
|
-
--vanilla-js (Vanilla) Copy js/main.js for interactive components (modal, dropdown, tabs, toast, search, navbar, theme switcher)
|
|
815
|
+
--vanilla-js (Vanilla) Copy js/main.js for interactive components (modal, dropdown, tabs, toast, search, navbar, copy-to-clipboard, theme switcher)
|
|
815
816
|
|
|
816
817
|
Package managers:
|
|
817
818
|
Supported: npm, pnpm, yarn, bun. Detection: lockfiles (pnpm-lock.yaml, yarn.lock, bun.lockb, package-lock.json) or package.json "packageManager"/"devEngines.packageManager". Use --package-manager to override.
|
|
@@ -983,7 +984,7 @@ async function promptComponentChoice(componentList, framework, initialSelection)
|
|
|
983
984
|
const choice = await selectMenu(
|
|
984
985
|
[
|
|
985
986
|
{ value: 'none', label: 'CSS only — no components' },
|
|
986
|
-
{ value: 'recommended', label: 'Recommended set (' + recommended.length + ' components: Button, Badge, Card, Modal, Tabs, ThemeSwitcher, FormGroup, Alert, Toast, Dropdown)' },
|
|
987
|
+
{ value: 'recommended', label: 'Recommended set (' + recommended.length + ' components: Button, Badge, Card, Modal, Tabs, ThemeSwitcher, FormGroup, Alert, Toast, Dropdown, Navbar, Search, Settings, Accordion, CopyToClipboard)' },
|
|
987
988
|
{ value: 'all', label: 'All components (' + componentList.length + ')' },
|
|
988
989
|
{ value: 'pick', label: 'Pick components (choose each one)' },
|
|
989
990
|
],
|
|
@@ -1465,7 +1466,7 @@ async function runAddToExisting(frameworkOverride, options) {
|
|
|
1465
1466
|
let mainJs = readFileSync(vanillaJsSrc, 'utf8');
|
|
1466
1467
|
mainJs = mainJs.replace(/\{\{DEFAULT_DARK\}\}/g, defaultDark).replace(/\{\{DEFAULT_LIGHT\}\}/g, defaultLight);
|
|
1467
1468
|
writeFileSync(vanillaJsPath, mainJs, 'utf8');
|
|
1468
|
-
console.log(' - Wrote js/main.js (for modal, dropdown, tabs, toast, search, navbar
|
|
1469
|
+
console.log(' - Wrote js/main.js (for modal, dropdown, tabs, toast, search, navbar, copy-to-clipboard, theme switcher)');
|
|
1469
1470
|
}
|
|
1470
1471
|
} else if (needsJs && !existsSync(vanillaJsPath)) {
|
|
1471
1472
|
options._vanillaJsHint = true;
|
|
@@ -1526,7 +1527,7 @@ async function runAddToExisting(frameworkOverride, options) {
|
|
|
1526
1527
|
console.log(' data-theme="' + theme + '" on <html> (themes: ' + cliExample + ')');
|
|
1527
1528
|
console.log(' Component HTML files are in components/.');
|
|
1528
1529
|
if (options._vanillaJsHint) {
|
|
1529
|
-
console.log(' For interactive components (modal, dropdown, tabs, toast, search, navbar, theme switcher), add js/main.js — run again with --vanilla-js or copy from a Full scaffold.');
|
|
1530
|
+
console.log(' For interactive components (modal, dropdown, tabs, toast, search, navbar, copy-to-clipboard, theme switcher), add js/main.js — run again with --vanilla-js or copy from a Full scaffold.');
|
|
1530
1531
|
}
|
|
1531
1532
|
}
|
|
1532
1533
|
console.log('\nTo install the package: ' + pm.add('rizzo-css'));
|
package/package.json
CHANGED
|
@@ -24,11 +24,52 @@ const { siteName = 'Site', logo } = Astro.props;
|
|
|
24
24
|
<span class="navbar__settings-label">Settings</span>
|
|
25
25
|
</button>
|
|
26
26
|
</div>
|
|
27
|
-
<button type="button" class="navbar__toggle" aria-label="Toggle menu" aria-expanded="false">
|
|
27
|
+
<button type="button" class="navbar__toggle" id="navbar-toggle" aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="navbar-menu">
|
|
28
|
+
<span class="sr-only">Menu</span>
|
|
28
29
|
<span class="navbar__toggle-icon" aria-hidden="true"><span></span><span></span><span></span></span>
|
|
29
30
|
</button>
|
|
30
|
-
<div class="navbar__menu" aria-hidden="true">
|
|
31
|
+
<div class="navbar__menu" id="navbar-menu" role="menu" aria-hidden="true">
|
|
31
32
|
<a href="/" class="navbar__link">Home</a>
|
|
32
33
|
</div>
|
|
33
34
|
</div>
|
|
34
35
|
</nav>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
(function initNavbarMobile() {
|
|
39
|
+
function init() {
|
|
40
|
+
var navbar = document.querySelector('.navbar');
|
|
41
|
+
if (!navbar) return;
|
|
42
|
+
var toggle = document.getElementById('navbar-toggle');
|
|
43
|
+
var menu = navbar.querySelector('.navbar__menu');
|
|
44
|
+
if (!toggle || !menu) return;
|
|
45
|
+
var outsideClickHandler = null;
|
|
46
|
+
function setMenuOpen(open) {
|
|
47
|
+
menu.classList.toggle('navbar__menu--open', open);
|
|
48
|
+
navbar.classList.toggle('navbar--menu-open', open);
|
|
49
|
+
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
50
|
+
menu.setAttribute('aria-hidden', open ? 'false' : 'true');
|
|
51
|
+
if (outsideClickHandler) {
|
|
52
|
+
document.removeEventListener('click', outsideClickHandler);
|
|
53
|
+
outsideClickHandler = null;
|
|
54
|
+
}
|
|
55
|
+
if (open) {
|
|
56
|
+
outsideClickHandler = function (e) {
|
|
57
|
+
if (e.target && !navbar.contains(e.target)) setMenuOpen(false);
|
|
58
|
+
};
|
|
59
|
+
setTimeout(function () { document.addEventListener('click', outsideClickHandler); }, 0);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
toggle.addEventListener('click', function () {
|
|
63
|
+
setMenuOpen(!menu.classList.contains('navbar__menu--open'));
|
|
64
|
+
});
|
|
65
|
+
menu.querySelectorAll('.navbar__link').forEach(function (link) {
|
|
66
|
+
link.addEventListener('click', function () { setMenuOpen(false); });
|
|
67
|
+
});
|
|
68
|
+
document.addEventListener('keydown', function (e) {
|
|
69
|
+
if (e.key === 'Escape' && menu.classList.contains('navbar__menu--open')) setMenuOpen(false);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
73
|
+
else init();
|
|
74
|
+
})();
|
|
75
|
+
</script>
|
|
@@ -1,16 +1,94 @@
|
|
|
1
1
|
---
|
|
2
|
+
import SearchIcon from './icons/Search.astro';
|
|
2
3
|
interface Props { id?: string; }
|
|
3
4
|
const { id = 'search-main' } = Astro.props;
|
|
4
5
|
---
|
|
5
6
|
<div class="search" data-search>
|
|
6
7
|
<div class="search__trigger-wrapper">
|
|
7
8
|
<button type="button" class="search__trigger" aria-label="Open search" aria-expanded="false" aria-controls="{id}-panel">
|
|
9
|
+
<SearchIcon width={20} height={20} class="search__icon" />
|
|
8
10
|
<span class="search__trigger-text">Search</span>
|
|
9
11
|
</button>
|
|
10
12
|
</div>
|
|
11
13
|
<div class="search__overlay" id="{id}-panel" aria-hidden="true" role="dialog" aria-modal="true" data-search-overlay>
|
|
12
14
|
<div class="search__panel">
|
|
13
15
|
<input type="search" class="search__input" placeholder="Search…" aria-label="Search" />
|
|
16
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
17
|
+
<div class="search__empty">
|
|
18
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
21
|
+
<a href="#" class="search__result-item" tabindex="-1" data-search-result-item><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
22
|
+
<a href="#" class="search__result-item" tabindex="-1" data-search-result-item><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
23
|
+
<a href="#" class="search__result-item" tabindex="-1" data-search-result-item><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
14
26
|
</div>
|
|
15
27
|
</div>
|
|
16
28
|
</div>
|
|
29
|
+
|
|
30
|
+
<script>
|
|
31
|
+
(function initSearch() {
|
|
32
|
+
var focusableSel = 'button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
33
|
+
function getFocusable(container) {
|
|
34
|
+
return Array.prototype.slice.call(container.querySelectorAll(focusableSel));
|
|
35
|
+
}
|
|
36
|
+
function init() {
|
|
37
|
+
document.querySelectorAll('[data-search]').forEach(function (search) {
|
|
38
|
+
if (search.__searchInited) return;
|
|
39
|
+
search.__searchInited = true;
|
|
40
|
+
var trigger = search.querySelector('.search__trigger');
|
|
41
|
+
var overlay = search.querySelector('[data-search-overlay]');
|
|
42
|
+
var panel = search.querySelector('.search__panel');
|
|
43
|
+
var input = search.querySelector('.search__input');
|
|
44
|
+
var resultItems = search.querySelectorAll('.search__result-item, [data-search-result-item]');
|
|
45
|
+
if (!trigger || !overlay || !input) return;
|
|
46
|
+
var previousActive = null;
|
|
47
|
+
var focusTrapHandler = null;
|
|
48
|
+
function openSearch() {
|
|
49
|
+
previousActive = document.activeElement;
|
|
50
|
+
overlay.setAttribute('aria-hidden', 'false');
|
|
51
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
52
|
+
for (var i = 0; i < resultItems.length; i++) resultItems[i].setAttribute('tabindex', '0');
|
|
53
|
+
input.focus();
|
|
54
|
+
focusTrapHandler = function (e) {
|
|
55
|
+
if (overlay.getAttribute('aria-hidden') === 'true') return;
|
|
56
|
+
if (e.key === 'Escape') { e.preventDefault(); closeSearch(); return; }
|
|
57
|
+
if (e.key === 'Tab' && panel) {
|
|
58
|
+
var els = getFocusable(panel);
|
|
59
|
+
if (els.length === 0) return;
|
|
60
|
+
var first = els[0], last = els[els.length - 1], active = document.activeElement;
|
|
61
|
+
if (e.shiftKey) {
|
|
62
|
+
if (active === first || !panel.contains(active)) { e.preventDefault(); last.focus(); }
|
|
63
|
+
} else {
|
|
64
|
+
if (active === last || !panel.contains(active)) { e.preventDefault(); first.focus(); }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
document.addEventListener('keydown', focusTrapHandler);
|
|
69
|
+
}
|
|
70
|
+
function closeSearch() {
|
|
71
|
+
document.removeEventListener('keydown', focusTrapHandler);
|
|
72
|
+
focusTrapHandler = null;
|
|
73
|
+
for (var i = 0; i < resultItems.length; i++) resultItems[i].setAttribute('tabindex', '-1');
|
|
74
|
+
overlay.setAttribute('aria-hidden', 'true');
|
|
75
|
+
trigger.setAttribute('aria-expanded', 'false');
|
|
76
|
+
if (previousActive && previousActive.focus) previousActive.focus();
|
|
77
|
+
previousActive = null;
|
|
78
|
+
}
|
|
79
|
+
trigger.addEventListener('click', function () {
|
|
80
|
+
if (overlay.getAttribute('aria-hidden') === 'true') openSearch();
|
|
81
|
+
else closeSearch();
|
|
82
|
+
});
|
|
83
|
+
overlay.addEventListener('click', function (e) {
|
|
84
|
+
if (e.target === overlay) closeSearch();
|
|
85
|
+
});
|
|
86
|
+
input.addEventListener('keydown', function (e) {
|
|
87
|
+
if (e.key === 'Escape') { e.preventDefault(); closeSearch(); }
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
92
|
+
else init();
|
|
93
|
+
})();
|
|
94
|
+
</script>
|
|
@@ -9,6 +9,25 @@
|
|
|
9
9
|
}
|
|
10
10
|
let { siteName = 'Site', logo }: Props = $props();
|
|
11
11
|
let menuOpen = $state(false);
|
|
12
|
+
|
|
13
|
+
// Click outside and Escape to close mobile menu
|
|
14
|
+
$effect(() => {
|
|
15
|
+
if (!menuOpen) return;
|
|
16
|
+
const onEscape = (e: KeyboardEvent) => {
|
|
17
|
+
if (e.key === 'Escape') menuOpen = false;
|
|
18
|
+
};
|
|
19
|
+
const onClick = (e: MouseEvent) => {
|
|
20
|
+
const target = e.target as Node;
|
|
21
|
+
if (target && !(target as Element).closest?.('.navbar')) menuOpen = false;
|
|
22
|
+
};
|
|
23
|
+
document.addEventListener('keydown', onEscape);
|
|
24
|
+
const t = setTimeout(() => document.addEventListener('click', onClick), 0);
|
|
25
|
+
return () => {
|
|
26
|
+
document.removeEventListener('keydown', onEscape);
|
|
27
|
+
document.removeEventListener('click', onClick);
|
|
28
|
+
clearTimeout(t);
|
|
29
|
+
};
|
|
30
|
+
});
|
|
12
31
|
</script>
|
|
13
32
|
|
|
14
33
|
<nav class="navbar" role="navigation" aria-label="Main navigation">
|
|
@@ -5,6 +5,45 @@
|
|
|
5
5
|
let { id = 'search-main' }: Props = $props();
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
let query = $state('');
|
|
8
|
+
let panelEl = $state<HTMLElement | null>(null);
|
|
9
|
+
|
|
10
|
+
const FOCUSABLE_SEL = 'button:not([disabled]),a[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
11
|
+
|
|
12
|
+
function getFocusable(container: HTMLElement | null): HTMLElement[] {
|
|
13
|
+
if (!container) return [];
|
|
14
|
+
return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SEL));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$effect(() => {
|
|
18
|
+
if (!open) return;
|
|
19
|
+
const onKeydown = (e: KeyboardEvent) => {
|
|
20
|
+
if (e.key === 'Escape') {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
open = false;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (e.key === 'Tab' && panelEl) {
|
|
26
|
+
const els = getFocusable(panelEl);
|
|
27
|
+
if (els.length === 0) return;
|
|
28
|
+
const first = els[0];
|
|
29
|
+
const last = els[els.length - 1];
|
|
30
|
+
const active = document.activeElement as HTMLElement;
|
|
31
|
+
if (e.shiftKey) {
|
|
32
|
+
if (active === first || !panelEl.contains(active)) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
last.focus();
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
if (active === last || !panelEl.contains(active)) {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
first.focus();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
document.addEventListener('keydown', onKeydown);
|
|
45
|
+
return () => document.removeEventListener('keydown', onKeydown);
|
|
46
|
+
});
|
|
8
47
|
</script>
|
|
9
48
|
|
|
10
49
|
<div class="search" data-search>
|
|
@@ -27,8 +66,9 @@
|
|
|
27
66
|
role="dialog"
|
|
28
67
|
aria-modal="true"
|
|
29
68
|
data-search-overlay
|
|
69
|
+
onclick={(e) => (e.target as HTMLElement) === (e.currentTarget as HTMLElement) && (open = false)}
|
|
30
70
|
>
|
|
31
|
-
<div class="search__panel">
|
|
71
|
+
<div class="search__panel" bind:this={panelEl}>
|
|
32
72
|
<input
|
|
33
73
|
type="search"
|
|
34
74
|
class="search__input"
|
|
@@ -36,6 +76,16 @@
|
|
|
36
76
|
aria-label="Search"
|
|
37
77
|
bind:value={query}
|
|
38
78
|
/>
|
|
79
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
80
|
+
<div class="search__empty">
|
|
81
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
84
|
+
<a href="#" class="search__result-item" tabindex={open ? 0 : -1}><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
85
|
+
<a href="#" class="search__result-item" tabindex={open ? 0 : -1}><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
86
|
+
<a href="#" class="search__result-item" tabindex={open ? 0 : -1}><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
39
89
|
</div>
|
|
40
90
|
</div>
|
|
41
91
|
</div>
|
|
@@ -2,24 +2,51 @@
|
|
|
2
2
|
interface Props {
|
|
3
3
|
open?: boolean;
|
|
4
4
|
}
|
|
5
|
-
let { open
|
|
5
|
+
let { open: openProp }: Props = $props();
|
|
6
|
+
let openInternal = $state(false);
|
|
7
|
+
const open = $derived(openProp !== undefined ? openProp : openInternal);
|
|
8
|
+
|
|
9
|
+
$effect(() => {
|
|
10
|
+
(window as unknown as { openSettings?: () => void }).openSettings = () => {
|
|
11
|
+
openInternal = true;
|
|
12
|
+
};
|
|
13
|
+
return () => {
|
|
14
|
+
if ((window as unknown as { openSettings?: () => void }).openSettings) {
|
|
15
|
+
delete (window as unknown as { openSettings?: () => void }).openSettings;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
$effect(() => {
|
|
21
|
+
if (!open) return;
|
|
22
|
+
const onEscape = (e: KeyboardEvent) => {
|
|
23
|
+
if (e.key === 'Escape') openInternal = false;
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener('keydown', onEscape);
|
|
26
|
+
return () => document.removeEventListener('keydown', onEscape);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function close() {
|
|
30
|
+
openInternal = false;
|
|
31
|
+
}
|
|
6
32
|
</script>
|
|
7
33
|
|
|
8
34
|
<div class="settings" data-settings aria-hidden={!open}>
|
|
9
|
-
<div class="settings__overlay" data-settings-overlay aria-hidden={!open}></div>
|
|
35
|
+
<div class="settings__overlay" data-settings-overlay aria-hidden={!open} onclick={close}></div>
|
|
10
36
|
<div
|
|
11
37
|
class="settings__panel"
|
|
12
38
|
role="dialog"
|
|
13
39
|
aria-modal="true"
|
|
14
40
|
aria-labelledby="settings-title"
|
|
15
41
|
aria-hidden={!open}
|
|
42
|
+
data-open={open ? 'true' : undefined}
|
|
16
43
|
>
|
|
17
44
|
<div class="settings__header">
|
|
18
45
|
<h2 id="settings-title" class="settings__title">Settings</h2>
|
|
19
|
-
<button type="button" class="settings__close" data-settings-close aria-label="Close">×</button>
|
|
46
|
+
<button type="button" class="settings__close" data-settings-close aria-label="Close settings" onclick={close}>×</button>
|
|
20
47
|
</div>
|
|
21
48
|
<div class="settings__content">
|
|
22
|
-
<p>Theme, font size, and accessibility options. Wire to your state
|
|
49
|
+
<p>Theme, font size, and accessibility options. Wire to your state or use <code>window.openSettings</code>.</p>
|
|
23
50
|
</div>
|
|
24
51
|
</div>
|
|
25
52
|
</div>
|
|
@@ -13,7 +13,7 @@ If you prefer to load CSS from a CDN instead of the local file, replace the `<li
|
|
|
13
13
|
- `<link rel="stylesheet" href="https://unpkg.com/rizzo-css@latest/dist/rizzo.min.css" />`
|
|
14
14
|
- Or jsDelivr: `https://cdn.jsdelivr.net/npm/rizzo-css@latest/dist/rizzo.min.css`
|
|
15
15
|
|
|
16
|
-
(Replace `@latest` with a specific version, e.g. `@0.0.
|
|
16
|
+
(Replace `@latest` with a specific version, e.g. `@0.0.32`, in production.)
|
|
17
17
|
|
|
18
18
|
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.
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ The CLI replaces placeholders in `index.html` (e.g. `{{DATA_THEME}}`, `{{TITLE}}
|
|
|
22
22
|
- **Home** — `index.html` (hero, links to component showcase and docs). Edit the main content or add your own.
|
|
23
23
|
- **Component showcase** — `components/index.html` lists all components; `components/<name>.html` (e.g. `button.html`) each has a "Read the full docs" link to the main site. Edit or add HTML files; keep the same header/footer if you want the theme switcher and settings on every page.
|
|
24
24
|
- **CSS** — The CLI copies `css/rizzo.min.css`; the link uses `{{LINK_HREF}}` (replaced at init). To use a CDN, replace that with the CDN URL.
|
|
25
|
-
- **Scripts** — `js/main.js` provides theme sync, settings panel, toast, tabs, modal, dropdown, accordion, search (overlay),
|
|
25
|
+
- **Scripts** — `js/main.js` provides theme sync, settings panel, toast, tabs, modal, dropdown, accordion, search (overlay), navbar mobile menu, and copy-to-clipboard. Customize or extend as needed.
|
|
26
26
|
|
|
27
27
|
## What's included
|
|
28
28
|
|
|
@@ -37,6 +37,8 @@ The CLI replaces placeholders in `index.html` (e.g. `{{DATA_THEME}}`, `{{TITLE}}
|
|
|
37
37
|
- **Accordion** — Any `[data-accordion]` with `[data-accordion-trigger]` and panels; `data-allow-multiple="true"` for multiple open.
|
|
38
38
|
- **Search** — Any `[data-search]` with `.search__trigger`, `[data-search-overlay]`, `.search__panel`, and `.search__input`; trigger toggles overlay, Escape or overlay click closes.
|
|
39
39
|
- **Navbar** — Mobile menu: `.navbar__toggle` toggles `.navbar__menu`; Escape closes.
|
|
40
|
+
- **Copy to clipboard** — Buttons with `.copy-to-clipboard` and `data-copy-value`, or `[data-copy]` with `value` or `data-copy-value`; click copies text and shows feedback (icons/aria-label). Optional `data-copy-format` for “Copied {format}!”.
|
|
41
|
+
- **Tooltips** — Use `.tooltip-wrapper` with a `.tooltip` child, or `[data-tooltip]` on the trigger; no JS required (CSS :hover and :focus-within).
|
|
40
42
|
|
|
41
43
|
## Commands
|
|
42
44
|
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
<div class="search__overlay" id="search-header-panel" aria-hidden="true" role="dialog" aria-modal="true" data-search-overlay>
|
|
61
61
|
<div class="search__panel">
|
|
62
62
|
<input type="search" class="search__input" placeholder="Search…" aria-label="Search" />
|
|
63
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
64
|
+
<div class="search__empty">
|
|
65
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
68
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
69
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
70
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
63
73
|
</div>
|
|
64
74
|
</div>
|
|
65
75
|
</div>
|
|
@@ -241,6 +251,13 @@
|
|
|
241
251
|
|
|
242
252
|
|
|
243
253
|
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
244
261
|
|
|
245
262
|
|
|
246
263
|
|
|
@@ -320,6 +337,13 @@
|
|
|
320
337
|
|
|
321
338
|
|
|
322
339
|
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
323
347
|
|
|
324
348
|
|
|
325
349
|
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
<div class="search__overlay" id="search-header-panel" aria-hidden="true" role="dialog" aria-modal="true" data-search-overlay>
|
|
61
61
|
<div class="search__panel">
|
|
62
62
|
<input type="search" class="search__input" placeholder="Search…" aria-label="Search" />
|
|
63
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
64
|
+
<div class="search__empty">
|
|
65
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
68
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
69
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
70
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
63
73
|
</div>
|
|
64
74
|
</div>
|
|
65
75
|
</div>
|
|
@@ -241,6 +251,13 @@
|
|
|
241
251
|
|
|
242
252
|
|
|
243
253
|
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
244
261
|
|
|
245
262
|
|
|
246
263
|
|
|
@@ -320,6 +337,13 @@
|
|
|
320
337
|
|
|
321
338
|
|
|
322
339
|
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
323
347
|
|
|
324
348
|
|
|
325
349
|
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
<div class="search__overlay" id="search-header-panel" aria-hidden="true" role="dialog" aria-modal="true" data-search-overlay>
|
|
61
61
|
<div class="search__panel">
|
|
62
62
|
<input type="search" class="search__input" placeholder="Search…" aria-label="Search" />
|
|
63
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
64
|
+
<div class="search__empty">
|
|
65
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
68
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
69
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
70
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
63
73
|
</div>
|
|
64
74
|
</div>
|
|
65
75
|
</div>
|
|
@@ -241,6 +251,13 @@
|
|
|
241
251
|
|
|
242
252
|
|
|
243
253
|
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
244
261
|
|
|
245
262
|
|
|
246
263
|
|
|
@@ -320,6 +337,13 @@
|
|
|
320
337
|
|
|
321
338
|
|
|
322
339
|
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
323
347
|
|
|
324
348
|
|
|
325
349
|
|
|
@@ -60,6 +60,16 @@
|
|
|
60
60
|
<div class="search__overlay" id="search-header-panel" aria-hidden="true" role="dialog" aria-modal="true" data-search-overlay>
|
|
61
61
|
<div class="search__panel">
|
|
62
62
|
<input type="search" class="search__input" placeholder="Search…" aria-label="Search" />
|
|
63
|
+
<div class="search__results" role="listbox" aria-label="Search results">
|
|
64
|
+
<div class="search__empty">
|
|
65
|
+
<p class="search__empty-text">Start typing to search…</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="search__results-list" role="group" aria-label="Sample results">
|
|
68
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Getting started</div></a>
|
|
69
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Components</div></a>
|
|
70
|
+
<a href="#" class="search__result-item" tabindex="-1"><div class="search__result-category">Docs</div><div class="search__result-title">Theming</div></a>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
63
73
|
</div>
|
|
64
74
|
</div>
|
|
65
75
|
</div>
|
|
@@ -241,6 +251,13 @@
|
|
|
241
251
|
|
|
242
252
|
|
|
243
253
|
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
244
261
|
|
|
245
262
|
|
|
246
263
|
|
|
@@ -320,6 +337,13 @@
|
|
|
320
337
|
|
|
321
338
|
|
|
322
339
|
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
323
347
|
|
|
324
348
|
|
|
325
349
|
|