@cfasim-ui/components 0.7.7 → 0.8.0
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.
- package/dist/Box/Box.d.ts +7 -11
- package/dist/Button/Button.d.ts +7 -11
- package/dist/ButtonGroup/ButtonGroup.d.ts +8 -12
- package/dist/Container/Container.d.ts +7 -11
- package/dist/Expander/Expander.d.ts +12 -14
- package/dist/Grid/Grid.d.ts +6 -5
- package/dist/Hint/Hint.d.ts +2 -1
- package/dist/Icon/Icon.d.ts +3 -2
- package/dist/LightDarkToggle/LightDarkToggle.d.ts +2 -1
- package/dist/MultiSelect/MultiSelect.d.ts +6 -6
- package/dist/NumberInput/NumberInput.d.ts +13 -11
- package/dist/ParamEditor/ParamEditor.d.ts +3 -2
- package/dist/ParamEditor/ParamEditorImpl.d.ts +3 -5
- package/dist/{ParamEditorImpl-D0xZTyAN.js → ParamEditorImpl-CkElC6PI.js} +3075 -3041
- package/dist/SelectBox/SelectBox.d.ts +8 -6
- package/dist/{SelectBox-CByXZfaC.js → SelectBox-Df8dE2r-.js} +6 -6
- package/dist/SidebarLayout/SidebarLayout.d.ts +21 -20
- package/dist/Spinner/Spinner.d.ts +3 -2
- package/dist/TextInput/TextInput.d.ts +7 -5
- package/dist/Toggle/Toggle.d.ts +8 -6
- package/dist/ToggleGroup/ToggleGroup.d.ts +8 -6
- package/dist/_internal/FieldLabel.d.ts +2 -1
- package/dist/index.js +20 -20
- package/docs/Box.md +49 -0
- package/docs/Button.md +67 -0
- package/docs/ButtonGroup.md +64 -0
- package/docs/Container.md +103 -0
- package/docs/Expander.md +34 -0
- package/docs/Grid.md +170 -0
- package/docs/Hint.md +29 -0
- package/docs/Icon.md +188 -0
- package/docs/MultiSelect.md +143 -0
- package/docs/NumberInput.md +485 -0
- package/docs/ParamEditor.md +97 -0
- package/docs/SelectBox.md +182 -0
- package/docs/SidebarLayout.md +106 -0
- package/docs/Spinner.md +51 -0
- package/docs/TextInput.md +83 -0
- package/docs/Toggle.md +81 -0
- package/docs/ToggleGroup.md +163 -0
- package/docs/index.json +194 -0
- package/package.json +24 -20
- package/src/Box/Box.md +41 -0
- package/src/Box/Box.vue +52 -0
- package/src/Button/Button.md +59 -0
- package/src/Button/Button.vue +81 -0
- package/src/ButtonGroup/ButtonGroup.md +62 -0
- package/src/ButtonGroup/ButtonGroup.vue +91 -0
- package/src/Container/Container.md +99 -0
- package/src/Container/Container.vue +62 -0
- package/src/Expander/Expander.md +23 -0
- package/src/Expander/Expander.vue +95 -0
- package/src/Grid/Grid.md +175 -0
- package/src/Grid/Grid.vue +145 -0
- package/src/Hint/Hint.md +24 -0
- package/src/Hint/Hint.vue +83 -0
- package/src/Icon/Icon.md +176 -0
- package/src/Icon/Icon.vue +104 -0
- package/src/Icon/defaultIcons.ts +92 -0
- package/src/Icon/github.svg +1 -0
- package/src/Icon/registry.ts +68 -0
- package/src/LightDarkToggle/LightDarkToggle.vue +49 -0
- package/src/MultiSelect/MultiSelect.md +142 -0
- package/src/MultiSelect/MultiSelect.vue +279 -0
- package/src/NumberInput/NumberInput.md +455 -0
- package/src/NumberInput/NumberInput.vue +575 -0
- package/src/ParamEditor/ParamEditor.md +87 -0
- package/src/ParamEditor/ParamEditor.vue +43 -0
- package/src/ParamEditor/ParamEditorImpl.vue +358 -0
- package/src/SelectBox/SelectBox.md +169 -0
- package/src/SelectBox/SelectBox.vue +260 -0
- package/src/SidebarLayout/SidebarLayout.md +106 -0
- package/src/SidebarLayout/SidebarLayout.vue +468 -0
- package/src/Spinner/Spinner.md +45 -0
- package/src/Spinner/Spinner.vue +55 -0
- package/src/TextInput/TextInput.md +68 -0
- package/src/TextInput/TextInput.vue +54 -0
- package/src/Toggle/Toggle.md +68 -0
- package/src/Toggle/Toggle.vue +81 -0
- package/src/ToggleGroup/ToggleGroup.md +158 -0
- package/src/ToggleGroup/ToggleGroup.vue +138 -0
- package/src/_internal/FieldLabel.vue +27 -0
- package/src/_internal/field.ts +27 -0
- package/src/_internal/gap.ts +17 -0
- package/src/_internal/input.css +54 -0
- package/src/_internal/listbox.css +53 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +33 -0
- package/src/svg.d.ts +5 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
ComboboxAnchor,
|
|
4
|
+
ComboboxContent,
|
|
5
|
+
ComboboxEmpty,
|
|
6
|
+
ComboboxInput,
|
|
7
|
+
ComboboxItem,
|
|
8
|
+
ComboboxItemIndicator,
|
|
9
|
+
ComboboxPortal,
|
|
10
|
+
ComboboxRoot,
|
|
11
|
+
ComboboxTrigger,
|
|
12
|
+
ComboboxViewport,
|
|
13
|
+
SelectContent,
|
|
14
|
+
SelectItem,
|
|
15
|
+
SelectItemIndicator,
|
|
16
|
+
SelectItemText,
|
|
17
|
+
SelectPortal,
|
|
18
|
+
SelectRoot,
|
|
19
|
+
SelectTrigger,
|
|
20
|
+
SelectValue,
|
|
21
|
+
SelectViewport,
|
|
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";
|
|
27
|
+
|
|
28
|
+
export interface SelectOption {
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const model = defineModel<string>();
|
|
34
|
+
|
|
35
|
+
interface Props extends FieldProps {
|
|
36
|
+
options: SelectOption[];
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** Turn the field into a filterable single-select autocomplete (combobox). */
|
|
39
|
+
autocomplete?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const props = defineProps<Props>();
|
|
43
|
+
|
|
44
|
+
const { id, labelId, ariaProps } = useField(props);
|
|
45
|
+
|
|
46
|
+
// Shows the selected option's label in the combobox input when not filtering.
|
|
47
|
+
function displayValue(value: string) {
|
|
48
|
+
return props.options.find((o) => o.value === value)?.label ?? "";
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<div class="select-box">
|
|
54
|
+
<FieldLabel
|
|
55
|
+
class="select-label"
|
|
56
|
+
:label="label"
|
|
57
|
+
:label-id="labelId"
|
|
58
|
+
:hide-label="hideLabel"
|
|
59
|
+
:hint="hint"
|
|
60
|
+
:html-for="autocomplete ? `${id}-input` : undefined"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<ComboboxRoot
|
|
64
|
+
v-if="autocomplete"
|
|
65
|
+
v-model="model"
|
|
66
|
+
open-on-click
|
|
67
|
+
class="select-combobox-root"
|
|
68
|
+
>
|
|
69
|
+
<ComboboxAnchor class="select-anchor">
|
|
70
|
+
<ComboboxInput
|
|
71
|
+
:id="`${id}-input`"
|
|
72
|
+
class="select-input"
|
|
73
|
+
:display-value="displayValue"
|
|
74
|
+
:placeholder="placeholder"
|
|
75
|
+
v-bind="ariaProps"
|
|
76
|
+
/>
|
|
77
|
+
<ComboboxTrigger class="select-icon-button" aria-label="Toggle options">
|
|
78
|
+
<span class="select-icon" aria-hidden="true">
|
|
79
|
+
<Icon icon="keyboard_arrow_down" :size="16" />
|
|
80
|
+
</span>
|
|
81
|
+
</ComboboxTrigger>
|
|
82
|
+
</ComboboxAnchor>
|
|
83
|
+
<ComboboxPortal>
|
|
84
|
+
<ComboboxContent
|
|
85
|
+
class="cfasim-listbox-content select-content-autocomplete"
|
|
86
|
+
position="popper"
|
|
87
|
+
:side-offset="4"
|
|
88
|
+
:body-lock="false"
|
|
89
|
+
>
|
|
90
|
+
<ComboboxViewport class="cfasim-listbox-viewport">
|
|
91
|
+
<ComboboxEmpty class="cfasim-listbox-empty">
|
|
92
|
+
No matches
|
|
93
|
+
</ComboboxEmpty>
|
|
94
|
+
<ComboboxItem
|
|
95
|
+
v-for="opt in options"
|
|
96
|
+
:key="opt.value"
|
|
97
|
+
:value="opt.value"
|
|
98
|
+
class="cfasim-listbox-item"
|
|
99
|
+
>
|
|
100
|
+
<span>{{ opt.label }}</span>
|
|
101
|
+
<ComboboxItemIndicator class="cfasim-listbox-indicator">
|
|
102
|
+
<Icon icon="check" :size="14" />
|
|
103
|
+
</ComboboxItemIndicator>
|
|
104
|
+
</ComboboxItem>
|
|
105
|
+
</ComboboxViewport>
|
|
106
|
+
</ComboboxContent>
|
|
107
|
+
</ComboboxPortal>
|
|
108
|
+
</ComboboxRoot>
|
|
109
|
+
|
|
110
|
+
<SelectRoot v-else v-model="model">
|
|
111
|
+
<SelectTrigger class="select-trigger" v-bind="ariaProps">
|
|
112
|
+
<SelectValue :placeholder="placeholder" />
|
|
113
|
+
<span class="select-icon" aria-hidden="true">
|
|
114
|
+
<Icon icon="keyboard_arrow_down" :size="16" />
|
|
115
|
+
</span>
|
|
116
|
+
</SelectTrigger>
|
|
117
|
+
<SelectPortal>
|
|
118
|
+
<SelectContent
|
|
119
|
+
class="cfasim-listbox-content select-content"
|
|
120
|
+
position="popper"
|
|
121
|
+
:side-offset="4"
|
|
122
|
+
:body-lock="false"
|
|
123
|
+
>
|
|
124
|
+
<SelectViewport class="cfasim-listbox-viewport">
|
|
125
|
+
<SelectItem
|
|
126
|
+
v-for="opt in options"
|
|
127
|
+
:key="opt.value"
|
|
128
|
+
:value="opt.value"
|
|
129
|
+
class="cfasim-listbox-item"
|
|
130
|
+
>
|
|
131
|
+
<SelectItemText>{{ opt.label }}</SelectItemText>
|
|
132
|
+
<SelectItemIndicator class="cfasim-listbox-indicator">
|
|
133
|
+
<Icon icon="check" :size="14" />
|
|
134
|
+
</SelectItemIndicator>
|
|
135
|
+
</SelectItem>
|
|
136
|
+
</SelectViewport>
|
|
137
|
+
</SelectContent>
|
|
138
|
+
</SelectPortal>
|
|
139
|
+
</SelectRoot>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<style scoped>
|
|
144
|
+
.select-box {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
gap: 0.25em;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.select-label {
|
|
151
|
+
font-size: var(--font-size-sm);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.select-trigger {
|
|
155
|
+
display: inline-flex;
|
|
156
|
+
align-items: center;
|
|
157
|
+
justify-content: space-between;
|
|
158
|
+
gap: 0.5em;
|
|
159
|
+
font-size: var(--font-size-sm);
|
|
160
|
+
height: 2.5em;
|
|
161
|
+
padding: 0 0.75em;
|
|
162
|
+
border: 1px solid var(--color-border);
|
|
163
|
+
border-radius: 0.375em;
|
|
164
|
+
background: var(--color-bg-0);
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
width: auto;
|
|
167
|
+
font-family: inherit;
|
|
168
|
+
color: inherit;
|
|
169
|
+
line-height: 1.4;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.select-trigger:hover {
|
|
173
|
+
border-color: var(--color-border-hover);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.select-trigger:focus-visible {
|
|
177
|
+
outline: 2px solid var(--color-primary);
|
|
178
|
+
outline-offset: -1px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.select-trigger[data-placeholder] {
|
|
182
|
+
color: var(--color-text-secondary);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Autocomplete (combobox) field */
|
|
186
|
+
.select-anchor {
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
gap: 0.5em;
|
|
190
|
+
height: 2.5em;
|
|
191
|
+
padding: 0 0.5em 0 0.75em;
|
|
192
|
+
font-size: var(--font-size-sm);
|
|
193
|
+
background: var(--color-bg-0);
|
|
194
|
+
border: 1px solid var(--color-border);
|
|
195
|
+
border-radius: 0.375em;
|
|
196
|
+
transition:
|
|
197
|
+
border-color var(--transition-fast),
|
|
198
|
+
box-shadow var(--transition-fast);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.select-anchor:hover {
|
|
202
|
+
border-color: var(--color-border-hover);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.select-anchor:focus-within {
|
|
206
|
+
outline: none;
|
|
207
|
+
border-color: var(--color-border-focus);
|
|
208
|
+
box-shadow: var(--shadow-focus);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.select-input {
|
|
212
|
+
flex: 1;
|
|
213
|
+
min-width: 0;
|
|
214
|
+
height: 100%;
|
|
215
|
+
padding: 0;
|
|
216
|
+
border: none;
|
|
217
|
+
background: none;
|
|
218
|
+
color: inherit;
|
|
219
|
+
font: inherit;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.select-input:focus {
|
|
223
|
+
outline: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.select-input::placeholder {
|
|
227
|
+
color: var(--color-text-secondary);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.select-icon-button {
|
|
231
|
+
display: inline-flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
flex-shrink: 0;
|
|
234
|
+
padding: 0;
|
|
235
|
+
border: none;
|
|
236
|
+
background: none;
|
|
237
|
+
color: inherit;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.select-icon {
|
|
242
|
+
display: flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
flex-shrink: 0;
|
|
245
|
+
}
|
|
246
|
+
</style>
|
|
247
|
+
|
|
248
|
+
<style>
|
|
249
|
+
/* Sizing only — the dropdown skin lives in _internal/listbox.css. */
|
|
250
|
+
.select-content {
|
|
251
|
+
min-width: var(--reka-select-trigger-width);
|
|
252
|
+
max-height: var(--reka-select-content-available-height);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Combobox exposes the anchor width under its own custom property. */
|
|
256
|
+
.select-content-autocomplete {
|
|
257
|
+
width: var(--reka-combobox-trigger-width);
|
|
258
|
+
max-height: var(--reka-combobox-content-available-height);
|
|
259
|
+
}
|
|
260
|
+
</style>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# SidebarLayout
|
|
2
|
+
|
|
3
|
+
A responsive two-panel layout with a collapsible sidebar and main content area. On mobile, the sidebar becomes an overlay.
|
|
4
|
+
|
|
5
|
+
When the sidebar is collapsed it is marked [`inert`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inert), so its contents (including the collapse button) are removed from the tab order and the accessibility tree while visually hidden. The expand button is likewise `inert` while the sidebar is open. This keeps keyboard and screen-reader focus on whichever control is actually visible.
|
|
6
|
+
|
|
7
|
+
## Demo
|
|
8
|
+
|
|
9
|
+
<a href="/cfa-simulator/docs/demos/sidebar-layout/index.html" target="_blank">Open in full window ↗</a>
|
|
10
|
+
|
|
11
|
+
<div style="border: 1px solid var(--vp-c-border); border-radius: 8px; overflow: hidden; height: 500px;">
|
|
12
|
+
<iframe src="/cfa-simulator/docs/demos/sidebar-layout/index.html" style="width: 100%; height: 100%; border: none;" />
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
## Tabs Demo (Router Mode)
|
|
16
|
+
|
|
17
|
+
<a href="/cfa-simulator/docs/demos/sidebar-tabs/index.html" target="_blank">Open in full window ↗</a>
|
|
18
|
+
|
|
19
|
+
<div style="border: 1px solid var(--vp-c-border); border-radius: 8px; overflow: hidden; height: 500px;">
|
|
20
|
+
<iframe src="/cfa-simulator/docs/demos/sidebar-tabs/index.html" style="width: 100%; height: 100%; border: none;" />
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```vue
|
|
26
|
+
<SidebarLayout>
|
|
27
|
+
<template #sidebar>
|
|
28
|
+
<h2>Controls</h2>
|
|
29
|
+
<NumberInput v-model="value" label="Parameter" slider live />
|
|
30
|
+
</template>
|
|
31
|
+
<h1>Main Content</h1>
|
|
32
|
+
<p>Your charts and data go here.</p>
|
|
33
|
+
</SidebarLayout>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Slots
|
|
37
|
+
|
|
38
|
+
| Slot | Description |
|
|
39
|
+
| --------- | ------------------------------------------ |
|
|
40
|
+
| `sidebar` | Content rendered in the left sidebar panel |
|
|
41
|
+
| `default` | Main content area |
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
| Prop | Type | Default | Description |
|
|
46
|
+
| ------------- | --------- | ----------- | ---------------------------------------------------- |
|
|
47
|
+
| `hideTopbar` | `boolean` | `false` | Hides the topbar that contains the light/dark toggle |
|
|
48
|
+
| `tabs` | `Tab[]` | `undefined` | Array of tab definitions to render in the main area |
|
|
49
|
+
| `v-model:tab` | `string` | `undefined` | The active tab value (two-way binding) |
|
|
50
|
+
|
|
51
|
+
### Tab type
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
interface Tab {
|
|
55
|
+
value: string; // unique identifier
|
|
56
|
+
label: string; // display text
|
|
57
|
+
to?: string; // optional route path for vue-router integration
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Tabs
|
|
62
|
+
|
|
63
|
+
When the `tabs` prop is provided, a tab bar renders at the top of the main content area. Tabs support two modes:
|
|
64
|
+
|
|
65
|
+
### Local mode
|
|
66
|
+
|
|
67
|
+
Use `v-model:tab` to control which tab is active. Render content conditionally in the default slot.
|
|
68
|
+
|
|
69
|
+
```vue
|
|
70
|
+
<script setup>
|
|
71
|
+
import { ref } from "vue";
|
|
72
|
+
const activeTab = ref("chart");
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<SidebarLayout
|
|
76
|
+
v-model:tab="activeTab"
|
|
77
|
+
:tabs="[
|
|
78
|
+
{ value: 'chart', label: 'Chart' },
|
|
79
|
+
{ value: 'data', label: 'Data' },
|
|
80
|
+
]"
|
|
81
|
+
>
|
|
82
|
+
<template #sidebar>
|
|
83
|
+
<h2>Controls</h2>
|
|
84
|
+
</template>
|
|
85
|
+
<div v-if="activeTab === 'chart'">Chart content</div>
|
|
86
|
+
<div v-if="activeTab === 'data'">Data table</div>
|
|
87
|
+
</SidebarLayout>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Router mode
|
|
91
|
+
|
|
92
|
+
When tabs include a `to` property and vue-router is installed, clicking a tab navigates to that route. The active tab is automatically determined from the current route.
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<SidebarLayout
|
|
96
|
+
:tabs="[
|
|
97
|
+
{ value: 'chart', label: 'Chart', to: '/model/chart' },
|
|
98
|
+
{ value: 'data', label: 'Data', to: '/model/data' },
|
|
99
|
+
]"
|
|
100
|
+
>
|
|
101
|
+
<template #sidebar>
|
|
102
|
+
<h2>Controls</h2>
|
|
103
|
+
</template>
|
|
104
|
+
<RouterView />
|
|
105
|
+
</SidebarLayout>
|
|
106
|
+
```
|