@cfasim-ui/docs 0.4.0 → 0.4.2

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,49 @@
1
+ <script setup lang="ts">
2
+ import { ref, useTemplateRef } from "vue";
3
+
4
+ export interface ChoroplethTooltipData {
5
+ id: string;
6
+ name: string;
7
+ value?: number | string;
8
+ feature: unknown;
9
+ }
10
+
11
+ defineSlots<{
12
+ default?(props: ChoroplethTooltipData): unknown;
13
+ }>();
14
+
15
+ // Local reactive state. Held inside the child so the parent's render scope
16
+ // never subscribes to it — hover updates re-render only this small tree,
17
+ // not the parent's 3,000+ paths.
18
+ const data = ref<ChoroplethTooltipData | null>(null);
19
+ const rootRef = useTemplateRef<HTMLDivElement>("root");
20
+
21
+ defineExpose({
22
+ setData(next: ChoroplethTooltipData | null) {
23
+ data.value = next;
24
+ },
25
+ getEl(): HTMLDivElement | null {
26
+ return rootRef.value;
27
+ },
28
+ });
29
+ </script>
30
+
31
+ <template>
32
+ <Teleport to="body">
33
+ <div
34
+ ref="root"
35
+ class="chart-tooltip-content"
36
+ style="
37
+ position: fixed;
38
+ left: 0;
39
+ top: 0;
40
+ visibility: hidden;
41
+ will-change: transform;
42
+ pointer-events: none;
43
+ transform: translateY(-50%);
44
+ "
45
+ >
46
+ <slot v-if="data" v-bind="data" />
47
+ </div>
48
+ </Teleport>
49
+ </template>
@@ -87,19 +87,48 @@ A table for displaying columnar data. Accepts a plain record of arrays or a `Mod
87
87
  </template>
88
88
  </ComponentDemo>
89
89
 
90
- ### Download data link
90
+ ### Full width
91
91
 
92
- Pass `download-link` to render a plain text link below the table for
93
- downloading the CSV data. Set it to `true` for the default label or a
94
- string to customize it. When set, the Download CSV menu item is hidden.
95
- Use `filename` to control the downloaded filename, and `csv` to supply
96
- custom CSV content.
92
+ By default the table sizes to its content (columns default to a fixed
93
+ medium width, so they're evenly spaced). Pass `full-width` to stretch the
94
+ table to fill its container; columns without an explicit width will share
95
+ the available space equally.
96
+
97
+ <ComponentDemo>
98
+ <DataTable
99
+ :data="{ day: [0, 1, 2, 3, 4], susceptible: [1000, 980, 945, 900, 860], infected: [1, 21, 56, 101, 141] }"
100
+ full-width
101
+ />
102
+
103
+ <template #code>
104
+
105
+ ```vue
106
+ <DataTable
107
+ :data="{
108
+ day: [0, 1, 2, 3, 4],
109
+ susceptible: [1000, 980, 945, 900, 860],
110
+ infected: [1, 21, 56, 101, 141],
111
+ }"
112
+ full-width
113
+ />
114
+ ```
115
+
116
+ </template>
117
+ </ComponentDemo>
118
+
119
+ ### Download menu
120
+
121
+ A `⋯` menu appears in the top-right corner of every table with a
122
+ **Download** item that exports the data as CSV. Use `download-menu-link`
123
+ to customize the menu item label, `filename` to control the downloaded
124
+ filename, and `csv` to supply custom CSV content. Pass `:menu="false"`
125
+ to hide the menu entirely.
97
126
 
98
127
  <ComponentDemo>
99
128
  <DataTable
100
129
  :data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
101
130
  filename="sir-cases"
102
- download-link="Download cases (CSV)"
131
+ download-menu-link="Download cases (CSV)"
103
132
  />
104
133
 
105
134
  <template #code>
@@ -111,7 +140,7 @@ custom CSV content.
111
140
  cases: [1, 21, 56, 101, 141],
112
141
  }"
113
142
  filename="sir-cases"
114
- download-link="Download cases (CSV)"
143
+ download-menu-link="Download cases (CSV)"
115
144
  />
116
145
  ```
