@cfasim-ui/docs 0.4.15 → 0.4.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.
@@ -593,6 +593,7 @@ anchor. `lineColor`, `lineWidth`, and `lineDash` style the line.
593
593
  | `csv` | `string \| (() => string)` | No | — |
594
594
  | `filename` | `string` | No | — |
595
595
  | `downloadLink` | `boolean \| string` | No | — |
596
+ | `downloadButton` | `boolean \| string` | No | — |
596
597
  | `annotations` | `readonly ChartAnnotation[]` | No | — |
597
598
  | `chartPadding` | `ChartPadding` | No | — |
598
599
  | `data` | `BarChartData` | No | — |
@@ -879,6 +879,8 @@ const {
879
879
  menuItems,
880
880
  downloadLinkText,
881
881
  csvHref,
882
+ downloadButtonText,
883
+ triggerCsvDownload,
882
884
  menuFilename,
883
885
  isFullscreen,
884
886
  } = useChartFoundation({
@@ -894,6 +896,7 @@ const {
894
896
  tooltipClamp: () => props.tooltipClamp,
895
897
  filename: () => props.filename,
896
898
  downloadLink: () => props.downloadLink,
899
+ downloadButton: () => props.downloadButton,
897
900
  chartPadding: () => props.chartPadding,
898
901
  inlineLegendLabels: () => inlineLegendLabels.value,
899
902
  hasTooltipSlot: () => hasTooltipSlot.value,
@@ -1320,6 +1323,14 @@ const positionedLegendItems = computed(() => {
1320
1323
  >
1321
1324
  {{ downloadLinkText }}
1322
1325
  </a>
1326
+ <button
1327
+ v-if="downloadButtonText"
1328
+ type="button"
1329
+ class="bar-chart-download-button"
1330
+ @click="triggerCsvDownload"
1331
+ >
1332
+ {{ downloadButtonText }}
1333
+ </button>
1323
1334
  </div>
1324
1335
  </template>
1325
1336
 
@@ -1355,3 +1366,27 @@ const positionedLegendItems = computed(() => {
1355
1366
  flex-shrink: 0;
1356
1367
  }
1357
1368
  </style>
1369
+
1370
+ <style>
1371
+ .bar-chart-download-button {
1372
+ display: inline-flex;
1373
+ align-items: center;
1374
+ margin-top: 0.5em;
1375
+ padding: 0.5em 1em;
1376
+ border: 1px solid var(--color-border);
1377
+ border-radius: 0.25em;
1378
+ background: var(--color-bg-0, #fff);
1379
+ color: var(--color-text);
1380
+ font-size: var(--font-size-sm);
1381
+ cursor: pointer;
1382
+ }
1383
+
1384
+ .bar-chart-download-button:hover {
1385
+ background: var(--color-bg-1, rgba(0, 0, 0, 0.05));
1386
+ }
1387
+
1388
+ .bar-chart-download-button:focus-visible {
1389
+ outline: 2px solid var(--color-primary);
1390
+ outline-offset: 2px;
1391
+ }
1392
+ </style>
@@ -157,9 +157,8 @@ the available space equally.
157
157
 
158
158
  A `⋯` menu appears in the top-right corner of every table with a
159
159
  **Download** item that exports the data as CSV. Use `download-menu-link`
160
- to customize the menu item label, `filename` to control the downloaded
161
- filename, and `csv` to supply custom CSV content. Pass `:menu="false"`
162
- to hide the menu entirely.
160
+ to customize the menu item label and `filename` to control the
161
+ downloaded filename. Pass `:menu="false"` to hide the menu entirely.
163
162
 
164
163
  <ComponentDemo>
165
164
  <DataTable
@@ -184,6 +183,115 @@ to hide the menu entirely.
184
183
  </template>
185
184
  </ComponentDemo>
186
185
 
186
+ ### Custom CSV download
187
+
188
+ By default, the Download menu item exports the displayed table as CSV.
189
+ Use the `csv` prop to supply your own content — for example, to include
190
+ ISO dates, extra columns that aren't in the table, or values formatted
191
+ differently from the on-screen rendering. Accepts a raw string or a
192
+ function returning one (called lazily on click).
193
+
194
+ <ComponentDemo>
195
+ <DataTable
196
+ :data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
197
+ filename="sir-cases"
198
+ :csv="'date,day,cases\n2024-01-01,0,1\n2024-01-02,1,21\n2024-01-03,2,56\n2024-01-04,3,101\n2024-01-05,4,141'"
199
+ />
200
+
201
+ <template #code>
202
+
203
+ ```vue
204
+ <DataTable
205
+ :data="{
206
+ day: [0, 1, 2, 3, 4],
207
+ cases: [1, 21, 56, 101, 141],
208
+ }"
209
+ filename="sir-cases"
210
+ :csv="`date,day,cases
211
+ 2024-01-01,0,1
212
+ 2024-01-02,1,21
213
+ 2024-01-03,2,56
214
+ 2024-01-04,3,101
215
+ 2024-01-05,4,141`"
216
+ />
217
+ ```
218
+
219
+ </template>
220
+ </ComponentDemo>
221
+
222
+ ### Download button
223
+
224
+ Pass `download-button` to render a visible, labeled button beneath the
225
+ table instead of exposing the download only via the top-right menu. The
226
+ button uses `download-menu-link` as its label, and the menu's Download
227
+ item is suppressed so the action isn't duplicated. The button has the
228
+ class `data-table-download-button` and its styles are unscoped, so it
229
+ can be targeted directly from custom CSS without specificity battles.
230
+
231
+ <ComponentDemo>
232
+ <DataTable
233
+ :data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
234
+ filename="sir-cases"
235
+ download-menu-link="Download CSV"
236
+ download-button
237
+ :csv="'date,day,cases\n2024-01-01,0,1\n2024-01-02,1,21\n2024-01-03,2,56\n2024-01-04,3,101\n2024-01-05,4,141'"
238
+ />
239
+
240
+ <template #code>
241
+
242
+ ```vue
243
+ <DataTable
244
+ :data="{
245
+ day: [0, 1, 2, 3, 4],
246
+ cases: [1, 21, 56, 101, 141],
247
+ }"
248
+ filename="sir-cases"
249
+ download-menu-link="Download CSV"
250
+ download-button
251
+ :csv="`date,day,cases
252
+ 2024-01-01,0,1
253
+ 2024-01-02,1,21
254
+ 2024-01-03,2,56
255
+ 2024-01-04,3,101
256
+ 2024-01-05,4,141`"
257
+ />
258
+ ```
259
+
260
+ </template>
261
+ </ComponentDemo>
262
+
263
+ ### Download link
264
+
265
+ Pass `download-link` to render a plain text link beneath the table
266
+ instead of (or alongside) the menu. It's a real `<a href download>` —
267
+ right-click → Save As works. Pass `true` for the default "Download data
268
+ (CSV)" label, or a string to customize. The menu's Download item is
269
+ suppressed when this is set. If both `download-link` and
270
+ `download-button` are set, the button wins.
271
+
272
+ <ComponentDemo>
273
+ <DataTable
274
+ :data="{ day: [0, 1, 2, 3, 4], cases: [1, 21, 56, 101, 141] }"
275
+ filename="sir-cases"
276
+ download-link
277
+ />
278
+
279
+ <template #code>
280
+
281
+ ```vue
282
+ <DataTable
283
+ :data="{
284
+ day: [0, 1, 2, 3, 4],
285
+ cases: [1, 21, 56, 101, 141],
286
+ }"
287
+ filename="sir-cases"
288
+ download-link
289
+ />
290
+ ```
291
+
292
+ </template>
293
+ </ComponentDemo>
294
+
187
295
  ## Props
188
296
 
189
297
  | Prop | Type | Required | Default |
@@ -195,6 +303,8 @@ to hide the menu entirely.
195
303
  | `csv` | `string \| (() =&gt; string)` | No | — |
196
304
  | `filename` | `string` | No | — |
197
305
  | `downloadMenuLink` | `string` | No | `"Download"` |
306
+ | `downloadButton` | `boolean` | No | `false` |
307
+ | `downloadLink` | `boolean \| string` | No | — |
198
308
  | `fullWidth` | `boolean` | No | `false` |
199
309
 
200
310
 
@@ -54,14 +54,34 @@ const props = withDefaults(
54
54
  /** Filename (without extension) for downloaded CSV files. */
55
55
  filename?: string;
56
56
  /**
57
- * Label for the Download item in the table's top-right menu.
58
- * Defaults to "Download".
57
+ * Label for the Download item in the table's top-right menu, and for
58
+ * the button rendered when `downloadButton` is true. Defaults to
59
+ * "Download".
59
60
  */
60
61
  downloadMenuLink?: string;
62
+ /**
63
+ * Render a visible "Download" button beneath the table instead of
64
+ * exposing the action only via the top-right menu. When enabled, the
65
+ * menu's Download item is suppressed to avoid duplicate controls.
66
+ */
67
+ downloadButton?: boolean;
68
+ /**
69
+ * Render a plain text link beneath the table for downloading CSV.
70
+ * Pass `true` for the default "Download data (CSV)" label, or a
71
+ * string to customize. When set, the menu's Download item is
72
+ * suppressed. Mutually exclusive with `downloadButton`; if both are
73
+ * set, `downloadButton` wins.
74
+ */
75
+ downloadLink?: boolean | string;
61
76
  /** Stretch the table to fill its container's width. */
62
77
  fullWidth?: boolean;
63
78
  }>(),
64
- { menu: true, fullWidth: false, downloadMenuLink: "Download" },
79
+ {
80
+ menu: true,
81
+ fullWidth: false,
82
+ downloadMenuLink: "Download",
83
+ downloadButton: false,
84
+ },
65
85
  );
66
86
 
67
87
  function columnLabel(name: string): string {
@@ -165,14 +185,30 @@ function toCsv(): string {
165
185
  return lines.join("\n");
166
186
  }
167
187
 
168
- const menuItems = computed<ChartMenuItem[]>(() => [
169
- {
170
- label: props.downloadMenuLink,
171
- action: () => downloadCsv(toCsv(), menuFilename()),
172
- },
173
- ]);
188
+ function triggerDownload() {
189
+ downloadCsv(toCsv(), menuFilename());
190
+ }
191
+
192
+ const menuItems = computed<ChartMenuItem[]>(() => {
193
+ if (props.downloadButton || props.downloadLink) return [];
194
+ return [{ label: props.downloadMenuLink, action: triggerDownload }];
195
+ });
196
+
197
+ const downloadLinkText = computed<string | null>(() => {
198
+ if (props.downloadButton) return null;
199
+ const v = props.downloadLink;
200
+ if (!v) return null;
201
+ return typeof v === "string" ? v : "Download data (CSV)";
202
+ });
203
+
204
+ const csvHref = computed<string | null>(() => {
205
+ if (!downloadLinkText.value) return null;
206
+ return `data:text/csv;charset=utf-8,${encodeURIComponent(toCsv())}`;
207
+ });
174
208
 
175
- const showMenu = computed(() => Boolean(props.menu));
209
+ const showMenu = computed(
210
+ () => Boolean(props.menu) && menuItems.value.length > 0,
211
+ );
176
212
  </script>
177
213
 
178
214
  <template>
@@ -215,6 +251,22 @@ const showMenu = computed(() => Boolean(props.menu));
215
251
  </tbody>
216
252
  </table>
217
253
  </div>
254
+ <button
255
+ v-if="downloadButton"
256
+ type="button"
257
+ class="data-table-download-button"
258
+ @click="triggerDownload"
259
+ >
260
+ {{ downloadMenuLink }}
261
+ </button>
262
+ <a
263
+ v-else-if="downloadLinkText"
264
+ class="data-table-download-link"
265
+ :href="csvHref!"
266
+ :download="`${menuFilename()}.csv`"
267
+ >
268
+ {{ downloadLinkText }}
269
+ </a>
218
270
  </div>
219
271
  </template>
220
272
 
@@ -288,3 +340,34 @@ const showMenu = computed(() => Boolean(props.menu));
288
340
  padding-right: 2.5em;
289
341
  }
290
342
  </style>
343
+
344
+ <style>
345
+ .data-table-download-button {
346
+ display: inline-flex;
347
+ align-items: center;
348
+ margin-top: 0.75em;
349
+ padding: 0.5em 1em;
350
+ border: 1px solid var(--color-border);
351
+ border-radius: 0.25em;
352
+ background: var(--color-bg-0, #fff);
353
+ color: var(--color-text);
354
+ font-size: var(--font-size-sm);
355
+ cursor: pointer;
356
+ }
357
+
358
+ .data-table-download-button:hover {
359
+ background: var(--color-bg-1, rgba(0, 0, 0, 0.05));
360
+ }
361
+
362
+ .data-table-download-button:focus-visible {
363
+ outline: 2px solid var(--color-primary);
364
+ outline-offset: 2px;
365
+ }
366
+
367
+ .data-table-download-link {
368
+ display: block;
369
+ text-align: right;
370
+ font-size: var(--font-size-sm);
371
+ margin-top: 0.25em;
372
+ }
373
+ </style>
@@ -941,7 +941,10 @@ dates, categorical labels, or extra columns that aren't plotted). Use
941
941
  `filename` to control the download filename (shared by SVG, PNG and CSV).
942
942
 
943
943
  Pass `download-link` to also render a plain text link below the chart — set
944
- it to `true` for the default label, or pass a string to customize it.
944
+ it to `true` for the default label, or pass a string to customize it. Use
945
+ `download-button` instead to render a styled `<button>` (with the class
946
+ `line-chart-download-button`, available for custom CSS) in place of the
947
+ link.
945
948
 
946
949
  <ComponentDemo>
947
950
  <LineChart
@@ -1004,6 +1007,7 @@ until the user clicks Download:
1004
1007
  | `csv` | `string \| (() =&gt; string)` | No | — |
1005
1008
  | `filename` | `string` | No | — |
1006
1009
  | `downloadLink` | `boolean \| string` | No | — |
1010
+ | `downloadButton` | `boolean \| string` | No | — |
1007
1011
  | `annotations` | `readonly ChartAnnotation[]` | No | — |
1008
1012
  | `chartPadding` | `ChartPadding` | No | — |
1009
1013
  | `y` | `LineChartData` | No | — |
@@ -929,6 +929,8 @@ const {
929
929
  menuItems,
930
930
  downloadLinkText,
931
931
  csvHref,
932
+ downloadButtonText,
933
+ triggerCsvDownload,
932
934
  menuFilename,
933
935
  isFullscreen,
934
936
  } = useChartFoundation({
@@ -944,6 +946,7 @@ const {
944
946
  tooltipClamp: () => props.tooltipClamp,
945
947
  filename: () => props.filename,
946
948
  downloadLink: () => props.downloadLink,
949
+ downloadButton: () => props.downloadButton,
947
950
  chartPadding: () => props.chartPadding,
948
951
  inlineLegendLabels: () => inlineLegendLabels.value,
949
952
  hasTooltipSlot: () => hasTooltipSlot.value,
@@ -1403,6 +1406,14 @@ const positionedLegendItems = computed(() => {
1403
1406
  >
1404
1407
  {{ downloadLinkText }}
1405
1408
  </a>
1409
+ <button
1410
+ v-if="downloadButtonText"
1411
+ type="button"
1412
+ class="line-chart-download-button"
1413
+ @click="triggerCsvDownload"
1414
+ >
1415
+ {{ downloadButtonText }}
1416
+ </button>
1406
1417
  </div>
1407
1418
  </template>
1408
1419
 
@@ -1438,3 +1449,27 @@ const positionedLegendItems = computed(() => {
1438
1449
  flex-shrink: 0;
1439
1450
  }
1440
1451
  </style>
1452
+
1453
+ <style>
1454
+ .line-chart-download-button {
1455
+ display: inline-flex;
1456
+ align-items: center;
1457
+ margin-top: 0.5em;
1458
+ padding: 0.5em 1em;
1459
+ border: 1px solid var(--color-border);
1460
+ border-radius: 0.25em;
1461
+ background: var(--color-bg-0, #fff);
1462
+ color: var(--color-text);
1463
+ font-size: var(--font-size-sm);
1464
+ cursor: pointer;
1465
+ }
1466
+
1467
+ .line-chart-download-button:hover {
1468
+ background: var(--color-bg-1, rgba(0, 0, 0, 0.05));
1469
+ }
1470
+
1471
+ .line-chart-download-button:focus-visible {
1472
+ outline: 2px solid var(--color-primary);
1473
+ outline-offset: 2px;
1474
+ }
1475
+ </style>
@@ -141,6 +141,13 @@ export interface ChartCommonProps {
141
141
  * for the default label or a string to customize.
142
142
  */
143
143
  downloadLink?: boolean | string;
144
+ /**
145
+ * Show a `<button>` below the chart to download CSV. Pass `true` for
146
+ * the default label or a string to customize. When set, the CSV menu
147
+ * item is suppressed. Mutually exclusive with `downloadLink`; if both
148
+ * are set, `downloadButton` wins.
149
+ */
150
+ downloadButton?: boolean | string;
144
151
  /** Annotations rendered as the top layer of the chart. */
145
152
  annotations?: readonly ChartAnnotation[];
146
153
  /**
@@ -25,6 +25,7 @@ export interface ChartFoundationOptions {
25
25
  tooltipClamp: () => TooltipClamp | undefined;
26
26
  filename: () => string | undefined;
27
27
  downloadLink: () => boolean | string | undefined;
28
+ downloadButton: () => boolean | string | undefined;
28
29
  chartPadding: () => ChartPadding | undefined;
29
30
  // Chart-specific hooks that the composable can't infer.
30
31
  /**
@@ -61,6 +62,8 @@ export function useChartFoundation(opts: ChartFoundationOptions) {
61
62
  items: menuItems,
62
63
  downloadLinkText,
63
64
  csvHref,
65
+ downloadButtonText,
66
+ triggerCsvDownload,
64
67
  resolvedFilename: menuFilename,
65
68
  isFullscreen,
66
69
  } = useChartMenu({
@@ -68,6 +71,7 @@ export function useChartFoundation(opts: ChartFoundationOptions) {
68
71
  legacyMenuLabel: opts.menu,
69
72
  getCsv: opts.getCsv,
70
73
  downloadLink: opts.downloadLink,
74
+ downloadButton: opts.downloadButton,
71
75
  fullscreen: true,
72
76
  });
73
77
 
@@ -130,6 +134,8 @@ export function useChartFoundation(opts: ChartFoundationOptions) {
130
134
  menuItems,
131
135
  downloadLinkText,
132
136
  csvHref,
137
+ downloadButtonText,
138
+ triggerCsvDownload,
133
139
  menuFilename,
134
140
  isFullscreen,
135
141
  measuredHeight,
@@ -11,6 +11,8 @@ export interface ChartMenuOptions {
11
11
  getCsv: () => string;
12
12
  /** Whether a separate download link is rendered (and the CSV menu item should be hidden). */
13
13
  downloadLink: () => boolean | string | undefined;
14
+ /** Whether a separate download button is rendered (and the CSV menu item should be hidden). */
15
+ downloadButton?: () => boolean | string | undefined;
14
16
  /**
15
17
  * When true, prepends an Expand/Collapse menu item that toggles the
16
18
  * chart into a full-window view. The consumer is responsible for
@@ -56,7 +58,8 @@ export function useChartMenu(opts: ChartMenuOptions) {
56
58
  },
57
59
  },
58
60
  );
59
- if (!opts.downloadLink()) {
61
+ const buttonOn = opts.downloadButton?.();
62
+ if (!opts.downloadLink() && !buttonOn) {
60
63
  out.push({
61
64
  label: "Download CSV",
62
65
  action: () => downloadCsv(opts.getCsv(), fname),
@@ -66,21 +69,35 @@ export function useChartMenu(opts: ChartMenuOptions) {
66
69
  });
67
70
 
68
71
  const downloadLinkText = computed<string | null>(() => {
72
+ if (opts.downloadButton?.()) return null;
69
73
  const v = opts.downloadLink();
70
74
  if (!v) return null;
71
75
  return typeof v === "string" ? v : "Download data (CSV)";
72
76
  });
73
77
 
74
78
  const csvHref = computed<string | null>(() => {
79
+ if (opts.downloadButton?.()) return null;
75
80
  if (!opts.downloadLink()) return null;
76
81
  return `data:text/csv;charset=utf-8,${encodeURIComponent(opts.getCsv())}`;
77
82
  });
78
83
 
84
+ const downloadButtonText = computed<string | null>(() => {
85
+ const v = opts.downloadButton?.();
86
+ if (!v) return null;
87
+ return typeof v === "string" ? v : "Download data (CSV)";
88
+ });
89
+
90
+ function triggerCsvDownload() {
91
+ downloadCsv(opts.getCsv(), resolvedFilename());
92
+ }
93
+
79
94
  return {
80
95
  svgRef: svgRef as Ref<SVGSVGElement | null>,
81
96
  items,
82
97
  downloadLinkText,
83
98
  csvHref,
99
+ downloadButtonText,
100
+ triggerCsvDownload,
84
101
  resolvedFilename,
85
102
  isFullscreen: fullscreen?.isFullscreen ?? ref(false),
86
103
  };
package/index.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.15",
2
+ "version": "0.4.16",
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.15",
3
+ "version": "0.4.16",
4
4
  "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {