@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,198 @@
1
+ <script lang="ts">
2
+ interface SelectOption {
3
+ value: string;
4
+ label: string;
5
+ disabled?: boolean;
6
+ }
7
+
8
+ interface Props {
9
+ options: SelectOption[];
10
+ selected?: string;
11
+ placeholder?: string;
12
+ label?: string;
13
+ disabled?: boolean;
14
+ error?: boolean;
15
+ class?: string;
16
+ id?: string;
17
+ name?: string;
18
+ onchange?: (value: string) => void;
19
+ }
20
+
21
+ let {
22
+ options,
23
+ selected = $bindable(''),
24
+ placeholder = 'Select...',
25
+ label,
26
+ disabled = false,
27
+ error = false,
28
+ class: className = '',
29
+ id,
30
+ name,
31
+ onchange
32
+ }: Props = $props();
33
+
34
+ let isFocused = $state(false);
35
+
36
+ function handleChange(e: Event) {
37
+ const target = e.target as HTMLSelectElement;
38
+ selected = target.value;
39
+ onchange?.(selected);
40
+ }
41
+ </script>
42
+
43
+ <div
44
+ class="ds-select-wrapper {className}"
45
+ class:is-focused={isFocused}
46
+ class:is-disabled={disabled}
47
+ class:has-error={error}
48
+ class:has-placeholder={!selected}
49
+ >
50
+ <select
51
+ {id}
52
+ {name}
53
+ {disabled}
54
+ value={selected}
55
+ onchange={handleChange}
56
+ onfocus={() => isFocused = true}
57
+ onblur={() => isFocused = false}
58
+ class="ds-select-element"
59
+ aria-label={label}
60
+ aria-invalid={error}
61
+ >
62
+ {#if placeholder}
63
+ <option value="" disabled hidden>{placeholder}</option>
64
+ {/if}
65
+ {#each options as option}
66
+ <option
67
+ value={option.value}
68
+ disabled={option.disabled}
69
+ selected={option.value === selected}
70
+ >
71
+ {option.label}
72
+ </option>
73
+ {/each}
74
+ </select>
75
+
76
+ <div class="ds-select-chevron" role="presentation">
77
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
78
+ <polyline points="4 6 8 10 12 6" />
79
+ </svg>
80
+ </div>
81
+
82
+ <div class="ds-select-glow"></div>
83
+ </div>
84
+
85
+ <style>
86
+ .ds-select-wrapper {
87
+ position: relative;
88
+ display: flex;
89
+ align-items: center;
90
+ width: 100%;
91
+ background: var(--color-bg-panel);
92
+ border: 1px solid var(--border-default);
93
+ border-radius: var(--radius-md);
94
+ transition: all var(--transition-fast);
95
+ overflow: hidden;
96
+ }
97
+
98
+ .ds-select-wrapper:hover:not(.is-disabled) {
99
+ border-color: var(--border-hover);
100
+ background: var(--color-bg-panel-elevated);
101
+ }
102
+
103
+ .ds-select-wrapper.is-focused {
104
+ border-color: var(--color-accent);
105
+ background: var(--color-bg-app);
106
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
107
+ }
108
+
109
+ .ds-select-wrapper.has-error {
110
+ border-color: var(--color-error);
111
+ }
112
+
113
+ .ds-select-wrapper.is-disabled {
114
+ opacity: 0.5;
115
+ background: rgba(0,0,0,0.2);
116
+ cursor: not-allowed;
117
+ }
118
+
119
+ .ds-select-element {
120
+ flex: 1;
121
+ width: 100%;
122
+ height: 2.25rem;
123
+ padding: 0 var(--space-8) 0 var(--space-3);
124
+ background: transparent;
125
+ border: none;
126
+ outline: none;
127
+ color: var(--color-text-primary);
128
+ font-family: var(--font-sans);
129
+ font-size: var(--text-sm);
130
+ cursor: pointer;
131
+ appearance: none;
132
+ -webkit-appearance: none;
133
+ }
134
+
135
+ .ds-select-wrapper.has-placeholder .ds-select-element {
136
+ color: var(--color-text-muted);
137
+ }
138
+
139
+ .ds-select-element:disabled {
140
+ cursor: not-allowed;
141
+ }
142
+
143
+ /* Style the option elements for dark backgrounds */
144
+ .ds-select-element option {
145
+ background: var(--color-bg-panel, #222);
146
+ color: var(--color-text-primary, #e8e0d0);
147
+ }
148
+
149
+ .ds-select-element option:disabled {
150
+ color: var(--color-text-muted, #a09880);
151
+ }
152
+
153
+ .ds-select-chevron {
154
+ position: absolute;
155
+ right: 0;
156
+ top: 50%;
157
+ transform: translateY(-50%);
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ width: var(--space-8);
162
+ height: 100%;
163
+ color: var(--color-text-muted);
164
+ pointer-events: none;
165
+ transition: color var(--transition-fast);
166
+ }
167
+
168
+ .ds-select-chevron svg {
169
+ width: 14px;
170
+ height: 14px;
171
+ }
172
+
173
+ .ds-select-wrapper.is-focused .ds-select-chevron {
174
+ color: var(--color-accent);
175
+ }
176
+
177
+ .ds-select-glow {
178
+ position: absolute;
179
+ bottom: 0;
180
+ left: 0;
181
+ width: 100%;
182
+ height: 1px;
183
+ background: var(--color-accent);
184
+ transform: scaleX(0);
185
+ transform-origin: center;
186
+ transition: transform var(--transition-normal);
187
+ box-shadow: 0 -1px 4px var(--color-accent-glow);
188
+ }
189
+
190
+ .ds-select-wrapper.is-focused .ds-select-glow {
191
+ transform: scaleX(1);
192
+ }
193
+
194
+ .ds-select-wrapper.has-error .ds-select-glow {
195
+ background: var(--color-error);
196
+ box-shadow: 0 -1px 4px rgba(166, 94, 94, 0.4);
197
+ }
198
+ </style>
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ value?: string;
4
+ placeholder?: string;
5
+ label?: string;
6
+ rows?: number;
7
+ maxLength?: number;
8
+ disabled?: boolean;
9
+ error?: boolean;
10
+ class?: string;
11
+ id?: string;
12
+ name?: string;
13
+ oninput?: (e: Event) => void;
14
+ onchange?: (e: Event) => void;
15
+ }
16
+
17
+ let {
18
+ value = $bindable(''),
19
+ placeholder = '',
20
+ label,
21
+ rows = 4,
22
+ maxLength,
23
+ disabled = false,
24
+ error = false,
25
+ class: className = '',
26
+ id,
27
+ name,
28
+ oninput,
29
+ onchange
30
+ }: Props = $props();
31
+
32
+ let isFocused = $state(false);
33
+ let charCount = $derived(value.length);
34
+ let isNearLimit = $derived(maxLength ? charCount > maxLength * 0.9 : false);
35
+ let isOverLimit = $derived(maxLength ? charCount > maxLength : false);
36
+ </script>
37
+
38
+ <div
39
+ class="ds-textarea-wrapper {className}"
40
+ class:is-focused={isFocused}
41
+ class:is-disabled={disabled}
42
+ class:has-error={error || isOverLimit}
43
+ >
44
+ <textarea
45
+ {id}
46
+ {name}
47
+ bind:value
48
+ {placeholder}
49
+ {rows}
50
+ {disabled}
51
+ maxlength={maxLength}
52
+ onfocus={() => isFocused = true}
53
+ onblur={() => isFocused = false}
54
+ {oninput}
55
+ {onchange}
56
+ class="ds-textarea-element"
57
+ aria-label={label}
58
+ aria-invalid={error || isOverLimit}
59
+ ></textarea>
60
+
61
+ {#if maxLength}
62
+ <div
63
+ class="ds-textarea-counter"
64
+ class:is-near={isNearLimit}
65
+ class:is-over={isOverLimit}
66
+ aria-live="polite"
67
+ >
68
+ {charCount}/{maxLength}
69
+ </div>
70
+ {/if}
71
+
72
+ <div class="ds-textarea-glow"></div>
73
+ </div>
74
+
75
+ <style>
76
+ .ds-textarea-wrapper {
77
+ position: relative;
78
+ display: flex;
79
+ flex-direction: column;
80
+ width: 100%;
81
+ background: var(--color-bg-panel);
82
+ border: 1px solid var(--border-default);
83
+ border-radius: var(--radius-md);
84
+ transition: all var(--transition-fast);
85
+ overflow: hidden;
86
+ }
87
+
88
+ .ds-textarea-wrapper:hover:not(.is-disabled) {
89
+ border-color: var(--border-hover);
90
+ background: var(--color-bg-panel-elevated);
91
+ }
92
+
93
+ .ds-textarea-wrapper.is-focused {
94
+ border-color: var(--color-accent);
95
+ background: var(--color-bg-app);
96
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
97
+ }
98
+
99
+ .ds-textarea-wrapper.has-error {
100
+ border-color: var(--color-error);
101
+ }
102
+
103
+ .ds-textarea-wrapper.is-disabled {
104
+ opacity: 0.5;
105
+ background: rgba(0,0,0,0.2);
106
+ cursor: not-allowed;
107
+ }
108
+
109
+ .ds-textarea-element {
110
+ width: 100%;
111
+ padding: var(--space-3);
112
+ background: transparent;
113
+ border: none;
114
+ outline: none;
115
+ color: var(--color-text-primary);
116
+ font-family: var(--font-sans);
117
+ font-size: var(--text-sm);
118
+ line-height: 1.5;
119
+ resize: vertical;
120
+ min-height: 2.5rem;
121
+ }
122
+
123
+ .ds-textarea-element::placeholder {
124
+ color: var(--color-text-muted);
125
+ }
126
+
127
+ .ds-textarea-element:disabled {
128
+ cursor: not-allowed;
129
+ resize: none;
130
+ }
131
+
132
+ .ds-textarea-counter {
133
+ align-self: flex-end;
134
+ padding: var(--space-1) var(--space-3) var(--space-2);
135
+ font-family: var(--font-mono);
136
+ font-size: var(--text-xs);
137
+ color: var(--color-text-muted);
138
+ transition: color var(--transition-fast);
139
+ }
140
+
141
+ .ds-textarea-counter.is-near {
142
+ color: var(--color-accent, #b87333);
143
+ }
144
+
145
+ .ds-textarea-counter.is-over {
146
+ color: var(--color-error);
147
+ }
148
+
149
+ .ds-textarea-glow {
150
+ position: absolute;
151
+ bottom: 0;
152
+ left: 0;
153
+ width: 100%;
154
+ height: 1px;
155
+ background: var(--color-accent);
156
+ transform: scaleX(0);
157
+ transform-origin: center;
158
+ transition: transform var(--transition-normal);
159
+ box-shadow: 0 -1px 4px var(--color-accent-glow);
160
+ }
161
+
162
+ .ds-textarea-wrapper.is-focused .ds-textarea-glow {
163
+ transform: scaleX(1);
164
+ }
165
+
166
+ .ds-textarea-wrapper.has-error .ds-textarea-glow {
167
+ background: var(--color-error);
168
+ box-shadow: 0 -1px 4px rgba(166, 94, 94, 0.4);
169
+ }
170
+ </style>
@@ -0,0 +1,147 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ checked?: boolean;
4
+ label?: string;
5
+ disabled?: boolean;
6
+ size?: 'sm' | 'md';
7
+ class?: string;
8
+ id?: string;
9
+ name?: string;
10
+ onchange?: (e: Event) => void;
11
+ }
12
+
13
+ let {
14
+ checked = $bindable(false),
15
+ label = '',
16
+ disabled = false,
17
+ size = 'md',
18
+ class: className = '',
19
+ id,
20
+ name,
21
+ onchange
22
+ }: Props = $props();
23
+
24
+ let trackWidth = $derived(size === 'sm' ? 32 : 40);
25
+ let trackHeight = $derived(size === 'sm' ? 18 : 22);
26
+ let thumbSize = $derived(size === 'sm' ? 14 : 18);
27
+ </script>
28
+
29
+ <label
30
+ class="ds-form-toggle {className}"
31
+ class:is-disabled={disabled}
32
+ class:is-checked={checked}
33
+ class:is-sm={size === 'sm'}
34
+ >
35
+ <input
36
+ type="checkbox"
37
+ bind:checked
38
+ {id}
39
+ {name}
40
+ {disabled}
41
+ {onchange}
42
+ class="ds-form-toggle-native"
43
+ role="switch"
44
+ aria-checked={checked}
45
+ />
46
+
47
+ <div
48
+ class="ds-form-toggle-track"
49
+ role="presentation"
50
+ style:width="{trackWidth}px"
51
+ style:height="{trackHeight}px"
52
+ >
53
+ <div
54
+ class="ds-form-toggle-thumb"
55
+ style:width="{thumbSize}px"
56
+ style:height="{thumbSize}px"
57
+ ></div>
58
+ </div>
59
+
60
+ {#if label}
61
+ <span class="ds-form-toggle-label">{label}</span>
62
+ {/if}
63
+ </label>
64
+
65
+ <style>
66
+ .ds-form-toggle {
67
+ display: inline-flex;
68
+ align-items: center;
69
+ gap: var(--space-2);
70
+ cursor: pointer;
71
+ user-select: none;
72
+ font-family: var(--font-sans);
73
+ font-size: var(--text-sm);
74
+ color: var(--color-text-primary);
75
+ }
76
+
77
+ .ds-form-toggle.is-sm {
78
+ font-size: var(--text-xs);
79
+ }
80
+
81
+ .ds-form-toggle.is-disabled {
82
+ opacity: 0.5;
83
+ cursor: not-allowed;
84
+ }
85
+
86
+ .ds-form-toggle-native {
87
+ position: absolute;
88
+ width: 1px;
89
+ height: 1px;
90
+ padding: 0;
91
+ margin: -1px;
92
+ overflow: hidden;
93
+ clip: rect(0, 0, 0, 0);
94
+ white-space: nowrap;
95
+ border: 0;
96
+ }
97
+
98
+ .ds-form-toggle-track {
99
+ position: relative;
100
+ border-radius: var(--radius-full);
101
+ background: var(--color-bg-panel);
102
+ border: 1.5px solid var(--border-default);
103
+ transition: all var(--transition-fast);
104
+ flex-shrink: 0;
105
+ }
106
+
107
+ .ds-form-toggle:hover:not(.is-disabled) .ds-form-toggle-track {
108
+ border-color: var(--border-hover);
109
+ background: var(--color-bg-panel-elevated);
110
+ }
111
+
112
+ .ds-form-toggle.is-checked .ds-form-toggle-track {
113
+ background: var(--color-accent, #b87333);
114
+ border-color: var(--color-accent, #b87333);
115
+ }
116
+
117
+ .ds-form-toggle.is-checked:hover:not(.is-disabled) .ds-form-toggle-track {
118
+ background: var(--color-accent-hover, #c4813d);
119
+ border-color: var(--color-accent-hover, #c4813d);
120
+ }
121
+
122
+ .ds-form-toggle-native:focus-visible + .ds-form-toggle-track {
123
+ outline: 2px solid var(--color-accent);
124
+ outline-offset: 2px;
125
+ }
126
+
127
+ .ds-form-toggle-thumb {
128
+ position: absolute;
129
+ top: 50%;
130
+ left: 2px;
131
+ transform: translateY(-50%);
132
+ border-radius: 50%;
133
+ background: var(--color-text-muted);
134
+ transition: all var(--transition-fast);
135
+ box-shadow: 0 1px 3px rgba(0,0,0,0.3);
136
+ }
137
+
138
+ .ds-form-toggle.is-checked .ds-form-toggle-thumb {
139
+ left: calc(100% - 2px);
140
+ transform: translate(-100%, -50%);
141
+ background: var(--color-bg-app, #1a1a1a);
142
+ }
143
+
144
+ .ds-form-toggle-label {
145
+ line-height: 1.4;
146
+ }
147
+ </style>
@@ -0,0 +1,47 @@
1
+ // F0 Forms — Pure validation utilities
2
+ // Each validator returns null on success, or an error message string on failure.
3
+
4
+ export type Validator = (value: string) => string | null;
5
+
6
+ /** Fails if the value is empty or whitespace-only. */
7
+ export function required(value: string): string | null {
8
+ if (value == null) value = '';
9
+ return value.trim().length === 0 ? 'This field is required' : null;
10
+ }
11
+
12
+ /** Returns a validator that fails if value length is below `min`. */
13
+ export function minLength(min: number): Validator {
14
+ return (value: string) => {
15
+ if (value == null) value = '';
16
+ return value.length < min ? `Must be at least ${min} characters` : null;
17
+ };
18
+ }
19
+
20
+ /** Returns a validator that fails if value length exceeds `max`. */
21
+ export function maxLength(max: number): Validator {
22
+ return (value: string) => {
23
+ if (value == null) value = '';
24
+ return value.length > max ? `Must be at most ${max} characters` : null;
25
+ };
26
+ }
27
+
28
+ /** Returns a validator that fails if value does not match the given regex. */
29
+ export function pattern(regex: RegExp, message: string): Validator {
30
+ return (value: string) => {
31
+ if (value == null) value = '';
32
+ return regex.test(value) ? null : message;
33
+ };
34
+ }
35
+
36
+ /** Composes multiple validators, returning the first error encountered or null. */
37
+ export function compose(...validators: Validator[]): Validator {
38
+ return (value: string) => {
39
+ for (const validator of validators) {
40
+ const error = validator(value);
41
+ if (error !== null) {
42
+ return error;
43
+ }
44
+ }
45
+ return null;
46
+ };
47
+ }