117
146
 
@@ -128,7 +157,8 @@ custom CSV content.
128
157
  | `menu` | `boolean \| string` | No | `true` |
129
158
  | `csv` | `string \| (() =&gt; string)` | No | — |
130
159
  | `filename` | `string` | No | — |
131
- | `downloadLink` | `boolean \| string` | No | |
160
+ | `downloadMenuLink` | `string` | No | `"Download"` |
161
+ | `fullWidth` | `boolean` | No | `false` |
132
162
 
133
163
 
134
164
  ### ColumnConfig
@@ -31,22 +31,22 @@ const props = withDefaults(
31
31
  columnConfig?: Record<string, ColumnConfig>;
32
32
  menu?: boolean | string;
33
33
  /**
34
- * Custom CSV content for the Download CSV menu item and download link.
35
- * Can be a raw CSV string or a function returning one. When omitted, CSV
36
- * is generated from the table data.
34
+ * Custom CSV content for the Download menu item. Can be a raw CSV string
35
+ * or a function returning one. When omitted, CSV is generated from the
36
+ * table data.
37
37
  */
38
38
  csv?: string | (() => string);
39
39
  /** Filename (without extension) for downloaded CSV files. */
40
40
  filename?: string;
41
41
  /**
42
- * Show a plain text link below the table to download the CSV data.
43
- * Pass `true` for the default label ("Download data (CSV)") or a string
44
- * to customize the link text. When set, the Download CSV menu item is
45
- * hidden.
42
+ * Label for the Download item in the table's top-right menu.
43
+ * Defaults to "Download".
46
44
  */
47
- downloadLink?: boolean | string;
45
+ downloadMenuLink?: string;
46
+ /** Stretch the table to fill its container's width. */
47
+ fullWidth?: boolean;
48
48
  }>(),
49
- { menu: true },
49
+ { menu: true, fullWidth: false, downloadMenuLink: "Download" },
50
50
  );
51
51
 
