@cfasim-ui/docs 0.3.11

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 (60) hide show
  1. package/LICENSE +201 -0
  2. package/charts/ChartMenu/ChartMenu.vue +140 -0
  3. package/charts/ChartMenu/download.ts +44 -0
  4. package/charts/ChartTooltip/ChartTooltip.vue +97 -0
  5. package/charts/ChoroplethMap/ChoroplethMap.md +398 -0
  6. package/charts/ChoroplethMap/ChoroplethMap.vue +777 -0
  7. package/charts/ChoroplethMap/hsaMapping.ts +4116 -0
  8. package/charts/DataTable/DataTable.md +143 -0
  9. package/charts/DataTable/DataTable.vue +277 -0
  10. package/charts/LineChart/LineChart.md +472 -0
  11. package/charts/LineChart/LineChart.vue +1216 -0
  12. package/charts/index.ts +23 -0
  13. package/charts/tooltip-position.ts +49 -0
  14. package/components/Box/Box.md +49 -0
  15. package/components/Box/Box.vue +52 -0
  16. package/components/Button/Button.md +67 -0
  17. package/components/Button/Button.vue +81 -0
  18. package/components/Expander/Expander.md +34 -0
  19. package/components/Expander/Expander.vue +95 -0
  20. package/components/Hint/Hint.md +29 -0
  21. package/components/Hint/Hint.vue +83 -0
  22. package/components/Icon/Icon.md +67 -0
  23. package/components/Icon/Icon.vue +112 -0
  24. package/components/LightDarkToggle/LightDarkToggle.vue +49 -0
  25. package/components/NumberInput/NumberInput.md +305 -0
  26. package/components/NumberInput/NumberInput.vue +531 -0
  27. package/components/SelectBox/SelectBox.md +110 -0
  28. package/components/SelectBox/SelectBox.vue +195 -0
  29. package/components/SidebarLayout/SidebarLayout.md +104 -0
  30. package/components/SidebarLayout/SidebarLayout.vue +466 -0
  31. package/components/Spinner/Spinner.md +51 -0
  32. package/components/Spinner/Spinner.vue +55 -0
  33. package/components/TextInput/TextInput.md +82 -0
  34. package/components/TextInput/TextInput.vue +94 -0
  35. package/components/Toggle/Toggle.md +81 -0
  36. package/components/Toggle/Toggle.vue +81 -0
  37. package/components/index.ts +15 -0
  38. package/index.json +121 -0
  39. package/package.json +24 -0
  40. package/pyodide/index.ts +7 -0
  41. package/pyodide/pyodide.worker.ts +233 -0
  42. package/pyodide/pyodideWorkerApi.ts +102 -0
  43. package/pyodide/useModel.ts +86 -0
  44. package/pyodide/vitePlugin.js +51 -0
  45. package/shared/ModelOutput.ts +88 -0
  46. package/shared/csv.ts +22 -0
  47. package/shared/index.ts +24 -0
  48. package/shared/transferUtils.ts +126 -0
  49. package/shared/useUrlParams.ts +296 -0
  50. package/theme/all.js +5 -0
  51. package/theme/base.css +176 -0
  52. package/theme/cfasim.css +3 -0
  53. package/theme/theme.css +113 -0
  54. package/theme/themes/cdc.css +22 -0
  55. package/theme/utilities.css +518 -0
  56. package/wasm/index.ts +2 -0
  57. package/wasm/useModel.ts +53 -0
  58. package/wasm/vitePlugin.js +35 -0
  59. package/wasm/wasm.worker.ts +74 -0
  60. package/wasm/wasmWorkerApi.ts +38 -0
