@cfasim-ui/docs 0.6.4 → 0.7.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.
@@ -19,8 +19,11 @@ import {
19
19
  SelectTrigger,
20
20
  SelectValue,
21
21
  SelectViewport,
22
- useId,
23
22
  } from "reka-ui";
23
+ import Icon from "../Icon/Icon.vue";
24
+ import FieldLabel from "../_internal/FieldLabel.vue";
25
+ import { useField, type FieldProps } from "../_internal/field";
26
+ import "../_internal/listbox.css";
24
27
 
25
28
  export interface SelectOption {
26
29
  value: string;
@@ -29,17 +32,16 @@ export interface SelectOption {
29
32
 
30
33
  const model = defineModel<string>();
31
34
 
32
- const props = defineProps<{
33
- label?: string;
34
- hideLabel?: boolean;
35
- ariaLabel?: string;
35
+ interface Props extends FieldProps {
36
36
  options: SelectOption[];
37
37
  placeholder?: string;
38
38
  /** Turn the field into a filterable single-select autocomplete (combobox). */
39
39
  autocomplete?: boolean;
40
- }>();
40
+ }
41
+
42
+ const props = defineProps<Props>();
41
43
 
42
- const id = useId();
44
+ const { id, labelId, ariaProps } = useField(props);
43
45
 
44
46
  // Shows the selected option's label in the combobox input when not filtering.
45
47
  function displayValue(value: string) {
@@ -49,14 +51,14 @@ function displayValue(value: string) {
49
51
 
50
52
  <template>
51
53
  <div class="select-box">
52
- <label
53
- v-if="label"
54
- :id="`${id}-label`"
55
- :for="autocomplete ? `${id}-input` : undefined"
54
+ <FieldLabel
56
55
  class="select-label"
57
- :class="{ 'visually-hidden': hideLabel }"
58
- >{{ label }}</label
59
- >
56
+ :label="label"
57
+ :label-id="labelId"
58
+ :hide-label="hideLabel"
59
+ :hint="hint"
60
+ :html-for="autocomplete ? `${id}-input` : undefined"
61
+ />
60
62
 
61
63
  <ComboboxRoot
62
64
  v-if="autocomplete"
@@ -70,55 +72,34 @@ function displayValue(value: string) {
70
72
  class="select-input"
71
73
  :display-value="displayValue"
72
74
  :placeholder="placeholder"
73
- :aria-labelledby="props.label ? `${id}-label` : undefined"
74
- :aria-label="!props.label ? props.ariaLabel : undefined"
75
+ v-bind="ariaProps"
75
76
  />
76
77
  <ComboboxTrigger class="select-icon-button" aria-label="Toggle options">
77
78
  <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>
79
+ <Icon icon="keyboard_arrow_down" :size="16" />
90
80
  </span>
91
81
  </ComboboxTrigger>
92
82
  </ComboboxAnchor>
93
83
  <ComboboxPortal>
94
84
  <ComboboxContent
95
- class="select-content select-content-autocomplete"
85
+ class="cfasim-listbox-content select-content-autocomplete"
96
86
  position="popper"
97
87
  :side-offset="4"
98
88
  :body-lock="false"
99
89
  >
100
- <ComboboxViewport class="select-viewport">
101
- <ComboboxEmpty class="select-empty">No matches</ComboboxEmpty>
90
+ <ComboboxViewport class="cfasim-listbox-viewport">
91
+ <ComboboxEmpty class="cfasim-listbox-empty">
92
+ No matches
93
+ </ComboboxEmpty>
102
94
  <ComboboxItem
103
95
  v-for="opt in options"
104
96
  :key="opt.value"
105
97
  :value="opt.value"
106
- class="select-item"
98
+ class="cfasim-listbox-item"
107
99
  >
108
100
  <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>
101
+ <ComboboxItemIndicator class="cfasim-listbox-indicator">
102
+ <Icon icon="check" :size="14" />
122
103
  </ComboboxItemIndicator>
123
104
  </ComboboxItem>
124
105
  </ComboboxViewport>
@@ -127,55 +108,29 @@ function displayValue(value: string) {
127
108
  </ComboboxRoot>
128
109
 
129
110
  <SelectRoot v-else v-model="model">
130
- <SelectTrigger
131
- class="select-trigger"
132
- :aria-labelledby="props.label ? `${id}-label` : undefined"
133
- :aria-label="!props.label ? props.ariaLabel : undefined"
134
- >
111
+ <SelectTrigger class="select-trigger" v-bind="ariaProps">
135
112
  <SelectValue :placeholder="placeholder" />
136
113
  <span class="select-icon" aria-hidden="true">
137
- <svg
138
- width="12"
139
- height="12"
140
- viewBox="0 0 12 12"
141
- fill="none"
142
- stroke="currentColor"
143
- stroke-width="2"
144
- stroke-linecap="round"
145
- stroke-linejoin="round"
146
- >
147
- <path d="M3 4.5L6 7.5L9 4.5" />
148
- </svg>
114
+ <Icon icon="keyboard_arrow_down" :size="16" />
149
115
  </span>
150
116
  </SelectTrigger>
151
117
  <SelectPortal>
152
118
  <SelectContent
153
- class="select-content"
119
+ class="cfasim-listbox-content select-content"
154
120
  position="popper"
155
121
  :side-offset="4"
156
122
  :body-lock="false"
157
123
  >
158
- <SelectViewport class="select-viewport">
124
+ <SelectViewport class="cfasim-listbox-viewport">
159
125
  <SelectItem
160
126
  v-for="opt in options"
161
127
  :key="opt.value"
162
128
  :value="opt.value"
163
- class="select-item"
129
+ class="cfasim-listbox-item"
164
130
  >
165
131
  <SelectItemText>{{ opt.label }}</SelectItemText>
166
- <SelectItemIndicator class="select-indicator">
167
- <svg
168
- width="12"
169
- height="12"
170
- viewBox="0 0 12 12"
171
- fill="none"
172
- stroke="currentColor"
173
- stroke-width="2"
174
- stroke-linecap="round"
175
- stroke-linejoin="round"
176
- >
177
- <path d="M2 6L5 9L10 3" />
178
- </svg>
132
+ <SelectItemIndicator class="cfasim-listbox-indicator">
133
+ <Icon icon="check" :size="14" />
179
134
  </SelectItemIndicator>
180
135
  </SelectItem>
181
136
  </SelectViewport>
@@ -291,14 +246,8 @@ function displayValue(value: string) {
291
246
  </style>
292
247
 
293
248
  <style>
249
+ /* Sizing only — the dropdown skin lives in _internal/listbox.css. */
294
250
  .select-content {
295
- z-index: 100;
296
- background: var(--color-bg-0);
297
- border: 1px solid var(--color-border);
298
- border-radius: 0.25em;
299
- box-shadow:
300
- 0 4px 6px -1px rgba(0, 0, 0, 0.1),
301
- 0 2px 4px -2px rgba(0, 0, 0, 0.1);
302
251
  min-width: var(--reka-select-trigger-width);
303
252
  max-height: var(--reka-select-content-available-height);
304
253
  }
@@ -308,44 +257,4 @@ function displayValue(value: string) {
308
257
  width: var(--reka-combobox-trigger-width);
309
258
  max-height: var(--reka-combobox-content-available-height);
310
259
  }
311
-
312
- .select-viewport {
313
- padding: 0.25em;
314
- }
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
-
323
- .select-item {
324
- display: flex;
325
- align-items: center;
326
- justify-content: space-between;
327
- gap: 0.5em;
328
- padding: 0.25em 0.5em;
329
- border-radius: 0.25em;
330
- font-size: var(--font-size-sm);
331
- white-space: nowrap;
332
- cursor: pointer;
333
- user-select: none;
334
- outline: none;
335
- }
336
-
337
- .select-item[data-highlighted] {
338
- background: var(--color-primary);
339
- color: white;
340
- }
341
-
342
- .select-item[data-state="checked"] {
343
- font-weight: 600;
344
- }
345
-
346
- .select-indicator {
347
- display: flex;
348
- align-items: center;
349
- flex-shrink: 0;
350
- }
351
260
  </style>
@@ -77,6 +77,7 @@ it.
77
77
  |------|------|----------|---------|
78
78
  | `label` | `string` | No | — |
79
79
  | `hideLabel` | `boolean` | No | — |
80
- | `placeholder` | `string` | No | — |
80
+ | `ariaLabel` | `string` | No | — |
81
81
  | `hint` | `string` | No | — |
82
+ | `placeholder` | `string` | No | — |
82
83
 
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { ref, watch } from "vue";
3
3
  import Hint from "../Hint/Hint.vue";
4
+ import type { FieldProps } from "../_internal/field";
5
+ import "../_internal/input.css";
4
6
 
5
7
  const model = defineModel<string>();
6
8
  const local = ref(model.value);
@@ -13,18 +15,17 @@ function commit() {
13
15
  model.value = local.value;
14
16
  }
15
17
 
16
- const props = defineProps<{
17
- label?: string;
18
- hideLabel?: boolean;
18
+ interface Props extends FieldProps {
19
19
  placeholder?: string;
20
- hint?: string;
21
- }>();
20
+ }
21
+
22
+ const props = defineProps<Props>();
22
23
  </script>
23
24
 
24
25
  <template>
25
- <label v-if="props.label" class="input-label">
26
+ <label v-if="props.label" class="cfasim-input-label">
26
27
  <span
27
- class="input-label-row"
28
+ class="cfasim-input-label-row"
28
29
  :class="{ 'visually-hidden': props.hideLabel }"
29
30
  >
30
31
  {{ props.label }}
@@ -32,6 +33,7 @@ const props = defineProps<{
32
33
  </span>
33
34
  <input
34
35
  type="text"
36
+ class="cfasim-input"
35
37
  v-model="local"
36
38
  :placeholder="props.placeholder"
37
39
  @blur="commit"
@@ -41,54 +43,12 @@ const props = defineProps<{
41
43
  <div v-else>
42
44
  <input
43
45
  type="text"
46
+ class="cfasim-input"
44
47
  v-model="local"
45
48
  :placeholder="props.placeholder"
49
+ :aria-label="props.ariaLabel"
46
50
  @blur="commit"
47
51
  @keydown.enter="commit"
48
52
  />
49
53
  </div>
50
54
  </template>
51
-
52
- <style scoped>
53
- .input-label {
54
- display: flex;
55
- flex-direction: column;
56
- gap: 0.25em;
57
- font-size: var(--font-size-sm);
58
- }
59
-
60
- .input-label-row {
61
- display: flex;
62
- align-items: center;
63
- justify-content: space-between;
64
- }
65
-
66
- input {
67
- display: block;
68
- width: 100%;
69
- height: 2.5em;
70
- padding: 0 0.75em;
71
- font-size: inherit;
72
- background-color: var(--color-bg-0);
73
- color: var(--color-text);
74
- border: 1px solid var(--color-border);
75
- border-radius: 0.375em;
76
- transition:
77
- border-color var(--transition-fast),
78
- box-shadow var(--transition-fast);
79
- }
80
-
81
- input:hover {
82
- border-color: var(--color-border-hover);
83
- }
84
-
85
- input:focus {
86
- outline: none;
87
- border-color: var(--color-border-focus);
88
- box-shadow: var(--shadow-focus);
89
- }
90
-
91
- input::placeholder {
92
- color: var(--color-text-tertiary);
93
- }
94
- </style>
@@ -142,12 +142,12 @@ Set `orientation="vertical"` to stack the items, and mark individual options
142
142
 
143
143
  | Prop | Type | Required | Default |
144
144
  |------|------|----------|---------|
145
- | `options` | `ToggleGroupOption[]` | Yes | — |
146
- | `multiple` | `boolean` | No | — |
147
145
  | `label` | `string` | No | — |
148
146
  | `hideLabel` | `boolean` | No | — |
149
147
  | `ariaLabel` | `string` | No | — |
150
148
  | `hint` | `string` | No | — |
149
+ | `options` | `ToggleGroupOption[]` | Yes | — |
150
+ | `multiple` | `boolean` | No | — |
151
151
  | `disabled` | `boolean` | No | — |
152
152
  | `orientation` | `"horizontal" \| "vertical"` | No | `"horizontal"` |
153
153
 
@@ -1,7 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { ToggleGroupItem, ToggleGroupRoot, useId } from "reka-ui";
2
+ import { ToggleGroupItem, ToggleGroupRoot } from "reka-ui";
3
3
  import { computed } from "vue";
4
- import Hint from "../Hint/Hint.vue";
4
+ import FieldLabel from "../_internal/FieldLabel.vue";
5
+ import { useField, type FieldProps } from "../_internal/field";
5
6
 
6
7
  export interface ToggleGroupOption {
7
8
  value: string;
@@ -12,21 +13,18 @@ export interface ToggleGroupOption {
12
13
  // `string` in single mode, `string[]` in multiple mode.
13
14
  const model = defineModel<string | string[]>();
14
15
 
15
- const props = withDefaults(
16
- defineProps<{
17
- options: ToggleGroupOption[];
18
- multiple?: boolean;
19
- label?: string;
20
- hideLabel?: boolean;
21
- ariaLabel?: string;
22
- hint?: string;
23
- disabled?: boolean;
24
- orientation?: "horizontal" | "vertical";
25
- }>(),
26
- { orientation: "horizontal" },
27
- );
16
+ interface Props extends FieldProps {
17
+ options: ToggleGroupOption[];
18
+ multiple?: boolean;
19
+ disabled?: boolean;
20
+ orientation?: "horizontal" | "vertical";
21
+ }
28
22
 
29
- const id = useId();
23
+ const props = withDefaults(defineProps<Props>(), {
24
+ orientation: "horizontal",
25
+ });
26
+
27
+ const { labelId, ariaProps } = useField(props);
30
28
  const type = computed<"single" | "multiple">(() =>
31
29
  props.multiple ? "multiple" : "single",
32
30
  );
@@ -34,23 +32,20 @@ const type = computed<"single" | "multiple">(() =>
34
32
 
35
33
  <template>
36
34
  <div class="toggle-group-field">
37
- <label
38
- v-if="label"
39
- :id="`${id}-label`"
35
+ <FieldLabel
40
36
  class="toggle-group-label"
41
- :class="{ 'visually-hidden': hideLabel }"
42
- >
43
- {{ label }}
44
- <Hint v-if="hint && !hideLabel" :text="hint" />
45
- </label>
37
+ :label="label"
38
+ :label-id="labelId"
39
+ :hide-label="hideLabel"
40
+ :hint="hint"
41
+ />
46
42
  <ToggleGroupRoot
47
43
  v-model="model"
48
44
  :type="type"
49
45
  :disabled="disabled"
50
46
  :orientation="orientation"
51
47
  :class="['toggle-group', `toggle-group--${orientation}`]"
52
- :aria-labelledby="label ? `${id}-label` : undefined"
53
- :aria-label="!label ? ariaLabel : undefined"
48
+ v-bind="ariaProps"
54
49
  >
55
50
  <ToggleGroupItem
56
51
  v-for="opt in options"
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import Hint from "../Hint/Hint.vue";
3
+
4
+ // The visible label for a form field, with the shared hideLabel/hint
5
+ // behavior. Renders nothing without a `label`; the parent supplies its
6
+ // own class via fallthrough attrs so existing scoped styles keep working.
7
+ defineProps<{
8
+ label?: string;
9
+ labelId?: string;
10
+ hideLabel?: boolean;
11
+ hint?: string;
12
+ /** `for` attribute, for fields whose control is a real labelable element. */
13
+ htmlFor?: string;
14
+ }>();
15
+ </script>
16
+
17
+ <template>
18
+ <label
19
+ v-if="label"
20
+ :id="labelId"
21
+ :for="htmlFor"
22
+ :class="{ 'visually-hidden': hideLabel }"
23
+ >
24
+ {{ label }}
25
+ <Hint v-if="hint && !hideLabel" :text="hint" />
26
+ </label>
27
+ </template>
@@ -0,0 +1,27 @@
1
+ import { computed } from "vue";
2
+ import { useId } from "reka-ui";
3
+
4
+ /** Props shared by every labeled form field. */
5
+ export interface FieldProps {
6
+ label?: string;
7
+ /** Visually hide the label while keeping it for assistive tech. */
8
+ hideLabel?: boolean;
9
+ /** Accessible name used when no visible `label` is provided. */
10
+ ariaLabel?: string;
11
+ hint?: string;
12
+ }
13
+
14
+ /**
15
+ * Generated field/label ids plus the aria attributes that point a control
16
+ * at its label: `aria-labelledby` when a visible `label` exists, else
17
+ * `aria-label`. Spread onto the control with `v-bind="ariaProps.value"`.
18
+ */
19
+ export function useField(props: FieldProps) {
20
+ const id = useId();
21
+ const labelId = `${id}-label`;
22
+ const ariaProps = computed(() => ({
23
+ "aria-labelledby": props.label ? labelId : undefined,
24
+ "aria-label": !props.label ? props.ariaLabel : undefined,
25
+ }));
26
+ return { id, labelId, ariaProps };
27
+ }
@@ -0,0 +1,54 @@
1
+ /* Shared text-field skin for TextInput/NumberInput. Global (not scoped)
2
+ * so both components can use one copy; prefixed to avoid consumer
3
+ * collisions. */
4
+
5
+ .cfasim-input-label {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: 0.25em;
9
+ font-size: var(--font-size-sm);
10
+ }
11
+
12
+ .cfasim-input-label-row {
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: space-between;
16
+ }
17
+
18
+ .cfasim-input {
19
+ display: block;
20
+ width: 100%;
21
+ height: 2.5em;
22
+ padding: 0 0.75em;
23
+ font-size: inherit;
24
+ background-color: var(--color-bg-0);
25
+ color: var(--color-text);
26
+ border: 1px solid var(--color-border);
27
+ border-radius: 0.375em;
28
+ transition:
29
+ border-color var(--transition-fast),
30
+ box-shadow var(--transition-fast);
31
+ }
32
+
33
+ .cfasim-input:hover {
34
+ border-color: var(--color-border-hover);
35
+ }
36
+
37
+ .cfasim-input:focus {
38
+ outline: none;
39
+ border-color: var(--color-border-focus);
40
+ box-shadow: var(--shadow-focus);
41
+ }
42
+
43
+ .cfasim-input[aria-invalid="true"] {
44
+ border-color: var(--color-error);
45
+ }
46
+
47
+ .cfasim-input[aria-invalid="true"]:focus {
48
+ border-color: var(--color-error);
49
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-error) 25%, transparent);
50
+ }
51
+
52
+ .cfasim-input::placeholder {
53
+ color: var(--color-text-tertiary);
54
+ }
@@ -0,0 +1,53 @@
1
+ /* Shared skin for the SelectBox/MultiSelect dropdown (reka-ui Select and
2
+ * Combobox portals render outside the component, so these are global).
3
+ * Sizing (width/max-height from the reka trigger vars) stays per-component. */
4
+
5
+ .cfasim-listbox-content {
6
+ z-index: 100;
7
+ background: var(--color-bg-0);
8
+ border: 1px solid var(--color-border);
9
+ border-radius: 0.25em;
10
+ box-shadow:
11
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
12
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1);
13
+ }
14
+
15
+ .cfasim-listbox-viewport {
16
+ padding: 0.25em;
17
+ }
18
+
19
+ .cfasim-listbox-empty {
20
+ padding: 0.5em;
21
+ font-size: var(--font-size-sm);
22
+ color: var(--color-text-secondary);
23
+ text-align: center;
24
+ }
25
+
26
+ .cfasim-listbox-item {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ gap: 0.5em;
31
+ padding: 0.25em 0.5em;
32
+ border-radius: 0.25em;
33
+ font-size: var(--font-size-sm);
34
+ white-space: nowrap;
35
+ cursor: pointer;
36
+ user-select: none;
37
+ outline: none;
38
+ }
39
+
40
+ .cfasim-listbox-item[data-highlighted] {
41
+ background: var(--color-primary);
42
+ color: white;
43
+ }
44
+
45
+ .cfasim-listbox-item[data-state="checked"] {
46
+ font-weight: 600;
47
+ }
48
+
49
+ .cfasim-listbox-indicator {
50
+ display: flex;
51
+ align-items: center;
52
+ flex-shrink: 0;
53
+ }
@@ -0,0 +1,4 @@
1
+ // Ambient declaration for side-effect CSS imports (e.g. `import "./x.css"`).
2
+ // Required since TypeScript 6, which errors (TS2882) on side-effect imports
3
+ // without a module declaration.
4
+ declare module "*.css";
package/index.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.4",
2
+ "version": "0.7.1",
3
3
  "package": "@cfasim-ui/docs",
4
4
  "content": {
5
5
  "components": [
@@ -216,6 +216,13 @@
216
216
  "source": "charts/ChoroplethMap/ChoroplethMap.vue",
217
217
  "keywords": []
218
218
  },
219
+ {
220
+ "name": "DataTable",
221
+ "slug": "data-table",
222
+ "docs": "charts/DataTable/DataTable.md",
223
+ "source": "charts/DataTable/DataTable.vue",
224
+ "keywords": []
225
+ },
219
226
  {
220
227
  "name": "LineChart",
221
228
  "slug": "line-chart",
@@ -231,13 +238,6 @@
231
238
  "confidence band",
232
239
  "svg"
233
240
  ]
234
- },
235
- {
236
- "name": "DataTable",
237
- "slug": "data-table",
238
- "docs": "charts/DataTable/DataTable.md",
239
- "source": "charts/DataTable/DataTable.vue",
240
- "keywords": []
241
241
  }
242
242
  ]
243
243
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfasim-ui/docs",
3
- "version": "0.6.4",
3
+ "version": "0.7.1",
4
4
  "description": "LLM-friendly component and chart documentation for cfasim-ui",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,23 @@
1
+ export interface RunMessage {
2
+ id: number;
3
+ type?: "run";
4
+ python: string;
5
+ context?: Record<string, unknown>;
6
+ }
7
+ export interface CallMessage {
8
+ id: number;
9
+ type: "call";
10
+ module: string;
11
+ fn: string;
12
+ kwargs?: Record<string, unknown>;
13
+ }
14
+ export interface LoadModuleMessage {
15
+ id: number;
16
+ type: "loadModule";
17
+ module: string;
18
+ }
19
+ export type WorkerMessage = RunMessage | CallMessage | LoadModuleMessage;
20
+ export type OutgoingMessage =
21
+ | Omit<RunMessage, "id">
22
+ | Omit<CallMessage, "id">
23
+ | Omit<LoadModuleMessage, "id">;