@globalbrain/sefirot 2.46.0 → 2.47.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/lib/components/SButton.vue +2 -3
- package/lib/components/SButtonGroup.vue +1 -1
- package/lib/components/SCard.vue +1 -0
- package/lib/components/SDropdownSectionFilter.vue +1 -2
- package/lib/components/SInputBase.vue +21 -22
- package/lib/components/SInputFile.vue +15 -7
- package/lib/components/STable.vue +139 -38
- package/lib/components/STableCell.vue +6 -0
- package/lib/components/STableCellCustom.vue +11 -0
- package/lib/components/STableColumn.vue +18 -17
- package/lib/components/STableFooter.vue +1 -1
- package/lib/components/STableHeader.vue +41 -45
- package/lib/components/STableHeaderActionItem.vue +28 -0
- package/lib/components/STableHeaderActions.vue +20 -0
- package/lib/components/STableItem.vue +1 -1
- package/lib/composables/Dropdown.ts +2 -2
- package/lib/composables/Table.ts +21 -3
- package/lib/composables/Utils.ts +2 -3
- package/lib/styles/bootstrap.css +0 -1
- package/lib/types/shims.d.ts +0 -6
- package/package.json +2 -2
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { type MaybeRef } from '
|
|
3
|
-
import { computed, unref, useSlots } from 'vue'
|
|
2
|
+
import { type MaybeRef, computed, unref, useSlots } from 'vue'
|
|
4
3
|
import { type Position } from '../composables/Tooltip'
|
|
5
4
|
import SFragment from './SFragment.vue'
|
|
6
5
|
import SIcon from './SIcon.vue'
|
|
@@ -24,7 +23,7 @@ export type Mode =
|
|
|
24
23
|
|
|
25
24
|
export interface Tooltip {
|
|
26
25
|
tag?: string
|
|
27
|
-
text?: MaybeRef<string>
|
|
26
|
+
text?: MaybeRef<string | null>
|
|
28
27
|
position?: Position
|
|
29
28
|
trigger?: 'hover' | 'focus' | 'both'
|
|
30
29
|
timeout?: number
|
package/lib/components/SCard.vue
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import IconCheck from '@iconify-icons/ph/check'
|
|
3
|
-
import { type MaybeRef } from '@vueuse/core'
|
|
4
3
|
import Fuse from 'fuse.js'
|
|
5
|
-
import { computed, onMounted, ref, unref } from 'vue'
|
|
4
|
+
import { type MaybeRef, computed, onMounted, ref, unref } from 'vue'
|
|
6
5
|
import { type DropdownSectionFilterOption, type DropdownSectionFilterSelectedValue } from '../composables/Dropdown'
|
|
7
6
|
import { isArray } from '../support/Utils'
|
|
8
7
|
import SDropdownSectionFilterItem from './SDropdownSectionFilterItem.vue'
|
|
@@ -57,16 +57,12 @@ function getErrorMsg(validation: Validatable) {
|
|
|
57
57
|
<template>
|
|
58
58
|
<div class="SInputBase" :class="{ 'has-error': error?.has }">
|
|
59
59
|
<label v-if="label" class="label" :for="name">
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
</div>
|
|
60
|
+
<span class="label-text">{{ label }}</span>
|
|
61
|
+
|
|
62
|
+
<STooltip v-if="hasInfo" :text="info" trigger="focus" @click.prevent>
|
|
63
|
+
<SIcon class="label-info" :icon="IconQuestion" />
|
|
65
64
|
<template v-if="$slots.info" #text><slot name="info" /></template>
|
|
66
65
|
</STooltip>
|
|
67
|
-
<div v-else class="label-text">
|
|
68
|
-
<span class="label-text-value">{{ label }}</span>
|
|
69
|
-
</div>
|
|
70
66
|
|
|
71
67
|
<span class="label-note">{{ note }}</span>
|
|
72
68
|
|
|
@@ -119,26 +115,29 @@ function getErrorMsg(validation: Validatable) {
|
|
|
119
115
|
}
|
|
120
116
|
|
|
121
117
|
.label-text {
|
|
122
|
-
display: flex;
|
|
123
|
-
align-items: center;
|
|
124
|
-
gap: 4px;
|
|
125
|
-
transition: color 0.25s;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.label-text-value {
|
|
129
118
|
font-weight: 500;
|
|
130
119
|
color: var(--input-label-color);
|
|
131
120
|
transition: color 0.25s;
|
|
132
121
|
}
|
|
133
122
|
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
123
|
+
:deep(.STooltip) {
|
|
124
|
+
.label-info {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
margin-left: 4px;
|
|
127
|
+
width: 16px;
|
|
128
|
+
height: 16px;
|
|
129
|
+
color: var(--c-text-2);
|
|
130
|
+
transition: color 0.25s;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&:hover, &:focus, &:focus-within {
|
|
134
|
+
.label-info {
|
|
135
|
+
color: var(--c-info-text);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
139
138
|
|
|
140
|
-
.
|
|
141
|
-
|
|
139
|
+
.content {
|
|
140
|
+
vertical-align: middle;
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
143
|
|
|
@@ -15,18 +15,20 @@ const props = defineProps<{
|
|
|
15
15
|
help?: string
|
|
16
16
|
text?: string
|
|
17
17
|
placeholder?: string
|
|
18
|
+
accept?: string
|
|
19
|
+
multiple?: boolean
|
|
18
20
|
checkIcon?: IconifyIcon | DefineComponent
|
|
19
21
|
checkText?: string
|
|
20
22
|
checkColor?: Color
|
|
21
|
-
value?: File | null
|
|
22
|
-
modelValue?: File | null
|
|
23
|
+
value?: File | File[] | null
|
|
24
|
+
modelValue?: File | File[] | null
|
|
23
25
|
hideError?: boolean
|
|
24
26
|
validation?: Validatable
|
|
25
27
|
}>()
|
|
26
28
|
|
|
27
29
|
const emit = defineEmits<{
|
|
28
|
-
(e: 'update:model-value', file: File | null): void
|
|
29
|
-
(e: 'change', file: File | null): void
|
|
30
|
+
(e: 'update:model-value', file: File | File[] | null): void
|
|
31
|
+
(e: 'change', file: File | File[] | null): void
|
|
30
32
|
}>()
|
|
31
33
|
|
|
32
34
|
const _value = computed(() => {
|
|
@@ -39,15 +41,19 @@ const input = ref<HTMLInputElement | null>(null)
|
|
|
39
41
|
|
|
40
42
|
const classes = computed(() => [props.size ?? 'small'])
|
|
41
43
|
|
|
42
|
-
const fileName = computed(() => _value.value
|
|
44
|
+
const fileName = computed(() => Array.isArray(_value.value)
|
|
45
|
+
? _value.value.map((file) => file.name).join(', ')
|
|
46
|
+
: _value.value?.name ?? ''
|
|
47
|
+
)
|
|
43
48
|
|
|
44
|
-
/* c8 ignore next 4 */
|
|
45
49
|
function open() {
|
|
46
50
|
input.value!.click()
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
function onChange(e: Event) {
|
|
50
|
-
const file =
|
|
54
|
+
const file = props.multiple
|
|
55
|
+
? Array.from((e.target as HTMLInputElement).files ?? [])
|
|
56
|
+
: ((e.target as HTMLInputElement).files ?? [])[0]
|
|
51
57
|
|
|
52
58
|
emit('update:model-value', file ?? null)
|
|
53
59
|
emit('change', file ?? null)
|
|
@@ -74,6 +80,8 @@ function onChange(e: Event) {
|
|
|
74
80
|
ref="input"
|
|
75
81
|
class="input"
|
|
76
82
|
type="file"
|
|
83
|
+
:accept="accept"
|
|
84
|
+
:multiple="multiple"
|
|
77
85
|
@change="onChange"
|
|
78
86
|
>
|
|
79
87
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useVirtualizer } from '@tanstack/vue-virtual'
|
|
3
3
|
import { useResizeObserver } from '@vueuse/core'
|
|
4
|
+
import xor from 'lodash-es/xor'
|
|
4
5
|
import {
|
|
5
6
|
computed,
|
|
6
7
|
nextTick,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
watch
|
|
12
13
|
} from 'vue'
|
|
13
14
|
import { type Table } from '../composables/Table'
|
|
15
|
+
import SInputCheckbox from './SInputCheckbox.vue'
|
|
14
16
|
import SSpinner from './SSpinner.vue'
|
|
15
17
|
import STableCell from './STableCell.vue'
|
|
16
18
|
import STableColumn from './STableColumn.vue'
|
|
@@ -20,6 +22,11 @@ import STableItem from './STableItem.vue'
|
|
|
20
22
|
|
|
21
23
|
const props = defineProps<{
|
|
22
24
|
options: Table
|
|
25
|
+
selected?: unknown[]
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
(e: 'update:selected', value: unknown[]): void
|
|
23
30
|
}>()
|
|
24
31
|
|
|
25
32
|
const head = shallowRef<HTMLElement | null>(null)
|
|
@@ -28,9 +35,13 @@ const block = shallowRef<HTMLElement | null>(null)
|
|
|
28
35
|
const row = shallowRef<HTMLElement | null>(null)
|
|
29
36
|
|
|
30
37
|
const ordersToShow = computed(() => {
|
|
31
|
-
|
|
38
|
+
const orders = unref(props.options.orders).filter((key) => {
|
|
32
39
|
return unref(props.options.columns)[key]?.show !== false
|
|
33
40
|
})
|
|
41
|
+
if (!props.selected) {
|
|
42
|
+
return orders
|
|
43
|
+
}
|
|
44
|
+
return ['__select', ...orders]
|
|
34
45
|
})
|
|
35
46
|
|
|
36
47
|
watch(() => ordersToShow.value, handleResize)
|
|
@@ -55,9 +66,6 @@ const cellOfColToGrow = computed(() => {
|
|
|
55
66
|
return row.value?.children[colToGrow.value]
|
|
56
67
|
})
|
|
57
68
|
|
|
58
|
-
let headLock = false
|
|
59
|
-
let bodyLock = false
|
|
60
|
-
|
|
61
69
|
const colWidths = reactive<Record<string, string>>({})
|
|
62
70
|
const blockWidth = ref<number | undefined>()
|
|
63
71
|
|
|
@@ -114,25 +122,80 @@ const recordsWithSummary = computed(() => {
|
|
|
114
122
|
return summary ? [...records, summary] : records
|
|
115
123
|
})
|
|
116
124
|
|
|
125
|
+
const indexes = computed(() => {
|
|
126
|
+
if (!props.selected) {
|
|
127
|
+
return []
|
|
128
|
+
}
|
|
129
|
+
const records = unref(props.options.records) ?? []
|
|
130
|
+
const indexField = unref(props.options.indexField)
|
|
131
|
+
|
|
132
|
+
return records.map((record, i) => indexField ? record[indexField] : i)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const selectedIndexes = reactive(new Set())
|
|
136
|
+
|
|
137
|
+
const control = computed({
|
|
138
|
+
get() {
|
|
139
|
+
if (!props.selected) {
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
const selected = indexes.value.filter((index) => {
|
|
143
|
+
return selectedIndexes.has(index)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
updateSelected(selected)
|
|
147
|
+
|
|
148
|
+
return selected.length === indexes.value.length
|
|
149
|
+
? true
|
|
150
|
+
: selected.length ? 'indeterminate' : false
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
set(newValue) {
|
|
154
|
+
if (newValue === false) {
|
|
155
|
+
selectedIndexes.clear()
|
|
156
|
+
} else if (newValue === true) {
|
|
157
|
+
indexes.value.forEach((index) => {
|
|
158
|
+
selectedIndexes.add(index)
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
watch(indexes, (newValue, oldValue) => {
|
|
165
|
+
if (!props.selected) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
xor(newValue, oldValue).forEach((index) => {
|
|
169
|
+
selectedIndexes.delete(index)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
117
173
|
const virtualizerOptions = computed(() => ({
|
|
118
174
|
count: recordsWithSummary.value.length,
|
|
119
175
|
getScrollElement: () => body.value,
|
|
120
|
-
estimateSize: () =>
|
|
176
|
+
estimateSize: (index: number) => {
|
|
177
|
+
const rowSize = unref(props.options.rowSize) ?? 40
|
|
178
|
+
const borderSize = unref(props.options.borderSize) ?? 1
|
|
179
|
+
return lastRow(index) ? rowSize : rowSize + borderSize
|
|
180
|
+
},
|
|
121
181
|
overscan: 10
|
|
122
182
|
}))
|
|
123
183
|
|
|
124
184
|
const rowVirtualizer = useVirtualizer(virtualizerOptions)
|
|
125
185
|
const virtualItems = computed(() => rowVirtualizer.value.getVirtualItems())
|
|
126
186
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
187
|
+
let isSyncingHead = false
|
|
188
|
+
let isSyncingBody = false
|
|
189
|
+
|
|
190
|
+
watch(() => unref(props.options.records), () => {
|
|
191
|
+
isSyncingHead = true
|
|
192
|
+
isSyncingBody = true
|
|
130
193
|
}, { flush: 'pre' })
|
|
131
194
|
|
|
132
|
-
watch(() => props.options.records, () => {
|
|
195
|
+
watch(() => unref(props.options.records), () => {
|
|
133
196
|
syncScroll(head.value, body.value)
|
|
134
|
-
|
|
135
|
-
|
|
197
|
+
isSyncingHead = false
|
|
198
|
+
isSyncingBody = false
|
|
136
199
|
}, { flush: 'post' })
|
|
137
200
|
|
|
138
201
|
useResizeObserver(block, ([entry]) => {
|
|
@@ -174,11 +237,19 @@ async function handleResize() {
|
|
|
174
237
|
}
|
|
175
238
|
|
|
176
239
|
function syncHeadScroll() {
|
|
177
|
-
|
|
240
|
+
if (!isSyncingHead) {
|
|
241
|
+
isSyncingBody = true
|
|
242
|
+
syncScroll(head.value, body.value)
|
|
243
|
+
}
|
|
244
|
+
isSyncingHead = false
|
|
178
245
|
}
|
|
179
246
|
|
|
180
247
|
function syncBodyScroll() {
|
|
181
|
-
|
|
248
|
+
if (!isSyncingBody) {
|
|
249
|
+
isSyncingHead = true
|
|
250
|
+
syncScroll(body.value, head.value)
|
|
251
|
+
}
|
|
252
|
+
isSyncingBody = false
|
|
182
253
|
}
|
|
183
254
|
|
|
184
255
|
function syncScroll(from: HTMLElement | null, to: HTMLElement | null) {
|
|
@@ -187,14 +258,6 @@ function syncScroll(from: HTMLElement | null, to: HTMLElement | null) {
|
|
|
187
258
|
}
|
|
188
259
|
}
|
|
189
260
|
|
|
190
|
-
function lockHead(value: boolean) {
|
|
191
|
-
headLock = value
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function lockBody(value: boolean) {
|
|
195
|
-
bodyLock = value
|
|
196
|
-
}
|
|
197
|
-
|
|
198
261
|
function updateColWidth(key: string, value: string, triggeredByUser = false) {
|
|
199
262
|
colWidths[key] = value
|
|
200
263
|
if (triggeredByUser && colToGrow.value >= 0) {
|
|
@@ -220,9 +283,18 @@ function lastRow(index: number) {
|
|
|
220
283
|
}
|
|
221
284
|
|
|
222
285
|
function getCell(key: string, index: number) {
|
|
286
|
+
if (key === '__select') {
|
|
287
|
+
return { type: 'custom' }
|
|
288
|
+
}
|
|
223
289
|
const col = unref(props.options.columns)[key]
|
|
224
290
|
return (isSummary(index) && col?.summaryCell) ? col?.summaryCell : col?.cell
|
|
225
291
|
}
|
|
292
|
+
|
|
293
|
+
function updateSelected(selected: unknown[]) {
|
|
294
|
+
if (xor(selected, props.selected ?? []).length) {
|
|
295
|
+
emit('update:selected', selected)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
226
298
|
</script>
|
|
227
299
|
|
|
228
300
|
<template>
|
|
@@ -233,16 +305,16 @@ function getCell(key: string, index: number) {
|
|
|
233
305
|
:total="unref(options.total)"
|
|
234
306
|
:reset="unref(options.reset)"
|
|
235
307
|
:menu="unref(options.menu)"
|
|
308
|
+
:actions="unref(options.actions)"
|
|
236
309
|
:borderless="unref(options.borderless)"
|
|
237
310
|
:on-reset="options.onReset"
|
|
311
|
+
:selected="selected"
|
|
238
312
|
/>
|
|
239
313
|
|
|
240
314
|
<div class="table" role="grid">
|
|
241
315
|
<div
|
|
242
316
|
class="container head"
|
|
243
317
|
ref="head"
|
|
244
|
-
@mouseenter="lockHead(true)"
|
|
245
|
-
@mouseleave="lockHead(false)"
|
|
246
318
|
@scroll="syncHeadScroll"
|
|
247
319
|
>
|
|
248
320
|
<div class="block" ref="block">
|
|
@@ -251,18 +323,23 @@ function getCell(key: string, index: number) {
|
|
|
251
323
|
v-for="key in ordersToShow"
|
|
252
324
|
:key="key"
|
|
253
325
|
:name="key"
|
|
254
|
-
:class-name="unref(options.columns)[key]
|
|
326
|
+
:class-name="unref(options.columns)[key]?.className"
|
|
255
327
|
:width="colWidths[key]"
|
|
256
328
|
>
|
|
257
329
|
<STableColumn
|
|
258
330
|
:name="key"
|
|
259
|
-
:label="unref(options.columns)[key]
|
|
260
|
-
:class-name="unref(options.columns)[key]
|
|
261
|
-
:dropdown="unref(options.columns)[key]
|
|
331
|
+
:label="unref(options.columns)[key]?.label"
|
|
332
|
+
:class-name="unref(options.columns)[key]?.className"
|
|
333
|
+
:dropdown="unref(options.columns)[key]?.dropdown"
|
|
262
334
|
:has-header="showHeader"
|
|
263
|
-
:resizable="unref(options.columns)[key]
|
|
335
|
+
:resizable="unref(options.columns)[key]?.resizable"
|
|
264
336
|
@resize="(value) => updateColWidth(key, value, true)"
|
|
265
|
-
|
|
337
|
+
>
|
|
338
|
+
<SInputCheckbox
|
|
339
|
+
v-if="key === '__select' && unref(options.records)?.length"
|
|
340
|
+
v-model="control"
|
|
341
|
+
/>
|
|
342
|
+
</STableColumn>
|
|
266
343
|
</STableItem>
|
|
267
344
|
</div>
|
|
268
345
|
</div>
|
|
@@ -272,8 +349,6 @@ function getCell(key: string, index: number) {
|
|
|
272
349
|
v-if="!unref(options.loading) && unref(options.records)?.length"
|
|
273
350
|
class="container body"
|
|
274
351
|
ref="body"
|
|
275
|
-
@mouseenter="lockBody(true)"
|
|
276
|
-
@mouseleave="lockBody(false)"
|
|
277
352
|
@scroll="syncBodyScroll"
|
|
278
353
|
>
|
|
279
354
|
<div
|
|
@@ -304,18 +379,24 @@ function getCell(key: string, index: number) {
|
|
|
304
379
|
v-for="key in ordersToShow"
|
|
305
380
|
:key="key"
|
|
306
381
|
:name="key"
|
|
307
|
-
:class-name="unref(options.columns)[key]
|
|
382
|
+
:class-name="unref(options.columns)[key]?.className"
|
|
308
383
|
:width="colWidths[key]"
|
|
309
384
|
>
|
|
310
385
|
<STableCell
|
|
311
386
|
:name="key"
|
|
312
387
|
:class="isSummary(index) && 'summary'"
|
|
313
|
-
:class-name="unref(options.columns)[key]
|
|
388
|
+
:class-name="unref(options.columns)[key]?.className"
|
|
314
389
|
:cell="getCell(key, index)"
|
|
315
390
|
:value="recordsWithSummary[index][key]"
|
|
316
391
|
:record="recordsWithSummary[index]"
|
|
317
392
|
:records="unref(options.records)!"
|
|
318
|
-
|
|
393
|
+
>
|
|
394
|
+
<SInputCheckbox
|
|
395
|
+
v-if="key === '__select' && !isSummary(index)"
|
|
396
|
+
:value="selectedIndexes.has(indexes[index])"
|
|
397
|
+
@change="c => selectedIndexes[c ? 'add' : 'delete'](indexes[index])"
|
|
398
|
+
/>
|
|
399
|
+
</STableCell>
|
|
319
400
|
</STableItem>
|
|
320
401
|
</div>
|
|
321
402
|
</div>
|
|
@@ -380,9 +461,10 @@ function getCell(key: string, index: number) {
|
|
|
380
461
|
position: var(--table-head-position, static);
|
|
381
462
|
top: var(--table-head-top, auto);
|
|
382
463
|
z-index: 100;
|
|
383
|
-
border-radius: var(--table-border-radius) var(--table-border-radius) 0 0;
|
|
464
|
+
border-radius: calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px) 0 0;
|
|
384
465
|
background-color: var(--bg-elv-2);
|
|
385
466
|
scrollbar-width: none;
|
|
467
|
+
line-height: 0;
|
|
386
468
|
|
|
387
469
|
&::-webkit-scrollbar {
|
|
388
470
|
display: none;
|
|
@@ -394,7 +476,7 @@ function getCell(key: string, index: number) {
|
|
|
394
476
|
}
|
|
395
477
|
|
|
396
478
|
.container.body {
|
|
397
|
-
border-radius:
|
|
479
|
+
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
|
|
398
480
|
line-height: 0;
|
|
399
481
|
max-height: var(--table-max-height, 100%);
|
|
400
482
|
|
|
@@ -419,7 +501,7 @@ function getCell(key: string, index: number) {
|
|
|
419
501
|
}
|
|
420
502
|
|
|
421
503
|
.missing {
|
|
422
|
-
border-radius: 0 0
|
|
504
|
+
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
|
|
423
505
|
padding: 48px 32px;
|
|
424
506
|
text-align: center;
|
|
425
507
|
background-color: var(--c-bg-elv-3);
|
|
@@ -427,10 +509,14 @@ function getCell(key: string, index: number) {
|
|
|
427
509
|
font-size: 14px;
|
|
428
510
|
font-weight: 500;
|
|
429
511
|
color: var(--c-text-3);
|
|
512
|
+
|
|
513
|
+
.has-footer & {
|
|
514
|
+
border-radius: 0;
|
|
515
|
+
}
|
|
430
516
|
}
|
|
431
517
|
|
|
432
518
|
.loading {
|
|
433
|
-
border-radius: 0 0
|
|
519
|
+
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
|
|
434
520
|
padding: 64px 32px;
|
|
435
521
|
background-color: var(--c-bg-elv-3);
|
|
436
522
|
}
|
|
@@ -446,4 +532,19 @@ function getCell(key: string, index: number) {
|
|
|
446
532
|
height: 48px;
|
|
447
533
|
color: var(--c-text-1);
|
|
448
534
|
}
|
|
535
|
+
|
|
536
|
+
.STable .col-__select {
|
|
537
|
+
--table-padding-left: 0;
|
|
538
|
+
--table-col-width: 48px;
|
|
539
|
+
|
|
540
|
+
:deep(.input) {
|
|
541
|
+
align-items: center;
|
|
542
|
+
padding: 0 16px;
|
|
543
|
+
min-height: 40px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
:deep(.container) {
|
|
547
|
+
padding: 0;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
449
550
|
</style>
|
|
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|
|
3
3
|
import { type TableCell } from '../composables/Table'
|
|
4
4
|
import STableCellAvatar from './STableCellAvatar.vue'
|
|
5
5
|
import STableCellAvatars from './STableCellAvatars.vue'
|
|
6
|
+
import STableCellCustom from './STableCellCustom.vue'
|
|
6
7
|
import STableCellDay from './STableCellDay.vue'
|
|
7
8
|
import STableCellEmpty from './STableCellEmpty.vue'
|
|
8
9
|
import STableCellNumber from './STableCellNumber.vue'
|
|
@@ -98,6 +99,11 @@ const computedCell = computed<TableCell | undefined>(() =>
|
|
|
98
99
|
:avatars="computedCell.avatars"
|
|
99
100
|
:color="computedCell.color"
|
|
100
101
|
/>
|
|
102
|
+
<STableCellCustom
|
|
103
|
+
v-else-if="computedCell.type === 'custom'"
|
|
104
|
+
>
|
|
105
|
+
<slot />
|
|
106
|
+
</STableCellCustom>
|
|
101
107
|
<STableCellEmpty
|
|
102
108
|
v-else-if="computedCell.type === 'empty'"
|
|
103
109
|
/>
|
|
@@ -125,21 +125,23 @@ function stopDialogPositionListener() {
|
|
|
125
125
|
<template>
|
|
126
126
|
<div class="STableColumn STableCell" :class="classes" ref="column">
|
|
127
127
|
<div class="container">
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
<
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
128
|
+
<slot>
|
|
129
|
+
<p class="label">{{ label }}</p>
|
|
130
|
+
|
|
131
|
+
<div v-if="dropdown" class="action" ref="container">
|
|
132
|
+
<button class="button" :class="{ active: buttonActive }" @click="toggle">
|
|
133
|
+
<SIcon :icon="IconDotsThree" class="icon" />
|
|
134
|
+
</button>
|
|
135
|
+
|
|
136
|
+
<transition name="fade">
|
|
137
|
+
<div v-if="isOpen" class="dialog" :style="{ top, left }" ref="dialog">
|
|
138
|
+
<SDropdown :sections="dropdown" />
|
|
139
|
+
</div>
|
|
140
|
+
</transition>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div v-if="resizable" class="grip" @mousedown="grip" />
|
|
144
|
+
</slot>
|
|
143
145
|
</div>
|
|
144
146
|
</div>
|
|
145
147
|
</template>
|
|
@@ -239,13 +241,12 @@ function stopDialogPositionListener() {
|
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
.grip {
|
|
242
|
-
position:
|
|
244
|
+
position: absolute;
|
|
243
245
|
right: -8px;
|
|
244
246
|
top: 0px;
|
|
245
247
|
bottom: 0px;
|
|
246
248
|
width: 16px;
|
|
247
249
|
z-index: 1;
|
|
248
|
-
position: absolute;
|
|
249
250
|
cursor: col-resize;
|
|
250
251
|
|
|
251
252
|
&::before {
|
|
@@ -57,7 +57,7 @@ const hasNext = computed(() => {
|
|
|
57
57
|
<style scoped lang="postcss">
|
|
58
58
|
.STableFooter {
|
|
59
59
|
border-top: 1px solid var(--c-divider-2);
|
|
60
|
-
border-radius: 0 0
|
|
60
|
+
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
|
|
61
61
|
padding-right: var(--table-padding-right);
|
|
62
62
|
padding-left: var(--table-padding-left);
|
|
63
63
|
background-color: var(--c-bg-elv-3);
|
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { type TableAction, type TableMenu } from '../composables/Table'
|
|
3
4
|
import { format } from '../support/Num'
|
|
4
5
|
import { isNullish } from '../support/Utils'
|
|
6
|
+
import STableHeaderActions from './STableHeaderActions.vue'
|
|
5
7
|
import STableHeaderMenu from './STableHeaderMenu.vue'
|
|
6
8
|
|
|
7
|
-
defineProps<{
|
|
9
|
+
const props = defineProps<{
|
|
8
10
|
total?: number | null
|
|
9
11
|
reset?: boolean
|
|
10
12
|
menu?: TableMenu[] | TableMenu[][]
|
|
13
|
+
actions?: TableAction[]
|
|
11
14
|
borderless?: boolean
|
|
12
15
|
onReset?(): void
|
|
16
|
+
selected?: unknown[]
|
|
13
17
|
}>()
|
|
18
|
+
|
|
19
|
+
const stats = computed(() => {
|
|
20
|
+
if (isNullish(props.total)) {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return props.selected?.length
|
|
25
|
+
? `${format(props.selected.length)} of ${props.total} selected`
|
|
26
|
+
: `${format(props.total)} ${props.total > 1 ? 'records' : 'record'}`
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// deprecated `reset` prop in favor of `actions`, remove this in next major version
|
|
30
|
+
const resetAction = computed(() => {
|
|
31
|
+
return props.reset
|
|
32
|
+
? [{ label: 'Reset filters', onClick: props.onReset, type: 'info' }]
|
|
33
|
+
: []
|
|
34
|
+
})
|
|
14
35
|
</script>
|
|
15
36
|
|
|
16
37
|
<template>
|
|
17
38
|
<div class="STableHeader" :class="{ borderless }">
|
|
18
39
|
<div class="container">
|
|
19
|
-
<div class="
|
|
20
|
-
<
|
|
21
|
-
{{
|
|
22
|
-
</
|
|
23
|
-
<div v-if="
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
<div class="primary">
|
|
41
|
+
<div v-if="stats" class="stats">
|
|
42
|
+
{{ stats }}
|
|
43
|
+
</div>
|
|
44
|
+
<div v-if="actions?.length" class="actions">
|
|
45
|
+
<STableHeaderActions :actions="actions" />
|
|
46
|
+
</div>
|
|
47
|
+
<div v-else-if="resetAction.length">
|
|
48
|
+
<STableHeaderActions :actions="resetAction" />
|
|
27
49
|
</div>
|
|
28
50
|
</div>
|
|
29
51
|
<div v-if="menu && menu.length" class="menu">
|
|
@@ -35,10 +57,14 @@ defineProps<{
|
|
|
35
57
|
|
|
36
58
|
<style scoped lang="postcss">
|
|
37
59
|
.STableHeader {
|
|
38
|
-
border-radius:
|
|
60
|
+
border-radius: calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px) 0 0;
|
|
39
61
|
padding-right: var(--table-padding-right);
|
|
40
62
|
padding-left: var(--table-padding-left);
|
|
41
63
|
background-color: var(--c-bg-soft);
|
|
64
|
+
|
|
65
|
+
&.borderless {
|
|
66
|
+
border-radius: 0;
|
|
67
|
+
}
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
.container {
|
|
@@ -46,14 +72,15 @@ defineProps<{
|
|
|
46
72
|
min-height: 48px;
|
|
47
73
|
}
|
|
48
74
|
|
|
49
|
-
.
|
|
75
|
+
.primary {
|
|
50
76
|
display: flex;
|
|
77
|
+
gap: 16px;
|
|
51
78
|
flex-grow: 1;
|
|
52
79
|
padding: 0 16px;
|
|
80
|
+
min-height: 48px;
|
|
53
81
|
}
|
|
54
82
|
|
|
55
|
-
.
|
|
56
|
-
margin: 0;
|
|
83
|
+
.stats {
|
|
57
84
|
padding: 12px 0;
|
|
58
85
|
line-height: 24px;
|
|
59
86
|
font-size: 12px;
|
|
@@ -61,37 +88,6 @@ defineProps<{
|
|
|
61
88
|
color: var(--c-text-2);
|
|
62
89
|
}
|
|
63
90
|
|
|
64
|
-
.reset {
|
|
65
|
-
position: relative;
|
|
66
|
-
|
|
67
|
-
.total + & {
|
|
68
|
-
margin-left: 16px;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.total + &::before {
|
|
72
|
-
display: inline-block;
|
|
73
|
-
margin-right: 16px;
|
|
74
|
-
width: 1px;
|
|
75
|
-
height: 16px;
|
|
76
|
-
background-color: var(--c-divider-2);
|
|
77
|
-
content: "";
|
|
78
|
-
transform: translateY(4px);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.button {
|
|
83
|
-
padding: 12px 0;
|
|
84
|
-
line-height: 24px;
|
|
85
|
-
font-size: 12px;
|
|
86
|
-
font-weight: 500;
|
|
87
|
-
color: var(--c-info-text);
|
|
88
|
-
transition: color 0.25s;
|
|
89
|
-
|
|
90
|
-
&:hover {
|
|
91
|
-
color: var(--c-info-text-dark);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
91
|
.menu {
|
|
96
92
|
flex-shrink: 0;
|
|
97
93
|
padding-right: 8px;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type TableAction } from '../composables/Table'
|
|
3
|
+
import SButton from './SButton.vue'
|
|
4
|
+
|
|
5
|
+
withDefaults(defineProps<TableAction>(), {
|
|
6
|
+
show: true,
|
|
7
|
+
mode: 'mute',
|
|
8
|
+
labelMode: 'neutral'
|
|
9
|
+
})
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div v-if="show" class="STableHeaderActionItem">
|
|
14
|
+
<SButton
|
|
15
|
+
size="mini"
|
|
16
|
+
:mode="mode"
|
|
17
|
+
:label="label"
|
|
18
|
+
:label-mode="labelMode"
|
|
19
|
+
@click="onClick"
|
|
20
|
+
/>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped lang="postcss">
|
|
25
|
+
.STableHeaderActionItem {
|
|
26
|
+
padding: 10px 0;
|
|
27
|
+
}
|
|
28
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type TableAction } from '../composables/Table'
|
|
3
|
+
import STableHeaderActionItem from './STableHeaderActionItem.vue'
|
|
4
|
+
|
|
5
|
+
defineProps<{ actions: TableAction[] }>()
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div class="STableHeaderActions">
|
|
10
|
+
<STableHeaderActionItem v-for="action in actions" :key="action.label" v-bind="action" />
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<style scoped lang="postcss">
|
|
15
|
+
.STableHeaderActions {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-grow: 1;
|
|
18
|
+
gap: 8px;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type Component, type Ref, ref, unref } from 'vue'
|
|
1
|
+
import { useElementBounding, useWindowSize } from '@vueuse/core'
|
|
2
|
+
import { type Component, type MaybeRef, type Ref, ref, unref } from 'vue'
|
|
3
3
|
|
|
4
4
|
export type DropdownSection =
|
|
5
5
|
| DropdownSectionMenu
|
package/lib/composables/Table.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { type MaybeRef } from '
|
|
2
|
-
import { type Component } from 'vue'
|
|
1
|
+
import { type Component, type MaybeRef } from 'vue'
|
|
3
2
|
import { type Day } from '../support/Day'
|
|
4
3
|
import { type DropdownSection } from './Dropdown'
|
|
5
4
|
|
|
6
5
|
export interface Table<
|
|
7
|
-
O extends string =
|
|
6
|
+
O extends string = string,
|
|
8
7
|
R extends Record<string, any> = any,
|
|
9
8
|
SR extends Record<string, any> = any
|
|
10
9
|
> {
|
|
11
10
|
orders: MaybeRef<O[]>
|
|
12
11
|
columns: MaybeRef<TableColumns<O, R, SR>>
|
|
13
12
|
menu?: MaybeRef<TableMenu[] | TableMenu[][]>
|
|
13
|
+
actions?: MaybeRef<TableAction[]>
|
|
14
14
|
records?: MaybeRef<R[] | null | undefined>
|
|
15
15
|
header?: MaybeRef<boolean | undefined>
|
|
16
16
|
footer?: MaybeRef<boolean | undefined>
|
|
@@ -18,12 +18,16 @@ export interface Table<
|
|
|
18
18
|
total?: MaybeRef<number | null | undefined>
|
|
19
19
|
page?: MaybeRef<number | null | undefined>
|
|
20
20
|
perPage?: MaybeRef<number | null | undefined>
|
|
21
|
+
/** @deprecated use `actions` instead */
|
|
21
22
|
reset?: MaybeRef<boolean | undefined>
|
|
22
23
|
borderless?: MaybeRef<boolean>
|
|
23
24
|
loading?: MaybeRef<boolean | undefined>
|
|
24
25
|
rowSize?: MaybeRef<number | undefined>
|
|
26
|
+
borderSize?: MaybeRef<number | undefined>
|
|
27
|
+
indexField?: keyof R
|
|
25
28
|
onPrev?(): void
|
|
26
29
|
onNext?(): void
|
|
30
|
+
/** @deprecated use `actions` instead */
|
|
27
31
|
onReset?(): void
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -57,6 +61,7 @@ export type TableCell =
|
|
|
57
61
|
| TableCellState
|
|
58
62
|
| TableCellAvatar
|
|
59
63
|
| TableCellAvatars
|
|
64
|
+
| TableCellCustom
|
|
60
65
|
| TableCellEmpty
|
|
61
66
|
| TableCellComponent
|
|
62
67
|
|
|
@@ -69,6 +74,7 @@ export type TableCellType =
|
|
|
69
74
|
| 'state'
|
|
70
75
|
| 'avatar'
|
|
71
76
|
| 'avatars'
|
|
77
|
+
| 'custom'
|
|
72
78
|
| 'empty'
|
|
73
79
|
| 'component'
|
|
74
80
|
|
|
@@ -154,6 +160,10 @@ export interface TableCellAvatarsOption {
|
|
|
154
160
|
name?: string | null
|
|
155
161
|
}
|
|
156
162
|
|
|
163
|
+
export interface TableCellCustom extends TableCellBase {
|
|
164
|
+
type: 'custom'
|
|
165
|
+
}
|
|
166
|
+
|
|
157
167
|
export interface TableCellEmpty extends TableCellBase {
|
|
158
168
|
type: 'empty'
|
|
159
169
|
}
|
|
@@ -176,6 +186,14 @@ export interface TableMenu {
|
|
|
176
186
|
dropdown: DropdownSection[]
|
|
177
187
|
}
|
|
178
188
|
|
|
189
|
+
export interface TableAction {
|
|
190
|
+
show?: boolean
|
|
191
|
+
mode?: 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
|
|
192
|
+
label: string
|
|
193
|
+
labelMode?: 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
|
|
194
|
+
onClick?(): void
|
|
195
|
+
}
|
|
196
|
+
|
|
179
197
|
export function useTable<
|
|
180
198
|
O extends string,
|
|
181
199
|
R extends Record<string, any>,
|
package/lib/composables/Utils.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { type MaybeRefOrGetter,
|
|
2
|
-
import { type ComputedRef, computed, useSlots } from 'vue'
|
|
1
|
+
import { type ComputedRef, type MaybeRefOrGetter, computed, toValue, useSlots } from 'vue'
|
|
3
2
|
import { isArray, isString } from '../support/Utils'
|
|
4
3
|
|
|
5
4
|
export type WhenCondition<T> = MaybeRefOrGetter<T>
|
|
@@ -21,7 +20,7 @@ export function computedWhen<T, C, D>(
|
|
|
21
20
|
whenFalse?: D
|
|
22
21
|
): ComputedRef<T | D> {
|
|
23
22
|
return computed(() => {
|
|
24
|
-
const c =
|
|
23
|
+
const c = toValue(condition)
|
|
25
24
|
|
|
26
25
|
return c ? fn(c) : whenFalse as D
|
|
27
26
|
})
|
package/lib/styles/bootstrap.css
CHANGED
package/lib/types/shims.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globalbrain/sefirot",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"packageManager": "pnpm@8.7.
|
|
3
|
+
"version": "2.47.0",
|
|
4
|
+
"packageManager": "pnpm@8.7.5",
|
|
5
5
|
"description": "Vue Components for Global Brain Design System.",
|
|
6
6
|
"author": "Kia Ishii <ka.ishii@globalbrains.com>",
|
|
7
7
|
"license": "MIT",
|