@flux-ui/components 3.0.0-next.73 → 3.0.0-next.75
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/component/FluxKanbanColumn.vue.d.ts +1 -1
- package/dist/component/FluxSegmentedControl.vue.d.ts +18 -8
- package/dist/component/FluxSegmentedControlItem.vue.d.ts +20 -0
- package/dist/component/FluxSpacing.vue.d.ts +2 -1
- package/dist/component/index.d.ts +1 -4
- package/dist/component/primitive/FilterMenuRenderer.d.ts +3 -3
- package/dist/composable/index.d.ts +1 -0
- package/dist/composable/useSegmentedControlInjection.d.ts +2 -0
- package/dist/data/di.d.ts +10 -1
- package/dist/index.css +57 -158
- package/dist/index.js +321 -405
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/component/FluxBoxedIcon.vue +1 -1
- package/src/component/FluxCalendar.vue +6 -6
- package/src/component/FluxColorPicker.vue +5 -5
- package/src/component/FluxDropZone.vue +0 -1
- package/src/component/FluxDynamicView.vue +2 -2
- package/src/component/FluxExpandableGroup.vue +0 -1
- package/src/component/FluxFilterBase.vue +11 -11
- package/src/component/FluxFormFieldAddition.vue +2 -2
- package/src/component/FluxInfo.vue +2 -2
- package/src/component/FluxInfoStack.vue +2 -2
- package/src/component/FluxKanbanColumn.vue +2 -4
- package/src/component/FluxLayerPane.vue +1 -1
- package/src/component/FluxPagination.vue +4 -4
- package/src/component/FluxSegmentedControl.vue +64 -67
- package/src/component/FluxSegmentedControlItem.vue +98 -0
- package/src/component/FluxSpacing.vue +5 -1
- package/src/component/FluxSplitButton.vue +2 -2
- package/src/component/FluxTable.vue +3 -1
- package/src/component/FluxToolbar.vue +1 -1
- package/src/component/index.ts +1 -4
- package/src/component/primitive/FilterBadge.vue +11 -2
- package/src/component/primitive/FilterMenuRenderer.ts +4 -4
- package/src/composable/index.ts +1 -0
- package/src/composable/useSegmentedControlInjection.ts +13 -0
- package/src/css/component/Form.module.scss +2 -2
- package/src/css/component/SegmentedControl.module.scss +51 -23
- package/src/css/component/Spinner.module.scss +1 -0
- package/src/css/component/Visual.module.scss +1 -0
- package/src/css/mixin/tree-node.scss +3 -3
- package/src/data/di.ts +13 -1
- package/src/data/iconRegistry.ts +1 -1
- package/src/util/createDialogRenderer.ts +1 -1
- package/dist/component/FluxLegend.vue.d.ts +0 -8
- package/dist/component/FluxPercentageBar.vue.d.ts +0 -8
- package/dist/component/FluxSegmentedView.vue.d.ts +0 -9
- package/dist/component/FluxStatistic.vue.d.ts +0 -17
- package/src/component/FluxLegend.vue +0 -27
- package/src/component/FluxPercentageBar.vue +0 -47
- package/src/component/FluxSegmentedView.vue +0 -15
- package/src/component/FluxStatistic.vue +0 -82
- package/src/css/component/Legend.module.scss +0 -29
- package/src/css/component/PercentageBar.module.scss +0 -31
- package/src/css/component/Statistic.module.scss +0 -91
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flux-ui/components",
|
|
3
3
|
"description": "A set of opiniated UI components.",
|
|
4
|
-
"version": "3.0.0-next.
|
|
4
|
+
"version": "3.0.0-next.75",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/basmilius",
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"**/dist/index.css"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@basmilius/common": "^3.
|
|
59
|
-
"@basmilius/utils": "^3.
|
|
60
|
-
"@flux-ui/internals": "3.0.0-next.
|
|
61
|
-
"@flux-ui/types": "3.0.0-next.
|
|
58
|
+
"@basmilius/common": "^3.40.0",
|
|
59
|
+
"@basmilius/utils": "^3.40.0",
|
|
60
|
+
"@flux-ui/internals": "3.0.0-next.75",
|
|
61
|
+
"@flux-ui/types": "3.0.0-next.75",
|
|
62
62
|
"@fortawesome/fontawesome-common-types": "^7.2.0",
|
|
63
63
|
"clsx": "^2.1.1",
|
|
64
64
|
"imask": "^7.6.1",
|
|
@@ -66,10 +66,10 @@
|
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
68
|
"luxon": "^3.7.2",
|
|
69
|
-
"vue": "^3.6.0-beta.
|
|
69
|
+
"vue": "^3.6.0-beta.13"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@basmilius/vite-preset": "^3.
|
|
72
|
+
"@basmilius/vite-preset": "^3.40.0",
|
|
73
73
|
"@types/lodash-es": "^4.17.12",
|
|
74
74
|
"@types/luxon": "^3.7.1",
|
|
75
75
|
"@types/node": "^25.9.1",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"@vue/tsconfig": "^0.9.1",
|
|
78
78
|
"sass-embedded": "^1.100.0",
|
|
79
79
|
"typescript": "^6.0.3",
|
|
80
|
-
"vite": "^8.0.
|
|
81
|
-
"vue-tsc": "^3.3.
|
|
80
|
+
"vite": "^8.0.16",
|
|
81
|
+
"vue-tsc": "^3.3.3"
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
color === 'danger' && $style.iconBoxedDanger,
|
|
8
8
|
color === 'info' && $style.iconBoxedInfo,
|
|
9
9
|
color === 'success' && $style.iconBoxedSuccess,
|
|
10
|
-
color === 'warning' && $style.iconBoxedWarning
|
|
10
|
+
color === 'warning' && $style.iconBoxedWarning
|
|
11
11
|
)"
|
|
12
12
|
:style="{
|
|
13
13
|
fontSize: size && `${size}px`
|
|
@@ -265,13 +265,13 @@
|
|
|
265
265
|
});
|
|
266
266
|
|
|
267
267
|
const timeGridDayCount = computed<1 | 2 | 7>(() => {
|
|
268
|
-
const
|
|
268
|
+
const resolved = unref(resolvedView);
|
|
269
269
|
|
|
270
|
-
if (
|
|
270
|
+
if (resolved === 'week') {
|
|
271
271
|
return 7;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
if (
|
|
274
|
+
if (resolved === 'two-days') {
|
|
275
275
|
return 2;
|
|
276
276
|
}
|
|
277
277
|
|
|
@@ -521,13 +521,13 @@
|
|
|
521
521
|
return;
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
-
const
|
|
525
|
-
const delta = (
|
|
524
|
+
const resolved = unref(resolvedView);
|
|
525
|
+
const delta = (resolved === 'month' ? MONTH_KEY_DELTAS : TIME_GRID_KEY_DELTAS)[direction];
|
|
526
526
|
const newDate = currentDate.plus(delta);
|
|
527
527
|
|
|
528
528
|
emit('reschedule', {id, fromDate: currentDate, toDate: newDate});
|
|
529
529
|
|
|
530
|
-
if (
|
|
530
|
+
if (resolved === 'month') {
|
|
531
531
|
monthFocusedDate.value = newDate.startOf('day');
|
|
532
532
|
|
|
533
533
|
if (newDate.month !== unref(monthViewDate).month) {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
<div
|
|
37
37
|
:class="$style.colorPickerPreview"
|
|
38
38
|
:style="{
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
'--color': rgb
|
|
40
|
+
}"
|
|
41
41
|
aria-hidden="true"/>
|
|
42
42
|
|
|
43
43
|
<FluxFormField
|
|
@@ -180,9 +180,9 @@
|
|
|
180
180
|
|
|
181
181
|
const saturationValue = computed({
|
|
182
182
|
get: (): [number, number] => [unref(saturation), unref(value)],
|
|
183
|
-
set: ([
|
|
184
|
-
saturation.value =
|
|
185
|
-
value.value =
|
|
183
|
+
set: ([nextSaturation, nextValue]: [number, number]) => {
|
|
184
|
+
saturation.value = nextSaturation;
|
|
185
|
+
value.value = nextValue;
|
|
186
186
|
}
|
|
187
187
|
});
|
|
188
188
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
const flattenedFilters = computed(() => flattenVNodeTree(slots.filters?.() ?? []));
|
|
42
42
|
|
|
43
43
|
const buttons = computed(() => {
|
|
44
|
-
const
|
|
44
|
+
const result: Record<string, FluxFilterDefinition> = {};
|
|
45
45
|
const items = unref(flattenedFilters);
|
|
46
46
|
|
|
47
47
|
for (const item of items) {
|
|
@@ -51,14 +51,14 @@
|
|
|
51
51
|
continue;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
result[definition.name] = definition;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
return
|
|
57
|
+
return result;
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
const filters = computed<Record<string, VNode>>(() => {
|
|
61
|
-
const
|
|
61
|
+
const result: { [key: string]: VNode; } = {};
|
|
62
62
|
const items = unref(flattenedFilters);
|
|
63
63
|
|
|
64
64
|
for (const item of items) {
|
|
@@ -68,33 +68,33 @@
|
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
result[definition.name] = item;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
return
|
|
74
|
+
return result;
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
const menuItems = computed<(FluxFilterDefinition | VNode)[][]>(() => {
|
|
78
|
-
const
|
|
78
|
+
const result: (FluxFilterDefinition | VNode)[][] = [[]];
|
|
79
79
|
const items = unref(flattenedFilters);
|
|
80
80
|
|
|
81
81
|
for (const item of items) {
|
|
82
82
|
if (getComponentName(item) === 'FluxSeparator') {
|
|
83
|
-
|
|
83
|
+
result.push([]);
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const definition = resolveDefinition(item);
|
|
88
88
|
|
|
89
89
|
if (definition) {
|
|
90
|
-
|
|
90
|
+
result[result.length - 1].push(definition);
|
|
91
91
|
continue;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
result[result.length - 1].push(item);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
return
|
|
97
|
+
return result;
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
watchEffect(() => {
|
|
@@ -65,9 +65,7 @@
|
|
|
65
65
|
<footer
|
|
66
66
|
v-if="hasFooter"
|
|
67
67
|
:class="$style.kanbanColumnFooter">
|
|
68
|
-
<slot
|
|
69
|
-
v-if="hasFooter"
|
|
70
|
-
name="footer"/>
|
|
68
|
+
<slot name="footer"/>
|
|
71
69
|
</footer>
|
|
72
70
|
</div>
|
|
73
71
|
</template>
|
|
@@ -90,7 +88,7 @@
|
|
|
90
88
|
label
|
|
91
89
|
} = defineProps<{
|
|
92
90
|
readonly columnId: string | number;
|
|
93
|
-
readonly count
|
|
91
|
+
readonly count?: string | number;
|
|
94
92
|
readonly disabled?: boolean;
|
|
95
93
|
readonly icon?: FluxIconName;
|
|
96
94
|
readonly label: string;
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
<template v-else>
|
|
37
37
|
<FluxPaginationButton
|
|
38
|
-
|
|
38
|
+
is-current
|
|
39
39
|
@click="prompt"
|
|
40
40
|
#before>
|
|
41
41
|
<strong>{{ page }}</strong>
|
|
@@ -140,12 +140,12 @@
|
|
|
140
140
|
fieldLabel: translate('flux.paginationNavigatePage')
|
|
141
141
|
});
|
|
142
142
|
|
|
143
|
-
const
|
|
143
|
+
const target = Number(pageStr);
|
|
144
144
|
|
|
145
|
-
if (isNaN(
|
|
145
|
+
if (isNaN(target) || target > unref(pages) || target <= 0) {
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
navigate(
|
|
149
|
+
navigate(target);
|
|
150
150
|
}
|
|
151
151
|
</script>
|
|
@@ -13,98 +13,90 @@
|
|
|
13
13
|
width: `${activeItemWidth}px`
|
|
14
14
|
}"/>
|
|
15
15
|
|
|
16
|
-
<
|
|
17
|
-
<div
|
|
18
|
-
v-if="index > 0"
|
|
19
|
-
:class="clsx(
|
|
20
|
-
$style.segmentedControlSeparator,
|
|
21
|
-
(index === modelValue || index === modelValue + 1) && $style.isActive
|
|
22
|
-
)"
|
|
23
|
-
role="separator"/>
|
|
24
|
-
|
|
25
|
-
<button
|
|
26
|
-
ref="items"
|
|
27
|
-
:class="clsx(
|
|
28
|
-
$style.segmentedControlItem,
|
|
29
|
-
index === modelValue && $style.isActive
|
|
30
|
-
)"
|
|
31
|
-
role="radio"
|
|
32
|
-
:aria-checked="index === modelValue"
|
|
33
|
-
:aria-label="item.label"
|
|
34
|
-
:tabindex="index === modelValue ? 0 : -1"
|
|
35
|
-
type="button"
|
|
36
|
-
@click="activate(index)">
|
|
37
|
-
<FluxIcon
|
|
38
|
-
v-if="item.icon"
|
|
39
|
-
:name="item.icon"
|
|
40
|
-
:size="15"/>
|
|
41
|
-
|
|
42
|
-
<span v-if="item.label">{{ item.label }}</span>
|
|
43
|
-
</button>
|
|
44
|
-
</template>
|
|
16
|
+
<slot/>
|
|
45
17
|
</div>
|
|
46
18
|
</template>
|
|
47
19
|
|
|
48
20
|
<script
|
|
49
21
|
lang="ts"
|
|
50
22
|
setup>
|
|
51
|
-
import { useResizeObserver } from '@basmilius/common';
|
|
52
|
-
import type {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import FluxIcon from './FluxIcon.vue';
|
|
23
|
+
import { useMutationObserver, useResizeObserver } from '@basmilius/common';
|
|
24
|
+
import type { FluxSize } from '@flux-ui/types';
|
|
25
|
+
import { onMounted, provide, ref, toRef, useTemplateRef, watch, type VNode } from 'vue';
|
|
26
|
+
import { FluxSegmentedControlInjectionKey, type FluxSegmentedControlValue } from '~flux/components/data';
|
|
56
27
|
import $style from '~flux/components/css/component/SegmentedControl.module.scss';
|
|
57
28
|
|
|
58
|
-
const modelValue = defineModel<
|
|
59
|
-
default: 0
|
|
60
|
-
});
|
|
29
|
+
const modelValue = defineModel<FluxSegmentedControlValue>();
|
|
61
30
|
|
|
62
|
-
defineProps<{
|
|
31
|
+
const { size = 'medium' } = defineProps<{
|
|
63
32
|
readonly ariaLabel?: string;
|
|
64
33
|
readonly isFill?: boolean;
|
|
65
|
-
readonly
|
|
34
|
+
readonly size?: FluxSize;
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
defineSlots<{
|
|
38
|
+
default(): VNode[];
|
|
66
39
|
}>();
|
|
67
40
|
|
|
68
41
|
const controlRef = useTemplateRef<HTMLElement>('control');
|
|
69
|
-
const itemRefs = useTemplateRef<HTMLButtonElement[]>('items');
|
|
70
42
|
|
|
71
43
|
const activeItemX = ref(0);
|
|
72
44
|
const activeItemWidth = ref(0);
|
|
73
|
-
const isAlive = ref(true);
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
|
|
46
|
+
const items = new Map<HTMLElement, FluxSegmentedControlValue>();
|
|
47
|
+
|
|
48
|
+
provide(FluxSegmentedControlInjectionKey, {
|
|
49
|
+
modelValue,
|
|
50
|
+
size: toRef(() => size),
|
|
51
|
+
select,
|
|
52
|
+
registerItem(element, value) {
|
|
53
|
+
items.set(element, value);
|
|
54
|
+
updateHighlight();
|
|
55
|
+
},
|
|
56
|
+
unregisterItem(element) {
|
|
57
|
+
items.delete(element);
|
|
58
|
+
updateHighlight();
|
|
59
|
+
}
|
|
77
60
|
});
|
|
78
61
|
|
|
79
|
-
|
|
62
|
+
onMounted(() => updateHighlight());
|
|
80
63
|
|
|
81
|
-
|
|
64
|
+
watch(modelValue, () => updateHighlight(), {flush: 'post'});
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
useMutationObserver(controlRef, () => updateHighlight(), {childList: true, subtree: true});
|
|
67
|
+
useResizeObserver(controlRef, () => updateHighlight());
|
|
85
68
|
|
|
86
|
-
|
|
87
|
-
|
|
69
|
+
function select(value: FluxSegmentedControlValue): void {
|
|
70
|
+
modelValue.value = value;
|
|
88
71
|
}
|
|
89
72
|
|
|
90
73
|
function onKeyDown(evt: KeyboardEvent): void {
|
|
91
|
-
const
|
|
74
|
+
const control = controlRef.value;
|
|
75
|
+
|
|
76
|
+
if (!control) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const radios = Array.from(control.querySelectorAll<HTMLButtonElement>('[role=radio]:not([disabled])'));
|
|
92
81
|
|
|
93
|
-
if (
|
|
82
|
+
if (radios.length === 0) {
|
|
94
83
|
return;
|
|
95
84
|
}
|
|
96
85
|
|
|
97
|
-
|
|
86
|
+
const activeElement = control.querySelector<HTMLButtonElement>('[role=radio][aria-checked=true]');
|
|
87
|
+
const currentIndex = activeElement ? radios.indexOf(activeElement) : -1;
|
|
88
|
+
|
|
89
|
+
let newIndex: number;
|
|
98
90
|
|
|
99
91
|
switch (evt.key) {
|
|
100
92
|
case 'ArrowLeft':
|
|
101
93
|
case 'ArrowUp':
|
|
102
|
-
newIndex = Math.max(0,
|
|
94
|
+
newIndex = Math.max(0, currentIndex - 1);
|
|
103
95
|
break;
|
|
104
96
|
|
|
105
97
|
case 'ArrowRight':
|
|
106
98
|
case 'ArrowDown':
|
|
107
|
-
newIndex = Math.min(
|
|
99
|
+
newIndex = Math.min(radios.length - 1, currentIndex + 1);
|
|
108
100
|
break;
|
|
109
101
|
|
|
110
102
|
case 'Home':
|
|
@@ -112,40 +104,45 @@
|
|
|
112
104
|
break;
|
|
113
105
|
|
|
114
106
|
case 'End':
|
|
115
|
-
newIndex =
|
|
107
|
+
newIndex = radios.length - 1;
|
|
116
108
|
break;
|
|
117
109
|
|
|
118
110
|
default:
|
|
119
111
|
return;
|
|
120
112
|
}
|
|
121
113
|
|
|
122
|
-
|
|
114
|
+
const target = radios[newIndex];
|
|
115
|
+
const value = items.get(target);
|
|
116
|
+
|
|
117
|
+
if (value !== undefined) {
|
|
118
|
+
select(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
target.focus();
|
|
123
122
|
evt.preventDefault();
|
|
124
123
|
}
|
|
125
124
|
|
|
126
|
-
function updateHighlight(
|
|
127
|
-
|
|
125
|
+
function updateHighlight(): void {
|
|
126
|
+
const control = controlRef.value;
|
|
127
|
+
|
|
128
|
+
if (!control) {
|
|
128
129
|
return;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
const
|
|
132
|
-
const control = controlRef.value;
|
|
132
|
+
const activeElement = control.querySelector<HTMLElement>('[role=radio][aria-checked=true]');
|
|
133
133
|
|
|
134
|
-
if (!
|
|
134
|
+
if (!activeElement) {
|
|
135
|
+
activeItemWidth.value = 0;
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
const width =
|
|
139
|
+
const width = activeElement.offsetWidth;
|
|
139
140
|
|
|
140
141
|
if (width === 0) {
|
|
141
142
|
return;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
const itemRect = itemRef.getBoundingClientRect();
|
|
146
|
-
const scaleX = control.offsetWidth > 0 ? controlRect.width / control.offsetWidth : 1;
|
|
147
|
-
|
|
148
|
-
activeItemX.value = (itemRect.left - controlRect.left) / scaleX;
|
|
145
|
+
activeItemX.value = activeElement.offsetLeft;
|
|
149
146
|
activeItemWidth.value = width;
|
|
150
147
|
}
|
|
151
148
|
</script>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
ref="item"
|
|
4
|
+
:class="itemClass"
|
|
5
|
+
role="radio"
|
|
6
|
+
:aria-checked="isActive"
|
|
7
|
+
:aria-disabled="disabled ? true : undefined"
|
|
8
|
+
:disabled="disabled ? true : undefined"
|
|
9
|
+
:tabindex="isActive ? 0 : -1"
|
|
10
|
+
type="button"
|
|
11
|
+
@click="onClick">
|
|
12
|
+
<slot>
|
|
13
|
+
<FluxIcon
|
|
14
|
+
v-if="icon"
|
|
15
|
+
:name="icon"
|
|
16
|
+
:size="iconSize"/>
|
|
17
|
+
|
|
18
|
+
<span v-if="label">{{ label }}</span>
|
|
19
|
+
</slot>
|
|
20
|
+
</button>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script
|
|
24
|
+
lang="ts"
|
|
25
|
+
setup>
|
|
26
|
+
import type { FluxIconName } from '@flux-ui/types';
|
|
27
|
+
import { clsx } from 'clsx';
|
|
28
|
+
import { computed, onBeforeUnmount, onMounted, toRef, unref, useTemplateRef } from 'vue';
|
|
29
|
+
import { useDisabled, useSegmentedControlInjection } from '~flux/components/composable';
|
|
30
|
+
import type { FluxSegmentedControlValue } from '~flux/components/data';
|
|
31
|
+
import FluxIcon from './FluxIcon.vue';
|
|
32
|
+
import $style from '~flux/components/css/component/SegmentedControl.module.scss';
|
|
33
|
+
|
|
34
|
+
const { value, disabled: componentDisabled } = defineProps<{
|
|
35
|
+
readonly value: FluxSegmentedControlValue;
|
|
36
|
+
readonly disabled?: boolean;
|
|
37
|
+
readonly icon?: FluxIconName;
|
|
38
|
+
readonly label?: string;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
defineSlots<{
|
|
42
|
+
default(): any;
|
|
43
|
+
}>();
|
|
44
|
+
|
|
45
|
+
const control = useSegmentedControlInjection();
|
|
46
|
+
const disabled = useDisabled(toRef(() => componentDisabled));
|
|
47
|
+
const itemRef = useTemplateRef<HTMLButtonElement>('item');
|
|
48
|
+
|
|
49
|
+
const sizeClasses = {
|
|
50
|
+
small: $style.isSmall,
|
|
51
|
+
medium: $style.isMedium,
|
|
52
|
+
large: $style.isLarge
|
|
53
|
+
};
|
|
54
|
+
const iconSizes = {
|
|
55
|
+
small: 14,
|
|
56
|
+
medium: 16,
|
|
57
|
+
large: 18
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const isActive = computed(() => control.modelValue.value === value);
|
|
61
|
+
const iconSize = computed(() => iconSizes[unref(control.size)]);
|
|
62
|
+
const itemClass = computed(() => clsx(
|
|
63
|
+
$style.segmentedControlItem,
|
|
64
|
+
sizeClasses[unref(control.size)],
|
|
65
|
+
isActive.value && $style.isActive
|
|
66
|
+
));
|
|
67
|
+
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
const element = unref(itemRef);
|
|
70
|
+
|
|
71
|
+
if (!element) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
control.registerItem(element, value);
|
|
76
|
+
|
|
77
|
+
if (control.modelValue.value === undefined && !unref(disabled)) {
|
|
78
|
+
control.select(value);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
onBeforeUnmount(() => {
|
|
83
|
+
const element = unref(itemRef);
|
|
84
|
+
|
|
85
|
+
if (element) {
|
|
86
|
+
control.unregisterItem(element);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function onClick(evt: MouseEvent): void {
|
|
91
|
+
if (unref(disabled)) {
|
|
92
|
+
evt.preventDefault();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
control.select(value);
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
120
|
|
27
27
|
] as const;
|
|
28
28
|
|
|
29
|
+
type Enumerate<N extends number, Acc extends number[] = []> = Acc['length'] extends N
|
|
30
|
+
? Acc[number]
|
|
31
|
+
: Enumerate<N, [...Acc, Acc['length']]>;
|
|
32
|
+
|
|
29
33
|
defineProps<{
|
|
30
|
-
readonly size:
|
|
34
|
+
readonly size: Enumerate<typeof spacings['length']>;
|
|
31
35
|
}>();
|
|
32
36
|
</script>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
floatingMode === 'top-end' && $style.isTopEnd,
|
|
7
7
|
floatingMode === 'top-start' && $style.isTopStart,
|
|
8
8
|
floatingMode === 'bottom-end' && $style.isBottomEnd,
|
|
9
|
-
floatingMode === 'bottom-start' && $style.isBottomStart
|
|
9
|
+
floatingMode === 'bottom-start' && $style.isBottomStart
|
|
10
10
|
)"
|
|
11
11
|
:gap="6"
|
|
12
12
|
tag="nav">
|