@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.
@@ -52,7 +52,7 @@ const items = computed(() => {
52
52
  gap: 1px;
53
53
  border: 1px solid var(--c-divider);
54
54
  border-radius: 6px;
55
- margin-top: 2px;
55
+ margin-top: 4px;
56
56
  background-color: var(--c-gutter);
57
57
  overflow: hidden;
58
58
  }
@@ -29,7 +29,17 @@ const labelWidth = computed(() => {
29
29
  }
30
30
 
31
31
  .SDesc.row > .SDescItem {
32
- grid-template-columns: var(--desc-label-width, v-bind(labelWidth)) minmax(0, 1fr);
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
- defineProps<{
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
- height: 24px;
78
+ gap: 16px;
79
+ height: 28px;
20
80
  }
21
81
 
22
82
  .value {
23
- line-height: 24px;
24
- font-size: 12px;
25
- font-weight: 500;
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
- type Color = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
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="{ 'has-error': error?.has }">
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="help" class="help-text">{{ help }}</p>
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: 500;
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-error {
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: 18px;
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-bold'
3
- import IconCaretUp from '~icons/ph/caret-up-bold'
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 emit = defineEmits<{
60
- (e: 'update:modelValue', value: PrimitiveValue | ArrayValue): void
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: props.modelValue,
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(props.modelValue)) {
83
- return props.options.filter((o) => (props.modelValue as ArrayValue).includes(o.value))
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 === props.modelValue)
93
+ const item = props.options.find((o) => o.value === model.value)
87
94
 
88
- return item ? [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(props.modelValue)) {
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(props.modelValue) ? handleArray(value) : handlePrimitive(value)
120
+ Array.isArray(model.value) ? handleArray(value) : handlePrimitive(value)
114
121
  }
115
122
 
116
123
  function handlePrimitive(value: OptionValue) {
117
- if (value !== props.modelValue) {
118
- return emit('update:modelValue', value)
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(props.modelValue as ArrayValue, [value])
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
- emit('update:modelValue', difference)
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
- :items="selected"
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: 3px 30px 3px 8px;
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: 5px 30px 5px 12px;
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: 11px 44px 11px 16px;
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;