rizzo-css 0.0.11 → 0.0.13

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 (233) hide show
  1. package/.env.example +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +17 -3
  4. package/bin/rizzo-css.js +98 -42
  5. package/dist/rizzo.min.css +3 -2
  6. package/package.json +5 -3
  7. package/scaffold/astro-app/README.md +13 -2
  8. package/scaffold/astro-app/src/components/Accordion.astro +178 -0
  9. package/scaffold/astro-app/src/components/Alert.astro +131 -0
  10. package/scaffold/astro-app/src/components/Avatar.astro +59 -0
  11. package/scaffold/astro-app/src/components/Badge.astro +24 -0
  12. package/scaffold/astro-app/src/components/Breadcrumb.astro +61 -0
  13. package/scaffold/astro-app/src/components/Button.astro +3 -0
  14. package/scaffold/astro-app/src/components/Card.astro +18 -0
  15. package/scaffold/astro-app/src/components/Checkbox.astro +38 -0
  16. package/scaffold/astro-app/src/components/CodeBlock.astro +393 -0
  17. package/scaffold/astro-app/src/components/CopyToClipboard.astro +219 -0
  18. package/scaffold/astro-app/src/components/Divider.astro +37 -0
  19. package/scaffold/astro-app/src/components/Dropdown.astro +807 -0
  20. package/scaffold/astro-app/src/components/FormGroup.astro +59 -0
  21. package/scaffold/astro-app/src/components/FrameworkSwitcher.astro +72 -0
  22. package/scaffold/astro-app/src/components/Input.astro +59 -0
  23. package/scaffold/astro-app/src/components/Modal.astro +212 -0
  24. package/scaffold/astro-app/src/components/Navbar.astro +701 -0
  25. package/scaffold/astro-app/src/components/Pagination.astro +240 -0
  26. package/scaffold/astro-app/src/components/ProgressBar.astro +65 -0
  27. package/scaffold/astro-app/src/components/Radio.astro +38 -0
  28. package/scaffold/astro-app/src/components/Search.astro +1259 -0
  29. package/scaffold/astro-app/src/components/Select.astro +49 -0
  30. package/scaffold/astro-app/src/components/Settings.astro +382 -0
  31. package/scaffold/astro-app/src/components/Spinner.astro +30 -0
  32. package/scaffold/astro-app/src/components/Table.astro +181 -0
  33. package/scaffold/astro-app/src/components/Tabs.astro +223 -0
  34. package/scaffold/astro-app/src/components/Textarea.astro +58 -0
  35. package/scaffold/astro-app/src/components/ThemeSwitcher.astro +504 -0
  36. package/scaffold/astro-app/src/components/Toast.astro +30 -0
  37. package/scaffold/astro-app/src/components/Tooltip.astro +32 -0
  38. package/scaffold/astro-app/src/components/icons/Brush.astro +10 -0
  39. package/scaffold/astro-app/src/components/icons/Cake.astro +11 -0
  40. package/scaffold/astro-app/src/components/icons/Check.astro +29 -0
  41. package/scaffold/astro-app/src/components/icons/Cherry.astro +11 -0
  42. package/scaffold/astro-app/src/components/icons/ChevronDown.astro +29 -0
  43. package/scaffold/astro-app/src/components/icons/Circle.astro +29 -0
  44. package/scaffold/astro-app/src/components/icons/Close.astro +30 -0
  45. package/scaffold/astro-app/src/components/icons/Cmd.astro +26 -0
  46. package/scaffold/astro-app/src/components/icons/Copy.astro +30 -0
  47. package/scaffold/astro-app/src/components/icons/Eye.astro +30 -0
  48. package/scaffold/astro-app/src/components/icons/Filter.astro +29 -0
  49. package/scaffold/astro-app/src/components/icons/Flame.astro +28 -0
  50. package/scaffold/astro-app/src/components/icons/Flower.astro +11 -0
  51. package/scaffold/astro-app/src/components/icons/Gear.astro +30 -0
  52. package/scaffold/astro-app/src/components/icons/Heart.astro +28 -0
  53. package/scaffold/astro-app/src/components/icons/IceCream.astro +31 -0
  54. package/scaffold/astro-app/src/components/icons/Leaf.astro +29 -0
  55. package/scaffold/astro-app/src/components/icons/Lemon.astro +11 -0
  56. package/scaffold/astro-app/src/components/icons/Moon.astro +29 -0
  57. package/scaffold/astro-app/src/components/icons/Owl.astro +34 -0
  58. package/scaffold/astro-app/src/components/icons/Palette.astro +33 -0
  59. package/scaffold/astro-app/src/components/icons/Rainbow.astro +31 -0
  60. package/scaffold/astro-app/src/components/icons/Search.astro +30 -0
  61. package/scaffold/astro-app/src/components/icons/Shield.astro +28 -0
  62. package/scaffold/astro-app/src/components/icons/Snowflake.astro +34 -0
  63. package/scaffold/astro-app/src/components/icons/Sort.astro +30 -0
  64. package/scaffold/astro-app/src/components/icons/Sun.astro +29 -0
  65. package/scaffold/astro-app/src/components/icons/Sunset.astro +10 -0
  66. package/scaffold/astro-app/src/components/icons/Zap.astro +9 -0
  67. package/scaffold/astro-app/src/components/icons/devicons/Astro.astro +53 -0
  68. package/scaffold/astro-app/src/components/icons/devicons/Bash.astro +34 -0
  69. package/scaffold/astro-app/src/components/icons/devicons/Css3.astro +29 -0
  70. package/scaffold/astro-app/src/components/icons/devicons/Git.astro +24 -0
  71. package/scaffold/astro-app/src/components/icons/devicons/Html5.astro +27 -0
  72. package/scaffold/astro-app/src/components/icons/devicons/Javascript.astro +25 -0
  73. package/scaffold/astro-app/src/components/icons/devicons/Nodejs.astro +47 -0
  74. package/scaffold/astro-app/src/components/icons/devicons/Plaintext.astro +33 -0
  75. package/scaffold/astro-app/src/components/icons/devicons/React.astro +27 -0
  76. package/scaffold/astro-app/src/components/icons/devicons/Svelte.astro +25 -0
  77. package/scaffold/astro-app/src/components/icons/devicons/Vue.astro +26 -0
  78. package/scaffold/astro-app/src/config/frameworks.ts +26 -0
  79. package/scaffold/astro-app/src/config/themes.ts +54 -0
  80. package/scaffold/astro-app/src/layouts/DocsLayout.astro +204 -0
  81. package/scaffold/astro-app/src/layouts/Layout.astro +11 -2
  82. package/scaffold/astro-app/src/pages/components/accordion.astro +172 -0
  83. package/scaffold/astro-app/src/pages/components/alert.astro +250 -0
  84. package/scaffold/astro-app/src/pages/components/avatar.astro +102 -0
  85. package/scaffold/astro-app/src/pages/components/badge.astro +119 -0
  86. package/scaffold/astro-app/src/pages/components/breadcrumb.astro +124 -0
  87. package/scaffold/astro-app/src/pages/components/button.astro +74 -0
  88. package/scaffold/astro-app/src/pages/components/cards.astro +247 -0
  89. package/scaffold/astro-app/src/pages/components/copy-to-clipboard.astro +49 -0
  90. package/scaffold/astro-app/src/pages/components/divider.astro +74 -0
  91. package/scaffold/astro-app/src/pages/components/dropdown.astro +394 -0
  92. package/scaffold/astro-app/src/pages/components/forms.astro +367 -0
  93. package/scaffold/astro-app/src/pages/components/icons.astro +246 -0
  94. package/scaffold/astro-app/src/pages/components/modal.astro +152 -0
  95. package/scaffold/astro-app/src/pages/components/navbar.astro +80 -0
  96. package/scaffold/astro-app/src/pages/components/pagination.astro +126 -0
  97. package/scaffold/astro-app/src/pages/components/progress-bar.astro +94 -0
  98. package/scaffold/astro-app/src/pages/components/search.astro +155 -0
  99. package/scaffold/astro-app/src/pages/components/settings.astro +78 -0
  100. package/scaffold/astro-app/src/pages/components/spinner.astro +81 -0
  101. package/scaffold/astro-app/src/pages/components/table.astro +144 -0
  102. package/scaffold/astro-app/src/pages/components/tabs.astro +220 -0
  103. package/scaffold/astro-app/src/pages/components/theme-switcher.astro +67 -0
  104. package/scaffold/astro-app/src/pages/components/toast.astro +157 -0
  105. package/scaffold/astro-app/src/pages/components/tooltip.astro +209 -0
  106. package/scaffold/astro-app/src/pages/components.astro +290 -0
  107. package/scaffold/astro-app/src/pages/docs/accessibility.astro +9 -0
  108. package/scaffold/astro-app/src/pages/docs/colors.astro +9 -0
  109. package/scaffold/astro-app/src/pages/docs/design-system.astro +9 -0
  110. package/scaffold/astro-app/src/pages/docs/getting-started.astro +9 -0
  111. package/scaffold/astro-app/src/pages/docs/index.astro +15 -0
  112. package/scaffold/astro-app/src/pages/docs/themes/[theme].astro +14 -0
  113. package/scaffold/astro-app/src/pages/docs/theming.astro +10 -0
  114. package/scaffold/astro-app/src/pages/index.astro +5 -11
  115. package/scaffold/svelte/Table.svelte +6 -5
  116. package/scaffold/svelte/Tabs.svelte +3 -1
  117. package/scaffold/svelte-app/README.md +9 -2
  118. package/scaffold/svelte-app/src/app.html +1 -1
  119. package/scaffold/svelte-app/src/lib/rizzo/Accordion.svelte +128 -0
  120. package/scaffold/svelte-app/src/lib/rizzo/Alert.svelte +85 -0
  121. package/scaffold/svelte-app/src/lib/rizzo/Avatar.svelte +39 -0
  122. package/scaffold/svelte-app/src/lib/rizzo/Badge.svelte +31 -0
  123. package/scaffold/svelte-app/src/lib/rizzo/Breadcrumb.svelte +49 -0
  124. package/scaffold/svelte-app/src/lib/rizzo/Button.svelte +27 -0
  125. package/scaffold/svelte-app/src/lib/rizzo/Card.svelte +17 -0
  126. package/scaffold/svelte-app/src/lib/rizzo/Checkbox.svelte +37 -0
  127. package/scaffold/svelte-app/src/lib/rizzo/CopyToClipboard.svelte +79 -0
  128. package/scaffold/svelte-app/src/lib/rizzo/Divider.svelte +28 -0
  129. package/scaffold/svelte-app/src/lib/rizzo/Dropdown.svelte +254 -0
  130. package/scaffold/svelte-app/src/lib/rizzo/FormGroup.svelte +51 -0
  131. package/scaffold/svelte-app/src/lib/rizzo/Input.svelte +59 -0
  132. package/scaffold/svelte-app/src/lib/rizzo/Modal.svelte +157 -0
  133. package/scaffold/svelte-app/src/lib/rizzo/Pagination.svelte +93 -0
  134. package/scaffold/svelte-app/src/lib/rizzo/ProgressBar.svelte +58 -0
  135. package/scaffold/svelte-app/src/lib/rizzo/Radio.svelte +38 -0
  136. package/scaffold/svelte-app/src/lib/rizzo/Select.svelte +51 -0
  137. package/scaffold/svelte-app/src/lib/rizzo/Spinner.svelte +14 -0
  138. package/scaffold/svelte-app/src/lib/rizzo/Table.svelte +158 -0
  139. package/scaffold/svelte-app/src/lib/rizzo/Tabs.svelte +117 -0
  140. package/scaffold/svelte-app/src/lib/rizzo/Textarea.svelte +59 -0
  141. package/scaffold/svelte-app/src/lib/rizzo/ThemeSwitcher.svelte +315 -0
  142. package/scaffold/svelte-app/src/lib/rizzo/Toast.svelte +33 -0
  143. package/scaffold/svelte-app/src/lib/rizzo/Tooltip.svelte +19 -0
  144. package/scaffold/svelte-app/src/lib/rizzo/icons/Check.svelte +29 -0
  145. package/scaffold/svelte-app/src/lib/rizzo/icons/ChevronDown.svelte +29 -0
  146. package/scaffold/svelte-app/src/lib/rizzo/icons/Circle.svelte +29 -0
  147. package/scaffold/svelte-app/src/lib/rizzo/icons/Close.svelte +30 -0
  148. package/scaffold/svelte-app/src/lib/rizzo/icons/Cmd.svelte +27 -0
  149. package/scaffold/svelte-app/src/lib/rizzo/icons/Copy.svelte +30 -0
  150. package/scaffold/svelte-app/src/lib/rizzo/icons/Eye.svelte +30 -0
  151. package/scaffold/svelte-app/src/lib/rizzo/icons/Filter.svelte +29 -0
  152. package/scaffold/svelte-app/src/lib/rizzo/icons/Gear.svelte +30 -0
  153. package/scaffold/svelte-app/src/lib/rizzo/icons/IceCream.svelte +31 -0
  154. package/scaffold/svelte-app/src/lib/rizzo/icons/Moon.svelte +29 -0
  155. package/scaffold/svelte-app/src/lib/rizzo/icons/Owl.svelte +34 -0
  156. package/scaffold/svelte-app/src/lib/rizzo/icons/Palette.svelte +33 -0
  157. package/scaffold/svelte-app/src/lib/rizzo/icons/Rainbow.svelte +31 -0
  158. package/scaffold/svelte-app/src/lib/rizzo/icons/Search.svelte +30 -0
  159. package/scaffold/svelte-app/src/lib/rizzo/icons/Snowflake.svelte +34 -0
  160. package/scaffold/svelte-app/src/lib/rizzo/icons/Sort.svelte +30 -0
  161. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Astro.svelte +45 -0
  162. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Bash.svelte +28 -0
  163. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Css3.svelte +23 -0
  164. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Git.svelte +18 -0
  165. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Html5.svelte +21 -0
  166. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Javascript.svelte +19 -0
  167. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Nodejs.svelte +44 -0
  168. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Plaintext.svelte +24 -0
  169. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/React.svelte +21 -0
  170. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/SvelteIcon.svelte +19 -0
  171. package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Vue.svelte +20 -0
  172. package/scaffold/svelte-app/src/lib/rizzo/index.ts +33 -0
  173. package/scaffold/svelte-app/src/lib/rizzo-docs/CodeBlock.svelte +239 -0
  174. package/scaffold/svelte-app/src/lib/rizzo-docs/SvelteDocPage.svelte +99 -0
  175. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AccordionDoc.svelte +53 -0
  176. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AlertDoc.svelte +114 -0
  177. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AvatarDoc.svelte +92 -0
  178. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/BadgeDoc.svelte +60 -0
  179. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/BreadcrumbDoc.svelte +55 -0
  180. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ButtonDoc.svelte +55 -0
  181. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/CardsDoc.svelte +173 -0
  182. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ComingSoon.svelte +12 -0
  183. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ComponentsOverview.svelte +92 -0
  184. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/CopyToClipboardDoc.svelte +26 -0
  185. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/DividerDoc.svelte +105 -0
  186. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/DropdownDoc.svelte +161 -0
  187. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/FormsDoc.svelte +375 -0
  188. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/IconsDoc.svelte +246 -0
  189. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/Index.svelte +8 -0
  190. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ModalDoc.svelte +50 -0
  191. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/NavbarDoc.svelte +79 -0
  192. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/PaginationDoc.svelte +44 -0
  193. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ProgressBarDoc.svelte +95 -0
  194. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SearchDoc.svelte +147 -0
  195. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SettingsDoc.svelte +158 -0
  196. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SpinnerDoc.svelte +41 -0
  197. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TableDoc.svelte +116 -0
  198. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TabsDoc.svelte +152 -0
  199. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ThemeSwitcherDoc.svelte +181 -0
  200. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/Theming.svelte +6 -0
  201. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ToastDoc.svelte +136 -0
  202. package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TooltipDoc.svelte +57 -0
  203. package/scaffold/svelte-app/src/routes/+page.svelte +2 -2
  204. package/scaffold/svelte-app/src/routes/components/+page.svelte +4 -0
  205. package/scaffold/svelte-app/src/routes/components/[slug]/+page.svelte +7 -0
  206. package/scaffold/vanilla/README.md +20 -8
  207. package/scaffold/vanilla/components/accordion.html +187 -0
  208. package/scaffold/vanilla/components/alert.html +187 -0
  209. package/scaffold/vanilla/components/avatar.html +187 -0
  210. package/scaffold/vanilla/components/badge.html +187 -0
  211. package/scaffold/vanilla/components/breadcrumb.html +187 -0
  212. package/scaffold/vanilla/components/button.html +187 -0
  213. package/scaffold/vanilla/components/cards.html +187 -0
  214. package/scaffold/vanilla/components/copy-to-clipboard.html +187 -0
  215. package/scaffold/vanilla/components/divider.html +187 -0
  216. package/scaffold/vanilla/components/dropdown.html +187 -0
  217. package/scaffold/vanilla/components/forms.html +187 -0
  218. package/scaffold/vanilla/components/icons.html +187 -0
  219. package/scaffold/vanilla/components/index.html +212 -0
  220. package/scaffold/vanilla/components/modal.html +187 -0
  221. package/scaffold/vanilla/components/navbar.html +187 -0
  222. package/scaffold/vanilla/components/pagination.html +187 -0
  223. package/scaffold/vanilla/components/progress-bar.html +187 -0
  224. package/scaffold/vanilla/components/search.html +187 -0
  225. package/scaffold/vanilla/components/settings.html +187 -0
  226. package/scaffold/vanilla/components/spinner.html +187 -0
  227. package/scaffold/vanilla/components/table.html +187 -0
  228. package/scaffold/vanilla/components/tabs.html +187 -0
  229. package/scaffold/vanilla/components/theme-switcher.html +187 -0
  230. package/scaffold/vanilla/components/toast.html +187 -0
  231. package/scaffold/vanilla/components/tooltip.html +187 -0
  232. package/scaffold/vanilla/index.html +17 -283
  233. package/scaffold/vanilla/js/main.js +748 -0
