@sewadah/selaras 0.7.1 → 0.9.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/README.md +1 -1
- package/dist/agent/catalog.json +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/Autocomplete.vue +6 -0
- package/dist/runtime/components/AvatarGroup.vue +3 -2
- package/dist/runtime/components/CodeGroup.vue +3 -15
- package/dist/runtime/components/ColorModeSwitch.vue +5 -4
- package/dist/runtime/components/ColorModeToggle.vue +5 -4
- package/dist/runtime/components/Input.d.vue.ts +7 -4
- package/dist/runtime/components/Input.vue +22 -5
- package/dist/runtime/components/Input.vue.d.ts +7 -4
- package/dist/runtime/components/Listbox.d.vue.ts +28 -0
- package/dist/runtime/components/Listbox.vue +53 -0
- package/dist/runtime/components/Listbox.vue.d.ts +28 -0
- package/dist/runtime/components/Select.vue +6 -0
- package/dist/runtime/components/Textarea.d.vue.ts +7 -4
- package/dist/runtime/components/Textarea.vue +24 -7
- package/dist/runtime/components/Textarea.vue.d.ts +7 -4
- package/dist/runtime/components/TransferList.d.vue.ts +47 -0
- package/dist/runtime/components/TransferList.vue +67 -0
- package/dist/runtime/components/TransferList.vue.d.ts +47 -0
- package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +7 -63
- package/dist/runtime/internal/ComboboxSelectBase.vue +18 -5
- package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +7 -63
- package/dist/runtime/internal/ListSelectionForm.d.vue.ts +15 -0
- package/dist/runtime/internal/ListSelectionForm.vue +33 -0
- package/dist/runtime/internal/ListSelectionForm.vue.d.ts +15 -0
- package/dist/runtime/internal/ListboxBase.d.vue.ts +49 -0
- package/dist/runtime/internal/ListboxBase.vue +177 -0
- package/dist/runtime/internal/ListboxBase.vue.d.ts +49 -0
- package/dist/runtime/internal/ListboxOption.d.vue.ts +31 -0
- package/dist/runtime/internal/ListboxOption.vue +37 -0
- package/dist/runtime/internal/ListboxOption.vue.d.ts +31 -0
- package/dist/runtime/internal/TransferListBase.d.vue.ts +63 -0
- package/dist/runtime/internal/TransferListBase.vue +206 -0
- package/dist/runtime/internal/TransferListBase.vue.d.ts +63 -0
- package/dist/runtime/internal/color-mode-control.d.ts +7 -0
- package/dist/runtime/internal/color-mode-control.js +12 -0
- package/dist/runtime/internal/list-selection.d.ts +15 -0
- package/dist/runtime/internal/list-selection.js +43 -0
- package/dist/runtime/theme/input.d.ts +6 -0
- package/dist/runtime/theme/input.js +2 -0
- package/dist/runtime/theme/listbox.d.ts +131 -0
- package/dist/runtime/theme/listbox.js +27 -0
- package/dist/runtime/theme/select.d.ts +6 -0
- package/dist/runtime/theme/select.js +3 -1
- package/dist/runtime/theme/textarea.d.ts +6 -0
- package/dist/runtime/theme/textarea.js +2 -0
- package/dist/runtime/theme/transfer-list.d.ts +74 -0
- package/dist/runtime/theme/transfer-list.js +21 -0
- package/dist/runtime/theme-config.d.ts +10 -0
- package/dist/runtime/types/index.d.ts +3 -0
- package/dist/runtime/utils/field-adornments.d.ts +14 -0
- package/dist/runtime/utils/field-adornments.js +36 -0
- package/dist/runtime/utils/listbox-contracts.d.ts +31 -0
- package/dist/runtime/utils/listbox-contracts.js +0 -0
- package/dist/runtime/utils/messages.d.ts +7 -0
- package/dist/runtime/utils/messages.js +7 -0
- package/dist/runtime/utils/select-contracts.d.ts +3 -0
- package/dist/runtime/utils/slot-children.d.ts +2 -0
- package/dist/runtime/utils/slot-children.js +15 -0
- package/dist/runtime/utils/transfer-list-contracts.d.ts +48 -0
- package/dist/runtime/utils/transfer-list-contracts.js +0 -0
- package/package.json +5 -4
- package/skills/selaras/SKILL.md +7 -7
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { computed, onMounted, ref } from "vue";
|
|
2
|
+
import { useColorMode } from "#imports";
|
|
3
|
+
export function useColorModeControl() {
|
|
4
|
+
const colorMode = useColorMode();
|
|
5
|
+
const mounted = ref(false);
|
|
6
|
+
onMounted(() => mounted.value = true);
|
|
7
|
+
return {
|
|
8
|
+
colorMode,
|
|
9
|
+
isDark: computed(() => mounted.value && colorMode.value === "dark"),
|
|
10
|
+
isDisabled: computed(() => !mounted.value || colorMode.unknown || colorMode.forced)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SelectModelValue } from '../utils/select-contracts.js';
|
|
2
|
+
export type ListSelection = SelectModelValue | SelectModelValue[] | undefined;
|
|
3
|
+
export declare function selectionForMode(value: ListSelection, multiple: boolean): ListSelection;
|
|
4
|
+
export declare function assertUniqueSelection(value: ListSelection): void;
|
|
5
|
+
export declare function useListSelection(props: {
|
|
6
|
+
modelValue?: ListSelection;
|
|
7
|
+
defaultValue?: ListSelection;
|
|
8
|
+
multiple?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}, emit: (value: ListSelection) => void): {
|
|
11
|
+
selection: import("vue").ComputedRef<string | number | object | undefined>;
|
|
12
|
+
values: import("vue").ComputedRef<SelectModelValue[]>;
|
|
13
|
+
update: (value: ListSelection) => void;
|
|
14
|
+
reset: () => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { computed, getCurrentInstance, ref, watch } from "vue";
|
|
2
|
+
export function selectionForMode(value, multiple) {
|
|
3
|
+
return multiple ? value === void 0 ? [] : Array.isArray(value) ? [...value] : [value] : Array.isArray(value) ? value[0] : value;
|
|
4
|
+
}
|
|
5
|
+
export function assertUniqueSelection(value) {
|
|
6
|
+
if (Array.isArray(value) && new Set(value).size !== value.length)
|
|
7
|
+
throw new TypeError("[selaras] List selection values must be unique.");
|
|
8
|
+
}
|
|
9
|
+
export function useListSelection(props, emit) {
|
|
10
|
+
const instance = getCurrentInstance();
|
|
11
|
+
const controlled = () => Object.hasOwn(instance.vnode.props ?? {}, "modelValue") || Object.hasOwn(instance.vnode.props ?? {}, "model-value");
|
|
12
|
+
const initial = Array.isArray(props.defaultValue) ? [...props.defaultValue] : props.defaultValue;
|
|
13
|
+
assertUniqueSelection(initial);
|
|
14
|
+
const local = ref(selectionForMode(initial, !!props.multiple));
|
|
15
|
+
const selection = computed(() => {
|
|
16
|
+
const value = controlled() ? props.modelValue : local.value;
|
|
17
|
+
if (controlled() && value !== void 0 && Array.isArray(value) !== !!props.multiple)
|
|
18
|
+
throw new TypeError("[selaras] List modelValue must be an array in multiple mode and a scalar in single mode.");
|
|
19
|
+
assertUniqueSelection(value);
|
|
20
|
+
return value;
|
|
21
|
+
});
|
|
22
|
+
void selection.value;
|
|
23
|
+
function update(value) {
|
|
24
|
+
if (props.disabled)
|
|
25
|
+
return;
|
|
26
|
+
assertUniqueSelection(value);
|
|
27
|
+
if (!controlled())
|
|
28
|
+
local.value = value;
|
|
29
|
+
emit(value);
|
|
30
|
+
}
|
|
31
|
+
watch(() => props.multiple, (multiple) => {
|
|
32
|
+
if (!controlled())
|
|
33
|
+
update(selectionForMode(local.value, !!multiple));
|
|
34
|
+
});
|
|
35
|
+
function reset() {
|
|
36
|
+
const value = selectionForMode(initial, !!props.multiple);
|
|
37
|
+
if (!controlled())
|
|
38
|
+
local.value = value;
|
|
39
|
+
emit(value);
|
|
40
|
+
}
|
|
41
|
+
const values = computed(() => selection.value === void 0 ? [] : Array.isArray(selection.value) ? selection.value : [selection.value]);
|
|
42
|
+
return { selection, values, update, reset };
|
|
43
|
+
}
|
|
@@ -42,6 +42,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
|
|
|
42
42
|
}, {
|
|
43
43
|
root: string;
|
|
44
44
|
base: string;
|
|
45
|
+
leading: string;
|
|
46
|
+
trailing: string;
|
|
45
47
|
leadingIcon: string;
|
|
46
48
|
trailingIcon: string;
|
|
47
49
|
clear: string;
|
|
@@ -89,6 +91,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
|
|
|
89
91
|
}, {
|
|
90
92
|
root: string;
|
|
91
93
|
base: string;
|
|
94
|
+
leading: string;
|
|
95
|
+
trailing: string;
|
|
92
96
|
leadingIcon: string;
|
|
93
97
|
trailingIcon: string;
|
|
94
98
|
clear: string;
|
|
@@ -136,6 +140,8 @@ export declare const inputTheme: import("tailwind-variants").TVReturnType<{
|
|
|
136
140
|
}, {
|
|
137
141
|
root: string;
|
|
138
142
|
base: string;
|
|
143
|
+
leading: string;
|
|
144
|
+
trailing: string;
|
|
139
145
|
leadingIcon: string;
|
|
140
146
|
trailingIcon: string;
|
|
141
147
|
clear: string;
|
|
@@ -10,6 +10,8 @@ export const inputTheme = tv({
|
|
|
10
10
|
// hardcoded here - hover's own color stays neutral regardless of
|
|
11
11
|
// `color`, matching every other component's own hover state.
|
|
12
12
|
base: "w-full rounded-[var(--selaras-resolved-radius-md)] bg-[var(--selaras-resolved-surface-default)] text-[var(--selaras-resolved-text-default)] ring-1 ring-inset ring-[var(--selaras-resolved-border-default)] placeholder:text-[var(--selaras-resolved-text-muted)] outline-none transition-[color,background-color,box-shadow] not-focus:hover:ring-[var(--selaras-resolved-border-hover)] hover:bg-[var(--selaras-resolved-surface-elevated)] focus:ring-2 focus:ring-[var(--_selaras-color-focus)] disabled:opacity-50 disabled:pointer-events-none",
|
|
13
|
+
leading: "absolute top-1/2 -translate-y-1/2 start-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
14
|
+
trailing: "absolute top-1/2 -translate-y-1/2 end-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
13
15
|
leadingIcon: "absolute start-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
|
|
14
16
|
trailingIcon: "absolute end-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
|
|
15
17
|
// A separate slot from trailingIcon (not reused) - that one is a
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export declare const listboxTheme: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
size: {
|
|
3
|
+
sm: {
|
|
4
|
+
item: string;
|
|
5
|
+
};
|
|
6
|
+
md: {
|
|
7
|
+
item: string;
|
|
8
|
+
};
|
|
9
|
+
lg: {
|
|
10
|
+
item: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
color: {
|
|
14
|
+
primary: "";
|
|
15
|
+
secondary: "";
|
|
16
|
+
neutral: "";
|
|
17
|
+
success: "";
|
|
18
|
+
danger: "";
|
|
19
|
+
info: "";
|
|
20
|
+
warning: "";
|
|
21
|
+
};
|
|
22
|
+
invalid: {
|
|
23
|
+
true: {
|
|
24
|
+
root: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
}, {
|
|
28
|
+
root: string;
|
|
29
|
+
header: string;
|
|
30
|
+
footer: string;
|
|
31
|
+
searchWrapper: string;
|
|
32
|
+
searchInput: string;
|
|
33
|
+
content: string;
|
|
34
|
+
group: string;
|
|
35
|
+
groupLabel: string;
|
|
36
|
+
item: string;
|
|
37
|
+
label: string;
|
|
38
|
+
description: string;
|
|
39
|
+
itemContent: string;
|
|
40
|
+
icon: string;
|
|
41
|
+
indicator: string;
|
|
42
|
+
checkbox: string;
|
|
43
|
+
empty: string;
|
|
44
|
+
}, undefined, {
|
|
45
|
+
size: {
|
|
46
|
+
sm: {
|
|
47
|
+
item: string;
|
|
48
|
+
};
|
|
49
|
+
md: {
|
|
50
|
+
item: string;
|
|
51
|
+
};
|
|
52
|
+
lg: {
|
|
53
|
+
item: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
color: {
|
|
57
|
+
primary: "";
|
|
58
|
+
secondary: "";
|
|
59
|
+
neutral: "";
|
|
60
|
+
success: "";
|
|
61
|
+
danger: "";
|
|
62
|
+
info: "";
|
|
63
|
+
warning: "";
|
|
64
|
+
};
|
|
65
|
+
invalid: {
|
|
66
|
+
true: {
|
|
67
|
+
root: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
}, {
|
|
71
|
+
root: string;
|
|
72
|
+
header: string;
|
|
73
|
+
footer: string;
|
|
74
|
+
searchWrapper: string;
|
|
75
|
+
searchInput: string;
|
|
76
|
+
content: string;
|
|
77
|
+
group: string;
|
|
78
|
+
groupLabel: string;
|
|
79
|
+
item: string;
|
|
80
|
+
label: string;
|
|
81
|
+
description: string;
|
|
82
|
+
itemContent: string;
|
|
83
|
+
icon: string;
|
|
84
|
+
indicator: string;
|
|
85
|
+
checkbox: string;
|
|
86
|
+
empty: string;
|
|
87
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
88
|
+
size: {
|
|
89
|
+
sm: {
|
|
90
|
+
item: string;
|
|
91
|
+
};
|
|
92
|
+
md: {
|
|
93
|
+
item: string;
|
|
94
|
+
};
|
|
95
|
+
lg: {
|
|
96
|
+
item: string;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
color: {
|
|
100
|
+
primary: "";
|
|
101
|
+
secondary: "";
|
|
102
|
+
neutral: "";
|
|
103
|
+
success: "";
|
|
104
|
+
danger: "";
|
|
105
|
+
info: "";
|
|
106
|
+
warning: "";
|
|
107
|
+
};
|
|
108
|
+
invalid: {
|
|
109
|
+
true: {
|
|
110
|
+
root: string;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
}, {
|
|
114
|
+
root: string;
|
|
115
|
+
header: string;
|
|
116
|
+
footer: string;
|
|
117
|
+
searchWrapper: string;
|
|
118
|
+
searchInput: string;
|
|
119
|
+
content: string;
|
|
120
|
+
group: string;
|
|
121
|
+
groupLabel: string;
|
|
122
|
+
item: string;
|
|
123
|
+
label: string;
|
|
124
|
+
description: string;
|
|
125
|
+
itemContent: string;
|
|
126
|
+
icon: string;
|
|
127
|
+
indicator: string;
|
|
128
|
+
checkbox: string;
|
|
129
|
+
empty: string;
|
|
130
|
+
}>>;
|
|
131
|
+
export type ListboxThemeSlots = keyof (typeof listboxTheme)['slots'];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { tv } from "tailwind-variants";
|
|
2
|
+
export const listboxTheme = tv({
|
|
3
|
+
slots: {
|
|
4
|
+
root: "flex min-h-0 min-w-0 flex-col overflow-hidden rounded-[var(--selaras-resolved-radius-md)] border border-[var(--selaras-resolved-border-default)] bg-[var(--selaras-resolved-surface-default)] text-[var(--selaras-resolved-text-default)]",
|
|
5
|
+
header: "flex items-center justify-between gap-2 border-b border-[var(--selaras-resolved-border-default)] px-3 py-2",
|
|
6
|
+
footer: "border-t border-[var(--selaras-resolved-border-default)] px-3 py-2",
|
|
7
|
+
searchWrapper: "flex items-center gap-2 border-b border-[var(--selaras-resolved-border-default)] px-3",
|
|
8
|
+
searchInput: "h-10 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-[var(--selaras-resolved-text-muted)]",
|
|
9
|
+
content: "max-h-72 min-h-0 overflow-y-auto p-1 outline-none focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--_selaras-color-focus)]",
|
|
10
|
+
group: "",
|
|
11
|
+
groupLabel: "px-2 py-2 text-xs font-medium text-[var(--selaras-resolved-text-muted)]",
|
|
12
|
+
item: "my-0.5 flex w-full min-w-0 items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] px-2 py-2 outline-none cursor-pointer select-none transition-colors data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[state=checked]:bg-[var(--_selaras-color-subtle)] data-[state=checked]:text-[var(--_selaras-color-on-subtle)] data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
13
|
+
label: "truncate",
|
|
14
|
+
description: "text-xs text-[var(--selaras-resolved-text-muted)]",
|
|
15
|
+
itemContent: "flex min-w-0 flex-1 flex-col gap-0.5",
|
|
16
|
+
icon: "size-4 shrink-0",
|
|
17
|
+
indicator: "ms-auto flex size-4 shrink-0 items-center justify-center text-[var(--_selaras-color-indicator)]",
|
|
18
|
+
checkbox: "pointer-events-none flex size-4 shrink-0 items-center justify-center rounded-[var(--selaras-resolved-radius-sm)] border border-[var(--selaras-resolved-border-default)] data-[state=checked]:border-[var(--_selaras-color-fill)] data-[state=checked]:bg-[var(--_selaras-color-fill)] data-[state=checked]:text-[var(--_selaras-color-on-fill)]",
|
|
19
|
+
empty: "px-3 py-6 text-center text-sm text-[var(--selaras-resolved-text-muted)]"
|
|
20
|
+
},
|
|
21
|
+
variants: {
|
|
22
|
+
size: { sm: { item: "py-1.5 text-xs" }, md: { item: "text-sm" }, lg: { item: "py-2.5 text-base" } },
|
|
23
|
+
color: { primary: "", secondary: "", neutral: "", success: "", danger: "", info: "", warning: "" },
|
|
24
|
+
invalid: { true: { root: "border-[var(--_selaras-color-border)]" } }
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: { size: "md", color: "primary" }
|
|
27
|
+
});
|
|
@@ -29,6 +29,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
|
|
|
29
29
|
}, {
|
|
30
30
|
root: string;
|
|
31
31
|
trigger: string;
|
|
32
|
+
leading: string;
|
|
33
|
+
trailing: string;
|
|
32
34
|
value: string;
|
|
33
35
|
chipOverflow: string;
|
|
34
36
|
icon: string;
|
|
@@ -76,6 +78,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
|
|
|
76
78
|
}, {
|
|
77
79
|
root: string;
|
|
78
80
|
trigger: string;
|
|
81
|
+
leading: string;
|
|
82
|
+
trailing: string;
|
|
79
83
|
value: string;
|
|
80
84
|
chipOverflow: string;
|
|
81
85
|
icon: string;
|
|
@@ -123,6 +127,8 @@ export declare const selectTheme: import("tailwind-variants").TVReturnType<{
|
|
|
123
127
|
}, {
|
|
124
128
|
root: string;
|
|
125
129
|
trigger: string;
|
|
130
|
+
leading: string;
|
|
131
|
+
trailing: string;
|
|
126
132
|
value: string;
|
|
127
133
|
chipOverflow: string;
|
|
128
134
|
icon: string;
|
|
@@ -8,6 +8,8 @@ export const selectTheme = tv({
|
|
|
8
8
|
// let the plain gray hover ring beat the primary one in the cascade,
|
|
9
9
|
// since they're all equal-specificity pseudo-class/attribute selectors.
|
|
10
10
|
trigger: "inline-flex w-full min-h-10 flex-wrap items-center gap-1.5 rounded-[var(--selaras-resolved-radius-md)] bg-[var(--selaras-resolved-surface-default)] px-3 py-1.5 text-sm ring-1 ring-inset ring-[var(--selaras-resolved-border-default)] outline-none transition-[color,background-color,box-shadow] not-focus:not-focus-within:not-data-[state=open]:hover:ring-[var(--selaras-resolved-border-hover)] hover:bg-[var(--selaras-resolved-surface-elevated)] focus:ring-2 focus:ring-[var(--_selaras-color-focus)] focus-within:ring-2 focus-within:ring-[var(--_selaras-color-focus)] data-[state=open]:ring-2 data-[state=open]:ring-[var(--_selaras-color-focus)] disabled:opacity-50 disabled:pointer-events-none",
|
|
11
|
+
leading: "absolute start-0 top-1/2 -translate-y-1/2 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
12
|
+
trailing: "absolute end-0 top-1/2 -translate-y-1/2 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
11
13
|
value: "flex-1 truncate text-start text-[var(--selaras-resolved-text-default)] data-[placeholder]:text-[var(--selaras-resolved-text-muted)]",
|
|
12
14
|
// Both chip display branches render a real Chip (see
|
|
13
15
|
// ComboboxSelectBase.vue) instead of hand-rolled markup, styled/
|
|
@@ -40,7 +42,7 @@ export const selectTheme = tv({
|
|
|
40
42
|
mobilePanel: "",
|
|
41
43
|
viewport: "max-h-72 overflow-y-auto p-1",
|
|
42
44
|
group: "px-2 pt-2 pb-1 text-xs font-medium text-[var(--selaras-resolved-text-muted)]",
|
|
43
|
-
item: "relative flex items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] py-1.5 ps-2 pe-8 text-sm text-[var(--selaras-resolved-text-default)] outline-none cursor-pointer select-none data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
|
|
45
|
+
item: "relative my-0.5 flex w-full min-w-0 items-center gap-2 rounded-[var(--selaras-resolved-radius-sm)] py-1.5 ps-2 pe-8 text-sm text-[var(--selaras-resolved-text-default)] outline-none cursor-pointer select-none transition-colors data-[highlighted]:bg-[var(--selaras-resolved-surface-elevated)] data-[state=checked]:bg-[var(--_selaras-color-subtle)] data-[state=checked]:text-[var(--_selaras-color-on-subtle)] data-[disabled]:opacity-50 data-[disabled]:pointer-events-none",
|
|
44
46
|
itemIndicator: "absolute end-2 flex items-center text-[var(--_selaras-color-indicator)]",
|
|
45
47
|
empty: "px-2 py-6 text-center text-sm text-[var(--selaras-resolved-text-muted)]",
|
|
46
48
|
arrow: "fill-[var(--selaras-resolved-surface-default)] stroke-[var(--selaras-resolved-border-default)] stroke-1"
|
|
@@ -51,6 +51,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
|
|
|
51
51
|
}, {
|
|
52
52
|
root: string;
|
|
53
53
|
base: string;
|
|
54
|
+
leading: string;
|
|
55
|
+
trailing: string;
|
|
54
56
|
leadingIcon: string;
|
|
55
57
|
trailingIcon: string;
|
|
56
58
|
clear: string;
|
|
@@ -107,6 +109,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
|
|
|
107
109
|
}, {
|
|
108
110
|
root: string;
|
|
109
111
|
base: string;
|
|
112
|
+
leading: string;
|
|
113
|
+
trailing: string;
|
|
110
114
|
leadingIcon: string;
|
|
111
115
|
trailingIcon: string;
|
|
112
116
|
clear: string;
|
|
@@ -163,6 +167,8 @@ export declare const textareaTheme: import("tailwind-variants").TVReturnType<{
|
|
|
163
167
|
}, {
|
|
164
168
|
root: string;
|
|
165
169
|
base: string;
|
|
170
|
+
leading: string;
|
|
171
|
+
trailing: string;
|
|
166
172
|
leadingIcon: string;
|
|
167
173
|
trailingIcon: string;
|
|
168
174
|
clear: string;
|
|
@@ -9,6 +9,8 @@ export const textareaTheme = tv({
|
|
|
9
9
|
// way Input's own icons are - once content wraps past one line,
|
|
10
10
|
// centering against the whole (variable) box height would drift the
|
|
11
11
|
// icon away from the first line of text it's meant to sit beside.
|
|
12
|
+
leading: "absolute top-1 start-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 ps-2.5 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
13
|
+
trailing: "absolute top-1 end-0 m-0 flex min-w-0 items-center gap-1 border-0 p-0 pe-1 text-sm text-[var(--selaras-resolved-text-muted)] disabled:opacity-50",
|
|
12
14
|
leadingIcon: "absolute start-3 top-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
|
|
13
15
|
trailingIcon: "absolute end-3 top-2.5 shrink-0 text-[var(--selaras-resolved-text-muted)]",
|
|
14
16
|
clear: "absolute end-1 top-1 shrink-0 rounded-full"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare const transferListTheme: import("tailwind-variants").TVReturnType<{
|
|
2
|
+
layout: {
|
|
3
|
+
auto: {
|
|
4
|
+
root: string;
|
|
5
|
+
actions: string;
|
|
6
|
+
};
|
|
7
|
+
horizontal: {
|
|
8
|
+
root: string;
|
|
9
|
+
actions: string;
|
|
10
|
+
};
|
|
11
|
+
vertical: {
|
|
12
|
+
root: string;
|
|
13
|
+
actions: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}, {
|
|
17
|
+
root: string;
|
|
18
|
+
panel: string;
|
|
19
|
+
header: string;
|
|
20
|
+
count: string;
|
|
21
|
+
listbox: string;
|
|
22
|
+
actions: string;
|
|
23
|
+
action: string;
|
|
24
|
+
announcement: string;
|
|
25
|
+
}, undefined, {
|
|
26
|
+
layout: {
|
|
27
|
+
auto: {
|
|
28
|
+
root: string;
|
|
29
|
+
actions: string;
|
|
30
|
+
};
|
|
31
|
+
horizontal: {
|
|
32
|
+
root: string;
|
|
33
|
+
actions: string;
|
|
34
|
+
};
|
|
35
|
+
vertical: {
|
|
36
|
+
root: string;
|
|
37
|
+
actions: string;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}, {
|
|
41
|
+
root: string;
|
|
42
|
+
panel: string;
|
|
43
|
+
header: string;
|
|
44
|
+
count: string;
|
|
45
|
+
listbox: string;
|
|
46
|
+
actions: string;
|
|
47
|
+
action: string;
|
|
48
|
+
announcement: string;
|
|
49
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
50
|
+
layout: {
|
|
51
|
+
auto: {
|
|
52
|
+
root: string;
|
|
53
|
+
actions: string;
|
|
54
|
+
};
|
|
55
|
+
horizontal: {
|
|
56
|
+
root: string;
|
|
57
|
+
actions: string;
|
|
58
|
+
};
|
|
59
|
+
vertical: {
|
|
60
|
+
root: string;
|
|
61
|
+
actions: string;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}, {
|
|
65
|
+
root: string;
|
|
66
|
+
panel: string;
|
|
67
|
+
header: string;
|
|
68
|
+
count: string;
|
|
69
|
+
listbox: string;
|
|
70
|
+
actions: string;
|
|
71
|
+
action: string;
|
|
72
|
+
announcement: string;
|
|
73
|
+
}>>;
|
|
74
|
+
export type TransferListThemeSlots = keyof (typeof transferListTheme)['slots'];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { tv } from "tailwind-variants";
|
|
2
|
+
export const transferListTheme = tv({
|
|
3
|
+
slots: {
|
|
4
|
+
root: "flex min-w-0 gap-3",
|
|
5
|
+
panel: "flex min-w-0 flex-1 flex-col gap-2",
|
|
6
|
+
header: "flex items-center justify-between gap-2 text-sm font-medium text-[var(--selaras-resolved-text-default)]",
|
|
7
|
+
count: "text-xs font-normal text-[var(--selaras-resolved-text-muted)]",
|
|
8
|
+
listbox: "flex-1",
|
|
9
|
+
actions: "flex shrink-0 items-center justify-center gap-2",
|
|
10
|
+
action: "",
|
|
11
|
+
announcement: "sr-only"
|
|
12
|
+
},
|
|
13
|
+
variants: {
|
|
14
|
+
layout: {
|
|
15
|
+
auto: { root: "flex-col sm:flex-row", actions: "flex-row sm:flex-col" },
|
|
16
|
+
horizontal: { root: "flex-row", actions: "flex-col" },
|
|
17
|
+
vertical: { root: "flex-col", actions: "flex-row" }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { layout: "auto" }
|
|
21
|
+
});
|
|
@@ -41,6 +41,7 @@ import type { inputTheme, InputThemeSlots } from './theme/input.js';
|
|
|
41
41
|
import type { inputGroupTheme, InputGroupThemeSlots } from './theme/input-group.js';
|
|
42
42
|
import type { inputNumberTheme, InputNumberThemeSlots } from './theme/input-number.js';
|
|
43
43
|
import type { kbdTheme, KbdThemeSlots } from './theme/kbd.js';
|
|
44
|
+
import type { listboxTheme, ListboxThemeSlots } from './theme/listbox.js';
|
|
44
45
|
import type { modalTheme, ModalThemeSlots } from './theme/modal.js';
|
|
45
46
|
import type { navigationMenuTheme, NavigationMenuThemeSlots } from './theme/navigation-menu.js';
|
|
46
47
|
import type { pageAsideTheme, PageAsideThemeSlots } from './theme/page-aside.js';
|
|
@@ -72,6 +73,7 @@ import type { toastTheme, ToastThemeSlots } from './theme/toast.js';
|
|
|
72
73
|
import type { toggleTheme, ToggleThemeSlots } from './theme/toggle.js';
|
|
73
74
|
import type { toggleGroupTheme, ToggleGroupThemeSlots } from './theme/toggle-group.js';
|
|
74
75
|
import type { tooltipTheme, TooltipThemeSlots } from './theme/tooltip.js';
|
|
76
|
+
import type { transferListTheme, TransferListThemeSlots } from './theme/transfer-list.js';
|
|
75
77
|
import type { treeTheme, TreeThemeSlots } from './theme/tree.js';
|
|
76
78
|
import type { ColorRole, RuntimeTokenOverrides } from './utils/color-registry.js';
|
|
77
79
|
type AccordionVariants = VariantProps<typeof accordionTheme>;
|
|
@@ -362,6 +364,14 @@ export interface ThemeComponentRegistry {
|
|
|
362
364
|
slots: RadioGroupThemeSlots;
|
|
363
365
|
conditions: WithRegisteredColor<Pick<RadioGroupVariants, 'color' | 'size' | 'orientation' | 'variant' | 'invalid'>>;
|
|
364
366
|
};
|
|
367
|
+
listbox: {
|
|
368
|
+
slots: ListboxThemeSlots;
|
|
369
|
+
conditions: WithRegisteredColor<Pick<VariantProps<typeof listboxTheme>, 'color' | 'size' | 'invalid'>>;
|
|
370
|
+
};
|
|
371
|
+
transferList: {
|
|
372
|
+
slots: TransferListThemeSlots;
|
|
373
|
+
conditions: Pick<VariantProps<typeof transferListTheme>, 'layout'>;
|
|
374
|
+
};
|
|
365
375
|
rating: {
|
|
366
376
|
slots: RatingThemeSlots;
|
|
367
377
|
conditions: WithRegisteredColor<Pick<RatingVariants, 'color' | 'size' | 'orientation'>>;
|
|
@@ -46,6 +46,7 @@ export type * from '../components/Input.vue';
|
|
|
46
46
|
export type * from '../components/InputGroup.vue';
|
|
47
47
|
export type * from '../components/InputNumber.vue';
|
|
48
48
|
export type * from '../components/Kbd.vue';
|
|
49
|
+
export type * from '../components/Listbox.vue';
|
|
49
50
|
export type * from '../components/Modal.vue';
|
|
50
51
|
export type * from '../components/NavigationMenu.vue';
|
|
51
52
|
export type * from '../components/PageAside.vue';
|
|
@@ -83,6 +84,7 @@ export type * from '../components/Toast.vue';
|
|
|
83
84
|
export type * from '../components/Toggle.vue';
|
|
84
85
|
export type * from '../components/ToggleGroup.vue';
|
|
85
86
|
export type * from '../components/Tooltip.vue';
|
|
87
|
+
export type * from '../components/TransferList.vue';
|
|
86
88
|
export type * from '../components/Tree.vue';
|
|
87
89
|
export type { UseCommandPaletteReturn } from '../composables/use-command-palette.js';
|
|
88
90
|
export type { UseConfirmOptions, UseConfirmReturn } from '../composables/use-confirm.js';
|
|
@@ -95,6 +97,7 @@ export type { ThemeComponentRegistry, ThemeConfiguration, ThemeDefaults, ThemeUi
|
|
|
95
97
|
export type { ArrowConfig, RoundedArrowConfig } from '../utils/arrow.js';
|
|
96
98
|
export type { AutocompleteValue } from '../utils/autocomplete-contracts.js';
|
|
97
99
|
export type { BuiltinColorName, ColorRecipe, ColorRecipeInput, ColorRole, RuntimeColorOverrides, RuntimeThemeMode, RuntimeTokenOverrides } from '../utils/color-registry.js';
|
|
100
|
+
export type { FieldAdornmentSlotProps } from '../utils/field-adornments.js';
|
|
98
101
|
export type { FunctionalTokenOverrides, GeometryTokenOverrides, ThemeTokenOverrides } from '../utils/functional-tokens.js';
|
|
99
102
|
export type { IconRegistry } from '../utils/icons.js';
|
|
100
103
|
export { defaultIcons } from '../utils/icons.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CSSProperties } from 'vue';
|
|
2
|
+
export interface FieldAdornmentSlotProps {
|
|
3
|
+
disabled: boolean;
|
|
4
|
+
size: 'sm' | 'md' | 'lg';
|
|
5
|
+
/** Button size that fits inside the field. */
|
|
6
|
+
buttonSize: 'sm' | 'md';
|
|
7
|
+
}
|
|
8
|
+
/** Reserve the rendered width of custom content, including dynamic suffixes. */
|
|
9
|
+
export declare function useFieldAdornments(): {
|
|
10
|
+
leading: import("vue").ShallowRef<HTMLElement | null, HTMLElement | null>;
|
|
11
|
+
trailing: import("vue").ShallowRef<HTMLElement | null, HTMLElement | null>;
|
|
12
|
+
padding: (hasLeading: boolean, hasTrailing: boolean, trailingExtra?: string) => CSSProperties | undefined;
|
|
13
|
+
clearOffset: () => CSSProperties;
|
|
14
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { shallowRef, watchEffect } from "vue";
|
|
2
|
+
export function useFieldAdornments() {
|
|
3
|
+
const leading = shallowRef(null);
|
|
4
|
+
const trailing = shallowRef(null);
|
|
5
|
+
const leadingWidth = shallowRef();
|
|
6
|
+
const trailingWidth = shallowRef();
|
|
7
|
+
watchEffect((onCleanup) => {
|
|
8
|
+
const start = leading.value;
|
|
9
|
+
const end = trailing.value;
|
|
10
|
+
function measure() {
|
|
11
|
+
leadingWidth.value = start ? `${start.getBoundingClientRect().width}px` : void 0;
|
|
12
|
+
trailingWidth.value = end ? `${end.getBoundingClientRect().width}px` : void 0;
|
|
13
|
+
}
|
|
14
|
+
measure();
|
|
15
|
+
if (!start && !end || typeof ResizeObserver === "undefined")
|
|
16
|
+
return;
|
|
17
|
+
const observer = new ResizeObserver(measure);
|
|
18
|
+
if (start)
|
|
19
|
+
observer.observe(start);
|
|
20
|
+
if (end)
|
|
21
|
+
observer.observe(end);
|
|
22
|
+
onCleanup(() => observer.disconnect());
|
|
23
|
+
}, { flush: "post" });
|
|
24
|
+
function padding(hasLeading, hasTrailing, trailingExtra = "0rem") {
|
|
25
|
+
if (!hasLeading && !hasTrailing)
|
|
26
|
+
return;
|
|
27
|
+
return {
|
|
28
|
+
paddingInlineStart: hasLeading ? `calc(${leadingWidth.value ?? "2rem"} + 0.5rem)` : void 0,
|
|
29
|
+
paddingInlineEnd: hasTrailing ? `calc(${trailingWidth.value ?? "2rem"} + 0.5rem + ${trailingExtra})` : void 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function clearOffset() {
|
|
33
|
+
return { insetInlineEnd: `calc(${trailingWidth.value ?? "2rem"} + 0.25rem)` };
|
|
34
|
+
}
|
|
35
|
+
return { leading, trailing, padding, clearOffset };
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ListboxThemeSlots } from '../theme/listbox.js';
|
|
2
|
+
import type { SelectEntry, SelectEntryGroup, SelectEntryItem, SelectIdentity, SelectModel, SelectProps } from './select-contracts.js';
|
|
3
|
+
import type { UiProp } from './ui.js';
|
|
4
|
+
export interface ListboxProps<Entry extends SelectEntry = SelectEntry, Key extends string = 'value', Multiple extends boolean = false> extends Pick<SelectProps<Entry, Key, Multiple>, 'items' | 'modelValue' | 'defaultValue' | 'valueKey' | 'labelKey' | 'multiple' | 'searchable' | 'searchTerm' | 'virtualize' | 'disabled' | 'loading' | 'invalid' | 'size' | 'color' | 'id' | 'name' | 'form' | 'required'> {
|
|
5
|
+
label?: string;
|
|
6
|
+
descriptionKey?: keyof SelectEntryItem<Entry> & string;
|
|
7
|
+
selectionBehavior?: 'toggle' | 'replace';
|
|
8
|
+
checkbox?: boolean;
|
|
9
|
+
selectAll?: boolean;
|
|
10
|
+
ui?: UiProp<ListboxThemeSlots>;
|
|
11
|
+
}
|
|
12
|
+
export interface ListboxEmits<Entry extends SelectEntry = SelectEntry, Key extends string = 'value', Multiple extends boolean = false> {
|
|
13
|
+
'update:modelValue': [value: SelectModel<SelectIdentity<Entry, Key>, Multiple>];
|
|
14
|
+
'update:searchTerm': [value: string];
|
|
15
|
+
}
|
|
16
|
+
export interface ListboxItemSlot<Entry extends SelectEntry> {
|
|
17
|
+
item: SelectEntryItem<Entry>;
|
|
18
|
+
index: number;
|
|
19
|
+
selected: boolean;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ListboxSlots<Entry extends SelectEntry> {
|
|
23
|
+
'item'?: (props: ListboxItemSlot<Entry>) => any;
|
|
24
|
+
'group'?: (props: {
|
|
25
|
+
group: SelectEntryGroup<Entry>;
|
|
26
|
+
}) => any;
|
|
27
|
+
'header'?: () => any;
|
|
28
|
+
'footer'?: () => any;
|
|
29
|
+
'empty'?: () => any;
|
|
30
|
+
'empty-filter'?: () => any;
|
|
31
|
+
}
|
|
File without changes
|
|
@@ -44,6 +44,13 @@ export interface MessageRegistry {
|
|
|
44
44
|
increment: string;
|
|
45
45
|
decrement: string;
|
|
46
46
|
noOptions: string;
|
|
47
|
+
selectAll: string;
|
|
48
|
+
transferSource: string;
|
|
49
|
+
transferTarget: string;
|
|
50
|
+
moveToTarget: string;
|
|
51
|
+
moveToSource: string;
|
|
52
|
+
listSelectionCount: (selected: number, total: number) => string;
|
|
53
|
+
transferItems: (count: number, target: string) => string;
|
|
47
54
|
noResultsFound: string;
|
|
48
55
|
createOption: (value: string) => string;
|
|
49
56
|
noData: string;
|
|
@@ -44,6 +44,13 @@ export const defaultMessages = {
|
|
|
44
44
|
increment: "Increment",
|
|
45
45
|
decrement: "Decrement",
|
|
46
46
|
noOptions: "No options",
|
|
47
|
+
selectAll: "Select all",
|
|
48
|
+
transferSource: "Available",
|
|
49
|
+
transferTarget: "Assigned",
|
|
50
|
+
moveToTarget: "Add selected",
|
|
51
|
+
moveToSource: "Remove selected",
|
|
52
|
+
listSelectionCount: (selected, total) => `${selected} of ${total} selected`,
|
|
53
|
+
transferItems: (count, target) => `${count} item${count === 1 ? "" : "s"} moved to ${target}`,
|
|
47
54
|
noResultsFound: "No results found",
|
|
48
55
|
createOption: (value) => `Create "${value}"`,
|
|
49
56
|
noData: "No data",
|
|
@@ -2,6 +2,7 @@ import type { VariantProps } from 'tailwind-variants';
|
|
|
2
2
|
import type { selectTheme, SelectThemeSlots } from '../theme/select.js';
|
|
3
3
|
import type { RoundedArrowConfig } from './arrow.js';
|
|
4
4
|
import type { ColorRole } from './color-registry.js';
|
|
5
|
+
import type { FieldAdornmentSlotProps } from './field-adornments.js';
|
|
5
6
|
import type { OverlayPortal, OverlayPositioning } from './overlay.js';
|
|
6
7
|
import type { UiProp } from './ui.js';
|
|
7
8
|
export type SelectValue = string | number;
|
|
@@ -94,6 +95,8 @@ export interface SelectEmits<Entry extends SelectEntry = {
|
|
|
94
95
|
'update:searchTerm': [value: string];
|
|
95
96
|
}
|
|
96
97
|
export interface SelectSlots<Entry extends SelectEntry, Key extends string = 'value'> {
|
|
98
|
+
'leading'?: (props: FieldAdornmentSlotProps) => any;
|
|
99
|
+
'trailing'?: (props: FieldAdornmentSlotProps) => any;
|
|
97
100
|
'item'?: (props: {
|
|
98
101
|
item: SelectEntryItem<Entry>;
|
|
99
102
|
}) => any;
|