rizzo-css 0.0.1 → 0.0.2

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 (89) hide show
  1. package/README.md +13 -7
  2. package/bin/rizzo-css.js +303 -0
  3. package/package.json +12 -4
  4. package/scaffold/astro/Accordion.astro +178 -0
  5. package/scaffold/astro/Alert.astro +131 -0
  6. package/scaffold/astro/Avatar.astro +59 -0
  7. package/scaffold/astro/Badge.astro +24 -0
  8. package/scaffold/astro/Breadcrumb.astro +61 -0
  9. package/scaffold/astro/Button.astro +3 -0
  10. package/scaffold/astro/Card.astro +18 -0
  11. package/scaffold/astro/Checkbox.astro +38 -0
  12. package/scaffold/astro/CopyToClipboard.astro +199 -0
  13. package/scaffold/astro/Divider.astro +37 -0
  14. package/scaffold/astro/Dropdown.astro +807 -0
  15. package/scaffold/astro/FormGroup.astro +51 -0
  16. package/scaffold/astro/Input.astro +59 -0
  17. package/scaffold/astro/Modal.astro +212 -0
  18. package/scaffold/astro/Pagination.astro +240 -0
  19. package/scaffold/astro/ProgressBar.astro +65 -0
  20. package/scaffold/astro/Radio.astro +38 -0
  21. package/scaffold/astro/Select.astro +49 -0
  22. package/scaffold/astro/Spinner.astro +30 -0
  23. package/scaffold/astro/Table.astro +181 -0
  24. package/scaffold/astro/Tabs.astro +223 -0
  25. package/scaffold/astro/Textarea.astro +58 -0
  26. package/scaffold/astro/Toast.astro +30 -0
  27. package/scaffold/astro/Tooltip.astro +32 -0
  28. package/scaffold/astro/icons/Brush.astro +11 -0
  29. package/scaffold/astro/icons/Cake.astro +12 -0
  30. package/scaffold/astro/icons/Check.astro +30 -0
  31. package/scaffold/astro/icons/Cherry.astro +12 -0
  32. package/scaffold/astro/icons/ChevronDown.astro +30 -0
  33. package/scaffold/astro/icons/Circle.astro +30 -0
  34. package/scaffold/astro/icons/Close.astro +31 -0
  35. package/scaffold/astro/icons/Copy.astro +31 -0
  36. package/scaffold/astro/icons/Eye.astro +31 -0
  37. package/scaffold/astro/icons/Filter.astro +30 -0
  38. package/scaffold/astro/icons/Flame.astro +29 -0
  39. package/scaffold/astro/icons/Flower.astro +12 -0
  40. package/scaffold/astro/icons/Gear.astro +31 -0
  41. package/scaffold/astro/icons/Heart.astro +29 -0
  42. package/scaffold/astro/icons/IceCream.astro +32 -0
  43. package/scaffold/astro/icons/Leaf.astro +30 -0
  44. package/scaffold/astro/icons/Lemon.astro +12 -0
  45. package/scaffold/astro/icons/Moon.astro +30 -0
  46. package/scaffold/astro/icons/Owl.astro +35 -0
  47. package/scaffold/astro/icons/Palette.astro +34 -0
  48. package/scaffold/astro/icons/Rainbow.astro +32 -0
  49. package/scaffold/astro/icons/Search.astro +31 -0
  50. package/scaffold/astro/icons/Shield.astro +29 -0
  51. package/scaffold/astro/icons/Snowflake.astro +35 -0
  52. package/scaffold/astro/icons/Sort.astro +31 -0
  53. package/scaffold/astro/icons/Sun.astro +30 -0
  54. package/scaffold/astro/icons/Sunset.astro +11 -0
  55. package/scaffold/astro/icons/Zap.astro +10 -0
  56. package/scaffold/astro/icons/devicons/Astro.astro +54 -0
  57. package/scaffold/astro/icons/devicons/Bash.astro +35 -0
  58. package/scaffold/astro/icons/devicons/Css3.astro +30 -0
  59. package/scaffold/astro/icons/devicons/Git.astro +25 -0
  60. package/scaffold/astro/icons/devicons/Html5.astro +28 -0
  61. package/scaffold/astro/icons/devicons/Javascript.astro +26 -0
  62. package/scaffold/astro/icons/devicons/Nodejs.astro +48 -0
  63. package/scaffold/astro/icons/devicons/Plaintext.astro +34 -0
  64. package/scaffold/svelte/.gitkeep +0 -0
  65. package/scaffold/svelte/Accordion.svelte +128 -0
  66. package/scaffold/svelte/Alert.svelte +79 -0
  67. package/scaffold/svelte/Avatar.svelte +39 -0
  68. package/scaffold/svelte/Badge.svelte +31 -0
  69. package/scaffold/svelte/Breadcrumb.svelte +46 -0
  70. package/scaffold/svelte/Button.svelte +23 -0
  71. package/scaffold/svelte/Card.svelte +14 -0
  72. package/scaffold/svelte/Checkbox.svelte +37 -0
  73. package/scaffold/svelte/CopyToClipboard.svelte +76 -0
  74. package/scaffold/svelte/Divider.svelte +28 -0
  75. package/scaffold/svelte/Dropdown.svelte +237 -0
  76. package/scaffold/svelte/FormGroup.svelte +41 -0
  77. package/scaffold/svelte/Input.svelte +57 -0
  78. package/scaffold/svelte/Modal.svelte +152 -0
  79. package/scaffold/svelte/Pagination.svelte +93 -0
  80. package/scaffold/svelte/ProgressBar.svelte +56 -0
  81. package/scaffold/svelte/Radio.svelte +38 -0
  82. package/scaffold/svelte/Select.svelte +47 -0
  83. package/scaffold/svelte/Spinner.svelte +14 -0
  84. package/scaffold/svelte/Table.svelte +155 -0
  85. package/scaffold/svelte/Tabs.svelte +109 -0
  86. package/scaffold/svelte/Textarea.svelte +57 -0
  87. package/scaffold/svelte/Toast.svelte +30 -0
  88. package/scaffold/svelte/Tooltip.svelte +19 -0
  89. package/scaffold/svelte/index.ts +33 -0
