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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/bin/rizzo-css.js +11 -10
  3. package/package.json +1 -1
  4. package/scaffold/astro/Navbar.astro +43 -2
  5. package/scaffold/astro/Search.astro +78 -0
  6. package/scaffold/svelte/Navbar.svelte +19 -0
  7. package/scaffold/svelte/Search.svelte +51 -1
  8. package/scaffold/svelte/Settings.svelte +31 -4
  9. package/scaffold/vanilla/README-RIZZO.md +4 -2
  10. package/scaffold/vanilla/components/accordion.html +24 -0
  11. package/scaffold/vanilla/components/alert.html +24 -0
  12. package/scaffold/vanilla/components/avatar.html +24 -0
  13. package/scaffold/vanilla/components/badge.html +24 -0
  14. package/scaffold/vanilla/components/breadcrumb.html +24 -0
  15. package/scaffold/vanilla/components/button.html +24 -0
  16. package/scaffold/vanilla/components/cards.html +24 -0
  17. package/scaffold/vanilla/components/copy-to-clipboard.html +24 -0
  18. package/scaffold/vanilla/components/divider.html +24 -0
  19. package/scaffold/vanilla/components/dropdown.html +24 -0
  20. package/scaffold/vanilla/components/forms.html +24 -0
  21. package/scaffold/vanilla/components/icons.html +24 -0
  22. package/scaffold/vanilla/components/index.html +24 -0
  23. package/scaffold/vanilla/components/modal.html +24 -0
  24. package/scaffold/vanilla/components/navbar.html +24 -0
  25. package/scaffold/vanilla/components/pagination.html +24 -0
  26. package/scaffold/vanilla/components/progress-bar.html +24 -0
  27. package/scaffold/vanilla/components/search.html +24 -0
  28. package/scaffold/vanilla/components/settings.html +24 -0
  29. package/scaffold/vanilla/components/spinner.html +24 -0
  30. package/scaffold/vanilla/components/table.html +24 -0
  31. package/scaffold/vanilla/components/tabs.html +24 -0
  32. package/scaffold/vanilla/components/theme-switcher.html +24 -0
  33. package/scaffold/vanilla/components/toast.html +24 -0
  34. package/scaffold/vanilla/components/tooltip.html +24 -0
  35. package/scaffold/vanilla/index.html +24 -0
  36. 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.30 or any version) -->
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 (modal, dropdown, tabs, toast, search, navbar mobile, theme switcher).
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 mobile, theme switcher? (Y/n) ');
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 mobile, theme switcher)');
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rizzo-css",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "scripts": {
5
5
  "prepublishOnly": "cd ../.. && pnpm run lint:css:fix && pnpm run build:css && node scripts/copy-scaffold.js && node scripts/prepare-vanilla-scaffold.js"
6
6
  },
@@ -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 = false }: Props = $props();
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 and <code>window.openSettings</code>.</p>
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.30`, in production.)
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), and navbar mobile menu. Customize or extend as needed.
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