@flux-ui/components 3.0.0-next.65 → 3.0.0-next.66
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/FluxDataTable.vue.d.ts +9 -3
- package/dist/component/FluxTableRow.vue.d.ts +4 -1
- package/dist/index.css +46 -12
- package/dist/index.js +382 -290
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
- package/src/component/FluxCheckbox.vue +2 -2
- package/src/component/FluxDataTable.vue +151 -5
- package/src/component/FluxTable.vue +1 -1
- package/src/component/FluxTableRow.vue +6 -1
- package/src/css/component/Form.module.scss +7 -5
- package/src/css/component/LayerPane.module.scss +4 -0
- package/src/css/component/Overlay.module.scss +1 -1
- package/src/css/component/Tab.module.scss +3 -3
- package/src/css/component/Table.module.scss +33 -3
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.66",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/basmilius",
|
|
@@ -50,11 +50,15 @@
|
|
|
50
50
|
"main": "./dist/index.js",
|
|
51
51
|
"module": "./dist/index.js",
|
|
52
52
|
"types": "./dist/index.d.ts",
|
|
53
|
+
"sideEffects": [
|
|
54
|
+
"**/css/index.scss",
|
|
55
|
+
"**/dist/index.css"
|
|
56
|
+
],
|
|
53
57
|
"dependencies": {
|
|
54
58
|
"@basmilius/common": "^3.25.0",
|
|
55
59
|
"@basmilius/utils": "^3.25.0",
|
|
56
|
-
"@flux-ui/internals": "3.0.0-next.
|
|
57
|
-
"@flux-ui/types": "3.0.0-next.
|
|
60
|
+
"@flux-ui/internals": "3.0.0-next.66",
|
|
61
|
+
"@flux-ui/types": "3.0.0-next.66",
|
|
58
62
|
"@fortawesome/fontawesome-common-types": "^7.2.0",
|
|
59
63
|
"clsx": "^2.1.1",
|
|
60
64
|
"imask": "^7.6.1",
|
|
@@ -13,13 +13,23 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<template
|
|
16
|
-
v-if="'header' in slots"
|
|
16
|
+
v-if="'header' in slots || selectionMode"
|
|
17
17
|
#header>
|
|
18
18
|
<slot
|
|
19
19
|
name="filter"
|
|
20
20
|
v-bind="{page, perPage, items: limitedItems, total}"/>
|
|
21
21
|
|
|
22
22
|
<FluxTableRow>
|
|
23
|
+
<FluxTableHeader
|
|
24
|
+
v-if="selectionMode"
|
|
25
|
+
is-shrinking
|
|
26
|
+
:class="$style.tableCellSelection">
|
|
27
|
+
<FluxCheckbox
|
|
28
|
+
v-if="selectionMode === 'multiple'"
|
|
29
|
+
:model-value="selectAllState"
|
|
30
|
+
@update:model-value="onSelectAll"/>
|
|
31
|
+
</FluxTableHeader>
|
|
32
|
+
|
|
23
33
|
<slot
|
|
24
34
|
name="header"
|
|
25
35
|
v-bind="{page, perPage, items: limitedItems, total}"/>
|
|
@@ -54,11 +64,22 @@
|
|
|
54
64
|
|
|
55
65
|
<FluxTableRow
|
|
56
66
|
v-for="(item, index) of limitedItems"
|
|
57
|
-
:key="uniqueKey ? item[uniqueKey] : index"
|
|
67
|
+
:key="uniqueKey ? item[uniqueKey] : index"
|
|
68
|
+
:class="selectionMode && $style.isSelectableRow"
|
|
69
|
+
:is-selected="selectionMode ? isItemSelected(item) : false"
|
|
70
|
+
@click="onRowClick(item, $event)">
|
|
71
|
+
<FluxTableCell
|
|
72
|
+
v-if="selectionMode"
|
|
73
|
+
:class="$style.tableCellSelection">
|
|
74
|
+
<FluxCheckbox
|
|
75
|
+
:model-value="isItemSelected(item)"
|
|
76
|
+
@update:model-value="onSelectRow(item)"/>
|
|
77
|
+
</FluxTableCell>
|
|
78
|
+
|
|
58
79
|
<template v-for="(_, name) of slots">
|
|
59
80
|
<slot
|
|
60
81
|
v-if="!IGNORED_SLOTS.includes(name as string)"
|
|
61
|
-
v-bind="{index, item, items: limitedItems, page, perPage, total}"
|
|
82
|
+
v-bind="{index, item, items: limitedItems, page, perPage, total, isSelected: isItemSelected(item)}"
|
|
62
83
|
:name="name"/>
|
|
63
84
|
</template>
|
|
64
85
|
</FluxTableRow>
|
|
@@ -69,10 +90,17 @@
|
|
|
69
90
|
lang="ts"
|
|
70
91
|
setup
|
|
71
92
|
generic="T extends Record<string, any>">
|
|
72
|
-
import { computed, type VNode } from 'vue';
|
|
93
|
+
import { computed, unref, type VNode } from 'vue';
|
|
94
|
+
import FluxCheckbox from './FluxCheckbox.vue';
|
|
73
95
|
import FluxPaginationBar from './FluxPaginationBar.vue';
|
|
74
96
|
import FluxTable from './FluxTable.vue';
|
|
97
|
+
import FluxTableCell from './FluxTableCell.vue';
|
|
98
|
+
import FluxTableHeader from './FluxTableHeader.vue';
|
|
75
99
|
import FluxTableRow from './FluxTableRow.vue';
|
|
100
|
+
import $style from '~flux/components/css/component/Table.module.scss';
|
|
101
|
+
|
|
102
|
+
type SelectionId = string | number;
|
|
103
|
+
type SelectionValue = SelectionId | null | SelectionId[];
|
|
76
104
|
|
|
77
105
|
const IGNORED_SLOTS: string[] = ['filter', 'header', 'footer', 'colgroups', 'pagination'];
|
|
78
106
|
|
|
@@ -81,6 +109,8 @@
|
|
|
81
109
|
navigate: [number];
|
|
82
110
|
}>();
|
|
83
111
|
|
|
112
|
+
const selected = defineModel<SelectionValue>('selected');
|
|
113
|
+
|
|
84
114
|
const {
|
|
85
115
|
isBordered = true,
|
|
86
116
|
isHoverable = false,
|
|
@@ -88,7 +118,9 @@
|
|
|
88
118
|
isSeparated = true,
|
|
89
119
|
isStriped = false,
|
|
90
120
|
items,
|
|
91
|
-
perPage
|
|
121
|
+
perPage,
|
|
122
|
+
selectionMode,
|
|
123
|
+
uniqueKey
|
|
92
124
|
} = defineProps<{
|
|
93
125
|
readonly fillColumns?: number;
|
|
94
126
|
readonly isBordered?: boolean;
|
|
@@ -100,6 +132,7 @@
|
|
|
100
132
|
readonly limits: number[];
|
|
101
133
|
readonly page: number;
|
|
102
134
|
readonly perPage: number;
|
|
135
|
+
readonly selectionMode?: 'single' | 'multiple';
|
|
103
136
|
readonly total: number;
|
|
104
137
|
readonly uniqueKey?: string;
|
|
105
138
|
}>();
|
|
@@ -112,6 +145,7 @@
|
|
|
112
145
|
readonly item: T;
|
|
113
146
|
readonly items: T[];
|
|
114
147
|
readonly total: number;
|
|
148
|
+
readonly isSelected: boolean;
|
|
115
149
|
}) => VNode;
|
|
116
150
|
|
|
117
151
|
filter(props: {
|
|
@@ -146,4 +180,116 @@
|
|
|
146
180
|
}>();
|
|
147
181
|
|
|
148
182
|
const limitedItems = computed(() => items.slice(0, perPage));
|
|
183
|
+
|
|
184
|
+
const currentPageIds = computed<SelectionId[]>(() => {
|
|
185
|
+
if (!uniqueKey) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return unref(limitedItems).map(item => item[uniqueKey] as SelectionId);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const selectAllState = computed<boolean | null>(() => {
|
|
193
|
+
const ids = unref(currentPageIds);
|
|
194
|
+
const value = unref(selected);
|
|
195
|
+
|
|
196
|
+
if (ids.length === 0 || !Array.isArray(value)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const selectedOnPage = ids.filter(id => value.includes(id)).length;
|
|
201
|
+
|
|
202
|
+
if (selectedOnPage === 0) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (selectedOnPage === ids.length) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
function getItemId(item: T): SelectionId | undefined {
|
|
214
|
+
if (!uniqueKey) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return item[uniqueKey] as SelectionId;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isItemSelected(item: T): boolean {
|
|
222
|
+
if (!selectionMode) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const id = getItemId(item);
|
|
227
|
+
|
|
228
|
+
if (id === undefined) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const value = unref(selected);
|
|
233
|
+
|
|
234
|
+
if (Array.isArray(value)) {
|
|
235
|
+
return value.includes(id);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return value === id;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function onRowClick(item: T, event: MouseEvent): void {
|
|
242
|
+
if (!selectionMode) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const target = event.target as HTMLElement | null;
|
|
247
|
+
|
|
248
|
+
if (target?.closest('a, button, input, label, select, textarea, [role="button"]')) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
onSelectRow(item);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function onSelectRow(item: T): void {
|
|
256
|
+
const id = getItemId(item);
|
|
257
|
+
|
|
258
|
+
if (id === undefined) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (selectionMode === 'multiple') {
|
|
263
|
+
const current = Array.isArray(unref(selected)) ? unref(selected) as SelectionId[] : [];
|
|
264
|
+
selected.value = current.includes(id)
|
|
265
|
+
? current.filter(v => v !== id)
|
|
266
|
+
: [...current, id];
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (selectionMode === 'single') {
|
|
271
|
+
selected.value = isItemSelected(item) ? null : id;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function onSelectAll(value: boolean | null): void {
|
|
276
|
+
if (selectionMode !== 'multiple') {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const ids = unref(currentPageIds);
|
|
281
|
+
const current = Array.isArray(unref(selected)) ? unref(selected) as SelectionId[] : [];
|
|
282
|
+
|
|
283
|
+
if (value) {
|
|
284
|
+
const additions = ids.filter(id => !current.includes(id));
|
|
285
|
+
selected.value = [...current, ...additions];
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
selected.value = current.filter(id => !ids.includes(id));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (import.meta.env.DEV && selectionMode && !uniqueKey) {
|
|
293
|
+
console.warn('[FluxDataTable] `uniqueKey` is required when `selectionMode` is set, otherwise rows cannot be tracked across renders.');
|
|
294
|
+
}
|
|
149
295
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<tr :class="$style.tableRow">
|
|
2
|
+
<tr :class="clsx($style.tableRow, isSelected && $style.isSelected)">
|
|
3
3
|
<slot/>
|
|
4
4
|
</tr>
|
|
5
5
|
</template>
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
<script
|
|
8
8
|
lang="ts"
|
|
9
9
|
setup>
|
|
10
|
+
import { clsx } from 'clsx';
|
|
10
11
|
import type { VNode } from 'vue';
|
|
11
12
|
import $style from '~flux/components/css/component/Table.module.scss';
|
|
12
13
|
|
|
14
|
+
defineProps<{
|
|
15
|
+
readonly isSelected?: boolean;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
13
18
|
defineSlots<{
|
|
14
19
|
default(): VNode[];
|
|
15
20
|
}>();
|
|
@@ -588,6 +588,7 @@
|
|
|
588
588
|
display: inline-flex;
|
|
589
589
|
flex-shrink: 0;
|
|
590
590
|
gap: 12px;
|
|
591
|
+
line-height: 20px;
|
|
591
592
|
outline: 0;
|
|
592
593
|
|
|
593
594
|
&.isDisabled {
|
|
@@ -619,16 +620,16 @@
|
|
|
619
620
|
|
|
620
621
|
.checkboxElement,
|
|
621
622
|
.checkboxNative {
|
|
622
|
-
margin: 1px 0;
|
|
623
|
-
height:
|
|
624
|
-
width:
|
|
623
|
+
margin: 1px 0 0;
|
|
624
|
+
height: 20px;
|
|
625
|
+
width: 20px;
|
|
625
626
|
}
|
|
626
627
|
|
|
627
628
|
.checkboxElement {
|
|
628
629
|
position: relative;
|
|
629
630
|
display: inline-flex;
|
|
630
|
-
height:
|
|
631
|
-
width:
|
|
631
|
+
height: 20px;
|
|
632
|
+
width: 20px;
|
|
632
633
|
padding: 0;
|
|
633
634
|
align-items: center;
|
|
634
635
|
justify-content: center;
|
|
@@ -662,6 +663,7 @@
|
|
|
662
663
|
|
|
663
664
|
.checkboxNative {
|
|
664
665
|
position: absolute;
|
|
666
|
+
cursor: pointer;
|
|
665
667
|
opacity: 0;
|
|
666
668
|
|
|
667
669
|
@include mixin.hover {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
.tabBarArrow {
|
|
32
32
|
position: absolute;
|
|
33
33
|
display: flex;
|
|
34
|
-
top:
|
|
34
|
+
top: 2px;
|
|
35
35
|
height: calc(100% - 6px);
|
|
36
36
|
width: 30px;
|
|
37
37
|
align-items: center;
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
.tabBarArrowStart {
|
|
54
54
|
composes: tabBarArrow;
|
|
55
55
|
|
|
56
|
-
left:
|
|
56
|
+
left: 3px;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.tabBarArrowEnd {
|
|
60
60
|
composes: tabBarArrow;
|
|
61
61
|
|
|
62
|
-
right:
|
|
62
|
+
right: 3px;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
.baseTabBarTabs {
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
height: 0;
|
|
40
40
|
margin: 0;
|
|
41
41
|
padding: 0;
|
|
42
|
+
|
|
43
|
+
&.isSelectableRow {
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
.tableCell {
|
|
@@ -88,11 +92,25 @@ tbody .tableRow:nth-child(even) .tableCell.isStriped {
|
|
|
88
92
|
background: rgb(from var(--gray-50) r g b / .5);
|
|
89
93
|
}
|
|
90
94
|
|
|
95
|
+
tbody .tableRow.isSelected .tableCell {
|
|
96
|
+
background: var(--primary-50);
|
|
97
|
+
border-color: var(--primary-100);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
tbody .tableRow.isSelected + .tableRow .tableCell {
|
|
101
|
+
border-top-color: var(--primary-100);
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
@media (hover: hover) {
|
|
92
105
|
tbody .tableRow:hover .tableCell.isHoverable,
|
|
93
106
|
tbody .tableRow:has(:focus-visible) .tableCell.isHoverable {
|
|
94
107
|
background: var(--gray-50);
|
|
95
108
|
}
|
|
109
|
+
|
|
110
|
+
tbody .tableRow.isSelected:hover .tableCell.isHoverable,
|
|
111
|
+
tbody .tableRow.isSelected:has(:focus-visible) .tableCell.isHoverable {
|
|
112
|
+
background: var(--primary-50);
|
|
113
|
+
}
|
|
96
114
|
}
|
|
97
115
|
|
|
98
116
|
tfoot .tableCell {
|
|
@@ -111,6 +129,10 @@ tfoot .tableCell {
|
|
|
111
129
|
margin: -4px 0 -4px -3px;
|
|
112
130
|
}
|
|
113
131
|
|
|
132
|
+
.tableCellSelection {
|
|
133
|
+
width: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
114
136
|
.tableFill {
|
|
115
137
|
pointer-events: none;
|
|
116
138
|
|
|
@@ -180,12 +202,20 @@ tfoot .tableCell {
|
|
|
180
202
|
}
|
|
181
203
|
}
|
|
182
204
|
|
|
205
|
+
.basePaneStructure:has(.table) {
|
|
206
|
+
--table-spacing: 18px;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.basePaneStructure .basePaneStructure {
|
|
210
|
+
--table-spacing: 15px;
|
|
211
|
+
}
|
|
212
|
+
|
|
183
213
|
.basePaneStructure > .table .tableCell:first-child .tableCellContent {
|
|
184
|
-
padding-left:
|
|
214
|
+
padding-left: var(--table-spacing);
|
|
185
215
|
}
|
|
186
216
|
|
|
187
217
|
.basePaneStructure > .table .tableCell:last-child .tableCellContent {
|
|
188
|
-
padding-right:
|
|
218
|
+
padding-right: var(--table-spacing);
|
|
189
219
|
}
|
|
190
220
|
|
|
191
221
|
.basePaneStructure > .table .tableActions {
|
|
@@ -193,7 +223,7 @@ tfoot .tableCell {
|
|
|
193
223
|
}
|
|
194
224
|
|
|
195
225
|
.basePaneStructure > .table :is(caption) {
|
|
196
|
-
padding: 12px
|
|
226
|
+
padding: 12px var(--table-spacing);
|
|
197
227
|
border-top: 1px solid var(--gray-100);
|
|
198
228
|
}
|
|
199
229
|
|