@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 select built on reka-ui.
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
- <SelectRoot v-model="model">
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.0",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfasim-ui/docs",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {