@globalbrain/sefirot 4.10.0 → 4.12.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/SDescFile.vue +1 -1
- package/lib/components/SDescItem.vue +11 -1
- package/lib/components/SDescLabel.vue +67 -6
- package/lib/components/SInputBase.vue +41 -18
- package/lib/components/SInputDropdown.vue +49 -29
- package/lib/components/SInputDropdownItem.vue +255 -25
- package/lib/components/SInputNumber.vue +16 -25
- package/lib/components/SInputText.vue +19 -23
- package/lib/components/SInputTextarea.vue +19 -23
- package/lib/composables/App.ts +22 -0
- package/lib/composables/Error.ts +3 -1
- package/lib/composables/Http.ts +17 -0
- package/lib/composables/Lang.ts +18 -6
- package/lib/composables/Theme.ts +25 -0
- package/lib/composables/Url.ts +12 -3
- package/lib/styles/variables.css +2 -0
- package/package.json +1 -1
- package/lib/components/SInputDropdownItemAvatar.vue +0 -175
- package/lib/components/SInputDropdownItemText.vue +0 -154
|
@@ -29,7 +29,17 @@ const labelWidth = computed(() => {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.SDesc.row > .SDescItem {
|
|
32
|
-
|
|
32
|
+
& {
|
|
33
|
+
grid-template-columns: var(--desc-label-width, v-bind(labelWidth)) minmax(0, 1fr);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
& > :deep(.SDescLabel) {
|
|
37
|
+
height: 24px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
& > :deep(.SDescLabel > .value) {
|
|
41
|
+
line-height: 24px;
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
.SDesc.divider > .SDescItem:not(:has(> .SDescFile)) {
|
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
2
|
+
import { type Component } from 'vue'
|
|
3
|
+
import { type ActionList } from './SActionList.vue'
|
|
4
|
+
import SActionMenu from './SActionMenu.vue'
|
|
5
|
+
import SButton, { type Mode, type Tooltip } from './SButton.vue'
|
|
6
|
+
|
|
7
|
+
export interface Props {
|
|
3
8
|
value?: string | null
|
|
4
|
-
|
|
9
|
+
actions?: Action[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Action = ActionButton | ActionMenu
|
|
13
|
+
|
|
14
|
+
export interface ActionBase {
|
|
15
|
+
type?: 'button' | 'menu'
|
|
16
|
+
mode?: Mode
|
|
17
|
+
icon: Component
|
|
18
|
+
loading?: boolean
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
tooltip?: string | Tooltip
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ActionButton extends ActionBase {
|
|
24
|
+
type?: 'button'
|
|
25
|
+
onClick(): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ActionMenu extends ActionBase {
|
|
29
|
+
type: 'menu'
|
|
30
|
+
options: ActionList
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
defineProps<Props>()
|
|
34
|
+
|
|
35
|
+
function isActionButton(action: Action): action is ActionButton {
|
|
36
|
+
return (action.type ?? 'button') === 'button'
|
|
37
|
+
}
|
|
5
38
|
</script>
|
|
6
39
|
|
|
7
40
|
<template>
|
|
@@ -10,19 +43,47 @@ defineProps<{
|
|
|
10
43
|
<slot v-if="$slots.default" />
|
|
11
44
|
<template v-else>{{ value }}</template>
|
|
12
45
|
</div>
|
|
46
|
+
<div v-if="actions?.length" class="actions">
|
|
47
|
+
<template v-for="action, index in actions" :key="index">
|
|
48
|
+
<SButton
|
|
49
|
+
v-if="isActionButton(action)"
|
|
50
|
+
type="text"
|
|
51
|
+
size="mini"
|
|
52
|
+
:mode="action.mode ?? 'mute'"
|
|
53
|
+
:icon="action.icon"
|
|
54
|
+
:loading="action.loading"
|
|
55
|
+
:disabled="action.disabled"
|
|
56
|
+
:tooltip="action.tooltip"
|
|
57
|
+
@click="action.onClick"
|
|
58
|
+
/>
|
|
59
|
+
<SActionMenu
|
|
60
|
+
v-else
|
|
61
|
+
type="text"
|
|
62
|
+
size="mini"
|
|
63
|
+
:mode="action.mode ?? 'mute'"
|
|
64
|
+
:icon="action.icon"
|
|
65
|
+
:loading="action.loading"
|
|
66
|
+
:disabled="action.disabled"
|
|
67
|
+
:tooltip="action.tooltip"
|
|
68
|
+
:options="[{ type: 'menu', options: action.options }]"
|
|
69
|
+
/>
|
|
70
|
+
</template>
|
|
71
|
+
</div>
|
|
13
72
|
</div>
|
|
14
73
|
</template>
|
|
15
74
|
|
|
16
75
|
<style scoped lang="postcss">
|
|
17
76
|
.SDescLabel {
|
|
18
77
|
display: flex;
|
|
19
|
-
|
|
78
|
+
gap: 16px;
|
|
79
|
+
height: 28px;
|
|
20
80
|
}
|
|
21
81
|
|
|
22
82
|
.value {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
font-
|
|
83
|
+
flex-grow: 1;
|
|
84
|
+
line-height: 28px;
|
|
85
|
+
font-size: 14px;
|
|
86
|
+
font-weight: 400;
|
|
26
87
|
color: var(--c-text-2);
|
|
27
88
|
white-space: nowrap;
|
|
28
89
|
overflow: hidden;
|
|
@@ -4,9 +4,8 @@ import { type Component, computed, unref, useSlots } from 'vue'
|
|
|
4
4
|
import { type Validatable } from '../composables/Validation'
|
|
5
5
|
import STooltip from './STooltip.vue'
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const props = defineProps<{
|
|
7
|
+
export interface Props {
|
|
8
|
+
size?: Size
|
|
10
9
|
name?: string
|
|
11
10
|
label?: string
|
|
12
11
|
info?: string
|
|
@@ -16,11 +15,24 @@ const props = defineProps<{
|
|
|
16
15
|
checkText?: string
|
|
17
16
|
checkColor?: Color
|
|
18
17
|
validation?: Validatable
|
|
18
|
+
warning?: string
|
|
19
19
|
hideError?: boolean
|
|
20
|
-
|
|
20
|
+
hideWarning?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Size = 'mini' | 'small' | 'medium'
|
|
24
|
+
export type Color = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
|
|
25
|
+
|
|
26
|
+
const props = defineProps<Props>()
|
|
21
27
|
|
|
22
28
|
const slots = useSlots()
|
|
23
29
|
|
|
30
|
+
const classes = computed(() => [
|
|
31
|
+
props.size ?? 'small',
|
|
32
|
+
{ 'has-error': error.value?.has },
|
|
33
|
+
{ 'has-warning': props.warning }
|
|
34
|
+
])
|
|
35
|
+
|
|
24
36
|
const hasInfo = computed(() => {
|
|
25
37
|
return slots.info || props.info
|
|
26
38
|
})
|
|
@@ -53,7 +65,7 @@ function getErrorMsg(validation: Validatable) {
|
|
|
53
65
|
</script>
|
|
54
66
|
|
|
55
67
|
<template>
|
|
56
|
-
<div class="SInputBase" :class="
|
|
68
|
+
<div class="SInputBase" :class="classes">
|
|
57
69
|
<label v-if="label" class="label" :for="name">
|
|
58
70
|
<span class="label-text">{{ label }}</span>
|
|
59
71
|
|
|
@@ -76,8 +88,9 @@ function getErrorMsg(validation: Validatable) {
|
|
|
76
88
|
|
|
77
89
|
<div class="help">
|
|
78
90
|
<slot name="before-help" />
|
|
79
|
-
<p v-if="error?.show" class="help-error">{{ error.msg }}</p>
|
|
80
|
-
<p v-if="
|
|
91
|
+
<p v-if="error?.show" class="help-value help-error">{{ error.msg }}</p>
|
|
92
|
+
<p v-if="warning && !hideWarning" class="help-value help-warning">{{ warning }}</p>
|
|
93
|
+
<p v-if="help" class="help-value help-text">{{ help }}</p>
|
|
81
94
|
</div>
|
|
82
95
|
</div>
|
|
83
96
|
</template>
|
|
@@ -102,12 +115,19 @@ function getErrorMsg(validation: Validatable) {
|
|
|
102
115
|
.label-text { font-size: var(--input-label-font-size, var(--input-medium-label-font-size)); }
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
.SInputBase.has-error
|
|
118
|
+
.SInputBase.has-error,
|
|
119
|
+
.SInputBase.has-warning.has-error {
|
|
106
120
|
.label-text {
|
|
107
121
|
color: var(--input-error-text-color);
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
|
|
125
|
+
.SInputBase.has-warning {
|
|
126
|
+
.label-text {
|
|
127
|
+
color: var(--input-warning-text-color);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
111
131
|
.label {
|
|
112
132
|
display: flex;
|
|
113
133
|
align-items: flex-start;
|
|
@@ -118,7 +138,7 @@ function getErrorMsg(validation: Validatable) {
|
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
.label-text {
|
|
121
|
-
font-weight:
|
|
141
|
+
font-weight: 400;
|
|
122
142
|
color: var(--input-label-color);
|
|
123
143
|
transition: color 0.25s;
|
|
124
144
|
}
|
|
@@ -173,29 +193,32 @@ function getErrorMsg(validation: Validatable) {
|
|
|
173
193
|
position: relative;
|
|
174
194
|
}
|
|
175
195
|
|
|
176
|
-
.help-
|
|
196
|
+
.help-value {
|
|
177
197
|
width: 100%;
|
|
178
198
|
max-width: 65ch;
|
|
179
199
|
margin: 0;
|
|
180
200
|
padding: 6px 0 0 0;
|
|
181
|
-
line-height:
|
|
201
|
+
line-height: 20px;
|
|
182
202
|
font-size: 12px;
|
|
183
203
|
font-weight: 400;
|
|
184
|
-
color: var(--input-error-text-color);
|
|
185
204
|
transition: opacity 0.25s, transform 0.25s;
|
|
186
205
|
}
|
|
187
206
|
|
|
207
|
+
.help-error {
|
|
208
|
+
color: var(--input-error-text-color);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.help-warning {
|
|
212
|
+
color: var(--input-warning-text-color);
|
|
213
|
+
}
|
|
214
|
+
|
|
188
215
|
.help-text {
|
|
189
|
-
max-width: 65ch;
|
|
190
|
-
margin: 0;
|
|
191
|
-
padding: 6px 0 0;
|
|
192
|
-
line-height: 20px;
|
|
193
|
-
font-size: 12px;
|
|
194
|
-
font-weight: 400;
|
|
195
216
|
color: var(--c-text-2);
|
|
196
217
|
}
|
|
197
218
|
|
|
219
|
+
.help-error + .help-warning,
|
|
198
220
|
.help-error + .help-text,
|
|
221
|
+
.help-warning + .help-text,
|
|
199
222
|
.help-text + .help-error,
|
|
200
223
|
.help-text + .help-text {
|
|
201
224
|
padding: 0;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import IconCaretDown from '~icons/ph/caret-down
|
|
3
|
-
import IconCaretUp from '~icons/ph/caret-up
|
|
2
|
+
import IconCaretDown from '~icons/ph/caret-down'
|
|
3
|
+
import IconCaretUp from '~icons/ph/caret-up'
|
|
4
4
|
import xor from 'lodash-es/xor'
|
|
5
5
|
import { type Component, computed, ref } from 'vue'
|
|
6
6
|
import { type DropdownSectionFilter, useManualDropdownPosition } from '../composables/Dropdown'
|
|
7
7
|
import { useFlyout } from '../composables/Flyout'
|
|
8
|
+
import { useTrans } from '../composables/Lang'
|
|
8
9
|
import { type Validatable } from '../composables/Validation'
|
|
9
10
|
import SDropdown from './SDropdown.vue'
|
|
10
11
|
import SInputBase from './SInputBase.vue'
|
|
@@ -52,13 +53,19 @@ const props = defineProps<{
|
|
|
52
53
|
nullable?: boolean
|
|
53
54
|
closeOnClick?: boolean
|
|
54
55
|
disabled?: boolean
|
|
55
|
-
modelValue: PrimitiveValue | ArrayValue
|
|
56
56
|
validation?: Validatable
|
|
57
57
|
}>()
|
|
58
58
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
}
|
|
59
|
+
const model = defineModel<PrimitiveValue | ArrayValue>({ required: true })
|
|
60
|
+
|
|
61
|
+
const { t } = useTrans({
|
|
62
|
+
en: {
|
|
63
|
+
ph: 'Select items'
|
|
64
|
+
},
|
|
65
|
+
ja: {
|
|
66
|
+
ph: '項目を選択してください'
|
|
67
|
+
}
|
|
68
|
+
})
|
|
62
69
|
|
|
63
70
|
const container = ref<any>(null)
|
|
64
71
|
|
|
@@ -73,28 +80,28 @@ const classes = computed(() => [
|
|
|
73
80
|
const dropdownOptions = computed<DropdownSectionFilter[]>(() => [{
|
|
74
81
|
type: 'filter',
|
|
75
82
|
search: props.noSearch === undefined ? true : !props.noSearch,
|
|
76
|
-
selected:
|
|
83
|
+
selected: model.value,
|
|
77
84
|
options: props.options,
|
|
78
85
|
onClick: handleSelect
|
|
79
86
|
}])
|
|
80
87
|
|
|
81
88
|
const selected = computed(() => {
|
|
82
|
-
if (Array.isArray(
|
|
83
|
-
return props.options.filter((o) => (
|
|
89
|
+
if (Array.isArray(model.value)) {
|
|
90
|
+
return props.options.filter((o) => (model.value as ArrayValue).includes(o.value))
|
|
84
91
|
}
|
|
85
92
|
|
|
86
|
-
const item = props.options.find((o) => o.value ===
|
|
93
|
+
const item = props.options.find((o) => o.value === model.value)
|
|
87
94
|
|
|
88
|
-
return item
|
|
95
|
+
return item ?? null
|
|
89
96
|
})
|
|
90
97
|
|
|
91
98
|
const hasSelected = computed(() => {
|
|
92
|
-
return selected.value.length > 0
|
|
99
|
+
return Array.isArray(selected.value) ? selected.value.length > 0 : !!selected.value
|
|
93
100
|
})
|
|
94
101
|
|
|
95
102
|
const removable = computed(() => {
|
|
96
|
-
if (Array.isArray(
|
|
97
|
-
return props.nullable || selected.value.length > 1
|
|
103
|
+
if (Array.isArray(model.value)) {
|
|
104
|
+
return props.nullable || (selected.value as Option[]).length > 1
|
|
98
105
|
}
|
|
99
106
|
|
|
100
107
|
return !!props.nullable
|
|
@@ -110,27 +117,25 @@ async function handleOpen() {
|
|
|
110
117
|
function handleSelect(value: OptionValue) {
|
|
111
118
|
props.validation?.$touch()
|
|
112
119
|
|
|
113
|
-
Array.isArray(
|
|
120
|
+
Array.isArray(model.value) ? handleArray(value) : handlePrimitive(value)
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
function handlePrimitive(value: OptionValue) {
|
|
117
|
-
if (value !==
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (props.nullable) {
|
|
122
|
-
emit('update:modelValue', null)
|
|
124
|
+
if (value !== model.value) {
|
|
125
|
+
model.value = value
|
|
126
|
+
} else if (props.nullable) {
|
|
127
|
+
model.value = null
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
function handleArray(value: OptionValue) {
|
|
127
|
-
const difference = xor(
|
|
132
|
+
const difference = xor(model.value as ArrayValue, [value])
|
|
128
133
|
|
|
129
134
|
if (!props.nullable && difference.length === 0) {
|
|
130
135
|
return
|
|
131
136
|
}
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
model.value = difference
|
|
134
139
|
}
|
|
135
140
|
</script>
|
|
136
141
|
|
|
@@ -160,14 +165,14 @@ function handleArray(value: OptionValue) {
|
|
|
160
165
|
<div class="box-content">
|
|
161
166
|
<SInputDropdownItem
|
|
162
167
|
v-if="hasSelected"
|
|
163
|
-
:
|
|
168
|
+
:item="selected!"
|
|
164
169
|
:size="size ?? 'small'"
|
|
165
170
|
:removable="removable"
|
|
166
171
|
:disabled="disabled ?? false"
|
|
167
172
|
@remove="handleSelect"
|
|
168
173
|
/>
|
|
169
174
|
|
|
170
|
-
<div v-else class="box-placeholder">{{ placeholder }}</div>
|
|
175
|
+
<div v-else class="box-placeholder">{{ placeholder ?? t.ph }}</div>
|
|
171
176
|
</div>
|
|
172
177
|
|
|
173
178
|
<div class="box-icon">
|
|
@@ -187,6 +192,7 @@ function handleArray(value: OptionValue) {
|
|
|
187
192
|
<style scoped lang="postcss">
|
|
188
193
|
.container {
|
|
189
194
|
position: relative;
|
|
195
|
+
width: 100%;
|
|
190
196
|
}
|
|
191
197
|
|
|
192
198
|
.box {
|
|
@@ -209,6 +215,8 @@ function handleArray(value: OptionValue) {
|
|
|
209
215
|
.box-content {
|
|
210
216
|
display: flex;
|
|
211
217
|
align-items: center;
|
|
218
|
+
flex-grow: 1;
|
|
219
|
+
max-width: 100%;
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
.box-placeholder {
|
|
@@ -250,11 +258,15 @@ function handleArray(value: OptionValue) {
|
|
|
250
258
|
}
|
|
251
259
|
|
|
252
260
|
.box-content {
|
|
253
|
-
padding:
|
|
261
|
+
padding: 0 30px 0 0;
|
|
254
262
|
line-height: 24px;
|
|
255
263
|
font-size: var(--input-font-size, var(--input-mini-font-size));
|
|
256
264
|
}
|
|
257
265
|
|
|
266
|
+
.box-placeholder {
|
|
267
|
+
padding-left: 10px;
|
|
268
|
+
}
|
|
269
|
+
|
|
258
270
|
.box-icon {
|
|
259
271
|
top: 3px;
|
|
260
272
|
right: 8px;
|
|
@@ -267,11 +279,15 @@ function handleArray(value: OptionValue) {
|
|
|
267
279
|
}
|
|
268
280
|
|
|
269
281
|
.box-content {
|
|
270
|
-
padding:
|
|
282
|
+
padding: 0 30px 0 0;
|
|
271
283
|
line-height: 24px;
|
|
272
284
|
font-size: var(--input-font-size, var(--input-small-font-size));
|
|
273
285
|
}
|
|
274
286
|
|
|
287
|
+
.box-placeholder {
|
|
288
|
+
padding-left: 12px;
|
|
289
|
+
}
|
|
290
|
+
|
|
275
291
|
.box-icon {
|
|
276
292
|
top: 7px;
|
|
277
293
|
right: 8px;
|
|
@@ -280,15 +296,19 @@ function handleArray(value: OptionValue) {
|
|
|
280
296
|
|
|
281
297
|
.SInputDropdown.medium {
|
|
282
298
|
.box {
|
|
283
|
-
height: 48px;
|
|
299
|
+
min-height: 48px;
|
|
284
300
|
}
|
|
285
301
|
|
|
286
302
|
.box-content {
|
|
287
|
-
padding:
|
|
303
|
+
padding: 0 40px 0 0;
|
|
288
304
|
line-height: 24px;
|
|
289
305
|
font-size: var(--input-font-size, var(--input-medium-font-size));
|
|
290
306
|
}
|
|
291
307
|
|
|
308
|
+
.box-placeholder {
|
|
309
|
+
padding-left: 16px;
|
|
310
|
+
}
|
|
311
|
+
|
|
292
312
|
.box-icon {
|
|
293
313
|
top: 11px;
|
|
294
314
|
right: 12px;
|