@miozu/jera 0.0.2 → 0.3.0

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 (53) hide show
  1. package/CLAUDE.md +443 -0
  2. package/README.md +211 -1
  3. package/llms.txt +64 -0
  4. package/package.json +44 -14
  5. package/src/actions/index.js +375 -0
  6. package/src/components/feedback/EmptyState.svelte +179 -0
  7. package/src/components/feedback/ProgressBar.svelte +116 -0
  8. package/src/components/feedback/Skeleton.svelte +107 -0
  9. package/src/components/feedback/Spinner.svelte +77 -0
  10. package/src/components/feedback/Toast.svelte +297 -0
  11. package/src/components/forms/Checkbox.svelte +147 -0
  12. package/src/components/forms/Dropzone.svelte +248 -0
  13. package/src/components/forms/FileUpload.svelte +266 -0
  14. package/src/components/forms/IconInput.svelte +184 -0
  15. package/src/components/forms/Input.svelte +121 -0
  16. package/src/components/forms/NumberInput.svelte +225 -0
  17. package/src/components/forms/PinInput.svelte +169 -0
  18. package/src/components/forms/Radio.svelte +143 -0
  19. package/src/components/forms/RadioGroup.svelte +62 -0
  20. package/src/components/forms/RangeSlider.svelte +212 -0
  21. package/src/components/forms/SearchInput.svelte +175 -0
  22. package/src/components/forms/Select.svelte +326 -0
  23. package/src/components/forms/Switch.svelte +159 -0
  24. package/src/components/forms/Textarea.svelte +122 -0
  25. package/src/components/navigation/Accordion.svelte +65 -0
  26. package/src/components/navigation/AccordionItem.svelte +146 -0
  27. package/src/components/navigation/Tabs.svelte +239 -0
  28. package/src/components/overlays/ConfirmDialog.svelte +272 -0
  29. package/src/components/overlays/Dropdown.svelte +153 -0
  30. package/src/components/overlays/DropdownDivider.svelte +23 -0
  31. package/src/components/overlays/DropdownItem.svelte +97 -0
  32. package/src/components/overlays/Modal.svelte +232 -0
  33. package/src/components/overlays/Popover.svelte +206 -0
  34. package/src/components/primitives/Avatar.svelte +132 -0
  35. package/src/components/primitives/Badge.svelte +118 -0
  36. package/src/components/primitives/Button.svelte +262 -0
  37. package/src/components/primitives/Card.svelte +104 -0
  38. package/src/components/primitives/Divider.svelte +105 -0
  39. package/src/components/primitives/LazyImage.svelte +104 -0
  40. package/src/components/primitives/Link.svelte +122 -0
  41. package/src/components/primitives/StatusBadge.svelte +122 -0
  42. package/src/index.js +128 -0
  43. package/src/tokens/colors.css +189 -0
  44. package/src/tokens/effects.css +128 -0
  45. package/src/tokens/index.css +81 -0
  46. package/src/tokens/spacing.css +49 -0
  47. package/src/tokens/typography.css +79 -0
  48. package/src/utils/cn.svelte.js +175 -0
  49. package/src/utils/index.js +17 -0
  50. package/src/utils/reactive.svelte.js +239 -0
  51. package/jera.js +0 -135
  52. package/www/components/jera/Input/Input.svelte +0 -63
  53. package/www/components/jera/Input/index.js +0 -1
@@ -0,0 +1,153 @@
1
+ <!--
2
+ @component Dropdown
3
+
4
+ An action menu dropdown component.
5
+
6
+ @example
7
+ <Dropdown>
8
+ {#snippet trigger()}
9
+ <Button>Options</Button>
10
+ {/snippet}
11
+ <DropdownItem onclick={handleEdit}>Edit</DropdownItem>
12
+ <DropdownItem onclick={handleDelete} variant="danger">Delete</DropdownItem>
13
+ </Dropdown>
14
+ -->
15
+ <script>
16
+ import { clickOutside, escapeKey } from '../../actions/index.js';
17
+ import { cn } from '../../utils/cn.svelte.js';
18
+
19
+ let {
20
+ open = $bindable(false),
21
+ position = 'bottom-start',
22
+ trigger,
23
+ children,
24
+ class: className = ''
25
+ } = $props();
26
+
27
+ let dropdownEl = $state(null);
28
+
29
+ function toggle() {
30
+ open = !open;
31
+ }
32
+
33
+ function close() {
34
+ open = false;
35
+ }
36
+
37
+ // Position classes
38
+ const positionMap = {
39
+ 'bottom-start': 'dropdown-bottom-start',
40
+ 'bottom-end': 'dropdown-bottom-end',
41
+ 'bottom-center': 'dropdown-bottom-center',
42
+ 'top-start': 'dropdown-top-start',
43
+ 'top-end': 'dropdown-top-end',
44
+ 'top-center': 'dropdown-top-center'
45
+ };
46
+ </script>
47
+
48
+ <div
49
+ class={cn('dropdown', className)}
50
+ bind:this={dropdownEl}
51
+ use:clickOutside={close}
52
+ use:escapeKey={close}
53
+ >
54
+ <div class="dropdown-trigger" onclick={toggle}>
55
+ {@render trigger?.()}
56
+ </div>
57
+
58
+ {#if open}
59
+ <div class="dropdown-content {positionMap[position] || positionMap['bottom-start']}">
60
+ {@render children?.()}
61
+ </div>
62
+ {/if}
63
+ </div>
64
+
65
+ <style>
66
+ .dropdown {
67
+ position: relative;
68
+ display: inline-block;
69
+ }
70
+
71
+ .dropdown-trigger {
72
+ display: inline-flex;
73
+ }
74
+
75
+ .dropdown-content {
76
+ position: absolute;
77
+ z-index: 50;
78
+ min-width: 10rem;
79
+ padding: var(--space-1);
80
+ background: var(--color-bg);
81
+ border: 1px solid var(--color-border);
82
+ border-radius: var(--radius-lg);
83
+ box-shadow: var(--shadow-lg);
84
+ animation: dropdown-enter 0.15s ease-out;
85
+ }
86
+
87
+ /* Position variants */
88
+ .dropdown-bottom-start {
89
+ top: 100%;
90
+ left: 0;
91
+ margin-top: var(--space-1);
92
+ }
93
+
94
+ .dropdown-bottom-end {
95
+ top: 100%;
96
+ right: 0;
97
+ margin-top: var(--space-1);
98
+ }
99
+
100
+ .dropdown-bottom-center {
101
+ top: 100%;
102
+ left: 50%;
103
+ transform: translateX(-50%);
104
+ margin-top: var(--space-1);
105
+ }
106
+
107
+ .dropdown-top-start {
108
+ bottom: 100%;
109
+ left: 0;
110
+ margin-bottom: var(--space-1);
111
+ }
112
+
113
+ .dropdown-top-end {
114
+ bottom: 100%;
115
+ right: 0;
116
+ margin-bottom: var(--space-1);
117
+ }
118
+
119
+ .dropdown-top-center {
120
+ bottom: 100%;
121
+ left: 50%;
122
+ transform: translateX(-50%);
123
+ margin-bottom: var(--space-1);
124
+ }
125
+
126
+ @keyframes dropdown-enter {
127
+ from {
128
+ opacity: 0;
129
+ transform: translateY(-4px);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: translateY(0);
134
+ }
135
+ }
136
+
137
+ .dropdown-top-start,
138
+ .dropdown-top-end,
139
+ .dropdown-top-center {
140
+ animation-name: dropdown-enter-up;
141
+ }
142
+
143
+ @keyframes dropdown-enter-up {
144
+ from {
145
+ opacity: 0;
146
+ transform: translateY(4px);
147
+ }
148
+ to {
149
+ opacity: 1;
150
+ transform: translateY(0);
151
+ }
152
+ }
153
+ </style>
@@ -0,0 +1,23 @@
1
+ <!--
2
+ @component DropdownDivider
3
+
4
+ A divider within a Dropdown menu.
5
+
6
+ @example
7
+ <DropdownItem>Edit</DropdownItem>
8
+ <DropdownDivider />
9
+ <DropdownItem variant="danger">Delete</DropdownItem>
10
+ -->
11
+ <script>
12
+ let { class: className = '' } = $props();
13
+ </script>
14
+
15
+ <div class="dropdown-divider {className}" role="separator"></div>
16
+
17
+ <style>
18
+ .dropdown-divider {
19
+ height: 1px;
20
+ margin: var(--space-1) 0;
21
+ background: var(--color-border-muted);
22
+ }
23
+ </style>
@@ -0,0 +1,97 @@
1
+ <!--
2
+ @component DropdownItem
3
+
4
+ An item within a Dropdown menu.
5
+
6
+ @example
7
+ <DropdownItem onclick={handleAction}>Action</DropdownItem>
8
+
9
+ @example
10
+ <DropdownItem onclick={handleDelete} variant="danger">Delete</DropdownItem>
11
+ -->
12
+ <script>
13
+ import { cn } from '../../utils/cn.svelte.js';
14
+
15
+ let {
16
+ variant = 'default',
17
+ disabled = false,
18
+ icon,
19
+ children,
20
+ onclick,
21
+ class: className = '',
22
+ ...rest
23
+ } = $props();
24
+ </script>
25
+
26
+ <button
27
+ type="button"
28
+ class={cn(
29
+ 'dropdown-item',
30
+ `dropdown-item-${variant}`,
31
+ disabled && 'dropdown-item-disabled',
32
+ className
33
+ )}
34
+ {disabled}
35
+ {onclick}
36
+ {...rest}
37
+ >
38
+ {#if icon}
39
+ <span class="dropdown-item-icon">
40
+ {@render icon()}
41
+ </span>
42
+ {/if}
43
+ <span class="dropdown-item-label">
44
+ {@render children?.()}
45
+ </span>
46
+ </button>
47
+
48
+ <style>
49
+ .dropdown-item {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: var(--space-2);
53
+ width: 100%;
54
+ padding: var(--space-2) var(--space-3);
55
+ font-size: var(--text-sm);
56
+ text-align: left;
57
+ color: var(--color-text);
58
+ background: transparent;
59
+ border: none;
60
+ border-radius: var(--radius-md);
61
+ cursor: pointer;
62
+ transition: var(--transition-colors);
63
+ }
64
+
65
+ .dropdown-item:hover:not(:disabled) {
66
+ background: var(--color-surface-hover);
67
+ }
68
+
69
+ .dropdown-item:focus-visible {
70
+ outline: none;
71
+ background: var(--color-surface-hover);
72
+ }
73
+
74
+ .dropdown-item-disabled {
75
+ opacity: 0.5;
76
+ cursor: not-allowed;
77
+ }
78
+
79
+ .dropdown-item-danger {
80
+ color: var(--color-error);
81
+ }
82
+
83
+ .dropdown-item-danger:hover:not(:disabled) {
84
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
85
+ }
86
+
87
+ .dropdown-item-icon {
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ flex-shrink: 0;
92
+ }
93
+
94
+ .dropdown-item-label {
95
+ flex: 1;
96
+ }
97
+ </style>
@@ -0,0 +1,232 @@
1
+ <!--
2
+ @component Modal
3
+
4
+ A flexible modal dialog component with backdrop, focus trap, and escape key support.
5
+
6
+ @example
7
+ <Modal bind:open={showModal} title="Confirm Action">
8
+ <p>Are you sure you want to proceed?</p>
9
+ {#snippet footer()}
10
+ <Button variant="ghost" onclick={() => showModal = false}>Cancel</Button>
11
+ <Button variant="primary" onclick={handleConfirm}>Confirm</Button>
12
+ {/snippet}
13
+ </Modal>
14
+ -->
15
+ <script>
16
+ import { focusTrap, escapeKey, portal } from '../../actions/index.js';
17
+ import { cv } from '../../utils/cn.svelte.js';
18
+
19
+ let {
20
+ open = $bindable(false),
21
+ title = '',
22
+ size = 'md',
23
+ variant = 'default',
24
+ closeOnBackdrop = true,
25
+ closeOnEscape = true,
26
+ showClose = true,
27
+ children,
28
+ footer,
29
+ icon,
30
+ onclose = () => {},
31
+ class: className = ''
32
+ } = $props();
33
+
34
+ // Variant styles for the icon container
35
+ const iconVariants = {
36
+ default: { bg: 'var(--color-surface-alt)', color: 'var(--color-text)' },
37
+ danger: { bg: 'color-mix(in srgb, var(--color-error) 10%, transparent)', color: 'var(--color-error)' },
38
+ warning: { bg: 'color-mix(in srgb, var(--color-warning) 10%, transparent)', color: 'var(--color-warning)' },
39
+ success: { bg: 'color-mix(in srgb, var(--color-success) 10%, transparent)', color: 'var(--color-success)' },
40
+ info: { bg: 'color-mix(in srgb, var(--color-info) 10%, transparent)', color: 'var(--color-info)' }
41
+ };
42
+
43
+ const iconStyle = $derived(iconVariants[variant] || iconVariants.default);
44
+
45
+ function close() {
46
+ open = false;
47
+ onclose();
48
+ }
49
+
50
+ function handleBackdropClick(e) {
51
+ if (closeOnBackdrop && e.target === e.currentTarget) {
52
+ close();
53
+ }
54
+ }
55
+
56
+ function handleEscape() {
57
+ if (closeOnEscape) {
58
+ close();
59
+ }
60
+ }
61
+ </script>
62
+
63
+ {#if open}
64
+ <div
65
+ class="modal-backdrop"
66
+ onclick={handleBackdropClick}
67
+ role="dialog"
68
+ aria-modal="true"
69
+ aria-labelledby={title ? 'modal-title' : undefined}
70
+ use:portal={'body'}
71
+ use:focusTrap={{ enabled: open }}
72
+ use:escapeKey={handleEscape}
73
+ >
74
+ <div class="modal modal-{size} {className}">
75
+ <!-- Close button -->
76
+ {#if showClose}
77
+ <button
78
+ class="modal-close"
79
+ onclick={close}
80
+ aria-label="Close modal"
81
+ type="button"
82
+ >
83
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
84
+ <line x1="18" y1="6" x2="6" y2="18"></line>
85
+ <line x1="6" y1="6" x2="18" y2="18"></line>
86
+ </svg>
87
+ </button>
88
+ {/if}
89
+
90
+ <!-- Content -->
91
+ <div class="modal-content">
92
+ {#if icon || title}
93
+ <div class="modal-header">
94
+ {#if icon}
95
+ <div class="modal-icon" style="background: {iconStyle.bg}; color: {iconStyle.color};">
96
+ {@render icon()}
97
+ </div>
98
+ {/if}
99
+
100
+ {#if title || children}
101
+ <div class="modal-text">
102
+ {#if title}
103
+ <h3 id="modal-title" class="modal-title">{title}</h3>
104
+ {/if}
105
+ {#if children}
106
+ <div class="modal-body">
107
+ {@render children()}
108
+ </div>
109
+ {/if}
110
+ </div>
111
+ {/if}
112
+ </div>
113
+ {:else if children}
114
+ <div class="modal-body">
115
+ {@render children()}
116
+ </div>
117
+ {/if}
118
+
119
+ {#if footer}
120
+ <div class="modal-footer">
121
+ {@render footer()}
122
+ </div>
123
+ {/if}
124
+ </div>
125
+ </div>
126
+ </div>
127
+ {/if}
128
+
129
+ <style>
130
+ .modal-backdrop {
131
+ position: fixed;
132
+ inset: 0;
133
+ z-index: 100;
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ background: color-mix(in srgb, var(--color-bg) 80%, transparent);
138
+ backdrop-filter: blur(4px);
139
+ }
140
+
141
+ .modal {
142
+ position: relative;
143
+ background: var(--color-surface);
144
+ border-radius: 0.75rem;
145
+ box-shadow: var(--shadow-2xl);
146
+ border: 1px solid var(--color-border);
147
+ margin: 1rem;
148
+ animation: modal-enter 0.2s ease-out;
149
+ }
150
+
151
+ /* Size variants */
152
+ .modal-sm { width: 100%; max-width: 20rem; }
153
+ .modal-md { width: 100%; max-width: 28rem; }
154
+ .modal-lg { width: 100%; max-width: 36rem; }
155
+ .modal-xl { width: 100%; max-width: 48rem; }
156
+ .modal-full { width: 100%; max-width: calc(100vw - 2rem); max-height: calc(100vh - 2rem); }
157
+
158
+ .modal-close {
159
+ position: absolute;
160
+ top: 1rem;
161
+ right: 1rem;
162
+ padding: 0.25rem;
163
+ background: transparent;
164
+ border: none;
165
+ border-radius: 0.5rem;
166
+ color: var(--color-text);
167
+ cursor: pointer;
168
+ transition: background 0.15s, color 0.15s;
169
+ }
170
+
171
+ .modal-close:hover {
172
+ background: var(--color-surface-alt);
173
+ color: var(--color-text-strong);
174
+ }
175
+
176
+ .modal-content {
177
+ padding: 1.5rem;
178
+ }
179
+
180
+ .modal-header {
181
+ display: flex;
182
+ align-items: flex-start;
183
+ gap: 1rem;
184
+ }
185
+
186
+ .modal-icon {
187
+ flex-shrink: 0;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ width: 2.5rem;
192
+ height: 2.5rem;
193
+ border-radius: 0.5rem;
194
+ }
195
+
196
+ .modal-text {
197
+ flex: 1;
198
+ padding-top: 0.25rem;
199
+ }
200
+
201
+ .modal-title {
202
+ margin: 0 0 0.5rem 0;
203
+ font-size: 1.125rem;
204
+ font-weight: 600;
205
+ color: var(--color-text-strong);
206
+ line-height: 1.4;
207
+ }
208
+
209
+ .modal-body {
210
+ font-size: 0.875rem;
211
+ color: var(--color-text);
212
+ line-height: 1.5;
213
+ }
214
+
215
+ .modal-footer {
216
+ display: flex;
217
+ gap: 0.75rem;
218
+ margin-top: 1.5rem;
219
+ justify-content: flex-end;
220
+ }
221
+
222
+ @keyframes modal-enter {
223
+ from {
224
+ opacity: 0;
225
+ transform: scale(0.95) translateY(10px);
226
+ }
227
+ to {
228
+ opacity: 1;
229
+ transform: scale(1) translateY(0);
230
+ }
231
+ }
232
+ </style>
@@ -0,0 +1,206 @@
1
+ <!--
2
+ @component Popover
3
+
4
+ A tooltip/popover component with smart positioning and hover interaction.
5
+
6
+ @example
7
+ <Popover content="This is helpful information" position="top">
8
+ <Button>Hover me</Button>
9
+ </Popover>
10
+
11
+ @example With custom content
12
+ <Popover position="bottom">
13
+ {#snippet content()}
14
+ <div>Custom <strong>HTML</strong> content</div>
15
+ {/snippet}
16
+ <Button>Hover for details</Button>
17
+ </Popover>
18
+ -->
19
+ <script>
20
+ import { fly } from 'svelte/transition';
21
+
22
+ let {
23
+ children,
24
+ content = '',
25
+ position = 'top',
26
+ delay = { show: 100, hide: 100 },
27
+ offset = 8,
28
+ class: className = ''
29
+ } = $props();
30
+
31
+ let visible = $state(false);
32
+ let timeoutId = $state(null);
33
+ let popoverEl = $state(null);
34
+ let triggerEl = $state(null);
35
+ let isHoveringPopover = $state(false);
36
+ let windowWidth = $state(0);
37
+ let windowHeight = $state(0);
38
+
39
+ // Animation configs based on position
40
+ const animations = {
41
+ top: { in: { y: 8, duration: 200 }, out: { y: -8, duration: 150 } },
42
+ bottom: { in: { y: -8, duration: 200 }, out: { y: 8, duration: 150 } },
43
+ left: { in: { x: 8, duration: 200 }, out: { x: -8, duration: 150 } },
44
+ right: { in: { x: -8, duration: 200 }, out: { x: 8, duration: 150 } }
45
+ };
46
+
47
+ const anim = $derived(animations[position] || animations.top);
48
+
49
+ function handleMouseEnter(event) {
50
+ clearTimeout(timeoutId);
51
+ triggerEl = event.currentTarget;
52
+ timeoutId = setTimeout(() => {
53
+ visible = true;
54
+ requestAnimationFrame(() => {
55
+ requestAnimationFrame(() => {
56
+ positionPopover();
57
+ });
58
+ });
59
+ }, delay.show);
60
+ }
61
+
62
+ function handleMouseLeave() {
63
+ clearTimeout(timeoutId);
64
+ timeoutId = setTimeout(() => {
65
+ if (!isHoveringPopover) {
66
+ visible = false;
67
+ }
68
+ }, delay.hide);
69
+ }
70
+
71
+ function handlePopoverEnter() {
72
+ clearTimeout(timeoutId);
73
+ isHoveringPopover = true;
74
+ }
75
+
76
+ function handlePopoverLeave() {
77
+ isHoveringPopover = false;
78
+ handleMouseLeave();
79
+ }
80
+
81
+ function positionPopover() {
82
+ if (!popoverEl || !triggerEl) return;
83
+
84
+ const triggerRect = triggerEl.getBoundingClientRect();
85
+ const popoverWidth = popoverEl.offsetWidth;
86
+ const popoverHeight = popoverEl.offsetHeight;
87
+
88
+ const triggerCenterX = triggerRect.left + triggerRect.width / 2;
89
+ const triggerCenterY = triggerRect.top + triggerRect.height / 2;
90
+
91
+ let left = 0;
92
+ let top = 0;
93
+
94
+ switch (position) {
95
+ case 'top':
96
+ left = triggerCenterX - popoverWidth / 2;
97
+ top = triggerRect.top - popoverHeight - offset;
98
+ break;
99
+ case 'bottom':
100
+ left = triggerCenterX - popoverWidth / 2;
101
+ top = triggerRect.bottom + offset;
102
+ break;
103
+ case 'left':
104
+ left = triggerRect.left - popoverWidth - offset;
105
+ top = triggerCenterY - popoverHeight / 2;
106
+ break;
107
+ case 'right':
108
+ left = triggerRect.right + offset;
109
+ top = triggerCenterY - popoverHeight / 2;
110
+ break;
111
+ }
112
+
113
+ // Viewport boundary detection
114
+ const margin = 8;
115
+
116
+ if (left < margin) {
117
+ left = margin;
118
+ } else if (left + popoverWidth > windowWidth - margin) {
119
+ left = windowWidth - popoverWidth - margin;
120
+ }
121
+
122
+ if (top < margin) {
123
+ if (position === 'top') {
124
+ top = triggerRect.bottom + offset;
125
+ } else {
126
+ top = margin;
127
+ }
128
+ } else if (top + popoverHeight > windowHeight - margin) {
129
+ if (position === 'bottom') {
130
+ top = triggerRect.top - popoverHeight - offset;
131
+ } else {
132
+ top = windowHeight - popoverHeight - margin;
133
+ }
134
+ }
135
+
136
+ popoverEl.style.left = `${left}px`;
137
+ popoverEl.style.top = `${top}px`;
138
+ }
139
+
140
+ function handleScroll() {
141
+ if (visible && triggerEl && popoverEl) {
142
+ requestAnimationFrame(positionPopover);
143
+ }
144
+ }
145
+ </script>
146
+
147
+ <svelte:window
148
+ bind:innerWidth={windowWidth}
149
+ bind:innerHeight={windowHeight}
150
+ onscroll={visible ? handleScroll : undefined}
151
+ onresize={visible ? handleScroll : undefined}
152
+ />
153
+
154
+ <div
155
+ class="popover-wrapper {className}"
156
+ onmouseenter={handleMouseEnter}
157
+ onmouseleave={handleMouseLeave}
158
+ >
159
+ {@render children?.()}
160
+
161
+ {#if visible}
162
+ <div
163
+ class="popover"
164
+ role="tooltip"
165
+ in:fly={anim.in}
166
+ out:fly={anim.out}
167
+ bind:this={popoverEl}
168
+ onmouseenter={handlePopoverEnter}
169
+ onmouseleave={handlePopoverLeave}
170
+ >
171
+ {#if typeof content === 'string'}
172
+ {content}
173
+ {:else if typeof content === 'function'}
174
+ {@render content()}
175
+ {:else}
176
+ {content}
177
+ {/if}
178
+ </div>
179
+ {/if}
180
+ </div>
181
+
182
+ <style>
183
+ .popover-wrapper {
184
+ position: relative;
185
+ display: inline-block;
186
+ }
187
+
188
+ .popover {
189
+ position: fixed;
190
+ z-index: 9999;
191
+ min-width: 8rem;
192
+ max-width: 18rem;
193
+ width: max-content;
194
+ padding: 0.5rem 0.75rem;
195
+ background: var(--color-surface);
196
+ color: var(--color-text-strong);
197
+ font-size: 0.875rem;
198
+ line-height: 1.5;
199
+ border-radius: 0.5rem;
200
+ box-shadow: var(--shadow-lg);
201
+ border: 1px solid var(--color-border);
202
+ pointer-events: auto;
203
+ word-wrap: break-word;
204
+ hyphens: auto;
205
+ }
206
+ </style>