@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,68 @@
|
|
|
1
|
+
# Toggle
|
|
2
|
+
|
|
3
|
+
A boolean switch built on reka-ui.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
<script setup>
|
|
8
|
+
import { ref } from 'vue'
|
|
9
|
+
const enabled = ref(false)
|
|
10
|
+
const disabled = ref(true)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
### Basic
|
|
14
|
+
|
|
15
|
+
<ComponentDemo>
|
|
16
|
+
<Toggle v-model="enabled" label="Enable vaccination" />
|
|
17
|
+
|
|
18
|
+
<template #code>
|
|
19
|
+
|
|
20
|
+
```vue
|
|
21
|
+
<script setup>
|
|
22
|
+
import { ref } from "vue";
|
|
23
|
+
const enabled = ref(false);
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Toggle v-model="enabled" label="Enable vaccination" />
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
</template>
|
|
30
|
+
</ComponentDemo>
|
|
31
|
+
|
|
32
|
+
### With hint
|
|
33
|
+
|
|
34
|
+
<ComponentDemo>
|
|
35
|
+
<Toggle
|
|
36
|
+
v-model="enabled"
|
|
37
|
+
label="Enable isolation"
|
|
38
|
+
hint="Whether symptomatic individuals are isolated"
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<template #code>
|
|
42
|
+
|
|
43
|
+
```vue
|
|
44
|
+
<Toggle
|
|
45
|
+
v-model="enabled"
|
|
46
|
+
label="Enable isolation"
|
|
47
|
+
hint="Whether symptomatic individuals are isolated"
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
</template>
|
|
52
|
+
</ComponentDemo>
|
|
53
|
+
|
|
54
|
+
### Disabled
|
|
55
|
+
|
|
56
|
+
<ComponentDemo>
|
|
57
|
+
<Toggle v-model="disabled" label="Locked setting" :disabled="true" />
|
|
58
|
+
|
|
59
|
+
<template #code>
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<Toggle v-model="value" label="Locked setting" :disabled="true" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
</template>
|
|
66
|
+
</ComponentDemo>
|
|
67
|
+
|
|
68
|
+
<!--@include: ./_api/toggle.md-->
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { SwitchRoot, SwitchThumb, useId } from "reka-ui";
|
|
3
|
+
import Hint from "../Hint/Hint.vue";
|
|
4
|
+
|
|
5
|
+
const model = defineModel<boolean>();
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
label: string;
|
|
9
|
+
hint?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}>();
|
|
12
|
+
|
|
13
|
+
const id = useId();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div class="toggle">
|
|
18
|
+
<label :for="id">{{ label }}</label>
|
|
19
|
+
<Hint v-if="props.hint" :text="props.hint" />
|
|
20
|
+
<SwitchRoot :id="id" v-model="model" :disabled="disabled" class="switch">
|
|
21
|
+
<SwitchThumb class="thumb" />
|
|
22
|
+
</SwitchRoot>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<style scoped>
|
|
27
|
+
.toggle {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 0.5em;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.toggle label {
|
|
34
|
+
user-select: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.switch {
|
|
38
|
+
--switch-w: 2.25em;
|
|
39
|
+
--switch-h: 1.25em;
|
|
40
|
+
--thumb-size: 1em;
|
|
41
|
+
--thumb-offset: 0.125em;
|
|
42
|
+
|
|
43
|
+
position: relative;
|
|
44
|
+
width: var(--switch-w);
|
|
45
|
+
height: var(--switch-h);
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
background: var(--color-border-hover);
|
|
48
|
+
border-radius: var(--switch-h);
|
|
49
|
+
border: none;
|
|
50
|
+
padding: 0;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
transition: background 0.15s;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.switch[data-state="checked"] {
|
|
56
|
+
background: var(--color-primary);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.switch[data-disabled] {
|
|
60
|
+
opacity: 0.5;
|
|
61
|
+
cursor: not-allowed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.thumb {
|
|
65
|
+
display: block;
|
|
66
|
+
width: var(--thumb-size);
|
|
67
|
+
height: var(--thumb-size);
|
|
68
|
+
background: white;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
position: absolute;
|
|
71
|
+
top: var(--thumb-offset);
|
|
72
|
+
left: var(--thumb-offset);
|
|
73
|
+
transition: transform 0.15s;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.switch[data-state="checked"] .thumb {
|
|
77
|
+
transform: translateX(
|
|
78
|
+
calc(var(--switch-w) - var(--thumb-size) - var(--thumb-offset) * 2)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
keywords:
|
|
3
|
+
[
|
|
4
|
+
toggle group,
|
|
5
|
+
segmented control,
|
|
6
|
+
single select,
|
|
7
|
+
multi select,
|
|
8
|
+
button group,
|
|
9
|
+
radio,
|
|
10
|
+
pressed,
|
|
11
|
+
]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# ToggleGroup
|
|
15
|
+
|
|
16
|
+
A segmented control that looks like a [`ButtonGroup`](./button-group) but tracks
|
|
17
|
+
which item(s) are pressed. By default it is single-select (like a radio group);
|
|
18
|
+
add `multiple` to let several items be pressed at once. Built on
|
|
19
|
+
[reka-ui's ToggleGroup](https://reka-ui.com/docs/components/toggle-group).
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
### Single select
|
|
24
|
+
|
|
25
|
+
`v-model` holds the pressed value (a `string`). Clicking the active item again
|
|
26
|
+
clears the selection.
|
|
27
|
+
|
|
28
|
+
<script setup>
|
|
29
|
+
import { ref } from 'vue'
|
|
30
|
+
const interval = ref('weekly')
|
|
31
|
+
const days = ref(['mon', 'wed'])
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<ComponentDemo>
|
|
35
|
+
<ToggleGroup
|
|
36
|
+
v-model="interval"
|
|
37
|
+
label="Interval"
|
|
38
|
+
:options="[
|
|
39
|
+
{ value: 'daily', label: 'Daily' },
|
|
40
|
+
{ value: 'weekly', label: 'Weekly' },
|
|
41
|
+
{ value: 'monthly', label: 'Monthly' },
|
|
42
|
+
]"
|
|
43
|
+
/>
|
|
44
|
+
<p>Selected: {{ interval }}</p>
|
|
45
|
+
|
|
46
|
+
<template #code>
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<script setup>
|
|
50
|
+
import { ref } from "vue";
|
|
51
|
+
const interval = ref("weekly");
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<ToggleGroup
|
|
55
|
+
v-model="interval"
|
|
56
|
+
label="Interval"
|
|
57
|
+
:options="[
|
|
58
|
+
{ value: 'daily', label: 'Daily' },
|
|
59
|
+
{ value: 'weekly', label: 'Weekly' },
|
|
60
|
+
{ value: 'monthly', label: 'Monthly' },
|
|
61
|
+
]"
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
</template>
|
|
66
|
+
</ComponentDemo>
|
|
67
|
+
|
|
68
|
+
### Multiple select
|
|
69
|
+
|
|
70
|
+
With `multiple`, `v-model` is a `string[]` and any number of items can be
|
|
71
|
+
pressed at once.
|
|
72
|
+
|
|
73
|
+
<ComponentDemo>
|
|
74
|
+
<ToggleGroup
|
|
75
|
+
v-model="days"
|
|
76
|
+
multiple
|
|
77
|
+
label="Active days"
|
|
78
|
+
:options="[
|
|
79
|
+
{ value: 'mon', label: 'Mon' },
|
|
80
|
+
{ value: 'tue', label: 'Tue' },
|
|
81
|
+
{ value: 'wed', label: 'Wed' },
|
|
82
|
+
{ value: 'thu', label: 'Thu' },
|
|
83
|
+
{ value: 'fri', label: 'Fri' },
|
|
84
|
+
]"
|
|
85
|
+
/>
|
|
86
|
+
<p>Selected: {{ days.join(', ') || 'none' }}</p>
|
|
87
|
+
|
|
88
|
+
<template #code>
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<script setup>
|
|
92
|
+
import { ref } from "vue";
|
|
93
|
+
const days = ref(["mon", "wed"]);
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<ToggleGroup
|
|
97
|
+
v-model="days"
|
|
98
|
+
multiple
|
|
99
|
+
label="Active days"
|
|
100
|
+
:options="[
|
|
101
|
+
{ value: 'mon', label: 'Mon' },
|
|
102
|
+
{ value: 'tue', label: 'Tue' },
|
|
103
|
+
{ value: 'wed', label: 'Wed' },
|
|
104
|
+
{ value: 'thu', label: 'Thu' },
|
|
105
|
+
{ value: 'fri', label: 'Fri' },
|
|
106
|
+
]"
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
</template>
|
|
111
|
+
</ComponentDemo>
|
|
112
|
+
|
|
113
|
+
### Vertical and disabled options
|
|
114
|
+
|
|
115
|
+
Set `orientation="vertical"` to stack the items, and mark individual options
|
|
116
|
+
`disabled`.
|
|
117
|
+
|
|
118
|
+
<ComponentDemo>
|
|
119
|
+
<ToggleGroup
|
|
120
|
+
model-value="map"
|
|
121
|
+
orientation="vertical"
|
|
122
|
+
aria-label="View"
|
|
123
|
+
:options="[
|
|
124
|
+
{ value: 'map', label: 'Map' },
|
|
125
|
+
{ value: 'chart', label: 'Chart' },
|
|
126
|
+
{ value: 'table', label: 'Table', disabled: true },
|
|
127
|
+
]"
|
|
128
|
+
/>
|
|
129
|
+
|
|
130
|
+
<template #code>
|
|
131
|
+
|
|
132
|
+
```vue
|
|
133
|
+
<ToggleGroup
|
|
134
|
+
v-model="view"
|
|
135
|
+
orientation="vertical"
|
|
136
|
+
aria-label="View"
|
|
137
|
+
:options="[
|
|
138
|
+
{ value: 'map', label: 'Map' },
|
|
139
|
+
{ value: 'chart', label: 'Chart' },
|
|
140
|
+
{ value: 'table', label: 'Table', disabled: true },
|
|
141
|
+
]"
|
|
142
|
+
/>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
</template>
|
|
146
|
+
</ComponentDemo>
|
|
147
|
+
|
|
148
|
+
<!--@include: ./_api/toggle-group.md-->
|
|
149
|
+
|
|
150
|
+
### ToggleGroupOption
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
interface ToggleGroupOption {
|
|
154
|
+
value: string;
|
|
155
|
+
label: string;
|
|
156
|
+
disabled?: boolean;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ToggleGroupItem, ToggleGroupRoot } from "reka-ui";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
import FieldLabel from "../_internal/FieldLabel.vue";
|
|
5
|
+
import { useField, type FieldProps } from "../_internal/field";
|
|
6
|
+
|
|
7
|
+
export interface ToggleGroupOption {
|
|
8
|
+
value: string;
|
|
9
|
+
label: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// `string` in single mode, `string[]` in multiple mode.
|
|
14
|
+
const model = defineModel<string | string[]>();
|
|
15
|
+
|
|
16
|
+
interface Props extends FieldProps {
|
|
17
|
+
options: ToggleGroupOption[];
|
|
18
|
+
multiple?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
orientation?: "horizontal" | "vertical";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
24
|
+
orientation: "horizontal",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const { labelId, ariaProps } = useField(props);
|
|
28
|
+
const type = computed<"single" | "multiple">(() =>
|
|
29
|
+
props.multiple ? "multiple" : "single",
|
|
30
|
+
);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<div class="toggle-group-field">
|
|
35
|
+
<FieldLabel
|
|
36
|
+
class="toggle-group-label"
|
|
37
|
+
:label="label"
|
|
38
|
+
:label-id="labelId"
|
|
39
|
+
:hide-label="hideLabel"
|
|
40
|
+
:hint="hint"
|
|
41
|
+
/>
|
|
42
|
+
<ToggleGroupRoot
|
|
43
|
+
v-model="model"
|
|
44
|
+
:type="type"
|
|
45
|
+
:disabled="disabled"
|
|
46
|
+
:orientation="orientation"
|
|
47
|
+
:class="['toggle-group', `toggle-group--${orientation}`]"
|
|
48
|
+
v-bind="ariaProps"
|
|
49
|
+
>
|
|
50
|
+
<ToggleGroupItem
|
|
51
|
+
v-for="opt in options"
|
|
52
|
+
:key="opt.value"
|
|
53
|
+
:value="opt.value"
|
|
54
|
+
:disabled="opt.disabled"
|
|
55
|
+
class="toggle-group-item"
|
|
56
|
+
>
|
|
57
|
+
{{ opt.label }}
|
|
58
|
+
</ToggleGroupItem>
|
|
59
|
+
</ToggleGroupRoot>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
.toggle-group-field {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
gap: 0.25em;
|
|
68
|
+
align-items: flex-start;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.toggle-group-label {
|
|
72
|
+
display: flex;
|
|
73
|
+
align-items: center;
|
|
74
|
+
gap: 0.25em;
|
|
75
|
+
font-size: var(--font-size-sm);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.toggle-group {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
/* 1px gaps reveal the group background as dividers between items. */
|
|
81
|
+
gap: 1px;
|
|
82
|
+
background: var(--color-border);
|
|
83
|
+
border: 1px solid var(--color-border);
|
|
84
|
+
border-radius: 0.375em;
|
|
85
|
+
/* Clip items to the rounded outer corners. Focus uses an inset outline
|
|
86
|
+
* (below) so it isn't clipped away. */
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.toggle-group--vertical {
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.toggle-group-item {
|
|
95
|
+
appearance: none;
|
|
96
|
+
display: inline-flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
min-height: 2.5em;
|
|
100
|
+
padding: 0 1em;
|
|
101
|
+
margin: 0;
|
|
102
|
+
border: none;
|
|
103
|
+
background: var(--color-bg-0);
|
|
104
|
+
color: var(--color-text);
|
|
105
|
+
font: inherit;
|
|
106
|
+
font-size: var(--font-size-sm);
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
cursor: pointer;
|
|
110
|
+
transition:
|
|
111
|
+
background var(--transition-fast),
|
|
112
|
+
color var(--transition-fast);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.toggle-group-item:hover:not([data-disabled]) {
|
|
116
|
+
background: var(--color-bg-2);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.toggle-group-item[data-state="on"] {
|
|
120
|
+
background: var(--color-primary);
|
|
121
|
+
color: var(--color-text-on-primary);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.toggle-group-item[data-state="on"]:hover:not([data-disabled]) {
|
|
125
|
+
background: var(--color-primary-hover);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.toggle-group-item:focus-visible {
|
|
129
|
+
outline: 2px solid var(--color-primary);
|
|
130
|
+
outline-offset: -2px;
|
|
131
|
+
z-index: 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.toggle-group-item[data-disabled] {
|
|
135
|
+
opacity: 0.5;
|
|
136
|
+
cursor: not-allowed;
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -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,17 @@
|
|
|
1
|
+
export type GapToken = "none" | "small" | "medium" | "large";
|
|
2
|
+
|
|
3
|
+
const GAP_TOKENS: Record<GapToken, string> = {
|
|
4
|
+
none: "0",
|
|
5
|
+
small: "var(--space-2)",
|
|
6
|
+
medium: "var(--space-4)",
|
|
7
|
+
large: "var(--space-6)",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function resolveGap(
|
|
11
|
+
gap: GapToken | string | undefined,
|
|
12
|
+
fallback: GapToken = "medium",
|
|
13
|
+
): string {
|
|
14
|
+
if (gap == null) return GAP_TOKENS[fallback];
|
|
15
|
+
if (gap in GAP_TOKENS) return GAP_TOKENS[gap as GapToken];
|
|
16
|
+
return gap;
|
|
17
|
+
}
|
|
@@ -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
|
+
}
|
package/src/env.d.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export { default as Box } from "./Box/Box.vue";
|
|
2
|
+
export type { BoxVariant } from "./Box/Box.vue";
|
|
3
|
+
export { default as Button } from "./Button/Button.vue";
|
|
4
|
+
export { default as ButtonGroup } from "./ButtonGroup/ButtonGroup.vue";
|
|
5
|
+
export { default as Container } from "./Container/Container.vue";
|
|
6
|
+
export type { ContainerGap } from "./Container/Container.vue";
|
|
7
|
+
export { default as Expander } from "./Expander/Expander.vue";
|
|
8
|
+
export { default as Grid } from "./Grid/Grid.vue";
|
|
9
|
+
export type { GridCols, GridGap } from "./Grid/Grid.vue";
|
|
10
|
+
export { default as Hint } from "./Hint/Hint.vue";
|
|
11
|
+
export { default as Icon } from "./Icon/Icon.vue";
|
|
12
|
+
export type { IconSize } from "./Icon/Icon.vue";
|
|
13
|
+
export { registerIcons, hasIcon } from "./Icon/registry";
|
|
14
|
+
export type { IconRegistration, IconVariants } from "./Icon/registry";
|
|
15
|
+
export { default as LightDarkToggle } from "./LightDarkToggle/LightDarkToggle.vue";
|
|
16
|
+
export { default as MultiSelect } from "./MultiSelect/MultiSelect.vue";
|
|
17
|
+
export type { MultiSelectOption } from "./MultiSelect/MultiSelect.vue";
|
|
18
|
+
export { default as NumberInput } from "./NumberInput/NumberInput.vue";
|
|
19
|
+
export type { NumberRange } from "./NumberInput/NumberInput.vue";
|
|
20
|
+
export { default as ParamEditor } from "./ParamEditor/ParamEditor.vue";
|
|
21
|
+
export type {
|
|
22
|
+
ParamEditorFormat,
|
|
23
|
+
ParamEditorValue,
|
|
24
|
+
} from "./ParamEditor/ParamEditor.vue";
|
|
25
|
+
export { default as SelectBox } from "./SelectBox/SelectBox.vue";
|
|
26
|
+
export type { SelectOption } from "./SelectBox/SelectBox.vue";
|
|
27
|
+
export { default as SidebarLayout } from "./SidebarLayout/SidebarLayout.vue";
|
|
28
|
+
export type { Tab } from "./SidebarLayout/SidebarLayout.vue";
|
|
29
|
+
export { default as Spinner } from "./Spinner/Spinner.vue";
|
|
30
|
+
export { default as TextInput } from "./TextInput/TextInput.vue";
|
|
31
|
+
export { default as Toggle } from "./Toggle/Toggle.vue";
|
|
32
|
+
export { default as ToggleGroup } from "./ToggleGroup/ToggleGroup.vue";
|
|
33
|
+
export type { ToggleGroupOption } from "./ToggleGroup/ToggleGroup.vue";
|