@@ -0,0 +1,49 @@
1
+ ---
2
+ interface Props {
3
+ id?: string;
4
+ name?: string;
5
+ value?: string;
6
+ required?: boolean;
7
+ disabled?: boolean;
8
+ size?: 'sm' | 'md' | 'lg';
9
+ error?: boolean;
10
+ success?: boolean;
11
+ class?: string;
12
+ ariaDescribedby?: string;
13
+ ariaInvalid?: boolean | 'true' | 'false';
14
+ }
15
+
16
+ const {
17
+ id,
18
+ name,
19
+ value,
20
+ required = false,
21
+ disabled = false,
22
+ size = 'md',
23
+ error = false,
24
+ success = false,
25
+ class: className = '',
26
+ ariaDescribedby,
27
+ ariaInvalid,
28
+ } = Astro.props;
29
+
30
+ const sizeClass = size !== 'md' ? `form-input--${size}` : '';
31
+ const errorClass = error ? 'form-input--error' : '';
32
+ const successClass = success ? 'form-input--success' : '';
33
+ const classes = `form-input ${sizeClass} ${errorClass} ${successClass} ${className}`.trim();
34
+
35
+ const invalid = error || ariaInvalid === true || ariaInvalid === 'true';
36
+ ---
37
+
38
+ <select
39
+ id={id}
40
+ name={name}
41
+ value={value}
42
+ required={required}
43
+ disabled={disabled}
44
+ class={classes}
45
+ aria-invalid={invalid ? 'true' : 'false'}
46
+ aria-describedby={ariaDescribedby}
47
+ >
48
+ <slot />
49
+ </select>
@@ -0,0 +1,382 @@
1
+ ---
2
+ import ThemeSwitcher from './ThemeSwitcher.astro';
3
+ import Close from './icons/Close.astro';
4
+
5
+ interface Props {
6
+ open?: boolean;
7
+ }
8
+
9
+ const { open = false } = Astro.props;
10
+ ---
11
+
12
+ <div class="settings" data-settings aria-hidden={open ? 'false' : 'true'}>
13
+ <div class="settings__overlay" data-settings-overlay aria-hidden="true"></div>
14
+
15
+ <div class="settings__panel" role="dialog" aria-modal="true" aria-labelledby="settings-title" aria-hidden={open ? 'false' : 'true'}>
16
+ <div class="settings__header">
17
+ <h2 id="settings-title" class="settings__title">Settings</h2>
18
+ <button
19
+ class="settings__close"
20
+ type="button"
21
+ aria-label="Close settings"
22
+ data-settings-close
23
+ >
24
+ <Close width={20} height={20} />
25
+ </button>
26
+ </div>
27
+
28
+ <div class="settings__content">
29
+ <!-- Theme Section -->
30
+ <section class="settings__section">
31
+ <h3 class="settings__section-title">Theme</h3>
32
+ <div class="settings__control">
33
+ <ThemeSwitcher />
34
+ </div>
35
+ </section>
36
+
37
+ <!-- Font Size Section -->
38
+ <section class="settings__section">
39
+ <h3 class="settings__section-title">Font Size</h3>
40
+ <div class="settings__control">
41
+ <label for="font-size-slider" class="settings__label">
42
+ <span class="settings__label-text">Adjust text size</span>
43
+ <span class="settings__label-value" data-font-size-value>100%</span>
44
+ </label>
45
+ <input
46
+ type="range"
47
+ id="font-size-slider"
48
+ class="settings__slider"
49
+ min="0.75"
50
+ max="1.5"
51
+ step="0.05"
52
+ value="1"
53
+ aria-label="Font size"
54
+ data-font-size-slider
55
+ style="--slider-progress: 50%;"
56
+ />
57
+ <div class="settings__slider-labels">
58
+ <span>Small</span>
59
+ <span>Default</span>
60
+ <span>Large</span>
61
+ </div>
62
+ </div>
63
+ </section>
64
+
65
+ <!-- Accessibility Section -->
66
+ <section class="settings__section">
67
+ <h3 class="settings__section-title">Accessibility</h3>
68
+
69
+ <div class="settings__control">
70
+ <label class="settings__checkbox-label">
71
+ <input
72
+ type="checkbox"
73
+ class="settings__checkbox"
74
+ data-reduced-motion
75
+ aria-label="Reduce motion"
76
+ />
77
+ <span>Reduce motion</span>
78
+ </label>
79
+ <p class="settings__help-text">Minimize animations and transitions</p>
80
+ </div>
81
+
82
+ <div class="settings__control">
83
+ <label class="settings__checkbox-label">
84
+ <input
85
+ type="checkbox"
86
+ class="settings__checkbox"
87
+ data-high-contrast
88
+ aria-label="High contrast"
89
+ />
90
+ <span>High contrast</span>
91
+ </label>
92
+ <p class="settings__help-text">Increase contrast for better visibility</p>
93
+ </div>
94
+
95
+ <div class="settings__control">
96
+ <div class="settings__label">
97
+ <span class="settings__label-text">Scrollbar style</span>
98
+ </div>
99
+ <div class="settings__radio-group" role="radiogroup" aria-label="Scrollbar style">
100
+ <label class="settings__radio-label">
101
+ <input
102
+ type="radio"
103
+ name="scrollbar-style"
104
+ value="thin"
105
+ class="settings__radio"
106
+ data-scrollbar-style
107
+ aria-label="Thin scrollbar"
108
+ checked
109
+ />
110
+ <span>Thin</span>
111
+ </label>
112
+ <label class="settings__radio-label">
113
+ <input
114
+ type="radio"
115
+ name="scrollbar-style"
116
+ value="thick"
117
+ class="settings__radio"
118
+ data-scrollbar-style
119
+ aria-label="Thick scrollbar"
120
+ />
121
+ <span>Thick</span>
122
+ </label>
123
+ <label class="settings__radio-label">
124
+ <input
125
+ type="radio"
126
+ name="scrollbar-style"
127
+ value="hidden"
128
+ class="settings__radio"
129
+ data-scrollbar-style
130
+ aria-label="Hidden scrollbars"
131
+ />
132
+ <span>Hidden</span>
133
+ </label>
134
+ </div>
135
+ <p class="settings__help-text">Choose your preferred scrollbar appearance</p>
136
+ </div>
137
+ </section>
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <script>
143
+ (function initSettings() {
144
+ const settings = document.querySelector('[data-settings]');
145
+ if (!settings) return;
146
+
147
+ const overlay = settings.querySelector('[data-settings-overlay]');
148
+ const panel = settings.querySelector('.settings__panel');
149
+ const closeBtn = settings.querySelector('[data-settings-close]');
150
+ const fontSizeSlider = settings.querySelector('[data-font-size-slider]');
151
+ const fontSizeValue = settings.querySelector('[data-font-size-value]');
152
+ const reducedMotion = settings.querySelector('[data-reduced-motion]');
153
+ const highContrast = settings.querySelector('[data-high-contrast]');
154
+ const scrollbarStyleRadios = settings.querySelectorAll('[data-scrollbar-style]');
155
+ const html = document.documentElement;
156
+
157
+ if (!panel || !overlay || !closeBtn) return;
158
+
159
+ // Update slider progress fill
160
+ const updateSliderProgress = (slider) => {
161
+ const min = parseFloat(slider.min);
162
+ const max = parseFloat(slider.max);
163
+ const value = parseFloat(slider.value);
164
+ const progress = ((value - min) / (max - min)) * 100;
165
+ slider.style.setProperty('--slider-progress', `${progress}%`);
166
+ };
167
+
168
+ // Load saved settings
169
+ const loadSettings = () => {
170
+ const savedFontScale = localStorage.getItem('fontSizeScale');
171
+ if (savedFontScale && fontSizeSlider) {
172
+ fontSizeSlider.value = savedFontScale;
173
+ const scale = parseFloat(savedFontScale);
174
+ applyFontSize(scale);
175
+ updateSliderProgress(fontSizeSlider);
176
+ } else if (fontSizeSlider) {
177
+ // Initialize with default value
178
+ updateSliderProgress(fontSizeSlider);
179
+ }
180
+
181
+ const savedReducedMotion = localStorage.getItem('reducedMotion') === 'true';
182
+ if (reducedMotion) {
183
+ reducedMotion.checked = savedReducedMotion;
184
+ html.classList.toggle('reduced-motion', savedReducedMotion);
185
+ }
186
+
187
+ const savedHighContrast = localStorage.getItem('highContrast') === 'true';
188
+ if (highContrast) {
189
+ highContrast.checked = savedHighContrast;
190
+ html.classList.toggle('high-contrast', savedHighContrast);
191
+ }
192
+
193
+ // Load scrollbar style preference
194
+ const savedScrollbarStyle = localStorage.getItem('scrollbarStyle') || 'thin';
195
+ scrollbarStyleRadios.forEach((radio) => {
196
+ const radioInput = radio as HTMLInputElement;
197
+ if (radioInput && radioInput.value === savedScrollbarStyle) {
198
+ radioInput.checked = true;
199
+ }
200
+ });
201
+ applyScrollbarStyle(savedScrollbarStyle);
202
+ };
203
+
204
+ // Apply font size
205
+ const applyFontSize = (scale) => {
206
+ html.style.setProperty('--font-size-scale', scale.toString());
207
+ if (fontSizeValue) {
208
+ fontSizeValue.textContent = `${Math.round(scale * 100)}%`;
209
+ }
210
+ };
211
+
212
+ // Focus trap helper
213
+ const getFocusableElements = (container) => {
214
+ const focusableSelectors = [
215
+ 'button:not([disabled])',
216
+ 'a[href]',
217
+ 'input:not([disabled])',
218
+ 'select:not([disabled])',
219
+ 'textarea:not([disabled])',
220
+ '[tabindex]:not([tabindex="-1"])',
221
+ ].join(', ');
222
+ return Array.from(container.querySelectorAll(focusableSelectors));
223
+ };
224
+
225
+ let previousActiveElement = null;
226
+
227
+ // Open settings
228
+ const openSettings = () => {
229
+ previousActiveElement = document.activeElement;
230
+
231
+ // First, make elements visible but keep panel off-screen
232
+ settings.setAttribute('aria-hidden', 'false');
233
+ if (overlay) overlay.setAttribute('aria-hidden', 'false');
234
+ panel.setAttribute('aria-hidden', 'false');
235
+
236
+ // Ensure panel starts in closed position (remove data-open if it exists)
237
+ panel.removeAttribute('data-open');
238
+
239
+ // Force a reflow to ensure the closed state is rendered
240
+ void panel.offsetHeight;
241
+
242
+ // Then animate in - use double requestAnimationFrame for reliable animation
243
+ requestAnimationFrame(() => {
244
+ requestAnimationFrame(() => {
245
+ panel.setAttribute('data-open', 'true');
246
+ if (closeBtn) closeBtn.focus();
247
+ });
248
+ });
249
+ };
250
+
251
+ // Close settings
252
+ const closeSettings = () => {
253
+ // Remove data-open first to trigger close animation
254
+ panel.removeAttribute('data-open');
255
+
256
+ // Wait for animation to complete before hiding
257
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
258
+ const animationDuration = prefersReducedMotion ? 0 : 300;
259
+
260
+ setTimeout(() => {
261
+ settings.setAttribute('aria-hidden', 'true');
262
+ overlay?.setAttribute('aria-hidden', 'true');
263
+ panel.setAttribute('aria-hidden', 'true');
264
+
265
+ // Return focus to previous element
266
+ if (previousActiveElement) {
267
+ previousActiveElement.focus();
268
+ previousActiveElement = null;
269
+ }
270
+ }, animationDuration);
271
+ };
272
+
273
+ // Event listeners
274
+ closeBtn?.addEventListener('click', closeSettings);
275
+
276
+ overlay?.addEventListener('click', closeSettings);
277
+
278
+ // Focus trapping and keyboard handlers
279
+ const handleKeyDown = (e: KeyboardEvent) => {
280
+ if (panel.getAttribute('data-open') !== 'true') return;
281
+
282
+ if (e.key === 'Escape') {
283
+ e.preventDefault();
284
+ closeSettings();
285
+ return;
286
+ }
287
+
288
+ // Focus trap: Tab key
289
+ if (e.key === 'Tab') {
290
+ const focusableElements = getFocusableElements(panel);
291
+ if (focusableElements.length === 0) return;
292
+
293
+ const firstElement = focusableElements[0];
294
+ const lastElement = focusableElements[focusableElements.length - 1];
295
+ const activeElement = document.activeElement as HTMLElement;
296
+
297
+ if (e.shiftKey) {
298
+ // Shift + Tab: move backwards
299
+ if (activeElement === firstElement || !panel.contains(activeElement)) {
300
+ e.preventDefault();
301
+ lastElement.focus();
302
+ }
303
+ } else {
304
+ // Tab: move forwards
305
+ if (activeElement === lastElement || !panel.contains(activeElement)) {
306
+ e.preventDefault();
307
+ firstElement.focus();
308
+ }
309
+ }
310
+ }
311
+ };
312
+
313
+ document.addEventListener('keydown', handleKeyDown);
314
+
315
+ // Font size slider
316
+ if (fontSizeSlider) {
317
+ fontSizeSlider.addEventListener('input', (e) => {
318
+ const target = e.target as HTMLInputElement;
319
+ if (target) {
320
+ const scale = parseFloat(target.value);
321
+ applyFontSize(scale);
322
+ updateSliderProgress(target);
323
+ localStorage.setItem('fontSizeScale', scale.toString());
324
+ }
325
+ });
326
+ }
327
+
328
+ // Reduced motion
329
+ if (reducedMotion) {
330
+ reducedMotion.addEventListener('change', (e) => {
331
+ const target = e.target as HTMLInputElement;
332
+ if (target) {
333
+ html.classList.toggle('reduced-motion', target.checked);
334
+ localStorage.setItem('reducedMotion', target.checked.toString());
335
+ }
336
+ });
337
+ }
338
+
339
+ // High contrast
340
+ if (highContrast) {
341
+ highContrast.addEventListener('change', (e) => {
342
+ const target = e.target as HTMLInputElement;
343
+ if (target) {
344
+ html.classList.toggle('high-contrast', target.checked);
345
+ localStorage.setItem('highContrast', target.checked.toString());
346
+ }
347
+ });
348
+ }
349
+
350
+ // Apply scrollbar style
351
+ const applyScrollbarStyle = (style: string) => {
352
+ // Remove all scrollbar classes
353
+ html.classList.remove('scrollbar-thin', 'scrollbar-thick', 'scrollbar-hidden', 'hide-scrollbars');
354
+
355
+ // Add the appropriate class
356
+ if (style === 'thick') {
357
+ html.classList.add('scrollbar-thick');
358
+ } else if (style === 'hidden') {
359
+ html.classList.add('scrollbar-hidden', 'hide-scrollbars'); // Keep hide-scrollbars for backward compatibility
360
+ }
361
+ // 'thin' is the default, no class needed
362
+ };
363
+
364
+ // Scrollbar style radio buttons
365
+ scrollbarStyleRadios.forEach((radio) => {
366
+ radio.addEventListener('change', (e) => {
367
+ const target = e.target as HTMLInputElement;
368
+ if (target && target.checked) {
369
+ const style = target.value;
370
+ applyScrollbarStyle(style);
371
+ localStorage.setItem('scrollbarStyle', style);
372
+ }
373
+ });
374
+ });
375
+
376
+ // Load settings on init
377
+ loadSettings();
378
+
379
+ // Expose open function globally
380
+ window.openSettings = openSettings;
381
+ })();
382
+ </script>
@@ -0,0 +1,30 @@
1
+ ---
2
+ interface Props {
3
+ /** Visual size */
4
+ size?: 'sm' | 'md' | 'lg';
5
+ /** Color variant */
6
+ variant?: 'primary' | 'success' | 'warning' | 'error' | 'info';
7
+ /** Accessible label (e.g. "Loading") */
8
+ label?: string;
9
+ class?: string;
10
+ }
11
+
12
+ const {
13
+ size = 'md',
14
+ variant = 'primary',
15
+ label = 'Loading',
16
+ class: className = '',
17
+ } = Astro.props;
18
+
19
+ const sizeClass = `spinner--${size}`;
20
+ const variantClass = `spinner--${variant}`;
21
+ const classes = `spinner ${sizeClass} ${variantClass} ${className}`.trim();
22
+ ---
23
+
24
+ <span
25
+ class={classes}
26
+ role="status"
27
+ aria-label={label}
28
+ >
29
+ <span class="spinner__ring" aria-hidden="true" />
30
+ </span>
@@ -0,0 +1,181 @@
1
+ ---
2
+ import Sort from './icons/Sort.astro';
3
+ import Filter from './icons/Filter.astro';
4
+
5
+ export interface TableColumn {
6
+ key: string;
7
+ label: string;
8
+ sortable?: boolean;
9
+ type?: 'string' | 'number';
10
+ }
11
+
12
+ interface Props {
13
+ /** Column definitions (key, label, sortable, type) */
14
+ columns: TableColumn[];
15
+ /** Row data: array of objects with keys matching column keys */
16
+ data: Record<string, string | number>[];
17
+ /** Table caption for accessibility */
18
+ caption?: string;
19
+ /** Enable column header sorting (default: true) */
20
+ sortable?: boolean;
21
+ /** Enable filter input above table (default: false) */
22
+ filterable?: boolean;
23
+ /** Placeholder for filter input */
24
+ filterPlaceholder?: string;
25
+ /** Striped rows (default: true) */
26
+ striped?: boolean;
27
+ class?: string;
28
+ }
29
+
30
+ const {
31
+ columns,
32
+ data,
33
+ caption,
34
+ sortable = true,
35
+ filterable = false,
36
+ filterPlaceholder = 'Filter table…',
37
+ striped = true,
38
+ class: className = '',
39
+ } = Astro.props;
40
+
41
+ const tableId = `table-${Math.random().toString(36).slice(2, 11)}`;
42
+ const stripClass = striped ? 'table--striped' : '';
43
+ const sortClass = sortable ? 'table--sortable' : '';
44
+ const filterClass = filterable ? 'table--filterable' : '';
45
+ const classes = `table ${stripClass} ${sortClass} ${filterClass} ${className}`.trim();
46
+ ---
47
+
48
+ <div class={classes} data-table-sortable={sortable ? 'true' : undefined} data-table-filterable={filterable ? 'true' : undefined} data-table-id={tableId}>
49
+ {filterable && (
50
+ <div class="table__filter-wrap">
51
+ <label for={`${tableId}-filter`} class="sr-only">Filter table</label>
52
+ <span class="table__filter-icon" aria-hidden="true">
53
+ <Filter width={20} height={20} class="table__filter-icon-svg" />
54
+ </span>
55
+ <input
56
+ type="search"
57
+ id={`${tableId}-filter`}
58
+ class="table__filter"
59
+ placeholder={filterPlaceholder}
60
+ aria-controls={tableId}
61
+ data-table-filter
62
+ autocomplete="off"
63
+ />
64
+ </div>
65
+ )}
66
+ <div class="table__wrapper">
67
+ <table class="table__table" id={tableId}>
68
+ {caption && <caption class="table__caption">{caption}</caption>}
69
+ <thead class="table__head">
70
+ <tr class="table__row">
71
+ {columns.map((col, i) => (
72
+ <th
73
+ class="table__cell table__cell--head"
74
+ scope="col"
75
+ data-column-index={i}
76
+ data-sortable={sortable && col.sortable !== false ? 'true' : undefined}
77
+ data-type={col.type ?? 'string'}
78
+ aria-sort={sortable && col.sortable !== false ? 'none' : undefined}
79
+ >
80
+ {sortable && col.sortable !== false ? (
81
+ <button
82
+ type="button"
83
+ class="table__sort-trigger"
84
+ data-column-index={i}
85
+ aria-label={`Sort by ${col.label}`}
86
+ >
87
+ <span class="table__cell-content">{col.label}</span>
88
+ <span class="table__sort-icon" aria-hidden="true">
89
+ <Sort width={20} height={20} class="table__sort-icon-svg" />
90
+ </span>
91
+ </button>
92
+ ) : (
93
+ <>
94
+ <span class="table__cell-content">{col.label}</span>
95
+ </>
96
+ )}
97
+ </th>
98
+ ))}
99
+ </tr>
100
+ </thead>
101
+ <tbody class="table__body">
102
+ {data.map((row, rowIndex) => (
103
+ <tr class="table__row" data-row-index={rowIndex}>
104
+ {columns.map((col) => (
105
+ <td class="table__cell" data-column-key={col.key}>
106
+ {row[col.key] ?? ''}
107
+ </td>
108
+ ))}
109
+ </tr>
110
+ ))}
111
+ </tbody>
112
+ </table>
113
+ </div>
114
+ </div>
115
+
116
+ {(sortable || filterable) && (
117
+ <script is:inline>
118
+ (function() {
119
+ function init() {
120
+ document.querySelectorAll('[data-table-sortable="true"], [data-table-filterable="true"]').forEach(function(wrapper) {
121
+ if (wrapper.hasAttribute('data-table-initialized')) return;
122
+ wrapper.setAttribute('data-table-initialized', 'true');
123
+
124
+ var table = wrapper.querySelector('table');
125
+ var tbody = table && table.querySelector('tbody');
126
+ var thead = table && table.querySelector('thead');
127
+ var filterInput = wrapper.querySelector('[data-table-filter]');
128
+
129
+ if (wrapper.getAttribute('data-table-sortable') === 'true' && thead && tbody) {
130
+ thead.querySelectorAll('th[data-sortable="true"] button.table__sort-trigger').forEach(function(btn) {
131
+ var th = btn.closest('th');
132
+ var colIndex = parseInt(th.getAttribute('data-column-index'), 10);
133
+ btn.addEventListener('click', function() { sortTable(tbody, colIndex, th); });
134
+ });
135
+ }
136
+
137
+ if (wrapper.getAttribute('data-table-filterable') === 'true' && filterInput && tbody) {
138
+ filterInput.addEventListener('input', function() {
139
+ var q = (filterInput.value || '').trim().toLowerCase();
140
+ tbody.querySelectorAll('tr').forEach(function(tr) {
141
+ var text = Array.from(tr.querySelectorAll('td')).map(function(td) { return td.textContent || ''; }).join(' ').toLowerCase();
142
+ tr.hidden = q ? text.indexOf(q) === -1 : false;
143
+ });
144
+ });
145
+ }
146
+ });
147
+
148
+ function sortTable(tbody, colIndex, th) {
149
+ var type = th.getAttribute('data-type') || 'string';
150
+ var dir = th.getAttribute('aria-sort') === 'ascending' ? 'descending' : 'ascending';
151
+ var rows = Array.from(tbody.querySelectorAll('tr'));
152
+
153
+ rows.sort(function(a, b) {
154
+ var aCell = a.querySelectorAll('td')[colIndex];
155
+ var bCell = b.querySelectorAll('td')[colIndex];
156
+ var aVal = aCell ? aCell.textContent : '';
157
+ var bVal = bCell ? bCell.textContent : '';
158
+ if (type === 'number') {
159
+ var aNum = parseFloat(String(aVal).replace(/[^0-9.-]/g, '')) || 0;
160
+ var bNum = parseFloat(String(bVal).replace(/[^0-9.-]/g, '')) || 0;
161
+ return dir === 'ascending' ? aNum - bNum : bNum - aNum;
162
+ }
163
+ var cmp = String(aVal).localeCompare(String(bVal), undefined, { numeric: true });
164
+ return dir === 'ascending' ? cmp : -cmp;
165
+ });
166
+
167
+ th.setAttribute('aria-sort', dir);
168
+ var table = tbody.parentNode;
169
+ if (table && table.querySelector) {
170
+ table.querySelectorAll('thead th[data-sortable="true"]').forEach(function(h) {
171
+ if (h !== th) h.setAttribute('aria-sort', 'none');
172
+ });
173
+ }
174
+ rows.forEach(function(tr) { tbody.appendChild(tr); });
175
+ }
176
+ }
177
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
178
+ else init();
179
+ })();
180
+ </script>
181
+ )}