@cfasim-ui/docs 0.3.14 → 0.3.16
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.
- package/components/Container/Container.md +103 -0
- package/components/Container/Container.vue +62 -0
- package/components/Grid/Grid.md +170 -0
- package/components/Grid/Grid.vue +145 -0
- package/components/_internal/gap.ts +17 -0
- package/components/index.ts +4 -0
- package/index.json +33 -1
- package/package.json +1 -1
- package/pyodide/pyodide.worker.ts +38 -8
- package/pyodide/vitePlugin.js +15 -1
- package/theme/themes/cdc.css +13 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Container
|
|
2
|
+
|
|
3
|
+
A flexible wrapper for grouping elements vertically (default) or horizontally. Optionally adds a border, fixed height with scrolling, and configurable gap between children.
|
|
4
|
+
|
|
5
|
+
For multi-column layouts use [Grid](./grid).
|
|
6
|
+
|
|
7
|
+
## Examples
|
|
8
|
+
|
|
9
|
+
### Vertical stack (default)
|
|
10
|
+
|
|
11
|
+
<ComponentDemo>
|
|
12
|
+
<Container gap="small">
|
|
13
|
+
<Box variant="info">First</Box>
|
|
14
|
+
<Box variant="info">Second</Box>
|
|
15
|
+
<Box variant="info">Third</Box>
|
|
16
|
+
</Container>
|
|
17
|
+
|
|
18
|
+
<template #code>
|
|
19
|
+
|
|
20
|
+
```vue
|
|
21
|
+
<Container gap="small">
|
|
22
|
+
<Box variant="info">First</Box>
|
|
23
|
+
<Box variant="info">Second</Box>
|
|
24
|
+
<Box variant="info">Third</Box>
|
|
25
|
+
</Container>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
</template>
|
|
29
|
+
</ComponentDemo>
|
|
30
|
+
|
|
31
|
+
### Horizontal row
|
|
32
|
+
|
|
33
|
+
A row of buttons or chips. Wraps onto multiple lines when space runs out.
|
|
34
|
+
|
|
35
|
+
<ComponentDemo>
|
|
36
|
+
<Container horizontal gap="small">
|
|
37
|
+
<Button>Save</Button>
|
|
38
|
+
<Button variant="secondary">Cancel</Button>
|
|
39
|
+
<Button variant="secondary">Reset</Button>
|
|
40
|
+
</Container>
|
|
41
|
+
|
|
42
|
+
<template #code>
|
|
43
|
+
|
|
44
|
+
```vue
|
|
45
|
+
<Container horizontal gap="small">
|
|
46
|
+
<Button>Save</Button>
|
|
47
|
+
<Button variant="secondary">Cancel</Button>
|
|
48
|
+
<Button variant="secondary">Reset</Button>
|
|
49
|
+
</Container>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
</template>
|
|
53
|
+
</ComponentDemo>
|
|
54
|
+
|
|
55
|
+
### Card with border
|
|
56
|
+
|
|
57
|
+
<ComponentDemo>
|
|
58
|
+
<Container border gap="small">
|
|
59
|
+
<strong>Run summary</strong>
|
|
60
|
+
<span>Generated 1,000 samples in 2.4s</span>
|
|
61
|
+
</Container>
|
|
62
|
+
|
|
63
|
+
<template #code>
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<Container border gap="small">
|
|
67
|
+
<strong>Run summary</strong>
|
|
68
|
+
<span>Generated 1,000 samples in 2.4s</span>
|
|
69
|
+
</Container>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
</template>
|
|
73
|
+
</ComponentDemo>
|
|
74
|
+
|
|
75
|
+
### Scrollable region
|
|
76
|
+
|
|
77
|
+
Setting `height` automatically enables scrolling when content overflows.
|
|
78
|
+
|
|
79
|
+
<ComponentDemo>
|
|
80
|
+
<Container border :height="180" gap="small">
|
|
81
|
+
<div v-for="i in 30" :key="i">Line {{ i }}</div>
|
|
82
|
+
</Container>
|
|
83
|
+
|
|
84
|
+
<template #code>
|
|
85
|
+
|
|
86
|
+
```vue
|
|
87
|
+
<Container border :height="180" gap="small">
|
|
88
|
+
<div v-for="i in 30" :key="i">Line {{ i }}</div>
|
|
89
|
+
</Container>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
</template>
|
|
93
|
+
</ComponentDemo>
|
|
94
|
+
|
|
95
|
+
## Props
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Required | Default |
|
|
98
|
+
|------|------|----------|---------|
|
|
99
|
+
| `border` | `boolean` | No | — |
|
|
100
|
+
| `height` | `number \| string` | No | — |
|
|
101
|
+
| `horizontal` | `boolean` | No | — |
|
|
102
|
+
| `gap` | `ContainerGap \| string` | No | — |
|
|
103
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { type GapToken, resolveGap } from "../_internal/gap";
|
|
4
|
+
|
|
5
|
+
export type ContainerGap = GapToken;
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
border?: boolean;
|
|
9
|
+
height?: number | string;
|
|
10
|
+
horizontal?: boolean;
|
|
11
|
+
gap?: ContainerGap | string;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const resolvedGap = computed(() => resolveGap(props.gap));
|
|
15
|
+
|
|
16
|
+
const resolvedHeight = computed(() => {
|
|
17
|
+
const h = props.height;
|
|
18
|
+
if (h == null) return undefined;
|
|
19
|
+
return typeof h === "number" ? `${h}px` : h;
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
class="container"
|
|
26
|
+
:class="{
|
|
27
|
+
'container-border': border,
|
|
28
|
+
'container-horizontal': horizontal,
|
|
29
|
+
'container-scrollable': height != null,
|
|
30
|
+
}"
|
|
31
|
+
:style="{
|
|
32
|
+
gap: resolvedGap,
|
|
33
|
+
height: resolvedHeight,
|
|
34
|
+
}"
|
|
35
|
+
>
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<style scoped>
|
|
41
|
+
.container {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.container-horizontal {
|
|
48
|
+
flex-direction: row;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
align-items: center;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.container-border {
|
|
54
|
+
padding: var(--space-4);
|
|
55
|
+
border: 1px solid var(--color-border);
|
|
56
|
+
border-radius: var(--radius-md);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.container-scrollable {
|
|
60
|
+
overflow: auto;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Grid
|
|
2
|
+
|
|
3
|
+
A CSS-grid wrapper for arranging elements in equal, proportional, or auto-fitting columns.
|
|
4
|
+
|
|
5
|
+
For single-direction stacks (vertical or a flex row that wraps), use [Container](./container) instead.
|
|
6
|
+
|
|
7
|
+
## Examples
|
|
8
|
+
|
|
9
|
+
### Equal columns
|
|
10
|
+
|
|
11
|
+
Pass a number for N equal-width columns.
|
|
12
|
+
|
|
13
|
+
<ComponentDemo>
|
|
14
|
+
<Grid :cols="3" gap="medium">
|
|
15
|
+
<Box variant="info">Column 1</Box>
|
|
16
|
+
<Box variant="info">Column 2</Box>
|
|
17
|
+
<Box variant="info">Column 3</Box>
|
|
18
|
+
</Grid>
|
|
19
|
+
|
|
20
|
+
<template #code>
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<Grid :cols="3" gap="medium">
|
|
24
|
+
<Box variant="info">Column 1</Box>
|
|
25
|
+
<Box variant="info">Column 2</Box>
|
|
26
|
+
<Box variant="info">Column 3</Box>
|
|
27
|
+
</Grid>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
</template>
|
|
31
|
+
</ComponentDemo>
|
|
32
|
+
|
|
33
|
+
### Proportional widths
|
|
34
|
+
|
|
35
|
+
Pass an array of `fr` weights for asymmetric layouts.
|
|
36
|
+
|
|
37
|
+
<ComponentDemo>
|
|
38
|
+
<Grid :cols="[2, 3, 1]" gap="medium">
|
|
39
|
+
<Box variant="info">2fr</Box>
|
|
40
|
+
<Box variant="success">3fr</Box>
|
|
41
|
+
<Box variant="warning">1fr</Box>
|
|
42
|
+
</Grid>
|
|
43
|
+
|
|
44
|
+
<template #code>
|
|
45
|
+
|
|
46
|
+
```vue
|
|
47
|
+
<Grid :cols="[2, 3, 1]" gap="medium">
|
|
48
|
+
<Box variant="info">2fr</Box>
|
|
49
|
+
<Box variant="success">3fr</Box>
|
|
50
|
+
<Box variant="warning">1fr</Box>
|
|
51
|
+
</Grid>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
</template>
|
|
55
|
+
</ComponentDemo>
|
|
56
|
+
|
|
57
|
+
### Mixed track sizes
|
|
58
|
+
|
|
59
|
+
Strings pass through unchanged, so you can combine fixed and flexible tracks.
|
|
60
|
+
|
|
61
|
+
<ComponentDemo>
|
|
62
|
+
<Grid :cols="['200px', '1fr']" gap="medium">
|
|
63
|
+
<Box variant="info">Fixed 200px</Box>
|
|
64
|
+
<Box variant="info">Fills remaining space</Box>
|
|
65
|
+
</Grid>
|
|
66
|
+
|
|
67
|
+
<template #code>
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<Grid :cols="['200px', '1fr']" gap="medium">
|
|
71
|
+
<Box variant="info">Fixed 200px</Box>
|
|
72
|
+
<Box variant="info">Fills remaining space</Box>
|
|
73
|
+
</Grid>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
</template>
|
|
77
|
+
</ComponentDemo>
|
|
78
|
+
|
|
79
|
+
### Small-width breakpoint
|
|
80
|
+
|
|
81
|
+
`colsSmall` overrides `cols` when the grid's own width is at or below `breakpoint`. The default breakpoint is `640px` unless you specify it. The check is a CSS container query against the grid itself, so it triggers based on the grid's available width (e.g. when nested inside a sidebar), not the viewport.
|
|
82
|
+
|
|
83
|
+
<ComponentDemo>
|
|
84
|
+
<Grid :cols="3" :cols-small="1" breakpoint="480px" gap="small">
|
|
85
|
+
<Box variant="info">Card 1</Box>
|
|
86
|
+
<Box variant="info">Card 2</Box>
|
|
87
|
+
<Box variant="info">Card 3</Box>
|
|
88
|
+
</Grid>
|
|
89
|
+
|
|
90
|
+
<template #code>
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<Grid :cols="3" :cols-small="1" breakpoint="480px" gap="small">
|
|
94
|
+
<Box variant="info">Card 1</Box>
|
|
95
|
+
<Box variant="info">Card 2</Box>
|
|
96
|
+
<Box variant="info">Card 3</Box>
|
|
97
|
+
</Grid>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
</template>
|
|
101
|
+
</ComponentDemo>
|
|
102
|
+
|
|
103
|
+
### Responsive auto-fit
|
|
104
|
+
|
|
105
|
+
`minColWidth` switches to `repeat(auto-fit, minmax(...))` so items reflow to fit the viewport without media queries. Great for metric tiles and card grids.
|
|
106
|
+
|
|
107
|
+
<ComponentDemo>
|
|
108
|
+
<Grid min-col-width="180px" gap="medium">
|
|
109
|
+
<Box variant="info">Card 1</Box>
|
|
110
|
+
<Box variant="info">Card 2</Box>
|
|
111
|
+
<Box variant="info">Card 3</Box>
|
|
112
|
+
<Box variant="info">Card 4</Box>
|
|
113
|
+
<Box variant="info">Card 5</Box>
|
|
114
|
+
</Grid>
|
|
115
|
+
|
|
116
|
+
<template #code>
|
|
117
|
+
|
|
118
|
+
```vue
|
|
119
|
+
<Grid min-col-width="180px" gap="medium">
|
|
120
|
+
<Box variant="info">Card 1</Box>
|
|
121
|
+
<Box variant="info">Card 2</Box>
|
|
122
|
+
<Box variant="info">Card 3</Box>
|
|
123
|
+
<Box variant="info">Card 4</Box>
|
|
124
|
+
<Box variant="info">Card 5</Box>
|
|
125
|
+
</Grid>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
</template>
|
|
129
|
+
</ComponentDemo>
|
|
130
|
+
|
|
131
|
+
### Nested grids
|
|
132
|
+
|
|
133
|
+
<ComponentDemo>
|
|
134
|
+
<Grid :cols="2" gap="medium">
|
|
135
|
+
<Box variant="info">Left</Box>
|
|
136
|
+
<Grid :cols="2" gap="small">
|
|
137
|
+
<Box variant="success">a</Box>
|
|
138
|
+
<Box variant="success">b</Box>
|
|
139
|
+
<Box variant="success">c</Box>
|
|
140
|
+
<Box variant="success">d</Box>
|
|
141
|
+
</Grid>
|
|
142
|
+
</Grid>
|
|
143
|
+
|
|
144
|
+
<template #code>
|
|
145
|
+
|
|
146
|
+
```vue
|
|
147
|
+
<Grid :cols="2" gap="medium">
|
|
148
|
+
<Box variant="info">Left</Box>
|
|
149
|
+
<Grid :cols="2" gap="small">
|
|
150
|
+
<Box variant="success">a</Box>
|
|
151
|
+
<Box variant="success">b</Box>
|
|
152
|
+
<Box variant="success">c</Box>
|
|
153
|
+
<Box variant="success">d</Box>
|
|
154
|
+
</Grid>
|
|
155
|
+
</Grid>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
</template>
|
|
159
|
+
</ComponentDemo>
|
|
160
|
+
|
|
161
|
+
## Props
|
|
162
|
+
|
|
163
|
+
| Prop | Type | Required | Default |
|
|
164
|
+
|------|------|----------|---------|
|
|
165
|
+
| `cols` | `GridCols` | No | — |
|
|
166
|
+
| `colsSmall` | `GridCols` | No | — |
|
|
167
|
+
| `breakpoint` | `string` | No | — |
|
|
168
|
+
| `gap` | `GridGap \| string` | No | — |
|
|
169
|
+
| `minColWidth` | `string` | No | — |
|
|
170
|
+
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, onUnmounted, watch } from "vue";
|
|
3
|
+
import { type GapToken, resolveGap } from "../_internal/gap";
|
|
4
|
+
|
|
5
|
+
export type GridGap = GapToken;
|
|
6
|
+
export type GridCols = number | (number | string)[];
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{
|
|
9
|
+
cols?: GridCols;
|
|
10
|
+
colsSmall?: GridCols;
|
|
11
|
+
breakpoint?: string;
|
|
12
|
+
gap?: GridGap | string;
|
|
13
|
+
minColWidth?: string;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const resolvedGap = computed(() => resolveGap(props.gap));
|
|
17
|
+
|
|
18
|
+
const resolvedColumns = computed(() => {
|
|
19
|
+
if (props.minColWidth) {
|
|
20
|
+
return `repeat(auto-fit, minmax(${props.minColWidth}, 1fr))`;
|
|
21
|
+
}
|
|
22
|
+
return colsToTemplate(props.cols ?? 2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const resolvedSmallColumns = computed(() => {
|
|
26
|
+
if (props.minColWidth || props.colsSmall == null) {
|
|
27
|
+
return resolvedColumns.value;
|
|
28
|
+
}
|
|
29
|
+
return colsToTemplate(props.colsSmall);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const safeBreakpoint = computed(() =>
|
|
33
|
+
sanitizeBreakpoint(props.breakpoint ?? DEFAULT_BREAKPOINT),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
let acquired: string | null = null;
|
|
37
|
+
const stopWatch = watch(
|
|
38
|
+
safeBreakpoint,
|
|
39
|
+
(next) => {
|
|
40
|
+
if (acquired === next) return;
|
|
41
|
+
acquireBreakpoint(next);
|
|
42
|
+
if (acquired) releaseBreakpoint(acquired);
|
|
43
|
+
acquired = next;
|
|
44
|
+
},
|
|
45
|
+
{ immediate: true },
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
onUnmounted(() => {
|
|
49
|
+
stopWatch();
|
|
50
|
+
if (acquired) {
|
|
51
|
+
releaseBreakpoint(acquired);
|
|
52
|
+
acquired = null;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<script lang="ts">
|
|
58
|
+
const DEFAULT_BREAKPOINT = "640px";
|
|
59
|
+
|
|
60
|
+
function colsToTemplate(cols: GridCols): string {
|
|
61
|
+
if (typeof cols === "number") return `repeat(${cols}, 1fr)`;
|
|
62
|
+
return cols.map((c) => (typeof c === "number" ? `${c}fr` : c)).join(" ");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Allow only digits, dot, and unit letters/percent. Falls back to the default
|
|
66
|
+
// when input doesn't look like a CSS length, preventing rule-escape via the
|
|
67
|
+
// breakpoint prop.
|
|
68
|
+
function sanitizeBreakpoint(value: string): string {
|
|
69
|
+
return /^\d+(\.\d+)?[a-zA-Z%]+$/.test(value) ? value : DEFAULT_BREAKPOINT;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Module-scope cache: one <style> per unique breakpoint, ref-counted across
|
|
73
|
+
// all Grid instances. The grid's inline `--grid-cols-small` is consumed by
|
|
74
|
+
// the rule in the cached <style>.
|
|
75
|
+
type Entry = { count: number; el: HTMLStyleElement };
|
|
76
|
+
const breakpointSheets = new Map<string, Entry>();
|
|
77
|
+
|
|
78
|
+
function acquireBreakpoint(breakpoint: string) {
|
|
79
|
+
if (typeof document === "undefined") return;
|
|
80
|
+
const existing = breakpointSheets.get(breakpoint);
|
|
81
|
+
if (existing) {
|
|
82
|
+
existing.count++;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const el = document.createElement("style");
|
|
86
|
+
el.setAttribute("data-cfasim-grid-bp", breakpoint);
|
|
87
|
+
el.textContent =
|
|
88
|
+
`@container (max-width: ${breakpoint}) {` +
|
|
89
|
+
`[data-cfasim-grid-bp="${breakpoint}"] > .grid {` +
|
|
90
|
+
`grid-template-columns: var(--grid-cols-small) !important;` +
|
|
91
|
+
`}}`;
|
|
92
|
+
document.head.appendChild(el);
|
|
93
|
+
breakpointSheets.set(breakpoint, { count: 1, el });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function releaseBreakpoint(breakpoint: string) {
|
|
97
|
+
if (typeof document === "undefined") return;
|
|
98
|
+
const entry = breakpointSheets.get(breakpoint);
|
|
99
|
+
if (!entry) return;
|
|
100
|
+
entry.count--;
|
|
101
|
+
if (entry.count === 0) {
|
|
102
|
+
entry.el.remove();
|
|
103
|
+
breakpointSheets.delete(breakpoint);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hmr = (import.meta as { hot?: { dispose: (cb: () => void) => void } })
|
|
108
|
+
.hot;
|
|
109
|
+
hmr?.dispose(() => {
|
|
110
|
+
breakpointSheets.forEach(({ el }) => el.remove());
|
|
111
|
+
breakpointSheets.clear();
|
|
112
|
+
});
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<template>
|
|
116
|
+
<div :data-cfasim-grid-bp="safeBreakpoint" class="grid-wrapper">
|
|
117
|
+
<div
|
|
118
|
+
class="grid"
|
|
119
|
+
:style="{
|
|
120
|
+
gap: resolvedGap,
|
|
121
|
+
gridTemplateColumns: resolvedColumns,
|
|
122
|
+
'--grid-cols-small': resolvedSmallColumns,
|
|
123
|
+
}"
|
|
124
|
+
>
|
|
125
|
+
<slot />
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
|
|
130
|
+
<style scoped>
|
|
131
|
+
.grid-wrapper {
|
|
132
|
+
container-type: inline-size;
|
|
133
|
+
width: 100%;
|
|
134
|
+
min-width: 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.grid {
|
|
138
|
+
display: grid;
|
|
139
|
+
min-width: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.grid > :deep(*) {
|
|
143
|
+
min-width: 0;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type GapToken = "none" | "small" | "medium" | "large";
|
|
2
|
+
|
|
3
|
+
const GAP_TOKENS: Record<GapToken, string> = {
|
|
4
|
+
none: "0",
|
|
5
|
+
small: "var(--space-2)",
|
|
6
|
+
medium: "var(--space-4)",
|
|
7
|
+
large: "var(--space-6)",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function resolveGap(
|
|
11
|
+
gap: GapToken | string | undefined,
|
|
12
|
+
fallback: GapToken = "medium",
|
|
13
|
+
): string {
|
|
14
|
+
if (gap == null) return GAP_TOKENS[fallback];
|
|
15
|
+
if (gap in GAP_TOKENS) return GAP_TOKENS[gap as GapToken];
|
|
16
|
+
return gap;
|
|
17
|
+
}
|
package/components/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { default as Box } from "./Box/Box.vue";
|
|
2
2
|
export type { BoxVariant } from "./Box/Box.vue";
|
|
3
3
|
export { default as Button } from "./Button/Button.vue";
|
|
4
|
+
export { default as Container } from "./Container/Container.vue";
|
|
5
|
+
export type { ContainerGap } from "./Container/Container.vue";
|
|
4
6
|
export { default as Expander } from "./Expander/Expander.vue";
|
|
7
|
+
export { default as Grid } from "./Grid/Grid.vue";
|
|
8
|
+
export type { GridCols, GridGap } from "./Grid/Grid.vue";
|
|
5
9
|
export { default as Hint } from "./Hint/Hint.vue";
|
|
6
10
|
export { default as Icon } from "./Icon/Icon.vue";
|
|
7
11
|
export { default as LightDarkToggle } from "./LightDarkToggle/LightDarkToggle.vue";
|
package/index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.3.
|
|
2
|
+
"version": "0.3.16",
|
|
3
3
|
"package": "@cfasim-ui/docs",
|
|
4
4
|
"content": {
|
|
5
5
|
"components": [
|
|
@@ -23,6 +23,22 @@
|
|
|
23
23
|
"secondary"
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
"name": "Container",
|
|
28
|
+
"slug": "container",
|
|
29
|
+
"docs": "components/Container/Container.md",
|
|
30
|
+
"source": "components/Container/Container.vue",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"container",
|
|
33
|
+
"layout",
|
|
34
|
+
"stack",
|
|
35
|
+
"row",
|
|
36
|
+
"flex",
|
|
37
|
+
"scrollable",
|
|
38
|
+
"border",
|
|
39
|
+
"card"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
26
42
|
{
|
|
27
43
|
"name": "Expander",
|
|
28
44
|
"slug": "expander",
|
|
@@ -30,6 +46,22 @@
|
|
|
30
46
|
"source": "components/Expander/Expander.vue",
|
|
31
47
|
"keywords": []
|
|
32
48
|
},
|
|
49
|
+
{
|
|
50
|
+
"name": "Grid",
|
|
51
|
+
"slug": "grid",
|
|
52
|
+
"docs": "components/Grid/Grid.md",
|
|
53
|
+
"source": "components/Grid/Grid.vue",
|
|
54
|
+
"keywords": [
|
|
55
|
+
"grid",
|
|
56
|
+
"columns",
|
|
57
|
+
"layout",
|
|
58
|
+
"responsive",
|
|
59
|
+
"auto-fit",
|
|
60
|
+
"cards",
|
|
61
|
+
"side-by-side",
|
|
62
|
+
"proportional"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
33
65
|
{
|
|
34
66
|
"name": "Hint",
|
|
35
67
|
"slug": "hint",
|
package/package.json
CHANGED
|
@@ -17,18 +17,48 @@ let wheelMap: Record<string, string> = {};
|
|
|
17
17
|
|
|
18
18
|
const baseUrl = import.meta.env.BASE_URL ?? "/";
|
|
19
19
|
|
|
20
|
+
const DEFAULT_PYODIDE_PACKAGES = ["micropip", "numpy"];
|
|
21
|
+
|
|
20
22
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
23
|
let micropip: any;
|
|
22
24
|
|
|
25
|
+
async function fetchPyodidePackages(): Promise<string[]> {
|
|
26
|
+
const url = `${self.location.origin}${baseUrl}pyodide-packages.json`;
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(url);
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
console.warn(
|
|
31
|
+
`[pyodide-worker] ${url} returned ${res.status}; falling back to default packages`,
|
|
32
|
+
);
|
|
33
|
+
return DEFAULT_PYODIDE_PACKAGES;
|
|
34
|
+
}
|
|
35
|
+
const list = await res.json();
|
|
36
|
+
if (!Array.isArray(list)) {
|
|
37
|
+
console.warn(
|
|
38
|
+
`[pyodide-worker] ${url} did not contain an array; falling back to default packages`,
|
|
39
|
+
);
|
|
40
|
+
return DEFAULT_PYODIDE_PACKAGES;
|
|
41
|
+
}
|
|
42
|
+
return list as string[];
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.warn(
|
|
45
|
+
`[pyodide-worker] failed to load ${url}; falling back to default packages`,
|
|
46
|
+
err,
|
|
47
|
+
);
|
|
48
|
+
return DEFAULT_PYODIDE_PACKAGES;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
const pyodideReadyPromise = (async () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
53
|
+
const [pyodideModule, packages] = await Promise.all([
|
|
54
|
+
// @ts-expect-error - Pyodide types from CDN
|
|
55
|
+
import(
|
|
56
|
+
/* @vite-ignore */ "https://cdn.jsdelivr.net/pyodide/v0.29.3/full/pyodide.mjs"
|
|
57
|
+
),
|
|
58
|
+
fetchPyodidePackages(),
|
|
59
|
+
]);
|
|
60
|
+
const { loadPyodide } = pyodideModule;
|
|
61
|
+
const pyodide = await loadPyodide({ packages });
|
|
32
62
|
|
|
33
63
|
micropip = pyodide.pyimport("micropip");
|
|
34
64
|
|
package/pyodide/vitePlugin.js
CHANGED
|
@@ -6,17 +6,23 @@ import { resolve } from "node:path";
|
|
|
6
6
|
* @typedef {object} CfasimPyodideOptions
|
|
7
7
|
* @property {string} [model] Path to the Python model directory (default: "model")
|
|
8
8
|
* @property {string[]} [pypiDeps] PyPI packages to download at build time and serve locally
|
|
9
|
+
* @property {string[]} [pyodidePackages] Pyodide built-in packages to preload in the worker (e.g. "scipy", "pandas"). See https://pyodide.org/en/stable/usage/packages-in-pyodide.html. `micropip` and `numpy` are always included.
|
|
9
10
|
* @property {string} [pipCommand] Command used to invoke pip for downloading `pypiDeps` (default: "uvx pip"). Override with e.g. "pip" or "uv run pip".
|
|
10
11
|
* @property {string} [pythonVersion] Python version passed to pip's --python-version flag when downloading `pypiDeps` (default: "3.12"). Should match the Python shipped by your Pyodide runtime.
|
|
11
12
|
*/
|
|
12
13
|
|
|
14
|
+
const DEFAULT_PYODIDE_PACKAGES = ["micropip", "numpy"];
|
|
15
|
+
|
|
13
16
|
/**
|
|
14
17
|
* Vite plugin that builds a Python wheel from a local model directory
|
|
15
|
-
* and generates wheels.json in the public directory.
|
|
18
|
+
* and generates wheels.json + pyodide-packages.json in the public directory.
|
|
16
19
|
*
|
|
17
20
|
* Use `pypiDeps` to prebuild PyPI packages (like cfasim-model) as local
|
|
18
21
|
* wheels for faster browser runtime — avoids PyPI round-trips on page load.
|
|
19
22
|
*
|
|
23
|
+
* Use `pyodidePackages` to preload Pyodide built-in packages (scipy, pandas,
|
|
24
|
+
* etc.) into the Pyodide worker on startup.
|
|
25
|
+
*
|
|
20
26
|
* @param {CfasimPyodideOptions} [options]
|
|
21
27
|
* @returns {import("vite").Plugin}
|
|
22
28
|
*/
|
|
@@ -24,6 +30,10 @@ export function cfasimPyodide(options) {
|
|
|
24
30
|
const modelDir = options?.model ?? "model";
|
|
25
31
|
const pipCommand = options?.pipCommand ?? "uvx pip";
|
|
26
32
|
const pythonVersion = options?.pythonVersion ?? "3.12";
|
|
33
|
+
const extraPackages = options?.pyodidePackages ?? [];
|
|
34
|
+
const pyodidePackages = [
|
|
35
|
+
...new Set([...DEFAULT_PYODIDE_PACKAGES, ...extraPackages]),
|
|
36
|
+
];
|
|
27
37
|
|
|
28
38
|
function build(root) {
|
|
29
39
|
const publicDir = resolve(root, "public");
|
|
@@ -40,6 +50,10 @@ export function cfasimPyodide(options) {
|
|
|
40
50
|
});
|
|
41
51
|
const wheels = readdirSync(publicDir).filter((f) => f.endsWith(".whl"));
|
|
42
52
|
writeFileSync(resolve(publicDir, "wheels.json"), JSON.stringify(wheels));
|
|
53
|
+
writeFileSync(
|
|
54
|
+
resolve(publicDir, "pyodide-packages.json"),
|
|
55
|
+
JSON.stringify(pyodidePackages),
|
|
56
|
+
);
|
|
43
57
|
}
|
|
44
58
|
|
|
45
59
|
return {
|
package/theme/themes/cdc.css
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
--font-weight-heading: 200;
|
|
9
9
|
--font-size-md: 1.125rem;
|
|
10
10
|
--font-size-2xl: 2.625rem;
|
|
11
|
+
--color-text: light-dark(#1c1d1f, #f8f9fa);
|
|
11
12
|
--color-bg-0: light-dark(#ffffff, #0f1e21);
|
|
12
13
|
--color-bg-1: light-dark(#f4fbfc, #162a2e);
|
|
13
14
|
--color-bg-2: light-dark(#e9f5f8, #1e373c);
|
|
@@ -20,3 +21,15 @@
|
|
|
20
21
|
--color-border-hover: light-dark(#a0d4e0, #3a5d64);
|
|
21
22
|
--color-border-focus: var(--color-primary);
|
|
22
23
|
}
|
|
24
|
+
|
|
25
|
+
[data-theme="cdc"] a {
|
|
26
|
+
text-decoration: underline;
|
|
27
|
+
text-decoration-thickness: 1px;
|
|
28
|
+
text-underline-offset: 0.25rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
[data-theme="cdc"] a:hover,
|
|
32
|
+
[data-theme="cdc"] a:focus,
|
|
33
|
+
[data-theme="cdc"] a:active {
|
|
34
|
+
color: light-dark(#007a99, #7dd6e8);
|
|
35
|
+
}
|