package/README.md CHANGED
@@ -12,23 +12,29 @@ pnpm add rizzo-css
12
12
  yarn add rizzo-css
13
13
  ```
14
14
 
15
+ **Quick start (no install):** `npx rizzo-css init` scaffolds a project (prompts for framework and optional Astro/Svelte components). `npx rizzo-css add` copies the CSS into the current project. `npx rizzo-css theme` lists themes.
16
+
15
17
  ## Use
16
18
 
17
- Import the built CSS once in your app (e.g. root layout or main entry):
19
+ Import or link the CSS **once** in your app (e.g. root layout or main entry).
18
20
 
19
- **In a bundler (Vite, Astro, etc.):**
21
+ **With a bundler (Vite, Astro, webpack, etc.):**
20
22
 
21
23
  ```js
22
24
  import 'rizzo-css';
23
25
  ```
24
26
 
25
- **In HTML:**
27
+ **Without a bundler (plain HTML):** Use a CDN (unpkg and jsDelivr both serve the package automatically):
26
28
 
27
29
  ```html
28
- <link rel="stylesheet" href="node_modules/rizzo-css/dist/rizzo.min.css" />
30
+ <!-- unpkg -->
31
+ <link rel="stylesheet" href="https://unpkg.com/rizzo-css@latest" />
32
+
33
+ <!-- or jsDelivr -->
34
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/rizzo-css@latest" />
29
35
  ```
30
36
 
31
- Then use the same class names and HTML structure as the [component docs](https://github.com/mingleusa/rizzo-css/tree/main/docs#components). For Astro or Svelte, you can copy the reference components from the [repo](https://github.com/mingleusa/rizzo-css).
37
+ Use the same class names and HTML structure as in the [component docs](https://rizzo-css.vercel.app/docs/components). For Astro or Svelte, reference components and examples are in the [documentation](https://rizzo-css.vercel.app/docs/getting-started).
32
38
 
33
39
  ## Themes
34
40
 
@@ -38,11 +44,11 @@ Set the theme via `data-theme` on `<html>`:
38
44
  <html lang="en" data-theme="github-dark-classic">
39
45
  ```
