rizzo-css 0.0.1 → 0.0.3

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 (93) hide show
  1. package/README.md +13 -7
  2. package/bin/rizzo-css.js +303 -0
  3. package/dist/rizzo.min.css +1 -1
  4. package/package.json +13 -4
  5. package/scaffold/astro/Accordion.astro +178 -0
  6. package/scaffold/astro/Alert.astro +131 -0
  7. package/scaffold/astro/Avatar.astro +59 -0
  8. package/scaffold/astro/Badge.astro +24 -0
  9. package/scaffold/astro/Breadcrumb.astro +61 -0
  10. package/scaffold/astro/Button.astro +3 -0
  11. package/scaffold/astro/Card.astro +18 -0
  12. package/scaffold/astro/Checkbox.astro +38 -0
  13. package/scaffold/astro/CopyToClipboard.astro +199 -0
  14. package/scaffold/astro/Divider.astro +37 -0
  15. package/scaffold/astro/Dropdown.astro +807 -0
  16. package/scaffold/astro/FormGroup.astro +51 -0
  17. package/scaffold/astro/Input.astro +59 -0
  18. package/scaffold/astro/Modal.astro +212 -0
  19. package/scaffold/astro/Pagination.astro +240 -0
  20. package/scaffold/astro/ProgressBar.astro +65 -0
  21. package/scaffold/astro/Radio.astro +38 -0
  22. package/scaffold/astro/Select.astro +49 -0
  23. package/scaffold/astro/Spinner.astro +30 -0
  24. package/scaffold/astro/Table.astro +181 -0
  25. package/scaffold/astro/Tabs.astro +223 -0
  26. package/scaffold/astro/Textarea.astro +58 -0
  27. package/scaffold/astro/Toast.astro +30 -0
  28. package/scaffold/astro/Tooltip.astro +32 -0
  29. package/scaffold/astro/icons/Brush.astro +11 -0
  30. package/scaffold/astro/icons/Cake.astro +12 -0
  31. package/scaffold/astro/icons/Check.astro +30 -0
  32. package/scaffold/astro/icons/Cherry.astro +12 -0
  33. package/scaffold/astro/icons/ChevronDown.astro +30 -0
  34. package/scaffold/astro/icons/Circle.astro +30 -0
  35. package/scaffold/astro/icons/Close.astro +31 -0
  36. package/scaffold/astro/icons/Copy.astro +31 -0
  37. package/scaffold/astro/icons/Eye.astro +31 -0
  38. package/scaffold/astro/icons/Filter.astro +30 -0
  39. package/scaffold/astro/icons/Flame.astro +29 -0
  40. package/scaffold/astro/icons/Flower.astro +12 -0
  41. package/scaffold/astro/icons/Gear.astro +31 -0
  42. package/scaffold/astro/icons/Heart.astro +29 -0
  43. package/scaffold/astro/icons/IceCream.astro +32 -0
  44. package/scaffold/astro/icons/Leaf.astro +30 -0
  45. package/scaffold/astro/icons/Lemon.astro +12 -0
  46. package/scaffold/astro/icons/Moon.astro +30 -0
  47. package/scaffold/astro/icons/Owl.astro +35 -0
  48. package/scaffold/astro/icons/Palette.astro +34 -0
  49. package/scaffold/astro/icons/Rainbow.astro +32 -0
  50. package/scaffold/astro/icons/Search.astro +31 -0
  51. package/scaffold/astro/icons/Shield.astro +29 -0
  52. package/scaffold/astro/icons/Snowflake.astro +35 -0
  53. package/scaffold/astro/icons/Sort.astro +31 -0
  54. package/scaffold/astro/icons/Sun.astro +30 -0
  55. package/scaffold/astro/icons/Sunset.astro +11 -0
  56. package/scaffold/astro/icons/Zap.astro +10 -0
  57. package/scaffold/astro/icons/devicons/Astro.astro +54 -0
  58. package/scaffold/astro/icons/devicons/Bash.astro +35 -0
  59. package/scaffold/astro/icons/devicons/Css3.astro +30 -0
  60. package/scaffold/astro/icons/devicons/Git.astro +25 -0
  61. package/scaffold/astro/icons/devicons/Html5.astro +28 -0
  62. package/scaffold/astro/icons/devicons/Javascript.astro +26 -0
  63. package/scaffold/astro/icons/devicons/Nodejs.astro +48 -0
  64. package/scaffold/astro/icons/devicons/Plaintext.astro +34 -0
  65. package/scaffold/astro/icons/devicons/React.astro +28 -0
  66. package/scaffold/astro/icons/devicons/Svelte.astro +26 -0
  67. package/scaffold/astro/icons/devicons/Vue.astro +27 -0
  68. package/scaffold/svelte/.gitkeep +0 -0
  69. package/scaffold/svelte/Accordion.svelte +128 -0
  70. package/scaffold/svelte/Alert.svelte +79 -0
  71. package/scaffold/svelte/Avatar.svelte +39 -0
  72. package/scaffold/svelte/Badge.svelte +31 -0
  73. package/scaffold/svelte/Breadcrumb.svelte +46 -0
  74. package/scaffold/svelte/Button.svelte +23 -0
  75. package/scaffold/svelte/Card.svelte +14 -0
  76. package/scaffold/svelte/Checkbox.svelte +37 -0
  77. package/scaffold/svelte/CopyToClipboard.svelte +76 -0
  78. package/scaffold/svelte/Divider.svelte +28 -0
  79. package/scaffold/svelte/Dropdown.svelte +237 -0
  80. package/scaffold/svelte/FormGroup.svelte +41 -0
  81. package/scaffold/svelte/Input.svelte +57 -0
  82. package/scaffold/svelte/Modal.svelte +152 -0
  83. package/scaffold/svelte/Pagination.svelte +93 -0
  84. package/scaffold/svelte/ProgressBar.svelte +56 -0
  85. package/scaffold/svelte/Radio.svelte +38 -0
  86. package/scaffold/svelte/Select.svelte +47 -0
  87. package/scaffold/svelte/Spinner.svelte +14 -0
  88. package/scaffold/svelte/Table.svelte +155 -0
  89. package/scaffold/svelte/Tabs.svelte +109 -0
  90. package/scaffold/svelte/Textarea.svelte +57 -0
  91. package/scaffold/svelte/Toast.svelte +30 -0
  92. package/scaffold/svelte/Tooltip.svelte +19 -0
  93. package/scaffold/svelte/index.ts +33 -0
