@cfasim-ui/docs 0.3.15 → 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.
@@ -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
+ }
@@ -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.15",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfasim-ui/docs",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -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
+ }