40
46
 
41
- Theme IDs and full docs: [Theming](https://github.com/mingleusa/rizzo-css/blob/main/docs/THEMING.md).
47
+ Theme IDs and full docs: [Theming](https://rizzo-css.vercel.app/docs/theming).
42
48
 
43
49
  ## Docs
44
50
 
45
- Full documentation (components, theming, usage in Astro/Svelte): **[Getting Started](https://github.com/mingleusa/rizzo-css/blob/main/docs/GETTING_STARTED.md#using-rizzo-in-your-project)** in the repo.
51
+ Full documentation: **[rizzo-css.vercel.app](https://rizzo-css.vercel.app)** Getting Started, Components, Themes, and usage for Astro & Svelte.
46
52
 
47
53
  ## License
48
54
 
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { copyFileSync, mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } = require('fs');
4
+ const { join, dirname } = require('path');
5
+ const readline = require('readline');
6
+
7
+ const COMMANDS = ['init', 'add', 'theme', 'help'];
8
+ const FRAMEWORKS = ['vanilla', 'astro', 'svelte'];
9
+ const THEMES = [
10
+ 'github-dark-classic',
11
+ 'github-light',
12
+ 'shades-of-purple',
13
+ 'sandstorm-classic',
14
+ 'rocky-blood-orange',
15
+ 'minimal-dark-neon-yellow',
16
+ 'hack-the-box',
17
+ 'pink-cat-boo',
18
+ 'red-velvet-cupcake',
19
+ 'orangy-one-light',
20
+ 'sunflower',
21
+ 'green-breeze-light',
22
+ 'cute-pink',
23
+ 'semi-light-purple',
24
+ ];
25
+ // Components available for scaffold (must match scaffold/svelte and scaffold/astro)
26
+ const SVELTE_COMPONENTS = [
27
+ 'Button', 'Badge', 'Card', 'Divider', 'Spinner', 'ProgressBar', 'Avatar', 'Alert',
28
+ 'Breadcrumb', 'FormGroup', 'Input', 'Checkbox', 'Textarea', 'Select', 'Radio',
29
+ 'CopyToClipboard', 'Tooltip', 'Pagination', 'Tabs', 'Accordion', 'Dropdown',
30
+ 'Modal', 'Toast', 'Table',
31
+ ];
32
+ const ASTRO_COMPONENTS = [
33
+ 'Button', 'Badge', 'Card', 'Divider', 'Spinner', 'ProgressBar', 'Avatar', 'Alert',
34
+ 'Breadcrumb', 'FormGroup', 'Input', 'Checkbox', 'Textarea', 'Select', 'Radio',
35
+ 'CopyToClipboard', 'Tooltip', 'Pagination', 'Tabs', 'Accordion', 'Dropdown',
36
+ 'Modal', 'Toast', 'Table',
37
+ ];
38
+
39
+ // Resolve path to this package (works when run via npx or from repo)
40
+ function getPackageRoot() {
41
+ return dirname(require.resolve('../package.json'));
42
+ }
43
+
44
+ function getCssPath() {
45
+ return join(getPackageRoot(), 'dist', 'rizzo.min.css');
46
+ }
47
+
48
+ function question(prompt) {
49
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
50
+ return new Promise((resolve) => {
51
+ rl.question(prompt, (answer) => {
52
+ rl.close();
53
+ resolve((answer || '').trim());
54
+ });
55
+ });
56
+ }
57
+
58
+ function printHelp() {
59
+ console.log(`
60
+ rizzo-css CLI — Add Rizzo CSS to your project
61
+
62
+ Usage:
63
+ npx rizzo-css <command> [options]
64
+
65
+ Commands:
66
+ init Scaffold a new project (prompts: name, framework, theme; Astro/Svelte: optional component picker)
67
+ add Copy Rizzo CSS into the current project
68
+ theme List available themes
69
+ help Show this help
70
+
71
+ Examples:
72
+ npx rizzo-css init
73
+ npx rizzo-css add
74
+ npx rizzo-css add --path public/css
75
+ npx rizzo-css theme
76
+
77
+ Docs: https://rizzo-css.vercel.app
78
+ `);
79
+ }
80
+
81
+ function cmdTheme() {
82
+ console.log('\nAvailable themes (set data-theme on <html>):\n');
83
+ THEMES.forEach((t) => console.log(' ' + t));
84
+ console.log('\nExample: <html lang="en" data-theme="github-dark-classic">\n');
85
+ }
86
+
87
+ function cmdAdd(argv) {
88
+ const pathIdx = argv.indexOf('--path');
89
+ const customPath = pathIdx !== -1 && argv[pathIdx + 1] ? argv[pathIdx + 1] : null;
90
+ const targetDir = customPath || 'css';
91
+ const targetFile = join(process.cwd(), targetDir, 'rizzo.min.css');
92
+ const cssSource = getCssPath();
93
+
94
+ if (!existsSync(cssSource)) {
95
+ console.error('Error: Rizzo CSS build not found. Run from repo root: pnpm build:css');
96
+ process.exit(1);
97
+ }
98
+
99
+ mkdirSync(join(process.cwd(), targetDir), { recursive: true });
100
+ copyFileSync(cssSource, targetFile);
101
+ const linkPath = targetDir + '/rizzo.min.css';
102
+ console.log('\n✓ Rizzo CSS copied to ' + targetFile);
103
+ console.log('\nAdd to your HTML or layout:\n');
104
+ console.log(' <link rel="stylesheet" href="' + linkPath + '" />');
105
+ console.log('\nSet a theme on <html>: data-theme="github-dark-classic" (see: npx rizzo-css theme)\n');
106
+ }
107
+
108
+ function parseComponentSelection(input, componentList, maxNum) {
109
+ const s = (input || '').trim().toLowerCase();
110
+ if (s === 'none' || s === 'n') return [];
111
+ if (s === 'all' || s === 'a') return componentList.slice();
112
+ const parts = s.split(/[\s,]+/).filter(Boolean);
113
+ const indices = new Set();
114
+ for (const p of parts) {
115
+ const n = parseInt(p, 10);
116
+ if (n >= 1 && n <= maxNum) indices.add(n - 1);
117
+ }
118
+ return indices.size === 0 ? [] : Array.from(indices).sort((a, b) => a - b).map((i) => componentList[i]);
119
+ }
120
+
121
+ function getScaffoldSvelteDir() {
122
+ return join(getPackageRoot(), 'scaffold', 'svelte');
123
+ }
124
+
125
+ function getScaffoldAstroDir() {
126
+ return join(getPackageRoot(), 'scaffold', 'astro');
127
+ }
128
+
129
+ function copyDirRecursive(src, dest) {
130
+ mkdirSync(dest, { recursive: true });
131
+ const entries = readdirSync(src, { withFileTypes: true });
132
+ for (const e of entries) {
133
+ const srcPath = join(src, e.name);
134
+ const destPath = join(dest, e.name);
135
+ if (e.isDirectory()) {
136
+ copyDirRecursive(srcPath, destPath);
137
+ } else {
138
+ copyFileSync(srcPath, destPath);
139
+ }
140
+ }
141
+ }
142
+
143
+ function copySvelteComponents(projectDir, selectedNames) {
144
+ const scaffoldDir = getScaffoldSvelteDir();
145
+ if (!existsSync(scaffoldDir)) {
146
+ console.log('\n Component templates not in this package; use CSS only or copy from repo: https://github.com/mingleusa/rizzo-css/tree/main/src/components/svelte');
147
+ return;
148
+ }
149
+ const files = readdirSync(scaffoldDir);
150
+ const available = files.filter((f) => f.endsWith('.svelte')).map((f) => f.replace('.svelte', ''));
151
+ const toCopy = selectedNames.filter((n) => available.includes(n));
152
+ if (toCopy.length === 0) {
153
+ console.log('\n No matching component files in scaffold; use CSS only or copy from repo.');
154
+ return;
155
+ }
156
+ const targetDir = join(projectDir, 'src', 'lib', 'rizzo');
157
+ mkdirSync(targetDir, { recursive: true });
158
+ const exports = [];
159
+ for (const name of toCopy) {
160
+ const src = join(scaffoldDir, name + '.svelte');
161
+ if (existsSync(src)) {
162
+ copyFileSync(src, join(targetDir, name + '.svelte'));
163
+ exports.push(`export { default as ${name} } from './${name}.svelte';`);
164
+ }
165
+ }
166
+ if (exports.length > 0) {
167
+ const indexContent = `/** Rizzo CSS Svelte components — selected via npx rizzo-css init */\n${exports.join('\n')}\n`;
168
+ writeFileSync(join(targetDir, 'index.ts'), indexContent, 'utf8');
169
+ console.log('\n ✓ ' + exports.length + ' Svelte components copied to ' + targetDir);
170
+ console.log(' Import in your app: import { Button, Badge, ... } from \'$lib/rizzo\';\n');
171
+ }
172
+ }
173
+
174
+ function copyAstroComponents(projectDir, selectedNames) {
175
+ const scaffoldDir = getScaffoldAstroDir();
176
+ if (!existsSync(scaffoldDir)) {
177
+ console.log('\n Astro component templates not in this package; use CSS only or copy from repo: https://github.com/mingleusa/rizzo-css/tree/main/src/components');
178
+ return;
179
+ }
180
+ const files = readdirSync(scaffoldDir).filter((f) => f.endsWith('.astro'));
181
+ const available = files.map((f) => f.replace('.astro', ''));
182
+ const toCopy = selectedNames.filter((n) => available.includes(n));
183
+ if (toCopy.length === 0) {
184
+ console.log('\n No matching Astro components in scaffold; use CSS only or copy from repo.');
185
+ return;
186
+ }
187
+ const targetDir = join(projectDir, 'src', 'components', 'rizzo');
188
+ mkdirSync(targetDir, { recursive: true });
189
+ let count = 0;
190
+ for (const name of toCopy) {
191
+ const src = join(scaffoldDir, name + '.astro');
192
+ if (existsSync(src)) {
193
+ copyFileSync(src, join(targetDir, name + '.astro'));
194
+ count++;
195
+ }
196
+ }
197
+ const iconsSrc = join(scaffoldDir, 'icons');
198
+ if (existsSync(iconsSrc)) {
199
+ copyDirRecursive(iconsSrc, join(targetDir, 'icons'));
200
+ }
201
+ if (count > 0) {
202
+ console.log('\n ✓ ' + count + ' Astro components + icons copied to ' + targetDir);
203
+ console.log(' Import in your pages: import Button from \'../components/rizzo/Button.astro\';\n');
204
+ }
205
+ }
206
+
207
+ async function cmdInit() {
208
+ const name = await question('Project name (folder name, or leave blank for current directory): ');
209
+ let framework = await question('Framework: vanilla, astro, svelte (default: vanilla): ') || 'vanilla';
210
+ framework = framework.toLowerCase();
211
+ if (!FRAMEWORKS.includes(framework)) framework = 'vanilla';
212
+
213
+ const theme = await question('Theme (default: github-dark-classic, or run "npx rizzo-css theme" for list): ') || 'github-dark-classic';
214
+
215
+ let selectedComponents = [];
216
+ const componentList = framework === 'svelte' ? SVELTE_COMPONENTS : framework === 'astro' ? ASTRO_COMPONENTS : [];
217
+ if (componentList.length > 0) {
218
+ const label = framework === 'svelte' ? 'Svelte' : 'Astro';
219
+ const include = await question('Include ' + label + ' components? (y/n, default: n): ') || 'n';
220
+ if (include.toLowerCase() === 'y' || include.toLowerCase() === 'yes') {
221
+ console.log('\nComponents (enter numbers to include, e.g. 1 3 5, or "all" / "none"):');
222
+ componentList.forEach((c, i) => console.log(' ' + (i + 1) + '. ' + c));
223
+ const choice = await question('\nSelection (e.g. 1 2 3 or all): ');
224
+ selectedComponents = parseComponentSelection(choice, componentList, componentList.length);
225
+ }
226
+ }
227
+
228
+ const projectDir = name ? join(process.cwd(), name) : process.cwd();
229
+ const cssDir = framework === 'astro' ? join(projectDir, 'public', 'css') : join(projectDir, 'css');
230
+ const cssTarget = join(cssDir, 'rizzo.min.css');
231
+ const cssSource = getCssPath();
232
+
233
+ if (!existsSync(cssSource)) {
234
+ console.error('Error: Rizzo CSS build not found. Run from repo root: pnpm build:css');
235
+ process.exit(1);
236
+ }
237
+
238
+ mkdirSync(cssDir, { recursive: true });
239
+ copyFileSync(cssSource, cssTarget);
240
+
241
+ const linkHref = framework === 'astro' ? '/css/rizzo.min.css' : 'css/rizzo.min.css';
242
+ const indexHtml = `<!DOCTYPE html>
243
+ <html lang="en" data-theme="${theme}">
244
+ <head>
245
+ <meta charset="UTF-8" />
246
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
247
+ <title>${name || 'App'}</title>
248
+ <link rel="stylesheet" href="${linkHref}" />
249
+ </head>
250
+ <body>
251
+ <h1>Hello, Rizzo CSS</h1>
252
+ <p>Edit this file and add components. Docs: <a href="https://rizzo-css.vercel.app">rizzo-css.vercel.app</a></p>
253
+ </body>
254
+ </html>
255
+ `;
256
+
257
+ const indexPath = join(projectDir, 'index.html');
258
+ writeFileSync(indexPath, indexHtml, 'utf8');
259
+
260
+ if (framework === 'svelte' && selectedComponents.length > 0) {
261
+ copySvelteComponents(projectDir, selectedComponents);
262
+ } else if (framework === 'astro' && selectedComponents.length > 0) {
263
+ copyAstroComponents(projectDir, selectedComponents);
264
+ }
265
+
266
+ console.log('\n✓ Project ready at ' + projectDir);
267
+ console.log(' - ' + cssTarget);
268
+ console.log(' - ' + indexPath);
269
+ console.log('\nRun a local server (e.g. npx serve .) or open index.html. Docs: https://rizzo-css.vercel.app\n');
270
+ }
271
+
272
+ function main() {
273
+ const argv = process.argv.slice(2);
274
+ const command = (argv[0] || 'help').toLowerCase().replace(/^--?/, '');
275
+
276
+ if (command === 'help' || command === 'h' || !COMMANDS.includes(command)) {
277
+ if (argv[0] && !COMMANDS.includes(command) && command !== 'h') {
278
+ console.error('Unknown command: ' + argv[0] + '\n');
279
+ }
280
+ printHelp();
281
+ return;
282
+ }
283
+
284
+ if (command === 'theme') {
285
+ cmdTheme();
286
+ return;
287
+ }
288
+
289
+ if (command === 'add') {
290
+ cmdAdd(argv);
291
+ return;
292
+ }
293
+
294
+ if (command === 'init') {
295
+ cmdInit().catch((err) => {
296
+ console.error(err);
297
+ process.exit(1);
298
+ });
299
+ return;
300
+ }
301
+ }
302
+
303
+ main();
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "rizzo-css",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "scripts": {
5
- "prepublishOnly": "cd ../.. && pnpm build:css"
5
+ "prepublishOnly": "cd ../.. && pnpm build:css && node scripts/copy-scaffold.js"
6
6
  },
7
7
  "description": "A modern CSS design system with semantic theming, 14 themes, and accessible components (BEM). Use with Astro, Svelte, or any framework.",
8
8
  "style": "dist/rizzo.min.css",
9
9
  "main": "dist/rizzo.min.css",
10
+ "unpkg": "dist/rizzo.min.css",
11
+ "jsdelivr": "dist/rizzo.min.css",
10
12
  "exports": {
11
13
  ".": {
12
14
  "style": "./dist/rizzo.min.css",
@@ -14,9 +16,14 @@
14
16
  "require": "./dist/rizzo.min.css"
15
17
  }
16
18
  },
19
+ "bin": {
20
+ "rizzo-css": "./bin/rizzo-css.js"
21
+ },
17
22
  "files": [
18
23
  "dist",
19
- "README.md"
24
+ "README.md",
25
+ "bin",
26
+ "scaffold"
20
27
  ],
21
28
  "keywords": [
22
29
  "css",
@@ -31,5 +38,6 @@
31
38
  "type": "git",
32
39
  "url": "https://github.com/mingleusa/rizzo-css.git"
33
40
  },
34
- "license": "MIT"
41
+ "license": "MIT",
42
+ "author": "mingleusa"
35
43
  }
@@ -0,0 +1,178 @@
1
+ ---
2
+ import ChevronDown from './icons/ChevronDown.astro';
3
+
4
+ interface AccordionItem {
5
+ id: string;
6
+ title: string;
7
+ content?: string;
8
+ }
9
+
10
+ interface Props {
11
+ items: AccordionItem[];
12
+ id?: string;
13
+ allowMultiple?: boolean;
14
+ defaultExpanded?: string | string[];
15
+ class?: string;
16
+ }
17
+
18
+ const {
19
+ items,
20
+ id,
21
+ allowMultiple = false,
22
+ defaultExpanded,
23
+ class: className = '',
24
+ } = Astro.props;
25
+
26
+ const accordionId = id || `accordion-${Math.random().toString(36).substr(2, 9)}`;
27
+
28
+ const getDefaultExpanded = (): Set<string> => {
29
+ if (defaultExpanded === undefined) return new Set(items[0] ? [items[0].id] : []);
30
+ if (typeof defaultExpanded === 'string') return new Set([defaultExpanded]);
31
+ return new Set(defaultExpanded);
32
+ };
33
+
34
+ const defaultOpenIds = getDefaultExpanded();
35
+ const classes = `accordion ${className}`.trim();
36
+ let slotIndex = 0;
37
+ ---
38
+
39
+ <div class={classes} data-accordion={accordionId} data-allow-multiple={allowMultiple ? 'true' : 'false'}>
40
+ {items.map((item) => {
41
+ const triggerId = `${accordionId}-trigger-${item.id}`;
42
+ const panelId = `${accordionId}-panel-${item.id}`;
43
+ const isExpanded = defaultOpenIds.has(item.id);
44
+ const useSlot = !item.content;
45
+ const currentSlotIndex = useSlot ? slotIndex++ : -1;
46
+
47
+ return (
48
+ <div class="accordion__item" data-accordion-item data-item-id={item.id}>
49
+ <h3 class="accordion__heading">
50
+ <button
51
+ type="button"
52
+ class={`accordion__trigger ${isExpanded ? 'accordion__trigger--expanded' : ''}`}
53
+ id={triggerId}
54
+ aria-expanded={isExpanded}
55
+ aria-controls={panelId}
56
+ data-accordion-trigger
57
+ >
58
+ <span class="accordion__title">{item.title}</span>
59
+ <ChevronDown class="accordion__icon" width={16} height={16} aria-hidden="true" />
60
+ </button>
61
+ </h3>
62
+ <div
63
+ class={`accordion__panel ${isExpanded ? 'accordion__panel--expanded' : ''}`}
64
+ id={panelId}
65
+ role="region"
66
+ aria-labelledby={triggerId}
67
+ hidden={!isExpanded}
68
+ data-accordion-panel
69
+ >
70
+ <div class="accordion__panel-inner">
71
+ {item.content ? (
72
+ <div class="accordion__panel-content" set:html={item.content} />
73
+ ) : (
74
+ <div class="accordion__panel-content accordion__panel-slot" data-accordion-slot-index={currentSlotIndex}>
75
+ <!-- Slot content distributed by script -->
76
+ </div>
77
+ )}
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ })}
83
+ <!-- Slot content for distribution (same order as items) -->
84
+ <div class="accordion__slot-content" style="display: none;">
85
+ <slot />
86
+ </div>
87
+ </div>
88
+
89
+ <script is:inline>
90
+ (function initAccordions() {
91
+ function initOne(accordion) {
92
+ if (accordion.dataset.accordionInit === 'true') return;
93
+ accordion.dataset.accordionInit = 'true';
94
+
95
+ const isMultiple = accordion.getAttribute('data-allow-multiple') === 'true';
96
+ const triggers = accordion.querySelectorAll('[data-accordion-trigger]');
97
+
98
+ const setExpanded = (trigger, expanded) => {
99
+ const panelId = trigger.getAttribute('aria-controls');
100
+ const panel = panelId ? accordion.querySelector('#' + CSS.escape(panelId)) : null;
101
+ trigger.setAttribute('aria-expanded', String(expanded));
102
+ trigger.classList.toggle('accordion__trigger--expanded', expanded);
103
+ if (panel) {
104
+ panel.classList.toggle('accordion__panel--expanded', expanded);
105
+ panel.hidden = !expanded;
106
+ }
107
+ };
108
+
109
+ const toggle = (trigger) => {
110
+ const expanded = trigger.getAttribute('aria-expanded') === 'true';
111
+ if (!isMultiple) {
112
+ triggers.forEach((t) => setExpanded(t, false));
113
+ }
114
+ setExpanded(trigger, !expanded);
115
+ };
116
+
117
+ triggers.forEach((trigger) => {
118
+ trigger.addEventListener('click', () => toggle(trigger));
119
+ });
120
+
121
+ triggers.forEach((trigger, index) => {
122
+ trigger.addEventListener('keydown', (e) => {
123
+ let targetIndex = index;
124
+ switch (e.key) {
125
+ case 'ArrowDown':
126
+ e.preventDefault();
127
+ targetIndex = Math.min(index + 1, triggers.length - 1);
128
+ break;
129
+ case 'ArrowUp':
130
+ e.preventDefault();
131
+ targetIndex = Math.max(index - 1, 0);
132
+ break;
133
+ case 'Home':
134
+ e.preventDefault();
135
+ targetIndex = 0;
136
+ break;
137
+ case 'End':
138
+ e.preventDefault();
139
+ targetIndex = triggers.length - 1;
140
+ break;
141
+ case 'Enter':
142
+ case ' ':
143
+ e.preventDefault();
144
+ toggle(trigger);
145
+ return;
146
+ default:
147
+ return;
148
+ }
149
+ if (targetIndex !== index) {
150
+ triggers[targetIndex].focus();
151
+ }
152
+ });
153
+ });
154
+
155
+ const slotContent = accordion.querySelector('.accordion__slot-content');
156
+ if (slotContent) {
157
+ const slotChildren = Array.from(slotContent.children);
158
+ slotChildren.forEach((child, index) => {
159
+ const slotPlaceholder = accordion.querySelector('[data-accordion-slot-index="' + index + '"]');
160
+ if (slotPlaceholder) {
161
+ slotPlaceholder.appendChild(child);
162
+ }
163
+ });
164
+ slotContent.remove();
165
+ }
166
+ }
167
+
168
+ function init() {
169
+ document.querySelectorAll('[data-accordion]').forEach(initOne);
170
+ }
171
+
172
+ if (document.readyState === 'loading') {
173
+ document.addEventListener('DOMContentLoaded', init);
174
+ } else {
175
+ init();
176
+ }
177
+ })();
178
+ </script>