@@ -0,0 +1,131 @@
1
+ ---
2
+ import Close from './icons/Close.astro';
3
+
4
+ interface Props {
5
+ variant?: 'success' | 'error' | 'warning' | 'info';
6
+ dismissible?: boolean;
7
+ autoDismiss?: number; // Duration in milliseconds (0 = disabled)
8
+ class?: string;
9
+ id?: string;
10
+ }
11
+
12
+ const {
13
+ variant = 'info',
14
+ dismissible = false,
15
+ autoDismiss = 0, // 0 means disabled
16
+ class: className = '',
17
+ id,
18
+ } = Astro.props;
19
+
20
+ const alertId = id || `alert-${Math.random().toString(36).substr(2, 9)}`;
21
+ const variantClass = `alert--${variant}`;
22
+ const classes = `alert ${variantClass} ${className}`.trim();
23
+
24
+ // ARIA labels based on variant
25
+ const ariaLabels = {
26
+ success: 'Success message',
27
+ error: 'Error message',
28
+ warning: 'Warning message',
29
+ info: 'Information message',
30
+ };
31
+ ---
32
+
33
+ <div
34
+ class={classes}
35
+ id={alertId}
36
+ role="alert"
37
+ aria-live="polite"
38
+ aria-atomic="true"
39
+ aria-label={ariaLabels[variant]}
40
+ >
41
+ <div class="alert__content">
42
+ <slot />
43
+ </div>
44
+ {dismissible && (
45
+ <button
46
+ type="button"
47
+ class="alert__close"
48
+ aria-label="Dismiss alert"
49
+ data-alert-close
50
+ aria-controls={alertId}
51
+ >
52
+ <Close width={16} height={16} />
53
+ </button>
54
+ )}
55
+ </div>
56
+
57
+ <script define:vars={{ alertId, autoDismiss }}>
58
+ (function initAlert() {
59
+ const init = () => {
60
+ const alert = document.querySelector(`#${alertId}`);
61
+ if (!alert) {
62
+ if (document.readyState === 'loading') {
63
+ document.addEventListener('DOMContentLoaded', init);
64
+ return;
65
+ }
66
+ return;
67
+ }
68
+
69
+ // Skip if already initialized
70
+ if (alert.hasAttribute('data-alert-initialized')) return;
71
+ alert.setAttribute('data-alert-initialized', 'true');
72
+
73
+ const closeBtn = alert.querySelector('[data-alert-close]');
74
+
75
+ let closeAlert = () => {
76
+ // Announce dismissal to screen readers
77
+ const announcement = document.createElement('div');
78
+ announcement.setAttribute('role', 'status');
79
+ announcement.setAttribute('aria-live', 'polite');
80
+ announcement.className = 'sr-only';
81
+ announcement.textContent = 'Alert dismissed';
82
+ document.body.appendChild(announcement);
83
+
84
+ // Remove alert with animation
85
+ alert.style.opacity = '0';
86
+ alert.style.transform = 'translateY(-10px)';
87
+
88
+ setTimeout(() => {
89
+ alert.remove();
90
+ if (document.body.contains(announcement)) {
91
+ document.body.removeChild(announcement);
92
+ }
93
+ }, 200);
94
+ };
95
+
96
+ // Auto-dismiss functionality
97
+ if (autoDismiss > 0) {
98
+ const timeoutId = setTimeout(() => {
99
+ closeAlert();
100
+ }, autoDismiss);
101
+
102
+ // Clear timeout if user manually dismisses
103
+ if (closeBtn) {
104
+ const originalClose = closeAlert;
105
+ closeAlert = () => {
106
+ clearTimeout(timeoutId);
107
+ originalClose();
108
+ };
109
+ }
110
+ }
111
+
112
+ if (closeBtn) {
113
+ closeBtn.addEventListener('click', closeAlert);
114
+
115
+ // Keyboard support
116
+ closeBtn.addEventListener('keydown', (e) => {
117
+ if (e.key === 'Enter' || e.key === ' ') {
118
+ e.preventDefault();
119
+ closeAlert();
120
+ }
121
+ });
122
+ }
123
+ };
124
+
125
+ if (document.readyState === 'loading') {
126
+ document.addEventListener('DOMContentLoaded', init);
127
+ } else {
128
+ init();
129
+ }
130
+ })();
131
+ </script>
@@ -0,0 +1,59 @@
1
+ ---
2
+ import { Image } from 'astro:assets';
3
+
4
+ interface Props {
5
+ /** Image URL (when provided, shows image; otherwise shows initials or placeholder). For remote URLs, add the domain to image.domains in astro.config. */
6
+ src?: string;
7
+ /** Alt text for the image */
8
+ alt?: string;
9
+ /** Full name used to derive initials when no image (e.g. "Jane Doe" → "JD") */
10
+ name?: string;
11
+ /** Override initials when no image (e.g. "JD"); ignored if name is provided */
12
+ initials?: string;
13
+ /** Size */
14
+ size?: 'sm' | 'md' | 'lg';
15
+ /** Shape */
16
+ shape?: 'circle' | 'square';
17
+ class?: string;
18
+ }
19
+
20
+ const {
21
+ src,
22
+ alt = '',
23
+ name = '',
24
+ initials: initialsProp = '',
25
+ size = 'md',
26
+ shape = 'circle',
27
+ class: className = '',
28
+ } = Astro.props;
29
+
30
+ function getInitials(name: string): string {
31
+ const parts = name.trim().split(/\s+/).filter(Boolean);
32
+ if (parts.length === 0) return '';
33
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
34
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
35
+ }
36
+
37
+ const displayInitials = name ? getInitials(name) : initialsProp;
38
+ const sizeClass = `avatar--${size}`;
39
+ const shapeClass = `avatar--${shape}`;
40
+ const classes = `avatar ${sizeClass} ${shapeClass} ${className}`.trim();
41
+
42
+ const sizePixels = size === 'sm' ? 32 : size === 'lg' ? 48 : 40;
43
+ ---
44
+
45
+ <span class={classes} role="img" aria-label={alt || name || (displayInitials ? `Avatar: ${displayInitials}` : 'Avatar')}>
46
+ {src ? (
47
+ <Image
48
+ src={src}
49
+ alt={alt || name || ''}
50
+ width={sizePixels}
51
+ height={sizePixels}
52
+ class="avatar__img"
53
+ />
54
+ ) : (
55
+ <span class="avatar__initials" aria-hidden="true">
56
+ {displayInitials || '?'}
57
+ </span>
58
+ )}
59
+ </span>
@@ -0,0 +1,24 @@
1
+ ---
2
+ interface Props {
3
+ variant?: 'primary' | 'success' | 'warning' | 'error' | 'info';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ pill?: boolean;
6
+ class?: string;
7
+ }
8
+
9
+ const {
10
+ variant = 'primary',
11
+ size = 'md',
12
+ pill = false,
13
+ class: className = '',
14
+ } = Astro.props;
15
+
16
+ const variantClass = `badge--${variant}`;
17
+ const sizeClass = `badge--${size}`;
18
+ const pillClass = pill ? 'badge--pill' : '';
19
+ const classes = `badge ${variantClass} ${sizeClass} ${pillClass} ${className}`.trim();
20
+ ---
21
+
22
+ <span class={classes}>
23
+ <slot />
24
+ </span>
@@ -0,0 +1,61 @@
1
+ ---
2
+ import ChevronDown from './icons/ChevronDown.astro';
3
+
4
+ interface BreadcrumbItem {
5
+ label: string;
6
+ href?: string;
7
+ }
8
+
9
+ interface Props {
10
+ items: BreadcrumbItem[];
11
+ separator?: 'chevron' | 'slash' | 'arrow' | string;
12
+ class?: string;
13
+ }
14
+
15
+ const {
16
+ items,
17
+ separator = 'chevron',
18
+ class: className = '',
19
+ } = Astro.props;
20
+
21
+ const separatorVariant =
22
+ separator === 'slash' ? 'breadcrumb--slash' :
23
+ separator === 'arrow' ? 'breadcrumb--arrow' :
24
+ 'breadcrumb--chevron';
25
+ const classes = `breadcrumb ${separatorVariant} ${className}`.trim();
26
+
27
+ const separatorChar = separator === 'slash' ? '/' : separator === 'arrow' ? '›' : typeof separator === 'string' ? separator : null;
28
+ const useIcon = separator === 'chevron';
29
+ ---
30
+
31
+ <nav class={classes} aria-label="Breadcrumb">
32
+ <ol class="breadcrumb__list">
33
+ {items.map((item, index) => {
34
+ const isLast = index === items.length - 1;
35
+ const isCurrent = isLast || !item.href;
36
+
37
+ return (
38
+ <li class={`breadcrumb__item ${isCurrent ? 'breadcrumb__item--current' : ''}`}>
39
+ {isCurrent ? (
40
+ <span class="breadcrumb__current" aria-current="page">
41
+ {item.label}
42
+ </span>
43
+ ) : (
44
+ <a class="breadcrumb__link" href={item.href}>
45
+ {item.label}
46
+ </a>
47
+ )}
48
+ {!isLast && (
49
+ <span class="breadcrumb__separator" aria-hidden="true">
50
+ {useIcon ? (
51
+ <ChevronDown class="breadcrumb__separator-icon" width={14} height={14} />
52
+ ) : (
53
+ separatorChar ?? '›'
54
+ )}
55
+ </span>
56
+ )}
57
+ </li>
58
+ );
59
+ })}
60
+ </ol>
61
+ </nav>
@@ -0,0 +1,3 @@
1
+ <button>
2
+ Click me
3
+ </button>
@@ -0,0 +1,18 @@
1
+ ---
2
+ interface Props {
3
+ variant?: 'default' | 'elevated' | 'outlined' | 'filled';
4
+ class?: string;
5
+ }
6
+
7
+ const {
8
+ variant = 'default',
9
+ class: className = '',
10
+ } = Astro.props;
11
+
12
+ const variantClass = variant !== 'default' ? `card--${variant}` : '';
13
+ const classes = `card ${variantClass} ${className}`.trim();
14
+ ---
15
+
16
+ <div class={classes}>
17
+ <slot />
18
+ </div>
@@ -0,0 +1,38 @@
1
+ ---
2
+ interface Props {
3
+ id?: string;
4
+ name?: string;
5
+ value?: string;
6
+ checked?: boolean;
7
+ required?: boolean;
8
+ disabled?: boolean;
9
+ class?: string;
10
+ ariaDescribedby?: string;
11
+ ariaLabel?: string;
12
+ }
13
+
14
+ const {
15
+ id,
16
+ name,
17
+ value,
18
+ checked = false,
19
+ required = false,
20
+ disabled = false,
21
+ class: className = '',
22
+ ariaDescribedby,
23
+ ariaLabel,
24
+ } = Astro.props;
25
+ ---
26
+
27
+ <input
28
+ type="checkbox"
29
+ id={id}
30
+ name={name}
31
+ value={value}
32
+ checked={checked}
33
+ required={required}
34
+ disabled={disabled}
35
+ class={className}
36
+ aria-describedby={ariaDescribedby}
37
+ aria-label={ariaLabel}
38
+ />
@@ -0,0 +1,199 @@
1
+ ---
2
+ import Copy from './icons/Copy.astro';
3
+ import Check from './icons/Check.astro';
4
+
5
+ interface Props {
6
+ value: string;
7
+ label?: string;
8
+ format?: string;
9
+ class?: string;
10
+ id?: string;
11
+ }
12
+
13
+ const {
14
+ value,
15
+ label,
16
+ format,
17
+ class: className = '',
18
+ id,
19
+ } = Astro.props;
20
+
21
+ const buttonId = id || `copy-btn-${Math.random().toString(36).substr(2, 9)}`;
22
+ ---
23
+
24
+ <button
25
+ type="button"
26
+ class={`copy-to-clipboard ${className}`.trim()}
27
+ data-copy-value={value}
28
+ data-copy-format={format}
29
+ aria-label={label || `Copy ${value} to clipboard`}
30
+ id={buttonId}
31
+ >
32
+ <span class="copy-to-clipboard__text">{value}</span>
33
+ <span class="copy-to-clipboard__icon copy-to-clipboard__icon--copy" aria-hidden="true">
34
+ <Copy width={16} height={16} />
35
+ </span>
36
+ <span class="copy-to-clipboard__icon copy-to-clipboard__icon--check" aria-hidden="true">
37
+ <Check width={16} height={16} />
38
+ </span>
39
+ <span class="copy-to-clipboard__feedback" aria-live="polite"></span>
40
+ </button>
41
+
42
+ <script>
43
+ (function initCopyToClipboard() {
44
+ // Wait for DOM to be ready
45
+ const init = () => {
46
+ const buttons = document.querySelectorAll('.copy-to-clipboard');
47
+
48
+ buttons.forEach((button) => {
49
+ // Skip if already initialized
50
+ if (button.hasAttribute('data-copy-initialized')) return;
51
+ button.setAttribute('data-copy-initialized', 'true');
52
+
53
+ const getValue = () => button.getAttribute('data-copy-value') || '';
54
+ const getFormat = () => button.getAttribute('data-copy-format') || '';
55
+ const feedback = button.querySelector('.copy-to-clipboard__feedback');
56
+ const copyIcon = button.querySelector('.copy-to-clipboard__icon--copy');
57
+ const checkIcon = button.querySelector('.copy-to-clipboard__icon--check');
58
+ const textSpan = button.querySelector('.copy-to-clipboard__text');
59
+
60
+ // Update text span with current value
61
+ const updateDisplay = () => {
62
+ const value = getValue();
63
+ if (textSpan) {
64
+ textSpan.textContent = value || '';
65
+ }
66
+ };
67
+
68
+ // Initial display update
69
+ updateDisplay();
70
+
71
+ // Watch for attribute changes (for dynamic updates)
72
+ const observer = new MutationObserver((mutations) => {
73
+ let shouldUpdate = false;
74
+ mutations.forEach((mutation) => {
75
+ if (mutation.type === 'attributes') {
76
+ if (mutation.attributeName === 'data-copy-value' || mutation.attributeName === 'data-copy-format') {
77
+ shouldUpdate = true;
78
+ }
79
+ }
80
+ });
81
+ if (shouldUpdate) {
82
+ // Use requestAnimationFrame to ensure DOM is ready
83
+ requestAnimationFrame(() => {
84
+ updateDisplay();
85
+ });
86
+ }
87
+ });
88
+ observer.observe(button, {
89
+ attributes: true,
90
+ attributeFilter: ['data-copy-value', 'data-copy-format'],
91
+ attributeOldValue: false
92
+ });
93
+
94
+ // Listen for custom value-updated event
95
+ button.addEventListener('value-updated', (event) => {
96
+ if (event && event.detail && event.detail.value) {
97
+ updateDisplay();
98
+ }
99
+ });
100
+
101
+ // Fallback: Periodically check for value changes (in case MutationObserver misses something)
102
+ let lastValue = getValue();
103
+ const intervalId = setInterval(() => {
104
+ const currentValue = getValue();
105
+ if (currentValue !== lastValue) {
106
+ lastValue = currentValue;
107
+ updateDisplay();
108
+ }
109
+ }, 300);
110
+
111
+ // Store interval ID for potential cleanup (though we don't need to clean it up in this case)
112
+ button.setAttribute('data-copy-interval-id', intervalId.toString());
113
+
114
+ const copyToClipboard = async () => {
115
+ // Always read fresh value on click (in case it was updated dynamically)
116
+ let value = getValue();
117
+ const format = getFormat();
118
+
119
+ // If value is still empty, try reading from text span as fallback
120
+ if (!value && textSpan) {
121
+ value = textSpan.textContent || '';
122
+ }
123
+
124
+ if (!value) {
125
+ return;
126
+ }
127
+
128
+ try {
129
+ await navigator.clipboard.writeText(value);
130
+
131
+ // Show success state
132
+ if (copyIcon) copyIcon.classList.add('copy-to-clipboard__icon--hidden');
133
+ if (checkIcon) checkIcon.classList.remove('copy-to-clipboard__icon--hidden');
134
+ if (feedback) {
135
+ feedback.textContent = format ? `Copied ${format}!` : 'Copied!';
136
+ }
137
+
138
+ // Reset after 2 seconds
139
+ setTimeout(() => {
140
+ if (copyIcon) copyIcon.classList.remove('copy-to-clipboard__icon--hidden');
141
+ if (checkIcon) checkIcon.classList.add('copy-to-clipboard__icon--hidden');
142
+ if (feedback) {
143
+ feedback.textContent = '';
144
+ }
145
+ }, 2000);
146
+ } catch (err) {
147
+ // Fallback for older browsers
148
+ const textArea = document.createElement('textarea');
149
+ textArea.value = value;
150
+ textArea.style.position = 'fixed';
151
+ textArea.style.left = '-999999px';
152
+ document.body.appendChild(textArea);
153
+ textArea.focus();
154
+ textArea.select();
155
+
156
+ try {
157
+ document.execCommand('copy');
158
+ if (copyIcon) copyIcon.classList.add('copy-to-clipboard__icon--hidden');
159
+ if (checkIcon) checkIcon.classList.remove('copy-to-clipboard__icon--hidden');
160
+ if (feedback) {
161
+ feedback.textContent = format ? `Copied ${format}!` : 'Copied!';
162
+ }
163
+
164
+ setTimeout(() => {
165
+ if (copyIcon) copyIcon.classList.remove('copy-to-clipboard__icon--hidden');
166
+ if (checkIcon) checkIcon.classList.add('copy-to-clipboard__icon--hidden');
167
+ if (feedback) {
168
+ feedback.textContent = '';
169
+ }
170
+ }, 2000);
171
+ } catch (fallbackErr) {
172
+ if (feedback) {
173
+ feedback.textContent = 'Failed to copy';
174
+ }
175
+ }
176
+
177
+ document.body.removeChild(textArea);
178
+ }
179
+ };
180
+
181
+ button.addEventListener('click', copyToClipboard);
182
+
183
+ // Keyboard support
184
+ button.addEventListener('keydown', (e) => {
185
+ if (e.key === 'Enter' || e.key === ' ') {
186
+ e.preventDefault();
187
+ copyToClipboard();
188
+ }
189
+ });
190
+ });
191
+ };
192
+
193
+ if (document.readyState === 'loading') {
194
+ document.addEventListener('DOMContentLoaded', init);
195
+ } else {
196
+ init();
197
+ }
198
+ })();
199
+ </script>
@@ -0,0 +1,37 @@
1
+ ---
2
+ interface Props {
3
+ /** Orientation of the divider line */
4
+ orientation?: 'horizontal' | 'vertical';
5
+ /** Optional label (e.g. "OR") shown in the middle; only used for horizontal */
6
+ label?: string;
7
+ class?: string;
8
+ }
9
+
10
+ const {
11
+ orientation = 'horizontal',
12
+ label,
13
+ class: className = '',
14
+ } = Astro.props;
15
+
16
+ const orientationClass = `divider--${orientation}`;
17
+ const hasLabel = Boolean(label && label.trim());
18
+ const labelClass = hasLabel ? 'divider--labeled' : '';
19
+ const classes = `divider ${orientationClass} ${labelClass} ${className}`.trim();
20
+ ---
21
+
22
+ <div
23
+ class={classes}
24
+ role="separator"
25
+ aria-orientation={orientation}
26
+ aria-label={label?.trim() || undefined}
27
+ >
28
+ {hasLabel && orientation === 'horizontal' ? (
29
+ <>
30
+ <span class="divider__line" aria-hidden="true" />
31
+ <span class="divider__label">{label!.trim()}</span>
32
+ <span class="divider__line" aria-hidden="true" />
33
+ </>
34
+ ) : (
35
+ <span class="divider__line" aria-hidden="true" />
36
+ )}
37
+ </div>