@cfasim-ui/docs 0.6.0 → 0.6.1
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.
|
@@ -180,6 +180,68 @@ grows automatically for additional lines.
|
|
|
180
180
|
</template>
|
|
181
181
|
</ComponentDemo>
|
|
182
182
|
|
|
183
|
+
### Font sizes
|
|
184
|
+
|
|
185
|
+
Every text element on the chart has its own style prop and default size (in px). The text-styling props (`axisLabelStyle`, `tickLabelStyle`, `legendStyle`) take a `LabelStyle` — `{ fontSize?, color?, fontWeight? }` — and `titleStyle` adds `lineHeight` and `align`. Unset fields fall back to the defaults below.
|
|
186
|
+
|
|
187
|
+
| Chart part | Prop / field | Type | Default size |
|
|
188
|
+
| ------------------------------------------- | ------------------------------------ | ------------ | -----------: |
|
|
189
|
+
| Title | `titleStyle` | `TitleStyle` | 14 |
|
|
190
|
+
| Axis labels (`xLabel` / `yLabel`) | `axisLabelStyle` | `LabelStyle` | 13 |
|
|
191
|
+
| Axis tick labels (the numbers on each axis) | `tickLabelStyle` | `LabelStyle` | 10 |
|
|
192
|
+
| Inline legend | `legendStyle` | `LabelStyle` | 11 |
|
|
193
|
+
| Area section label | `areaSection.inlineLabelStyle` | `LabelStyle` | 11 |
|
|
194
|
+
| Area section description | `areaSection.inlineDescriptionStyle` | `LabelStyle` | 11 |
|
|
195
|
+
| Annotation text | `annotation.fontSize` | `number` | 13 |
|
|
196
|
+
|
|
197
|
+
These are single styles applied to both axes — there's no separate x vs y font size. The same props (except the LineChart-only area-section fields) exist on `BarChart`, since both compose `ChartCommonProps`.
|
|
198
|
+
|
|
199
|
+
<ComponentDemo>
|
|
200
|
+
<LineChart
|
|
201
|
+
:series="[
|
|
202
|
+
{ data: [0, 12, 28, 45, 60, 55, 40, 25, 12], color: '#0057b7', legend: 'No interventions' },
|
|
203
|
+
{ data: [0, 8, 18, 30, 40, 35, 25, 14, 6], color: '#ef4444', legend: 'Masking + distancing' },
|
|
204
|
+
]"
|
|
205
|
+
:height="240"
|
|
206
|
+
title="Daily ED visits"
|
|
207
|
+
x-label="Days"
|
|
208
|
+
y-label="Cases"
|
|
209
|
+
:title-style="{ fontSize: 18 }"
|
|
210
|
+
:axis-label-style="{ fontSize: 15, fontWeight: 700 }"
|
|
211
|
+
:tick-label-style="{ fontSize: 13 }"
|
|
212
|
+
:legend-style="{ fontSize: 14 }"
|
|
213
|
+
/>
|
|
214
|
+
|
|
215
|
+
<template #code>
|
|
216
|
+
|
|
217
|
+
```vue
|
|
218
|
+
<LineChart
|
|
219
|
+
:series="[
|
|
220
|
+
{
|
|
221
|
+
data: [0, 12, 28, 45, 60, 55, 40, 25, 12],
|
|
222
|
+
color: '#0057b7',
|
|
223
|
+
legend: 'No interventions',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
data: [0, 8, 18, 30, 40, 35, 25, 14, 6],
|
|
227
|
+
color: '#ef4444',
|
|
228
|
+
legend: 'Masking + distancing',
|
|
229
|
+
},
|
|
230
|
+
]"
|
|
231
|
+
:height="240"
|
|
232
|
+
title="Daily ED visits"
|
|
233
|
+
x-label="Days"
|
|
234
|
+
y-label="Cases"
|
|
235
|
+
:title-style="{ fontSize: 18 }"
|
|
236
|
+
:axis-label-style="{ fontSize: 15, fontWeight: 700 }"
|
|
237
|
+
:tick-label-style="{ fontSize: 13 }"
|
|
238
|
+
:legend-style="{ fontSize: 14 }"
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
</template>
|
|
243
|
+
</ComponentDemo>
|
|
244
|
+
|
|
183
245
|
### Multiple series
|
|
184
246
|
|
|
185
247
|
<ComponentDemo>
|
|
@@ -628,7 +690,9 @@ inverts. The white `outline`, if any, is unaffected.
|
|
|
628
690
|
Use the `areas` prop to fill a band between two y-series — useful for
|
|
629
691
|
confidence intervals or min/max envelopes around a mean line. Each `Area`
|
|
630
692
|
takes parallel `upper` and `lower` arrays (and an optional `x` array, just
|
|
631
|
-
like `Series`).
|
|
693
|
+
like `Series`). Set `blendMode` on an `Area` to apply a CSS
|
|
694
|
+
`mix-blend-mode` to its fill so overlapping bands combine their colors
|
|
695
|
+
(e.g. `"multiply"`) instead of the later one obscuring the earlier.
|
|
632
696
|
|
|
633
697
|
<ComponentDemo>
|
|
634
698
|
<LineChart
|
|
@@ -1103,6 +1167,7 @@ interface Area {
|
|
|
1103
1167
|
x?: LineChartData; // optional parallel x-values
|
|
1104
1168
|
color?: string;
|
|
1105
1169
|
opacity?: number;
|
|
1170
|
+
blendMode?: BlendMode; // CSS mix-blend-mode for the fill
|
|
1106
1171
|
}
|
|
1107
1172
|
```
|
|
1108
1173
|
|
|
@@ -114,6 +114,12 @@ export interface Area {
|
|
|
114
114
|
x?: LineChartXInput;
|
|
115
115
|
color?: string;
|
|
116
116
|
opacity?: number;
|
|
117
|
+
/**
|
|
118
|
+
* CSS `mix-blend-mode` applied to the area fill. Lets overlapping
|
|
119
|
+
* areas (e.g. confidence bands) combine their colors instead of one
|
|
120
|
+
* obscuring the other.
|
|
121
|
+
*/
|
|
122
|
+
blendMode?: BlendMode;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
125
|
export interface AreaSection {
|
|
@@ -1218,6 +1224,7 @@ const positionedLegendItems = computed(() => {
|
|
|
1218
1224
|
:fill="a.color ?? 'currentColor'"
|
|
1219
1225
|
:fill-opacity="a.opacity ?? 0.2"
|
|
1220
1226
|
stroke="none"
|
|
1227
|
+
:style="a.blendMode ? { mixBlendMode: a.blendMode } : undefined"
|
|
1221
1228
|
/>
|
|
1222
1229
|
<!-- data lines and dots -->
|
|
1223
1230
|
<template v-for="(s, i) in allSeries" :key="i">
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# SelectBox
|
|
2
2
|
|
|
3
|
-
A dropdown
|
|
3
|
+
A single-select dropdown built on reka-ui. Set `autocomplete` to turn it into a
|
|
4
|
+
filterable single-select combobox: type to narrow the options, just like
|
|
5
|
+
[MultiSelect](./multi-select) but bound to a single `string`.
|
|
4
6
|
|
|
5
7
|
## Examples
|
|
6
8
|
|
|
7
9
|
<script setup>
|
|
8
10
|
import { ref } from 'vue'
|
|
9
11
|
const interval = ref('weekly')
|
|
12
|
+
const state = ref('')
|
|
10
13
|
</script>
|
|
11
14
|
|
|
12
15
|
<ComponentDemo>
|
|
@@ -83,6 +86,73 @@ DOM.
|
|
|
83
86
|
</template>
|
|
84
87
|
</ComponentDemo>
|
|
85
88
|
|
|
89
|
+
### Autocomplete
|
|
90
|
+
|
|
91
|
+
Add `autocomplete` to make the field filterable. Type to narrow the list; the
|
|
92
|
+
selected option's label fills the input. `v-model` is still a single `string`.
|
|
93
|
+
|
|
94
|
+
<ComponentDemo>
|
|
95
|
+
<div style="width: 240px">
|
|
96
|
+
<SelectBox
|
|
97
|
+
v-model="state"
|
|
98
|
+
autocomplete
|
|
99
|
+
label="State"
|
|
100
|
+
placeholder="Search states…"
|
|
101
|
+
:options="[
|
|
102
|
+
{ value: 'ca', label: 'California' },
|
|
103
|
+
{ value: 'ny', label: 'New York' },
|
|
104
|
+
{ value: 'tx', label: 'Texas' },
|
|
105
|
+
{ value: 'wa', label: 'Washington' },
|
|
106
|
+
{ value: 'fl', label: 'Florida' },
|
|
107
|
+
]"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<template #code>
|
|
112
|
+
|
|
113
|
+
```vue
|
|
114
|
+
<script setup>
|
|
115
|
+
import { ref } from "vue";
|
|
116
|
+
const state = ref("");
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
<SelectBox
|
|
120
|
+
v-model="state"
|
|
121
|
+
autocomplete
|
|
122
|
+
label="State"
|
|
123
|
+
placeholder="Search states…"
|
|
124
|
+
:options="[
|
|
125
|
+
{ value: 'ca', label: 'California' },
|
|
126
|
+
{ value: 'ny', label: 'New York' },
|
|
127
|
+
{ value: 'tx', label: 'Texas' },
|
|
128
|
+
{ value: 'wa', label: 'Washington' },
|
|
129
|
+
{ value: 'fl', label: 'Florida' },
|
|
130
|
+
]"
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
</template>
|
|
135
|
+
</ComponentDemo>
|
|
136
|
+
|
|
137
|
+
## Accessibility
|
|
138
|
+
|
|
139
|
+
In the default mode the field is a reka-ui Select; with `autocomplete` it's a
|
|
140
|
+
reka-ui Combobox following the
|
|
141
|
+
[ARIA combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/):
|
|
142
|
+
|
|
143
|
+
- The input has `role="combobox"` with `aria-expanded`, `aria-controls`, and
|
|
144
|
+
`aria-autocomplete="list"`; the popup is a `role="listbox"` and each option a
|
|
145
|
+
`role="option"` that reflects its state via `aria-selected`.
|
|
146
|
+
- Keyboard support is handled for you: type to filter, ↑/↓ to move through the
|
|
147
|
+
list, Enter to select the highlighted option, and Escape to close.
|
|
148
|
+
- Pass `label` so the field is named by a real `<label>`. In autocomplete mode
|
|
149
|
+
the label is also wired to the input via `for`/`id`, so clicking it focuses
|
|
150
|
+
the field; use `hide-label` to keep the label for screen readers while hiding
|
|
151
|
+
it visually, or `aria-label` when there's no visible label text.
|
|
152
|
+
- The chevron is a labelled toggle (`aria-label="Toggle options"`) kept out of
|
|
153
|
+
the tab order, and native browser autofill is disabled so it can't cover the
|
|
154
|
+
option list.
|
|
155
|
+
|
|
86
156
|
## Model
|
|
87
157
|
|
|
88
158
|
| Name | Type |
|
|
@@ -98,6 +168,7 @@ DOM.
|
|
|
98
168
|
| `ariaLabel` | `string` | No | — |
|
|
99
169
|
| `options` | `SelectOption[]` | Yes | — |
|
|
100
170
|
| `placeholder` | `string` | No | — |
|
|
171
|
+
| `autocomplete` | `boolean` | No | — |
|
|
101
172
|
|
|
102
173
|
|
|
103
174
|
### SelectOption
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import {
|
|
3
|
+
ComboboxAnchor,
|
|
4
|
+
ComboboxContent,
|
|
5
|
+
ComboboxEmpty,
|
|
6
|
+
ComboboxInput,
|
|
7
|
+
ComboboxItem,
|
|
8
|
+
ComboboxItemIndicator,
|
|
9
|
+
ComboboxPortal,
|
|
10
|
+
ComboboxRoot,
|
|
11
|
+
ComboboxTrigger,
|
|
12
|
+
ComboboxViewport,
|
|
3
13
|
SelectContent,
|
|
4
14
|
SelectItem,
|
|
5
15
|
SelectItemIndicator,
|
|
@@ -25,9 +35,16 @@ const props = defineProps<{
|
|
|
25
35
|
ariaLabel?: string;
|
|
26
36
|
options: SelectOption[];
|
|
27
37
|
placeholder?: string;
|
|
38
|
+
/** Turn the field into a filterable single-select autocomplete (combobox). */
|
|
39
|
+
autocomplete?: boolean;
|
|
28
40
|
}>();
|
|
29
41
|
|
|
30
42
|
const id = useId();
|
|
43
|
+
|
|
44
|
+
// Shows the selected option's label in the combobox input when not filtering.
|
|
45
|
+
function displayValue(value: string) {
|
|
46
|
+
return props.options.find((o) => o.value === value)?.label ?? "";
|
|
47
|
+
}
|
|
31
48
|
</script>
|
|
32
49
|
|
|
33
50
|
<template>
|
|
@@ -35,11 +52,81 @@ const id = useId();
|
|
|
35
52
|
<label
|
|
36
53
|
v-if="label"
|
|
37
54
|
:id="`${id}-label`"
|
|
55
|
+
:for="autocomplete ? `${id}-input` : undefined"
|
|
38
56
|
class="select-label"
|
|
39
57
|
:class="{ 'visually-hidden': hideLabel }"
|
|
40
58
|
>{{ label }}</label
|
|
41
59
|
>
|
|
42
|
-
|
|
60
|
+
|
|
61
|
+
<ComboboxRoot
|
|
62
|
+
v-if="autocomplete"
|
|
63
|
+
v-model="model"
|
|
64
|
+
open-on-click
|
|
65
|
+
class="select-combobox-root"
|
|
66
|
+
>
|
|
67
|
+
<ComboboxAnchor class="select-anchor">
|
|
68
|
+
<ComboboxInput
|
|
69
|
+
:id="`${id}-input`"
|
|
70
|
+
class="select-input"
|
|
71
|
+
:display-value="displayValue"
|
|
72
|
+
:placeholder="placeholder"
|
|
73
|
+
:aria-labelledby="props.label ? `${id}-label` : undefined"
|
|
74
|
+
:aria-label="!props.label ? props.ariaLabel : undefined"
|
|
75
|
+
/>
|
|
76
|
+
<ComboboxTrigger class="select-icon-button" aria-label="Toggle options">
|
|
77
|
+
<span class="select-icon" aria-hidden="true">
|
|
78
|
+
<svg
|
|
79
|
+
width="12"
|
|
80
|
+
height="12"
|
|
81
|
+
viewBox="0 0 12 12"
|
|
82
|
+
fill="none"
|
|
83
|
+
stroke="currentColor"
|
|
84
|
+
stroke-width="2"
|
|
85
|
+
stroke-linecap="round"
|
|
86
|
+
stroke-linejoin="round"
|
|
87
|
+
>
|
|
88
|
+
<path d="M3 4.5L6 7.5L9 4.5" />
|
|
89
|
+
</svg>
|
|
90
|
+
</span>
|
|
91
|
+
</ComboboxTrigger>
|
|
92
|
+
</ComboboxAnchor>
|
|
93
|
+
<ComboboxPortal>
|
|
94
|
+
<ComboboxContent
|
|
95
|
+
class="select-content select-content-autocomplete"
|
|
96
|
+
position="popper"
|
|
97
|
+
:side-offset="4"
|
|
98
|
+
:body-lock="false"
|
|
99
|
+
>
|
|
100
|
+
<ComboboxViewport class="select-viewport">
|
|
101
|
+
<ComboboxEmpty class="select-empty">No matches</ComboboxEmpty>
|
|
102
|
+
<ComboboxItem
|
|
103
|
+
v-for="opt in options"
|
|
104
|
+
:key="opt.value"
|
|
105
|
+
:value="opt.value"
|
|
106
|
+
class="select-item"
|
|
107
|
+
>
|
|
108
|
+
<span>{{ opt.label }}</span>
|
|
109
|
+
<ComboboxItemIndicator class="select-indicator">
|
|
110
|
+
<svg
|
|
111
|
+
width="12"
|
|
112
|
+
height="12"
|
|
113
|
+
viewBox="0 0 12 12"
|
|
114
|
+
fill="none"
|
|
115
|
+
stroke="currentColor"
|
|
116
|
+
stroke-width="2"
|
|
117
|
+
stroke-linecap="round"
|
|
118
|
+
stroke-linejoin="round"
|
|
119
|
+
>
|
|
120
|
+
<path d="M2 6L5 9L10 3" />
|
|
121
|
+
</svg>
|
|
122
|
+
</ComboboxItemIndicator>
|
|
123
|
+
</ComboboxItem>
|
|
124
|
+
</ComboboxViewport>
|
|
125
|
+
</ComboboxContent>
|
|
126
|
+
</ComboboxPortal>
|
|
127
|
+
</ComboboxRoot>
|
|
128
|
+
|
|
129
|
+
<SelectRoot v-else v-model="model">
|
|
43
130
|
<SelectTrigger
|
|
44
131
|
class="select-trigger"
|
|
45
132
|
:aria-labelledby="props.label ? `${id}-label` : undefined"
|
|
@@ -140,6 +227,62 @@ const id = useId();
|
|
|
140
227
|
color: var(--color-text-secondary);
|
|
141
228
|
}
|
|
142
229
|
|
|
230
|
+
/* Autocomplete (combobox) field */
|
|
231
|
+
.select-anchor {
|
|
232
|
+
display: flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
gap: 0.5em;
|
|
235
|
+
height: 2.5em;
|
|
236
|
+
padding: 0 0.5em 0 0.75em;
|
|
237
|
+
font-size: var(--font-size-sm);
|
|
238
|
+
background: var(--color-bg-0);
|
|
239
|
+
border: 1px solid var(--color-border);
|
|
240
|
+
border-radius: 0.375em;
|
|
241
|
+
transition:
|
|
242
|
+
border-color var(--transition-fast),
|
|
243
|
+
box-shadow var(--transition-fast);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.select-anchor:hover {
|
|
247
|
+
border-color: var(--color-border-hover);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.select-anchor:focus-within {
|
|
251
|
+
outline: none;
|
|
252
|
+
border-color: var(--color-border-focus);
|
|
253
|
+
box-shadow: var(--shadow-focus);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.select-input {
|
|
257
|
+
flex: 1;
|
|
258
|
+
min-width: 0;
|
|
259
|
+
height: 100%;
|
|
260
|
+
padding: 0;
|
|
261
|
+
border: none;
|
|
262
|
+
background: none;
|
|
263
|
+
color: inherit;
|
|
264
|
+
font: inherit;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.select-input:focus {
|
|
268
|
+
outline: none;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.select-input::placeholder {
|
|
272
|
+
color: var(--color-text-secondary);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.select-icon-button {
|
|
276
|
+
display: inline-flex;
|
|
277
|
+
align-items: center;
|
|
278
|
+
flex-shrink: 0;
|
|
279
|
+
padding: 0;
|
|
280
|
+
border: none;
|
|
281
|
+
background: none;
|
|
282
|
+
color: inherit;
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
}
|
|
285
|
+
|
|
143
286
|
.select-icon {
|
|
144
287
|
display: flex;
|
|
145
288
|
align-items: center;
|
|
@@ -160,10 +303,23 @@ const id = useId();
|
|
|
160
303
|
max-height: var(--reka-select-content-available-height);
|
|
161
304
|
}
|
|
162
305
|
|
|
306
|
+
/* Combobox exposes the anchor width under its own custom property. */
|
|
307
|
+
.select-content-autocomplete {
|
|
308
|
+
width: var(--reka-combobox-trigger-width);
|
|
309
|
+
max-height: var(--reka-combobox-content-available-height);
|
|
310
|
+
}
|
|
311
|
+
|
|
163
312
|
.select-viewport {
|
|
164
313
|
padding: 0.25em;
|
|
165
314
|
}
|
|
166
315
|
|
|
316
|
+
.select-empty {
|
|
317
|
+
padding: 0.5em;
|
|
318
|
+
font-size: var(--font-size-sm);
|
|
319
|
+
color: var(--color-text-secondary);
|
|
320
|
+
text-align: center;
|
|
321
|
+
}
|
|
322
|
+
|
|
167
323
|
.select-item {
|
|
168
324
|
display: flex;
|
|
169
325
|
align-items: center;
|
package/index.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.6.
|
|
2
|
+
"version": "0.6.1",
|
|
3
3
|
"package": "@cfasim-ui/docs",
|
|
4
4
|
"content": {
|
|
5
5
|
"components": [
|
|
@@ -136,7 +136,15 @@
|
|
|
136
136
|
"slug": "select-box",
|
|
137
137
|
"docs": "components/SelectBox/SelectBox.md",
|
|
138
138
|
"source": "components/SelectBox/SelectBox.vue",
|
|
139
|
-
"keywords": [
|
|
139
|
+
"keywords": [
|
|
140
|
+
"select",
|
|
141
|
+
"dropdown",
|
|
142
|
+
"autocomplete",
|
|
143
|
+
"combobox",
|
|
144
|
+
"filter",
|
|
145
|
+
"search",
|
|
146
|
+
"typeahead"
|
|
147
|
+
]
|
|
140
148
|
},
|
|
141
149
|
{
|
|
142
150
|
"name": "SidebarLayout",
|