@@ -0,0 +1,466 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ ref,
4
+ computed,
5
+ watch,
6
+ onMounted,
7
+ onUnmounted,
8
+ getCurrentInstance,
9
+ } from "vue";
10
+ import { TabsRoot, TabsList, TabsTrigger, TabsIndicator } from "reka-ui";
11
+ import Icon from "../Icon/Icon.vue";
12
+ import LightDarkToggle from "../LightDarkToggle/LightDarkToggle.vue";
13
+
14
+ // Optional vue-router integration (no hard dependency).
15
+ // $router/$route on globalProperties is vue-router's stable public API.
16
+ const instance = getCurrentInstance();
17
+ const router = instance?.appContext.config.globalProperties.$router;
18
+ const route = instance?.appContext.config.globalProperties.$route;
19
+
20
+ export interface Tab {
21
+ value: string;
22
+ label: string;
23
+ to?: string;
24
+ }
25
+
26
+ const props = defineProps<{
27
+ hideTopbar?: boolean;
28
+ tabs?: Tab[];
29
+ }>();
30
+
31
+ const tab = defineModel<string>("tab");
32
+
33
+ const mql = window.matchMedia("(max-width: 767px)");
34
+ const isMobile = ref(mql.matches);
35
+ const collapsed = ref(mql.matches);
36
+
37
+ function onMediaChange(e: MediaQueryListEvent) {
38
+ isMobile.value = e.matches;
39
+ collapsed.value = e.matches;
40
+ }
41
+
42
+ onMounted(() => {
43
+ mql.addEventListener("change", onMediaChange);
44
+ });
45
+
46
+ onUnmounted(() => {
47
+ mql.removeEventListener("change", onMediaChange);
48
+ });
49
+
50
+ function toggle() {
51
+ collapsed.value = !collapsed.value;
52
+ }
53
+
54
+ const isRouterMode = computed(() => !!router && props.tabs?.some((t) => t.to));
55
+
56
+ const activeTab = computed({
57
+ get() {
58
+ return tab.value ?? props.tabs?.[0]?.value;
59
+ },
60
+ set(value: string | undefined) {
61
+ if (!value) return;
62
+ tab.value = value;
63
+ if (isRouterMode.value && router) {
64
+ const target = props.tabs?.find((t) => t.value === value);
65
+ if (target?.to) router.push(target.to);
66
+ }
67
+ },
68
+ });
69
+
70
+ // Sync active tab from route changes in router mode
71
+ if (route) {
72
+ watch(
73
+ () => route.path,
74
+ () => {
75
+ if (isRouterMode.value) {
76
+ const match = props.tabs?.find((t) => t.to === route.path);
77
+ if (match) tab.value = match.value;
78
+ }
79
+ },
80
+ { immediate: true },
81
+ );
82
+ }
83
+ </script>
84
+
85
+ <template>
86
+ <div class="SidebarLayout" :data-collapsed="collapsed">
87
+ <div class="SidebarRail">
88
+ <aside class="Sidebar">
89
+ <div class="SidebarScroll">
90
+ <div class="SidebarHeader">
91
+ <button
92
+ type="button"
93
+ class="Toggle"
94
+ aria-label="Collapse sidebar"
95
+ title="Collapse sidebar"
96
+ @click="toggle"
97
+ >
98
+ <Icon icon="keyboard_double_arrow_left" size="sm" />
99
+ </button>
100
+ </div>
101
+ <slot name="sidebar" />
102
+ </div>
103
+ </aside>
104
+ <button
105
+ type="button"
106
+ class="Toggle Toggle--expand"
107
+ aria-label="Expand sidebar"
108
+ title="Expand sidebar"
109
+ @click="toggle"
110
+ >
111
+ <Icon icon="keyboard_double_arrow_right" size="sm" />
112
+ </button>
113
+ </div>
114
+ <main class="Main">
115
+ <TabsRoot
116
+ v-if="tabs?.length"
117
+ :model-value="activeTab"
118
+ class="TabsLayout"
119
+ @update:model-value="activeTab = $event as string"
120
+ >
121
+ <div class="TabsBar">
122
+ <button
123
+ v-if="isMobile && collapsed"
124
+ type="button"
125
+ class="Toggle"
126
+ aria-label="Expand sidebar"
127
+ title="Expand sidebar"
128
+ @click="toggle"
129
+ >
130
+ <Icon icon="keyboard_double_arrow_right" size="sm" />
131
+ </button>
132
+ <TabsList class="TabsList" aria-label="Tabs">
133
+ <TabsTrigger
134
+ v-for="t in tabs"
135
+ :key="t.value"
136
+ :value="t.value"
137
+ class="TabsTrigger"
138
+ >
139
+ {{ t.label }}
140
+ </TabsTrigger>
141
+ <TabsIndicator
142
+ class="TabsIndicator"
143
+ :style="{
144
+ width: 'var(--reka-tabs-indicator-size)',
145
+ left: 'var(--reka-tabs-indicator-position)',
146
+ }"
147
+ />
148
+ </TabsList>
149
+ <div class="TabsBarEnd">
150
+ <slot name="topbar" />
151
+ <LightDarkToggle v-if="!hideTopbar" />
152
+ </div>
153
+ </div>
154
+ <div class="MainScroll">
155
+ <div class="MainContent">
156
+ <slot />
157
+ </div>
158
+ </div>
159
+ </TabsRoot>
160
+ <template v-else>
161
+ <div class="Topbar">
162
+ <button
163
+ v-if="isMobile && collapsed"
164
+ type="button"
165
+ class="Toggle"
166
+ aria-label="Expand sidebar"
167
+ title="Expand sidebar"
168
+ @click="toggle"
169
+ >
170
+ <Icon icon="keyboard_double_arrow_right" size="sm" />
171
+ </button>
172
+ <div class="TopbarEnd">
173
+ <slot name="topbar" />
174
+ <LightDarkToggle v-if="!hideTopbar" />
175
+ </div>
176
+ </div>
177
+ <div class="MainScroll">
178
+ <div class="MainContent">
179
+ <slot />
180
+ </div>
181
+ </div>
182
+ </template>
183
+ </main>
184
+ </div>
185
+ </template>
186
+
187
+ <style scoped>
188
+ .SidebarLayout {
189
+ --bar-height: 3rem;
190
+ display: flex;
191
+ height: 100vh;
192
+ height: 100dvh;
193
+ background-color: var(--color-bg-0);
194
+ color: var(--color-text);
195
+ font-family: var(--font-family);
196
+ position: relative;
197
+ }
198
+
199
+ .SidebarRail {
200
+ flex-shrink: 0;
201
+ width: var(--sidebar-width);
202
+ height: 100%;
203
+ overflow: hidden;
204
+ transition: width var(--transition-normal);
205
+ position: relative;
206
+ }
207
+
208
+ @media (min-width: 768px) {
209
+ .SidebarLayout[data-collapsed="true"] .SidebarRail {
210
+ width: var(--toggle-size);
211
+ background-color: var(--color-bg-1);
212
+ border-right: 1px solid var(--color-border);
213
+ box-shadow: var(--shadow-sm);
214
+ }
215
+ }
216
+
217
+ .Sidebar {
218
+ font-size: var(--font-size-sm);
219
+ display: flex;
220
+ width: var(--sidebar-width);
221
+ height: 100%;
222
+ transform: translateX(0);
223
+ transition: transform var(--transition-normal);
224
+ }
225
+
226
+ .SidebarLayout[data-collapsed="true"] .Sidebar {
227
+ transform: translateX(-100%);
228
+ }
229
+
230
+ .SidebarScroll {
231
+ flex: 1;
232
+ min-width: 0;
233
+ overflow-y: auto;
234
+ padding: var(--space-4);
235
+ background-color: var(--color-bg-1);
236
+ border-right: 1px solid var(--color-border);
237
+ box-shadow: var(--shadow-sm);
238
+ scrollbar-width: thin;
239
+ scrollbar-color: transparent transparent;
240
+ display: flex;
241
+ flex-direction: column;
242
+ gap: var(--space-3);
243
+ }
244
+
245
+ .SidebarScroll:hover {
246
+ scrollbar-color: var(--color-border) transparent;
247
+ }
248
+
249
+ .SidebarScroll :deep(h2) {
250
+ font-size: var(--font-size-sm);
251
+ font-weight: 600;
252
+ text-transform: uppercase;
253
+ letter-spacing: 0.05em;
254
+ color: var(--color-text-secondary);
255
+ margin: var(--space-4) 0 var(--space-2);
256
+ }
257
+
258
+ .SidebarScroll > :deep(h2:first-child) {
259
+ margin-top: 0;
260
+ }
261
+
262
+ .Toggle {
263
+ flex-shrink: 0;
264
+ display: flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ width: var(--toggle-size);
268
+ height: var(--toggle-size);
269
+ margin: 0;
270
+ padding: 0;
271
+ border: none;
272
+ background: transparent;
273
+ color: var(--color-text-secondary);
274
+ cursor: pointer;
275
+ transition:
276
+ color var(--transition-fast),
277
+ background-color var(--transition-fast);
278
+ }
279
+
280
+ .Toggle:hover {
281
+ color: var(--color-text);
282
+ background-color: var(--color-bg-2);
283
+ }
284
+
285
+ .Toggle:focus-visible {
286
+ outline: none;
287
+ box-shadow: var(--shadow-focus);
288
+ }
289
+
290
+ .SidebarHeader {
291
+ display: flex;
292
+ justify-content: flex-end;
293
+ margin: calc(-1 * var(--space-4)) calc(-1 * var(--space-4))
294
+ calc(-1 * var(--space-3));
295
+ }
296
+
297
+ .Toggle--expand {
298
+ position: absolute;
299
+ top: 0;
300
+ left: 50%;
301
+ transform: translateX(-50%);
302
+ opacity: 0;
303
+ pointer-events: none;
304
+ transition: opacity var(--transition-fast);
305
+ }
306
+
307
+ .SidebarLayout[data-collapsed="true"] .Toggle--expand {
308
+ opacity: 1;
309
+ pointer-events: auto;
310
+ }
311
+
312
+ .Main {
313
+ flex: 1;
314
+ min-width: 0;
315
+ display: flex;
316
+ flex-direction: column;
317
+ overflow: hidden;
318
+ background-color: var(--color-bg-0);
319
+ font-size: var(--font-size-md);
320
+ container-type: inline-size;
321
+ }
322
+
323
+ .Topbar {
324
+ display: flex;
325
+ align-items: center;
326
+ min-height: var(--bar-height);
327
+ padding: 0 var(--space-4);
328
+ flex-shrink: 0;
329
+ }
330
+
331
+ .TopbarEnd {
332
+ margin-left: auto;
333
+ display: flex;
334
+ align-items: center;
335
+ gap: var(--space-1);
336
+ }
337
+
338
+ @container (min-width: 768px) {
339
+ .Topbar {
340
+ padding: 0 var(--space-4) 0 var(--space-20);
341
+ }
342
+ }
343
+
344
+ .MainScroll {
345
+ flex: 1;
346
+ min-height: 0;
347
+ overflow-y: auto;
348
+ overflow-x: hidden;
349
+ padding: var(--space-6) 0;
350
+ scrollbar-width: thin;
351
+ scrollbar-color: transparent transparent;
352
+ }
353
+
354
+ .MainScroll:hover {
355
+ scrollbar-color: var(--color-border) transparent;
356
+ }
357
+
358
+ .MainContent {
359
+ max-width: 1024px;
360
+ padding: 0 var(--space-4);
361
+ }
362
+
363
+ @container (min-width: 768px) {
364
+ .MainContent {
365
+ padding: 0 var(--space-4) 0 var(--space-20);
366
+ }
367
+ }
368
+
369
+ /* Mobile: use transform instead of width resize */
370
+ @media (max-width: 767px) {
371
+ .SidebarLayout {
372
+ transition: transform var(--transition-normal);
373
+ }
374
+
375
+ .SidebarLayout[data-collapsed="true"] {
376
+ transform: translateX(calc(-1 * var(--sidebar-width)));
377
+ }
378
+
379
+ .SidebarLayout[data-collapsed="true"] .Sidebar {
380
+ transform: translateX(0);
381
+ }
382
+
383
+ .SidebarRail {
384
+ min-width: var(--sidebar-width);
385
+ }
386
+
387
+ .Main {
388
+ min-width: 100vw;
389
+ }
390
+
391
+ .Toggle--expand {
392
+ display: none;
393
+ }
394
+ }
395
+
396
+ /* Tabs */
397
+ .TabsLayout {
398
+ display: flex;
399
+ flex-direction: column;
400
+ flex: 1;
401
+ min-height: 0;
402
+ }
403
+
404
+ .TabsBar {
405
+ flex-shrink: 0;
406
+ display: flex;
407
+ align-items: center;
408
+ min-height: var(--bar-height);
409
+ border-bottom: 1px solid var(--color-border);
410
+ padding: 0 var(--space-4);
411
+ }
412
+
413
+ .TabsBarEnd {
414
+ margin-left: auto;
415
+ display: flex;
416
+ align-items: center;
417
+ gap: var(--space-1);
418
+ }
419
+
420
+ .TabsList {
421
+ display: flex;
422
+ gap: var(--space-1);
423
+ position: relative;
424
+ align-self: stretch;
425
+ }
426
+
427
+ .TabsTrigger {
428
+ position: relative;
429
+ padding: var(--space-2) var(--space-3);
430
+ font-size: var(--font-size-sm);
431
+ font-weight: 500;
432
+ font-family: inherit;
433
+ color: var(--color-text-secondary);
434
+ background: none;
435
+ border: none;
436
+ cursor: pointer;
437
+ transition:
438
+ color var(--transition-fast),
439
+ background-color var(--transition-fast);
440
+ border-radius: var(--radius-md) var(--radius-md) 0 0;
441
+ }
442
+
443
+ .TabsTrigger:hover {
444
+ color: var(--color-text);
445
+ background-color: var(--color-bg-1);
446
+ }
447
+
448
+ .TabsTrigger[data-state="active"] {
449
+ color: var(--color-text);
450
+ }
451
+
452
+ .TabsTrigger:focus-visible {
453
+ outline: none;
454
+ box-shadow: var(--shadow-focus);
455
+ }
456
+
457
+ .TabsIndicator {
458
+ position: absolute;
459
+ bottom: 0;
460
+ height: 2px;
461
+ background-color: var(--color-text);
462
+ transition:
463
+ width var(--transition-fast),
464
+ left var(--transition-fast);
465
+ }
466
+ </style>
@@ -0,0 +1,51 @@
1
+ # Spinner
2
+
3
+ A loading indicator with accessible labeling.
4
+
5
+ ## Examples
6
+
7
+ ### Sizes
8
+
9
+ <ComponentDemo>
10
+ <Spinner size="sm" label="Loading" />
11
+ <Spinner size="md" label="Loading" />
12
+ <Spinner size="lg" label="Loading" />
13
+
14
+ <template #code>
15
+
16
+ ```vue
17
+ <Spinner size="sm" label="Loading" />
18
+ <Spinner size="md" label="Loading" />
19
+ <Spinner size="lg" label="Loading" />
20
+ ```
21
+
22
+ </template>
23
+ </ComponentDemo>
24
+
25
+ ### In context
26
+
27
+ <ComponentDemo>
28
+ <div style="display: flex; align-items: center; gap: 8px;">
29
+ <Spinner size="sm" label="Running model" />
30
+ <span>Running model...</span>
31
+ </div>
32
+
33
+ <template #code>
34
+
35
+ ```vue
36
+ <div style="display: flex; align-items: center; gap: 8px;">
37
+ <Spinner size="sm" label="Running model" />
38
+ <span>Running model...</span>
39
+ </div>
40
+ ```
41
+
42
+ </template>
43
+ </ComponentDemo>
44
+
45
+ ## Props
46
+
47
+ | Prop | Type | Required | Default |
48
+ |------|------|----------|---------|
49
+ | `size` | `SpinnerSize` | No | `"sm"` |
50
+ | `label` | `string` | No | — |
51
+
@@ -0,0 +1,55 @@
1
+ <script setup lang="ts">
2
+ export type SpinnerSize = "sm" | "md" | "lg";
3
+
4
+ withDefaults(
5
+ defineProps<{
6
+ size?: SpinnerSize;
7
+ label?: string;
8
+ }>(),
9
+ {
10
+ size: "sm",
11
+ },
12
+ );
13
+ </script>
14
+
15
+ <template>
16
+ <span
17
+ class="Spinner"
18
+ :data-size="size"
19
+ role="status"
20
+ :aria-label="label ?? 'Loading'"
21
+ />
22
+ </template>
23
+
24
+ <style>
25
+ .Spinner {
26
+ display: inline-block;
27
+ border: 2px solid currentColor;
28
+ border-top-color: transparent;
29
+ border-radius: 50%;
30
+ animation: cfa-spin 0.6s linear infinite;
31
+ vertical-align: middle;
32
+ }
33
+
34
+ .Spinner[data-size="sm"] {
35
+ width: 14px;
36
+ height: 14px;
37
+ }
38
+
39
+ .Spinner[data-size="md"] {
40
+ width: 20px;
41
+ height: 20px;
42
+ }
43
+
44
+ .Spinner[data-size="lg"] {
45
+ width: 28px;
46
+ height: 28px;
47
+ border-width: 3px;
48
+ }
49
+
50
+ @keyframes cfa-spin {
51
+ to {
52
+ transform: rotate(360deg);
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,82 @@
1
+ # TextInput
2
+
3
+ A text input field with optional label and hint.
4
+
5
+ ## Examples
6
+
7
+ <script setup>
8
+ import { ref } from 'vue'
9
+ const name = ref('')
10
+ </script>
11
+
12
+ <ComponentDemo>
13
+ <div style="width: 300px">
14
+ <TextInput
15
+ v-model="name"
16
+ label="Model name"
17
+ placeholder="e.g. my-model"
18
+ hint="A short identifier used in URLs"
19
+ />
20
+ </div>
21
+
22
+ <template #code>
23
+
24
+ ```vue
25
+ <script setup>
26
+ import { ref } from "vue";
27
+ const name = ref("");
28
+ </script>
29
+
30
+ <TextInput
31
+ v-model="name"
32
+ label="Model name"
33
+ placeholder="e.g. my-model"
34
+ hint="A short identifier used in URLs"
35
+ />
36
+ ```
37
+
38
+ </template>
39
+ </ComponentDemo>
40
+
41
+ ### Hidden label
42
+
43
+ Use `hide-label` to visually hide the label while keeping it available to screen
44
+ readers. The label is still a real `<label>` in the DOM, so clicking the input
45
+ area still works and the control is properly named for assistive tech. Prefer
46
+ this over omitting the label whenever the input has no visible text describing
47
+ it.
48
+
49
+ <ComponentDemo>
50
+ <div style="width: 300px">
51
+ <TextInput
52
+ v-model="name"
53
+ label="Search"
54
+ placeholder="Search…"
55
+ hide-label
56
+ />
57
+ </div>
58
+
59
+ <template #code>
60
+
61
+ ```vue
62
+ <TextInput v-model="name" label="Search" placeholder="Search…" hide-label />
63
+ ```
64
+
65
+ </template>
66
+ </ComponentDemo>
67
+
68
+ ## Model
69
+
70
+ | Name | Type |
71
+ |------|------|
72
+ | `v-model` | `string` |
73
+
74
+ ## Props
75
+
76
+ | Prop | Type | Required | Default |
77
+ |------|------|----------|---------|
78
+ | `label` | `string` | No | — |
79
+ | `hideLabel` | `boolean` | No | — |
80
+ | `placeholder` | `string` | No | — |
81
+ | `hint` | `string` | No | — |
82
+