52
52
  function columnLabel(name: string): string {
@@ -55,7 +55,10 @@ function columnLabel(name: string): string {
55
55
 
56
56
  function columnStyle(name: string): Record<string, string> | undefined {
57
57
  const w = props.columnConfig?.[name]?.width;
58
- if (w == null) return undefined;
58
+ if (w == null) {
59
+ if (props.fullWidth) return undefined;
60
+ return { width: COLUMN_WIDTHS.medium, minWidth: COLUMN_WIDTHS.medium };
61
+ }
59
62
  const value = typeof w === "number" ? `${w}px` : COLUMN_WIDTHS[w];
60
63
  return { width: value, minWidth: value };
61
64
  }
@@ -135,36 +138,24 @@ function toCsv(): string {
135
138
  return lines.join("\n");
136
139
  }
137
140
 
138
- const menuItems = computed<ChartMenuItem[]>(() => {
139
- if (props.downloadLink) return [];
140
- return [
141
- {
142
- label: "Download CSV",
143
- action: () => downloadCsv(toCsv(), menuFilename()),
144
- },
145
- ];
146
- });
147
-
148
- const downloadLinkText = computed(() => {
149
- if (!props.downloadLink) return null;
150
- return typeof props.downloadLink === "string"
151
- ? props.downloadLink
152
- : "Download data (CSV)";
153
- });
154
-
155
- const csvHref = computed(() => {
156
- if (!props.downloadLink) return null;
157
- return `data:text/csv;charset=utf-8,${encodeURIComponent(toCsv())}`;
158
- });
141
+ const menuItems = computed<ChartMenuItem[]>(() => [
142
+ {
143
+ label: props.downloadMenuLink,
144
+ action: () => downloadCsv(toCsv(), menuFilename()),
145
+ },
146
+ ]);
159
147
 
160
- const showMenu = computed(() => props.menu && menuItems.value.length > 0);
148
+ const showMenu = computed(() => Boolean(props.menu));
161
149
  </script>
162
150
 
163
151
  <template>
164
- <div class="TableOuter" :class="{ 'has-menu': showMenu }">
165
- <ChartMenu v-if="showMenu" :items="menuItems" />
152
+ <div
153
+ class="TableOuter"
154
+ :class="{ 'full-width': fullWidth, 'has-menu': showMenu }"
155
+ >
156
+ <ChartMenu v-if="showMenu" :items="menuItems" force-dropdown />
166
157
  <div class="TableWrapper">
167
- <table class="Table">
158
+ <table class="Table" :class="{ 'full-width': fullWidth }">
168
159
  <colgroup>
169
160
  <col
170
161
  v-for="col in columns"
@@ -197,14 +188,6 @@ const showMenu = computed(() => props.menu && menuItems.value.length > 0);
197
188
  </tbody>
198
189
  </table>
199
190
  </div>
200
- <a
201
- v-if="downloadLinkText"
202
- class="data-table-download-link"
203
- :href="csvHref!"
204
- :download="`${menuFilename()}.csv`"
205
- >
206
- {{ downloadLinkText }}
207
- </a>
208
191
  </div>
209
192
  </template>
210
193
 
@@ -214,17 +197,8 @@ const showMenu = computed(() => props.menu && menuItems.value.length > 0);
214
197
  display: inline-block;
215
198
  }
216
199
 
217
- .TableOuter.has-menu {
218
- margin-top: 32px;
219
- }
220
-
221
- .TableOuter :deep(.chart-menu-trigger-area) {
222
- top: -32px;
223
- right: 0;
224
- }
225
-
226
- .TableOuter:hover :deep(.chart-menu-button) {
227
- opacity: 1;
200
+ .TableOuter.full-width {
201
+ display: block;
228
202
  }
229
203
 
230
204
  .TableWrapper {
@@ -238,6 +212,11 @@ const showMenu = computed(() => props.menu && menuItems.value.length > 0);
238
212
  border-collapse: collapse;
239
213
  font-variant-numeric: tabular-nums;
240
214
  border: 1px solid var(--color-border);
215
+ table-layout: fixed;
216
+ }
217
+
218
+ .Table.full-width {
219
+ width: 100%;
241
220
  }
242
221
 
243
222
  .Table tr,
@@ -251,6 +230,7 @@ const showMenu = computed(() => props.menu && menuItems.value.length > 0);
251
230
  .Table td {
252
231
  padding: 0.75em 1.25em;
253
232
  white-space: nowrap;
233
+ text-align: left;
254
234
  }
255
235
 
256
236
  .Table th {
@@ -268,10 +248,16 @@ const showMenu = computed(() => props.menu && menuItems.value.length > 0);
268
248
  border-bottom: none;
269
249
  }
270
250
 
271
- .data-table-download-link {
272
- display: block;
273
- text-align: right;
274
- font-size: var(--font-size-sm);
275
- margin-top: 0.25em;
251
+ .TableOuter :deep(.chart-menu-trigger-area) {
252
+ top: 4px;
253
+ right: 4px;
254
+ }
255
+
256
+ .TableOuter :deep(.chart-menu-button) {
257
+ opacity: 1;
258
+ }
259
+
260
+ .TableOuter.has-menu .Table thead th:last-child {
261
+ padding-right: 2.5em;
276
262
  }
277
263
  </style>
@@ -100,7 +100,7 @@ When `x` is omitted, `y`/`data` values are plotted at indices 0, 1, 2, etc.
100
100
 
101
101
  ### Tooltip
102
102
 
103
- Hover over the chart to see a tooltip with values at each data point. Set `tooltip-trigger="hover"` to enable the built-in tooltip with crosshair and highlight dots. Use the `#tooltip` slot for custom content.
103
+ Hover over the chart to see a tooltip with values at each data point. Set `tooltip-trigger="hover"` to enable the built-in tooltip with crosshair and highlight dots. Use the `#tooltip` slot for custom content. Pass `tooltip-value-format` to control how numeric values render (e.g. percentages, currency); it falls back to `y-tick-format` when omitted.
104
104
 
105
105
  <ComponentDemo>
106
106
  <LineChart
@@ -462,12 +462,13 @@ until the user clicks Download:
462
462
  | `yTicks` | `number \| number[]` | No | — |
463
463
  | `xTickFormat` | `(value: number, index: number) =&gt; string` | No | — |
464
464
  | `yTickFormat` | `(value: number) =&gt; string` | No | — |
465
+ | `tooltipValueFormat` | `(value: number) =&gt; string` | No | — |
465
466
  | `xLabels` | `string[]` | No | — |
466
467
  | `debounce` | `number` | No | — |
467
468
  | `menu` | `boolean \| string` | No | `true` |
468
469
  | `xGrid` | `boolean` | No | — |
469
470
  | `yGrid` | `boolean` | No | — |
470
- | `tooltipData` | `unknown[]` | No | — |
471
+ | `tooltipData` | `ArrayLike&lt;unknown&gt;` | No | — |
471
472
  | `tooltipTrigger` | `"hover" \| "click"` | No | — |
472
473
  | `tooltipClamp` | `"none" \| "chart" \| "window"` | No | `"chart"` |
473
474
  | `csv` | `string \| (() =&gt; string)` | No | — |
@@ -128,6 +128,11 @@ const props = withDefaults(
128
128
  xTickFormat?: (value: number, index: number) => string;
129
129
  /** Formatter for y-axis tick labels. Receives the raw numeric value. */
130
130
  yTickFormat?: (value: number) => string;
131
+ /**
132
+ * Formatter for numeric values shown in the default tooltip. Receives
133
+ * the raw value. Defaults to the same tick formatter used for axes.
134
+ */
135
+ tooltipValueFormat?: (value: number) => string;
131
136
  /**
132
137
  * @deprecated Use `xTickFormat` (e.g. `(_, i) => labels[i]`) together
133
138
  * with `xTicks` for explicit control. Still honored for tooltip x-labels
@@ -138,8 +143,12 @@ const props = withDefaults(
138
143
  menu?: boolean | string;
139
144
  xGrid?: boolean;
140
145
  yGrid?: boolean;
141
- /** Custom per-index data passed to the tooltip slot */
142
- tooltipData?: unknown[];
146
+ /**
147
+ * Custom per-index data passed to the tooltip slot. Accepts a plain
148
+ * array or any `ArrayLike` (e.g. a typed array column from a
149
+ * `ModelOutput`).
150
+ */
151
+ tooltipData?: ArrayLike<unknown>;
143
152
  /** Tooltip activation mode. Default: 'hover' */
144
153
  tooltipTrigger?: "hover" | "click";
145
154
  /**
@@ -216,6 +225,12 @@ function resolveSeries(s: Series): ResolvedSeries {
216
225
  return { ...s, data: s.y ?? s.data ?? EMPTY_DATA };
217
226
  }
218
227
 
228
+ function formatTooltipValue(v: number): string {
229
+ if (props.tooltipValueFormat) return props.tooltipValueFormat(v);
230
+ if (props.yTickFormat) return props.yTickFormat(v);
231
+ return formatTick(v);
232
+ }
233
+
219
234
  const allSeries = computed<ResolvedSeries[]>(() => {
220
235
  if (props.series && props.series.length > 0)
221
236
  return props.series.map(resolveSeries);
@@ -1122,7 +1137,7 @@ const {
1122
1137
  class="line-chart-tooltip-swatch"
1123
1138
  :style="{ background: v.color }"
1124
1139
  />
1125
- {{ isFinite(v.value) ? formatTick(v.value) : "—" }}
1140
+ {{ isFinite(v.value) ? formatTooltipValue(v.value) : "—" }}
1126
1141
  </div>
1127
1142
  </div>
1128
1143
  </slot>
package/index.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0",
2
+ "version": "0.4.2",
3
3
  "package": "@cfasim-ui/docs",
4
4
  "content": {
5
5
  "components": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfasim-ui/docs",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {