@stephen-netu/design-system 0.1.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 (204) hide show
  1. package/README.md +66 -0
  2. package/package.json +130 -0
  3. package/src/b0-app-shell/clarification-overlay/ClarificationOverlay.svelte +221 -0
  4. package/src/b0-app-shell/clarification-overlay/index.ts +1 -0
  5. package/src/b0-app-shell/command-bar/CommandBar.svelte +565 -0
  6. package/src/b0-app-shell/index.ts +14 -0
  7. package/src/base.css +152 -0
  8. package/src/d0-data-viz/BarChart.svelte +236 -0
  9. package/src/d0-data-viz/CheckpointBar.svelte +574 -0
  10. package/src/d0-data-viz/CheckpointPill.svelte +297 -0
  11. package/src/d0-data-viz/MetricCard.svelte +159 -0
  12. package/src/d0-data-viz/SparkLine.svelte +66 -0
  13. package/src/d0-data-viz/StorageMap.svelte +182 -0
  14. package/src/d0-data-viz/Timeline.svelte +188 -0
  15. package/src/d0-data-viz/checkpoint-types.ts +162 -0
  16. package/src/d0-data-viz/d0-tokens.css +32 -0
  17. package/src/d0-data-viz/index.ts +58 -0
  18. package/src/d0-data-viz/storage-map-types.ts +142 -0
  19. package/src/d0-data-viz/theme-integration.ts +134 -0
  20. package/src/d0-data-viz/types.ts +53 -0
  21. package/src/demos/BspSizingInfographic.svelte +386 -0
  22. package/src/demos/BspZoneDemo.svelte +255 -0
  23. package/src/demos/ControlRoomDemo.svelte +570 -0
  24. package/src/demos/NodepadBlocksShowcase.svelte +189 -0
  25. package/src/demos/index.ts +7 -0
  26. package/src/editor/CodeEditor.svelte +188 -0
  27. package/src/editor/FindReplaceDialog.svelte +516 -0
  28. package/src/editor/MarkdownEditor.svelte +660 -0
  29. package/src/editor/index.ts +2 -0
  30. package/src/entity-graph-view/WorldView.svelte +171 -0
  31. package/src/entity-graph-view/index.ts +6 -0
  32. package/src/entity-graph-view/types.ts +364 -0
  33. package/src/epistemic/index.ts +2 -0
  34. package/src/epistemic/types.ts +134 -0
  35. package/src/epistemic/utils.ts +32 -0
  36. package/src/f0-forms/checkbox/Checkbox.svelte +158 -0
  37. package/src/f0-forms/field/FormField.svelte +91 -0
  38. package/src/f0-forms/index.ts +11 -0
  39. package/src/f0-forms/radio/RadioGroup.svelte +192 -0
  40. package/src/f0-forms/search/SearchBar.svelte +99 -0
  41. package/src/f0-forms/select/Select.svelte +198 -0
  42. package/src/f0-forms/textarea/TextArea.svelte +170 -0
  43. package/src/f0-forms/toggle/FormToggle.svelte +147 -0
  44. package/src/f0-forms/validation/validate.ts +47 -0
  45. package/src/fabric/ai/ChatPanel.svelte +648 -0
  46. package/src/fabric/ai/index.ts +11 -0
  47. package/src/fabric/data/index.ts +21 -0
  48. package/src/fabric/feedback/StatusBar.svelte +274 -0
  49. package/src/fabric/feedback/ToastContainer.svelte +122 -0
  50. package/src/fabric/feedback/index.ts +14 -0
  51. package/src/fabric/index.ts +37 -0
  52. package/src/fabric/input/CommandBar.svelte +79 -0
  53. package/src/fabric/input/KeyboardShortcuts.svelte +388 -0
  54. package/src/fabric/input/index.ts +12 -0
  55. package/src/fabric/layout/GlassBox.svelte +193 -0
  56. package/src/fabric/layout/KanbanBoard.svelte +158 -0
  57. package/src/fabric/layout/KanbanCard.svelte +226 -0
  58. package/src/fabric/layout/KanbanColumn.svelte +278 -0
  59. package/src/fabric/layout/index.ts +17 -0
  60. package/src/fabric/navigation/ActivityRail.svelte +271 -0
  61. package/src/fabric/navigation/index.ts +11 -0
  62. package/src/g0-geo/SovereignMap.svelte +149 -0
  63. package/src/g0-geo/index.ts +2 -0
  64. package/src/index.ts +68 -0
  65. package/src/l0-layout/block-writer/BlockConnectionLines.svelte +98 -0
  66. package/src/l0-layout/block-writer/BlockContent.svelte +213 -0
  67. package/src/l0-layout/block-writer/BlockHeader.svelte +230 -0
  68. package/src/l0-layout/block-writer/BlockItem.svelte +177 -0
  69. package/src/l0-layout/block-writer/BlockWriter.svelte +219 -0
  70. package/src/l0-layout/block-writer/block-writer-types.ts +63 -0
  71. package/src/l0-layout/block-writer/index.ts +24 -0
  72. package/src/l0-layout/block-writer/markdown.ts +110 -0
  73. package/src/l0-layout/chat-view/ChatViewLayout.svelte +61 -0
  74. package/src/l0-layout/epistemic/AllayDetailPanel.svelte +113 -0
  75. package/src/l0-layout/epistemic/AssayDetailPanel.svelte +164 -0
  76. package/src/l0-layout/epistemic/AvenueTrace.svelte +253 -0
  77. package/src/l0-layout/epistemic/KnowledgeDetailPanel.svelte +204 -0
  78. package/src/l0-layout/epistemic/ParadigmLens.svelte +170 -0
  79. package/src/l0-layout/epistemic/ProvenanceChain.svelte +164 -0
  80. package/src/l0-layout/epistemic/TrustView.svelte +195 -0
  81. package/src/l0-layout/epistemic/index.ts +7 -0
  82. package/src/l0-layout/grove/AccordCard.svelte +156 -0
  83. package/src/l0-layout/grove/GroveCard.svelte +197 -0
  84. package/src/l0-layout/grove/MemberBadge.svelte +96 -0
  85. package/src/l0-layout/grove/MemberList.svelte +59 -0
  86. package/src/l0-layout/grove/grove-types.ts +29 -0
  87. package/src/l0-layout/grove/index.ts +14 -0
  88. package/src/l0-layout/index.ts +45 -0
  89. package/src/l0-layout/kanban/KanbanBoard.svelte +71 -0
  90. package/src/l0-layout/kanban/KanbanCard.svelte +276 -0
  91. package/src/l0-layout/kanban/KanbanColumn.svelte +198 -0
  92. package/src/l0-layout/kanban/KanbanViewLayout.svelte +61 -0
  93. package/src/l0-layout/workspace/WorkspaceLayout.svelte +93 -0
  94. package/src/n0-node-graph/GraphEditor.svelte +342 -0
  95. package/src/n0-node-graph/GraphHistory.svelte +122 -0
  96. package/src/n0-node-graph/Node.svelte +269 -0
  97. package/src/n0-node-graph/NodeCanvas.svelte +187 -0
  98. package/src/n0-node-graph/NodeEdge.svelte +152 -0
  99. package/src/n0-node-graph/NodePalette.svelte +318 -0
  100. package/src/n0-node-graph/NodePort.svelte +128 -0
  101. package/src/n0-node-graph/index.ts +33 -0
  102. package/src/n0-node-graph/n0-tokens.css +29 -0
  103. package/src/n0-node-graph/node-port-types.ts +19 -0
  104. package/src/n0-node-graph/types.ts +96 -0
  105. package/src/n0-node-graph/validate.test.ts +722 -0
  106. package/src/n0-node-graph/validate.ts +371 -0
  107. package/src/n1-flow-canvas/FlowCanvas.svelte +202 -0
  108. package/src/n1-flow-canvas/graph-state.svelte.ts +95 -0
  109. package/src/n1-flow-canvas/index.ts +21 -0
  110. package/src/n1-flow-canvas/layout.ts +118 -0
  111. package/src/n1-flow-canvas/layout.worker.ts +128 -0
  112. package/src/n1-flow-canvas/types.ts +102 -0
  113. package/src/n1-flow-canvas/utils.ts +146 -0
  114. package/src/n2-manifold-disk/ManifoldDisk.svelte +314 -0
  115. package/src/n2-manifold-disk/index.ts +6 -0
  116. package/src/n2-manifold-disk/poincare.ts +184 -0
  117. package/src/n3-force-graph/ForceGraph.svelte +180 -0
  118. package/src/n3-force-graph/GraphTooltip.svelte +99 -0
  119. package/src/n3-force-graph/animated-edge.ts +107 -0
  120. package/src/n3-force-graph/health-node.ts +128 -0
  121. package/src/n3-force-graph/index.ts +31 -0
  122. package/src/n3-force-graph/types.ts +94 -0
  123. package/src/n4-adaptive-manifold/AdaptiveManifold.svelte +1165 -0
  124. package/src/n4-adaptive-manifold/DetailCard.svelte +212 -0
  125. package/src/n4-adaptive-manifold/SuggestionIndicator.svelte +91 -0
  126. package/src/n4-adaptive-manifold/classifier.ts +139 -0
  127. package/src/n4-adaptive-manifold/index.ts +45 -0
  128. package/src/n4-adaptive-manifold/n4-tokens.css +155 -0
  129. package/src/n4-adaptive-manifold/orchestrator.ts +301 -0
  130. package/src/n4-adaptive-manifold/phase-theme.ts +92 -0
  131. package/src/n4-adaptive-manifold/pinnacle-events.ts +135 -0
  132. package/src/n4-adaptive-manifold/providers/gravitational-provider.ts +191 -0
  133. package/src/n4-adaptive-manifold/providers/manifold-provider.ts +276 -0
  134. package/src/n4-adaptive-manifold/transition.ts +90 -0
  135. package/src/n4-adaptive-manifold/types.ts +313 -0
  136. package/src/p0-primitives/index.ts +6 -0
  137. package/src/p0-primitives/tokens/control-room.css +309 -0
  138. package/src/p0-primitives/tokens/index.ts +6 -0
  139. package/src/p0-primitives/tokens/tokens.css +140 -0
  140. package/src/p0-primitives/tokens/tokens.json +132 -0
  141. package/src/p0-primitives/types/index.ts +123 -0
  142. package/src/p0-primitives/types/plugin-types.ts +74 -0
  143. package/src/p0-primitives/utils/index.ts +39 -0
  144. package/src/platform/hud-protocol.ts +20 -0
  145. package/src/platform/index.ts +3 -0
  146. package/src/s0-state/create-store.ts +84 -0
  147. package/src/s0-state/derived.ts +41 -0
  148. package/src/s0-state/index.ts +6 -0
  149. package/src/showcase/Showcase.svelte +401 -0
  150. package/src/spatial/BspTilingCanvas.svelte +232 -0
  151. package/src/spatial/ForceCanvas.svelte +289 -0
  152. package/src/spatial/LodRenderer.svelte +334 -0
  153. package/src/spatial/SnapZoomCamera.svelte +185 -0
  154. package/src/spatial/ZoneTiler.svelte +311 -0
  155. package/src/spatial/camera-types.ts +45 -0
  156. package/src/spatial/content-proportional-bsp.ts +253 -0
  157. package/src/spatial/force-canvas-types.ts +102 -0
  158. package/src/spatial/index.ts +15 -0
  159. package/src/spatial/lod-types.ts +70 -0
  160. package/src/spatial/zone-tiler-types.ts +177 -0
  161. package/src/stores/vaultStore.svelte +14 -0
  162. package/src/t0-transport/connection.ts +34 -0
  163. package/src/t0-transport/error.ts +37 -0
  164. package/src/t0-transport/events.ts +50 -0
  165. package/src/t0-transport/index.ts +6 -0
  166. package/src/t0-transport/invoke.ts +30 -0
  167. package/src/tauri/index.ts +13 -0
  168. package/src/u0-primitives/allay-gauge/AllayGauge.svelte +90 -0
  169. package/src/u0-primitives/allay-gauge/index.ts +1 -0
  170. package/src/u0-primitives/animated-icon/AnimatedIcon.svelte +273 -0
  171. package/src/u0-primitives/animated-icon/README.md +53 -0
  172. package/src/u0-primitives/animated-icon/index.ts +31 -0
  173. package/src/u0-primitives/assay-badge/AssayBadge.svelte +121 -0
  174. package/src/u0-primitives/assay-badge/AssaySignalBar.svelte +83 -0
  175. package/src/u0-primitives/assay-badge/index.ts +2 -0
  176. package/src/u0-primitives/avatar/Avatar.svelte +177 -0
  177. package/src/u0-primitives/badge/Badge.svelte +149 -0
  178. package/src/u0-primitives/button/Button.svelte +304 -0
  179. package/src/u0-primitives/card/Card.svelte +182 -0
  180. package/src/u0-primitives/dropdown/Dropdown.svelte +305 -0
  181. package/src/u0-primitives/expert-badge/ExpertBadge.svelte +89 -0
  182. package/src/u0-primitives/expert-badge/index.ts +1 -0
  183. package/src/u0-primitives/index.ts +35 -0
  184. package/src/u0-primitives/input/Input.svelte +237 -0
  185. package/src/u0-primitives/modal/Modal.svelte +331 -0
  186. package/src/u0-primitives/modal/index.ts +1 -0
  187. package/src/u0-primitives/spinner/Spinner.svelte +129 -0
  188. package/src/u0-primitives/tabs/Tabs.svelte +225 -0
  189. package/src/u0-primitives/tabs/tabs-types.ts +11 -0
  190. package/src/u0-primitives/toggle/Toggle.svelte +161 -0
  191. package/src/u0-primitives/tooltip/Tooltip.svelte +234 -0
  192. package/src/v0-diagrams/DiagramPanel.svelte +68 -0
  193. package/src/v0-diagrams/MarkmapDiagram.svelte +56 -0
  194. package/src/v0-diagrams/MermaidDiagram.svelte +53 -0
  195. package/src/v0-diagrams/index.ts +4 -0
  196. package/src/v0-diagrams/types.ts +28 -0
  197. package/src/v0-diagrams/v0-tokens.css +6 -0
  198. package/src/vite-env.d.ts +7 -0
  199. package/src/x0-enchanted-blocks/ENCHANTED_BLOCKS_SPEC.md +457 -0
  200. package/src/x0-enchanted-blocks/components/EnchantedBlock.svelte +485 -0
  201. package/src/x0-enchanted-blocks/components/NodepadBlock.svelte +262 -0
  202. package/src/x0-enchanted-blocks/core/block-layout.ts +211 -0
  203. package/src/x0-enchanted-blocks/core/block-types.ts +227 -0
  204. package/src/x0-enchanted-blocks/index.ts +46 -0
@@ -0,0 +1,331 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Modal Component
4
+ *
5
+ * A reusable modal dialog that uses CSS visibility toggling instead of
6
+ * conditional rendering to avoid blank modal issues in Tauri.
7
+ *
8
+ * Usage:
9
+ * <Modal bind:isOpen title="My Modal" onClose={handleClose}>
10
+ * <p>Modal content here</p>
11
+ * </Modal>
12
+ */
13
+
14
+ import { tick } from 'svelte';
15
+
16
+ interface Props {
17
+ /** Controls modal visibility */
18
+ isOpen?: boolean;
19
+ /** Modal title (optional) */
20
+ title?: string;
21
+ /** Called when modal is closed */
22
+ onClose?: () => void;
23
+ /** Additional CSS class for the modal container */
24
+ class?: string;
25
+ /** Whether to show the close button (default: true) */
26
+ showCloseButton?: boolean;
27
+ /** Whether to close on overlay click (default: true) */
28
+ closeOnOverlayClick?: boolean;
29
+ /** Whether to close on Escape key (default: true) */
30
+ closeOnEscape?: boolean;
31
+ /** Z-index for the modal (default: 9980) */
32
+ zIndex?: number;
33
+ /** Modal content */
34
+ children?: import('svelte').Snippet;
35
+ /** Additional actions for the header (optional) */
36
+ headerActions?: import('svelte').Snippet;
37
+ /** Footer content (optional) */
38
+ footer?: import('svelte').Snippet;
39
+ }
40
+
41
+ let {
42
+ isOpen = $bindable(false),
43
+ title = '',
44
+ onClose = () => {},
45
+ class: className = '',
46
+ showCloseButton = true,
47
+ closeOnOverlayClick = true,
48
+ closeOnEscape = true,
49
+ zIndex = 9980,
50
+ children,
51
+ headerActions,
52
+ footer
53
+ }: Props = $props();
54
+
55
+ let modalElement = $state<HTMLDivElement | undefined>(undefined);
56
+ let previouslyFocusedElement: Element | null = null;
57
+ let isVisible = $state(false);
58
+ const modalId = `modal-${Math.random().toString(36).slice(2, 9)}`;
59
+
60
+ // Handle isOpen changes with animation delay
61
+ $effect(() => {
62
+ if (isOpen) {
63
+ // Store previously focused element
64
+ previouslyFocusedElement = document.activeElement;
65
+ // Show modal (triggers CSS transition)
66
+ isVisible = true;
67
+ // Focus management after render
68
+ tick().then(() => {
69
+ modalElement?.focus();
70
+ trapFocus();
71
+ });
72
+ } else {
73
+ // Hide modal (triggers CSS transition)
74
+ isVisible = false;
75
+ // Restore focus after transition
76
+ setTimeout(() => {
77
+ if (previouslyFocusedElement instanceof HTMLElement) {
78
+ previouslyFocusedElement.focus();
79
+ }
80
+ }, 150);
81
+ }
82
+ });
83
+
84
+ function handleClose() {
85
+ isOpen = false;
86
+ onClose();
87
+ }
88
+
89
+ function handleOverlayClick(e: MouseEvent) {
90
+ if (closeOnOverlayClick && e.target === e.currentTarget) {
91
+ handleClose();
92
+ }
93
+ }
94
+
95
+ function handleKeydown(e: KeyboardEvent) {
96
+ if (!isOpen) return;
97
+
98
+ // Close on Escape
99
+ if (closeOnEscape && e.key === 'Escape') {
100
+ e.preventDefault();
101
+ handleClose();
102
+ return;
103
+ }
104
+
105
+ // Trap focus with Tab key
106
+ if (e.key === 'Tab' && modalElement) {
107
+ const focusableElements = modalElement.querySelectorAll(
108
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
109
+ );
110
+ const firstElement = focusableElements[0] as HTMLElement;
111
+ const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;
112
+
113
+ if (e.shiftKey && document.activeElement === firstElement) {
114
+ e.preventDefault();
115
+ lastElement?.focus();
116
+ } else if (!e.shiftKey && document.activeElement === lastElement) {
117
+ e.preventDefault();
118
+ firstElement?.focus();
119
+ }
120
+ }
121
+ }
122
+
123
+ function trapFocus() {
124
+ // Initial focus on close button if available
125
+ const closeButton = modalElement?.querySelector('.modal-close-btn') as HTMLElement;
126
+ closeButton?.focus();
127
+ }
128
+
129
+ // Expose methods for programmatic control
130
+ export function open() {
131
+ isOpen = true;
132
+ }
133
+
134
+ export function close() {
135
+ isOpen = false;
136
+ }
137
+ </script>
138
+
139
+ <svelte:window onkeydown={handleKeydown} />
140
+
141
+ <!-- Always mounted, visibility toggled via CSS -->
142
+ <div
143
+ class="modal-container"
144
+ class:open={isOpen}
145
+ class:visible={isVisible}
146
+ style="z-index: {zIndex}"
147
+ role="presentation"
148
+ inert={!isOpen}
149
+ >
150
+ <!-- Overlay -->
151
+ <div
152
+ class="modal-overlay"
153
+ onclick={handleOverlayClick}
154
+ role="presentation"
155
+ ></div>
156
+
157
+ <!-- Modal Dialog -->
158
+ <div
159
+ bind:this={modalElement}
160
+ class="modal-dialog {className}"
161
+ tabindex="-1"
162
+ role="dialog"
163
+ aria-modal="true"
164
+ aria-labelledby={title ? `${modalId}-title` : undefined}
165
+ >
166
+ {#if title || showCloseButton || headerActions}
167
+ <header class="modal-header">
168
+ {#if title}
169
+ <h2 id="{modalId}-title" class="modal-title">{title}</h2>
170
+ {:else}
171
+ <div></div>
172
+ {/if}
173
+
174
+ <div class="modal-header-actions">
175
+ {#if headerActions}
176
+ {@render headerActions()}
177
+ {/if}
178
+ {#if showCloseButton}
179
+ <button
180
+ class="modal-close-btn"
181
+ onclick={handleClose}
182
+ aria-label="Close"
183
+ type="button"
184
+ >
185
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
186
+ </button>
187
+ {/if}
188
+ </div>
189
+ </header>
190
+ {/if}
191
+
192
+ <div class="modal-content">
193
+ {#if children}
194
+ {@render children()}
195
+ {/if}
196
+ </div>
197
+
198
+ {#if footer}
199
+ <footer class="modal-footer">
200
+ {@render footer()}
201
+ </footer>
202
+ {/if}
203
+ </div>
204
+ </div>
205
+
206
+ <style>
207
+ .modal-container {
208
+ position: fixed;
209
+ inset: 0;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ pointer-events: none;
214
+ opacity: 0;
215
+ transition: opacity 0.15s ease-out;
216
+ }
217
+
218
+ .modal-container.open {
219
+ pointer-events: auto;
220
+ opacity: 1;
221
+ }
222
+
223
+ .modal-overlay {
224
+ position: absolute;
225
+ inset: 0;
226
+ background: rgba(0, 0, 0, 0.6);
227
+ opacity: 0;
228
+ transition: opacity 0.15s ease-out;
229
+ }
230
+
231
+ .modal-container.open .modal-overlay {
232
+ opacity: 1;
233
+ }
234
+
235
+ .modal-dialog {
236
+ position: relative;
237
+ background: var(--color-bg-panel-elevated);
238
+ border: 1px solid var(--border-default);
239
+ border-radius: var(--radius-xl);
240
+ width: 90%;
241
+ max-width: 600px;
242
+ max-height: 80vh;
243
+ display: flex;
244
+ flex-direction: column;
245
+ z-index: 1;
246
+ outline: none;
247
+ box-shadow: var(--shadow-xl);
248
+ transform: translateY(20px) scale(0.98);
249
+ transition: transform var(--transition-normal), opacity var(--transition-normal);
250
+ opacity: 0;
251
+ }
252
+
253
+ .modal-container.open .modal-dialog {
254
+ transform: translateY(0) scale(1);
255
+ opacity: 1;
256
+ }
257
+
258
+ .modal-dialog:focus {
259
+ box-shadow: 0 0 0 2px var(--color-accent), var(--shadow-xl);
260
+ }
261
+
262
+ .modal-header {
263
+ display: flex;
264
+ justify-content: space-between;
265
+ align-items: center;
266
+ padding: var(--space-5) var(--space-6);
267
+ border-bottom: 1px solid var(--border-default);
268
+ flex-shrink: 0;
269
+ }
270
+
271
+ .modal-title {
272
+ margin: 0;
273
+ font-size: var(--text-lg);
274
+ color: var(--color-text-primary);
275
+ display: flex;
276
+ align-items: center;
277
+ gap: var(--space-3);
278
+ font-weight: 600;
279
+ font-family: var(--font-sans);
280
+ }
281
+
282
+ .modal-header-actions {
283
+ display: flex;
284
+ align-items: center;
285
+ gap: var(--space-2);
286
+ }
287
+
288
+ .modal-close-btn {
289
+ background: none;
290
+ border: none;
291
+ color: var(--color-text-secondary);
292
+ cursor: pointer;
293
+ padding: var(--space-1);
294
+ display: flex;
295
+ align-items: center;
296
+ border-radius: var(--radius-sm);
297
+ transition: all var(--transition-fast);
298
+ }
299
+
300
+ .modal-close-btn:hover {
301
+ color: var(--color-text-primary);
302
+ background: rgba(255, 255, 255, 0.1);
303
+ }
304
+
305
+ .modal-close-btn:focus-visible {
306
+ outline: 2px solid var(--color-accent);
307
+ outline-offset: 2px;
308
+ }
309
+
310
+ .modal-content {
311
+ overflow-y: auto;
312
+ padding: var(--space-6);
313
+ flex: 1;
314
+ min-height: 0;
315
+ }
316
+
317
+ .modal-footer {
318
+ display: flex;
319
+ align-items: center;
320
+ justify-content: flex-end;
321
+ gap: var(--space-3);
322
+ padding: var(--space-4) var(--space-6);
323
+ border-top: 1px solid var(--border-default);
324
+ flex-shrink: 0;
325
+ }
326
+
327
+ /* Size variants via data attribute — :global() required since the value is set dynamically */
328
+ :global(.modal-dialog[data-size="small"]) { max-width: 400px; }
329
+ :global(.modal-dialog[data-size="large"]) { max-width: 800px; }
330
+ :global(.modal-dialog[data-size="full"]) { max-width: 95vw; max-height: 95vh; }
331
+ </style>
@@ -0,0 +1 @@
1
+ export { default as Modal } from './Modal.svelte';
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ size?: 'sm' | 'md' | 'lg' | 'xl';
4
+ color?: 'accent' | 'muted' | 'white';
5
+ class?: string;
6
+ }
7
+
8
+ let {
9
+ size = 'md',
10
+ color = 'accent',
11
+ class: className = ''
12
+ }: Props = $props();
13
+ </script>
14
+
15
+ <div class="ds-spinner-wrapper size-{size} color-{color} {className}" role="status" aria-label="Loading">
16
+ <div class="ds-spinner-ring"></div>
17
+ <div class="ds-spinner-core"></div>
18
+ <!-- Floating magical dust particles -->
19
+ <div class="ds-spinner-particle p1"></div>
20
+ <div class="ds-spinner-particle p2"></div>
21
+ <div class="ds-spinner-particle p3"></div>
22
+ </div>
23
+
24
+ <style>
25
+ .ds-spinner-wrapper {
26
+ position: relative;
27
+ display: inline-flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ }
31
+
32
+ /* Base Ring */
33
+ .ds-spinner-ring {
34
+ position: absolute;
35
+ inset: 0;
36
+ border-radius: var(--radius-full);
37
+ border: 2px solid transparent;
38
+ border-top-color: currentColor;
39
+ border-right-color: currentColor;
40
+ opacity: 0.8;
41
+ animation: ds-spin 1s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
42
+ }
43
+
44
+ /* Inner Core */
45
+ .ds-spinner-core {
46
+ position: absolute;
47
+ inset: 20%;
48
+ border-radius: var(--radius-full);
49
+ background: currentColor;
50
+ opacity: 0.3;
51
+ filter: blur(4px);
52
+ animation: ds-pulse-core 2s ease-in-out infinite alternate;
53
+ }
54
+
55
+ /* Magical Dust Particles */
56
+ .ds-spinner-particle {
57
+ position: absolute;
58
+ width: 3px;
59
+ height: 3px;
60
+ border-radius: var(--radius-full);
61
+ background: currentColor;
62
+ opacity: 0;
63
+ }
64
+
65
+ .p1 { animation: ds-particle-drift-1 2s ease-out infinite; top: 10%; left: 10%; }
66
+ .p2 { animation: ds-particle-drift-2 2.5s ease-out infinite 0.5s; bottom: 10%; right: 10%; }
67
+ .p3 { animation: ds-particle-drift-3 1.8s ease-out infinite 1s; top: 10%; right: 20%; }
68
+
69
+ @keyframes ds-spin {
70
+ 0% { transform: rotate(0deg); }
71
+ 100% { transform: rotate(360deg); }
72
+ }
73
+
74
+ @keyframes ds-pulse-core {
75
+ 0% { transform: scale(0.8); opacity: 0.2; }
76
+ 100% { transform: scale(1.2); opacity: 0.5; }
77
+ }
78
+
79
+ @keyframes ds-particle-drift-1 {
80
+ 0% { transform: translate(0, 0) scale(0); opacity: 0; }
81
+ 50% { transform: translate(-10px, -15px) scale(1); opacity: 0.8; }
82
+ 100% { transform: translate(-20px, -30px) scale(0); opacity: 0; }
83
+ }
84
+
85
+ @keyframes ds-particle-drift-2 {
86
+ 0% { transform: translate(0, 0) scale(0); opacity: 0; }
87
+ 50% { transform: translate(15px, 10px) scale(1.2); opacity: 0.6; }
88
+ 100% { transform: translate(30px, 20px) scale(0); opacity: 0; }
89
+ }
90
+
91
+ @keyframes ds-particle-drift-3 {
92
+ 0% { transform: translate(0, 0) scale(0); opacity: 0; }
93
+ 50% { transform: translate(10px, -20px) scale(0.8); opacity: 0.9; }
94
+ 100% { transform: translate(20px, -40px) scale(0); opacity: 0; }
95
+ }
96
+
97
+ /* --- Sizes --- */
98
+ .size-sm { width: 1.25rem; height: 1.25rem; }
99
+ .size-md { width: 2rem; height: 2rem; }
100
+ .size-lg { width: 3rem; height: 3rem; }
101
+ .size-lg .ds-spinner-ring { border-width: 3px; }
102
+ .size-xl { width: 4.5rem; height: 4.5rem; }
103
+ .size-xl .ds-spinner-ring { border-width: 4px; }
104
+
105
+ /* --- Colors --- */
106
+ .color-accent {
107
+ color: var(--color-accent);
108
+ filter: drop-shadow(0 0 4px var(--color-accent-glow));
109
+ }
110
+
111
+ .color-muted {
112
+ color: var(--color-text-muted);
113
+ }
114
+
115
+ .color-white {
116
+ color: #ffffff;
117
+ filter: drop-shadow(0 0 4px rgba(255,255,255,0.4));
118
+ }
119
+
120
+ /* Reduced motion */
121
+ @media (prefers-reduced-motion: reduce) {
122
+ .ds-spinner-ring {
123
+ animation: ds-spin 2s linear infinite;
124
+ }
125
+ .ds-spinner-core, .ds-spinner-particle {
126
+ display: none;
127
+ }
128
+ }
129
+ </style>
@@ -0,0 +1,225 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { Tab } from './tabs-types';
4
+
5
+ // Re-export for backward compatibility
6
+ export type { Tab } from './tabs-types';
7
+
8
+ interface Props {
9
+ tabs: Tab[];
10
+ activeId?: string;
11
+ class?: string;
12
+ fullWidth?: boolean;
13
+ onchange?: (id: string) => void;
14
+ }
15
+
16
+ let {
17
+ tabs = [],
18
+ activeId = $bindable(''),
19
+ class: className = '',
20
+ fullWidth = false,
21
+ onchange
22
+ }: Props = $props();
23
+ // Handle uncontrolled init reactively
24
+ $effect(() => {
25
+ if (!activeId && tabs.length > 0) {
26
+ activeId = tabs[0].id;
27
+ }
28
+ });
29
+ let activeIndex = $derived(tabs.findIndex(t => t.id === activeId));
30
+ let isClient = $state(false); // Used to delay indicator rendering until mounted
31
+
32
+ $effect(() => {
33
+ isClient = true;
34
+ });
35
+
36
+ function selectTab(id: string) {
37
+ const tab = tabs.find(t => t.id === id);
38
+ if (!tab || tab.disabled) return;
39
+
40
+ activeId = id;
41
+ onchange?.(id);
42
+ }
43
+
44
+ function handleKeydown(e: KeyboardEvent, index: number) {
45
+ let nextIndex = index;
46
+
47
+ if (e.key === 'ArrowRight') {
48
+ nextIndex = (index + 1) % tabs.length;
49
+ while (tabs[nextIndex].disabled && nextIndex !== index) {
50
+ nextIndex = (nextIndex + 1) % tabs.length;
51
+ }
52
+ } else if (e.key === 'ArrowLeft') {
53
+ nextIndex = (index - 1 + tabs.length) % tabs.length;
54
+ while (tabs[nextIndex].disabled && nextIndex !== index) {
55
+ nextIndex = (nextIndex - 1 + tabs.length) % tabs.length;
56
+ }
57
+ } else if (e.key === 'Home') {
58
+ nextIndex = 0;
59
+ while (tabs[nextIndex].disabled && nextIndex < tabs.length - 1) nextIndex++;
60
+ } else if (e.key === 'End') {
61
+ nextIndex = tabs.length - 1;
62
+ while (tabs[nextIndex].disabled && nextIndex > 0) nextIndex--;
63
+ } else if (e.key === 'Enter' || e.key === ' ') {
64
+ e.preventDefault();
65
+ selectTab(tabs[index].id);
66
+ return;
67
+ } else {
68
+ return; // Do nothing
69
+ }
70
+
71
+ if (nextIndex !== index) {
72
+ e.preventDefault();
73
+ const newId = tabs[nextIndex].id;
74
+ selectTab(newId);
75
+ // Focus the new tab
76
+ const newTabEl = document.getElementById(`tab-${newId}`);
77
+ newTabEl?.focus();
78
+ }
79
+ }
80
+ </script>
81
+
82
+ <div class="ds-tabs-container {className}" class:full-width={fullWidth}>
83
+ <div class="ds-tabs-list" role="tablist">
84
+ {#each tabs as tab, i (tab.id)}
85
+ {@const isActive = activeId === tab.id}
86
+ <button
87
+ id="tab-{tab.id}"
88
+ class="ds-tab"
89
+ class:is-active={isActive}
90
+ class:is-disabled={tab.disabled}
91
+ role="tab"
92
+ aria-selected={isActive}
93
+ aria-controls="panel-{tab.id}"
94
+ tabindex={isActive ? 0 : -1}
95
+ disabled={tab.disabled}
96
+ onclick={() => selectTab(tab.id)}
97
+ onkeydown={(e) => handleKeydown(e, i)}
98
+ >
99
+ {#if tab.icon}
100
+ <span class="ds-tab-icon">
101
+ {@render tab.icon()}
102
+ </span>
103
+ {/if}
104
+
105
+ <span class="ds-tab-label">
106
+ {#if typeof tab.label === 'string'}
107
+ {tab.label}
108
+ {:else}
109
+ {@render tab.label()}
110
+ {/if}
111
+ </span>
112
+ </button>
113
+ {/each}
114
+
115
+ <!-- Animated Indicator -->
116
+ {#if isClient && activeIndex >= 0}
117
+ <div
118
+ class="ds-tab-indicator"
119
+ style="
120
+ width: {fullWidth ? `calc(100% / ${tabs.length})` : `max-content`};
121
+ left: calc({fullWidth ? `(100% / ${tabs.length}) * ${activeIndex}` : '0px'});
122
+ "
123
+ >
124
+ </div>
125
+ {/if}
126
+ </div>
127
+ </div>
128
+
129
+ <style>
130
+ .ds-tabs-container {
131
+ width: 100%;
132
+ border-bottom: 1px solid var(--border-default);
133
+ }
134
+
135
+ .ds-tabs-list {
136
+ display: flex;
137
+ position: relative;
138
+ overflow-x: auto;
139
+ overflow-y: hidden;
140
+ scrollbar-width: none;
141
+ -ms-overflow-style: none;
142
+ }
143
+
144
+ .ds-tabs-list::-webkit-scrollbar {
145
+ display: none;
146
+ }
147
+
148
+ .ds-tab {
149
+ position: relative;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ gap: var(--space-2);
154
+ padding: var(--space-3) var(--space-4);
155
+ background: transparent;
156
+ border: none;
157
+ color: var(--color-text-secondary);
158
+ font-family: var(--font-sans);
159
+ font-size: var(--text-sm);
160
+ font-weight: 500;
161
+ cursor: pointer;
162
+ transition: color var(--transition-fast), background var(--transition-fast);
163
+ white-space: nowrap;
164
+ z-index: 1;
165
+ }
166
+
167
+ .full-width .ds-tab {
168
+ flex: 1;
169
+ }
170
+
171
+ .ds-tab:hover:not(.is-disabled) {
172
+ color: var(--color-text-primary);
173
+ background: var(--color-bg-panel-elevated);
174
+ }
175
+
176
+ .ds-tab:focus-visible {
177
+ outline: none;
178
+ background: rgba(184, 115, 51, 0.1);
179
+ }
180
+
181
+ .ds-tab.is-active {
182
+ color: var(--color-accent);
183
+ }
184
+
185
+ .ds-tab.is-disabled {
186
+ opacity: 0.4;
187
+ cursor: not-allowed;
188
+ }
189
+
190
+ .ds-tab-icon {
191
+ display: flex;
192
+ align-items: center;
193
+ }
194
+
195
+ .ds-tab-icon :global(svg) {
196
+ width: 1.1em;
197
+ height: 1.1em;
198
+ }
199
+
200
+ .ds-tab::after {
201
+ content: '';
202
+ position: absolute;
203
+ bottom: -1px;
204
+ left: 0;
205
+ right: 0;
206
+ height: 2px;
207
+ background: var(--color-accent);
208
+ transform: scaleX(0);
209
+ transition: transform var(--transition-bounce);
210
+ transform-origin: center;
211
+ box-shadow: 0 -1px 4px var(--color-accent-glow);
212
+ }
213
+
214
+ .ds-tab.is-active::after {
215
+ transform: scaleX(1);
216
+ }
217
+
218
+ .ds-tab:focus-visible::before {
219
+ content: '';
220
+ position: absolute;
221
+ inset: 4px;
222
+ border-radius: var(--radius-sm);
223
+ border: 2px solid var(--color-accent-subtle);
224
+ }
225
+ </style>
@@ -0,0 +1,11 @@
1
+ // U0 Tabs — Tab type definition
2
+ // Extracted from Tabs.svelte so tsc can resolve it without Svelte type declarations.
3
+
4
+ import type { Snippet } from 'svelte';
5
+
6
+ export interface Tab {
7
+ id: string;
8
+ label: string | Snippet;
9
+ disabled?: boolean;
10
+ icon?: Snippet;
11
+ }