@globalbrain/sefirot 4.11.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;
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import SInputDropdownItemAvatar from './SInputDropdownItemAvatar.vue'
3
- import SInputDropdownItemText from './SInputDropdownItemText.vue'
2
+ import IconX from '~icons/ph/x'
3
+ import SAvatar from './SAvatar.vue'
4
4
 
5
5
  export type Item = ItemText | ItemAvatar
6
6
 
@@ -23,47 +23,277 @@ export interface ItemAvatar extends ItemBase {
23
23
  export type Size = 'mini' | 'small' | 'medium'
24
24
 
25
25
  defineProps<{
26
- items: Item[]
26
+ item: Item | Item[]
27
27
  size: Size
28
28
  removable: boolean
29
29
  disabled: boolean
30
30
  }>()
31
31
 
32
32
  defineEmits<{
33
- (e: 'remove', value: any): void
33
+ remove: [value: any]
34
34
  }>()
35
35
  </script>
36
36
 
37
37
  <template>
38
- <div class="SInputDropdownItem">
39
- <div v-for="(item, index) in items" :key="index" class="item">
40
- <SInputDropdownItemText
41
- v-if="item.type === 'text' || item.type === undefined"
42
- :size="size"
43
- :label="item.label"
44
- :value="item.value"
45
- :removable="removable"
46
- :disabled="disabled"
47
- @remove="(v) => $emit('remove', v)"
48
- />
49
- <SInputDropdownItemAvatar
50
- v-if="item.type === 'avatar'"
51
- :size="size"
52
- :label="item.label"
53
- :image="item.image"
54
- :value="item.value"
55
- :removable="removable"
56
- :disabled="disabled"
57
- @remove="(v) => $emit('remove', v)"
58
- />
38
+ <div class="SInputDropdownItem" :class="[size, { disabled }]">
39
+ <div v-if="Array.isArray(item)" class="many">
40
+ <template v-for="i, index in item" :key="index">
41
+ <div v-if="i.type === undefined || i.type === 'text'" class="many-text">
42
+ <div class="many-text-value">{{ i.label }}</div>
43
+ <button v-if="removable" class="many-text-close" @click.stop="$emit('remove', i.value)">
44
+ <IconX class="many-text-close-icon" />
45
+ </button>
46
+ </div>
47
+ <div v-else-if="i.type === 'avatar'" class="many-avatar">
48
+ <div class="many-avatar-body">
49
+ <div class="many-avatar-image"><SAvatar size="fill" :avatar="i.image" /></div>
50
+ <div class="many-avatar-name">{{ i.label }}</div>
51
+ </div>
52
+ <button v-if="removable" class="many-avatar-close" @click.stop="$emit('remove', i.value)">
53
+ <IconX class="many-avatar-close-icon" />
54
+ </button>
55
+ </div>
56
+ </template>
57
+ </div>
58
+ <div v-else class="one">
59
+ <div v-if="item.type === undefined || item.type === 'text'" class="one-text">
60
+ <div class="one-text-value">{{ item.label }}</div>
61
+ </div>
62
+ <div v-else-if="item.type === 'avatar'" class="one-avatar">
63
+ <div class="one-avatar-image"><SAvatar size="fill" :avatar="item.image" /></div>
64
+ <div class="one-avatar-name">{{ item.label }}</div>
65
+ </div>
66
+ <button v-if="removable" class="one-close" @click.stop="$emit('remove', item.value)">
67
+ <IconX class="one-close-icon" />
68
+ </button>
59
69
  </div>
60
70
  </div>
61
71
  </template>
62
72
 
63
73
  <style scoped lang="postcss">
64
74
  .SInputDropdownItem {
75
+ flex-grow: 1;
76
+ width: 100%;
77
+ }
78
+
79
+ .many {
65
80
  display: flex;
66
81
  flex-wrap: wrap;
67
82
  gap: 4px;
68
83
  }
84
+
85
+ .many-text {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 4px;
89
+ border: 1px solid var(--c-divider);
90
+ border-radius: 12px;
91
+ padding: 0 10px;
92
+ height: 24px;
93
+
94
+ &:has(.many-text-close) {
95
+ padding-right: 4px;
96
+ }
97
+ }
98
+
99
+ .many-text-value {
100
+ line-height: 20px;
101
+ font-size: 12px;
102
+ font-weight: 400;
103
+ }
104
+
105
+ .many-text-close {
106
+ display: flex;
107
+ justify-content: center;
108
+ align-items: center;
109
+ width: 16px;
110
+ height: 16px;
111
+ color: var(--c-text-2);
112
+
113
+ &:hover {
114
+ color: var(--c-text-1);
115
+ }
116
+ }
117
+
118
+ .many-text-close-icon {
119
+ width: 14px;
120
+ height: 14px;
121
+ }
122
+
123
+ .many-avatar {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 4px;
127
+ border: 1px solid var(--c-divider);
128
+ border-radius: 12px;
129
+ padding: 1px 8px 1px 1px;
130
+ height: 24px;
131
+
132
+ &:has(.many-avatar-close) {
133
+ gap: 6px;
134
+ padding-right: 4px;
135
+ }
136
+ }
137
+
138
+ .many-avatar-body {
139
+ display: flex;
140
+ align-items: center;
141
+ gap: 6px;
142
+ }
143
+
144
+ .many-avatar-image {
145
+ width: 20px;
146
+ height: 20px;
147
+ }
148
+
149
+ .many-avatar-name {
150
+ line-height: 20px;
151
+ font-size: 12px;
152
+ font-weight: 400;
153
+ }
154
+
155
+ .many-avatar-close {
156
+ display: flex;
157
+ justify-content: center;
158
+ align-items: center;
159
+ width: 16px;
160
+ height: 16px;
161
+ color: var(--c-text-2);
162
+
163
+ &:hover {
164
+ color: var(--c-text-1);
165
+ }
166
+ }
167
+
168
+ .many-avatar-close-icon {
169
+ width: 14px;
170
+ height: 14px;
171
+ }
172
+
173
+ .one {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ gap: 8px;
178
+ width: 100%;
179
+ }
180
+
181
+ .one-text {
182
+ flex-grow: 1;
183
+ max-width: 100%;
184
+ overflow: hidden;
185
+ white-space: nowrap;
186
+ text-overflow: ellipsis;
187
+ }
188
+
189
+ .one-text-value {
190
+ max-width: 100%;
191
+ overflow: hidden;
192
+ white-space: nowrap;
193
+ text-overflow: ellipsis;
194
+ }
195
+
196
+ .one-avatar {
197
+ display: flex;
198
+ align-items: center;
199
+ flex-grow: 1;
200
+ max-width: 100%;
201
+ overflow: hidden;
202
+ white-space: nowrap;
203
+ text-overflow: ellipsis;
204
+ }
205
+
206
+ .one-avatar-image {
207
+ flex-shrink: 0;
208
+ width: 20px;
209
+ height: 20px;
210
+ }
211
+
212
+ .one-avatar-name {
213
+ flex-grow: 1;
214
+ max-width: 100%;
215
+ line-height: 24px;
216
+ font-size: 14px;
217
+ font-weight: 400;
218
+ overflow: hidden;
219
+ white-space: nowrap;
220
+ text-overflow: ellipsis;
221
+ }
222
+
223
+ .one-close {
224
+ display: flex;
225
+ justify-content: center;
226
+ align-items: center;
227
+ flex-shrink: 0;
228
+ width: 16px;
229
+ height: 16px;
230
+ color: var(--c-text-2);
231
+ transition: color 0.25s;
232
+
233
+ &:hover {
234
+ color: var(--c-text-1);
235
+ }
236
+ }
237
+
238
+ .one-close-icon {
239
+ width: 16px;
240
+ height: 16px;
241
+ }
242
+
243
+ .SInputDropdownItem.mini {
244
+ .many {
245
+ padding: 3px 0 3px 3px;
246
+ }
247
+
248
+ .one-text {
249
+ padding-left: 10px;
250
+ }
251
+
252
+ .one-text-value {
253
+ font-size: var(--input-font-size, var(--input-mini-font-size));
254
+ }
255
+
256
+ .one-avatar {
257
+ gap: 6px;
258
+ padding-left: 7px;
259
+ }
260
+ }
261
+
262
+ .SInputDropdownItem.small {
263
+ .many {
264
+ padding: 7px 0 7px 7px;
265
+ }
266
+
267
+ .one-text {
268
+ padding-left: 12px;
269
+ }
270
+
271
+ .one-text-value {
272
+ font-size: var(--input-font-size, var(--input-small-font-size));
273
+ }
274
+
275
+ .one-avatar {
276
+ gap: 8px;
277
+ padding-left: 10px;
278
+ }
279
+ }
280
+
281
+ .SInputDropdownItem.medium {
282
+ .many {
283
+ padding: 11px 0 11px 11px;
284
+ }
285
+
286
+ .one-text {
287
+ padding-left: 16px;
288
+ }
289
+
290
+ .one-text-value {
291
+ font-size: var(--input-font-size, var(--input-medium-font-size));
292
+ }
293
+
294
+ .one-avatar {
295
+ gap: 8px;
296
+ padding-left: 12px;
297
+ }
298
+ }
69
299
  </style>
@@ -1,41 +1,30 @@
1
1
  <script setup lang="ts">
2
- import { type Component, computed } from 'vue'
3
- import { type Validatable } from '../composables/Validation'
2
+ import { computed } from 'vue'
4
3
  import { isString } from '../support/Utils'
4
+ import { type Props as BaseProps } from './SInputBase.vue'
5
5
  import SInputText from './SInputText.vue'
6
6
 
7
- export type Size = 'mini' | 'small' | 'medium'
8
- export type Align = 'left' | 'center' | 'right'
9
- export type CheckColor = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
10
- export type TextColor = 'neutral' | 'info' | 'success' | 'warning' | 'danger'
11
-
12
- const props = defineProps<{
13
- size?: Size
14
- name?: string
15
- label?: string
16
- info?: string
17
- note?: string
18
- help?: string
7
+ export interface Props extends BaseProps {
19
8
  placeholder?: string
20
9
  unitBefore?: any
21
10
  unitAfter?: any
22
- checkIcon?: Component
23
- checkText?: string
24
- checkColor?: CheckColor
25
11
  textColor?: TextColor | ((value: number | null) => TextColor)
26
- align?: Align
27
12
  separator?: boolean
13
+ align?: Align
28
14
  disabled?: boolean
29
15
  value?: number | null
30
16
  modelValue?: number | null
31
17
  displayValue?: string | null
32
- hideError?: boolean
33
- validation?: Validatable
34
- }>()
18
+ }
19
+
20
+ export type Align = 'left' | 'center' | 'right'
21
+ export type TextColor = 'neutral' | 'info' | 'success' | 'warning' | 'danger'
22
+
23
+ const props = defineProps<Props>()
35
24
 
36
25
  const emit = defineEmits<{
37
- (e: 'update:model-value', value: number | null): void
38
- (e: 'input', value: number | null): void
26
+ 'update:model-value': [value: number | null]
27
+ 'input': [value: number | null]
39
28
  }>()
40
29
 
41
30
  const _value = computed(() => {
@@ -86,8 +75,8 @@ function emitUpdate(value: string | null) {
86
75
  <template>
87
76
  <SInputText
88
77
  class="SInputNumber"
89
- :name="name"
90
78
  :size="size"
79
+ :name="name"
91
80
  type="number"
92
81
  :label="label"
93
82
  :note="note"
@@ -102,10 +91,12 @@ function emitUpdate(value: string | null) {
102
91
  :text-color="_textColor"
103
92
  :align="align"
104
93
  :disabled="disabled"
105
- :hide-error="hideError"
106
94
  :display-value="displayValue"
107
95
  :model-value="_value == null ? null : String(_value)"
108
96
  :validation="validation"
97
+ :warning="warning"
98
+ :hide-error="hideError"
99
+ :hide-warning="hideWarning"
109
100
  @update:model-value="emitUpdate"
110
101
  @input="emitUpdate"
111
102
  >
@@ -1,51 +1,36 @@
1
1
  <script setup lang="ts">
2
2
  import { type Component, computed, ref } from 'vue'
3
- import { type Validatable } from '../composables/Validation'
4
3
  import { isString } from '../support/Utils'
5
- import SInputBase from './SInputBase.vue'
6
-
7
- export interface Props {
8
- size?: Size
9
- name?: string
10
- label?: string
11
- info?: string
12
- note?: string
13
- help?: string
4
+ import SInputBase, { type Props as BaseProps } from './SInputBase.vue'
5
+
6
+ export interface Props extends BaseProps {
14
7
  type?: string
15
8
  placeholder?: string
16
9
  unitBefore?: Component | string
17
10
  unitAfter?: Component | string
18
- checkIcon?: Component
19
- checkText?: string
20
- checkColor?: CheckColor
21
11
  textColor?: TextColor | ((value: string | null) => TextColor)
22
12
  align?: Align
23
13
  disabled?: boolean
24
14
  modelValue: string | null
25
15
  displayValue?: string | null
26
- hideError?: boolean
27
- validation?: Validatable
28
16
  }
29
17
 
30
- export type Size = 'mini' | 'small' | 'medium'
31
18
  export type Align = 'left' | 'center' | 'right'
32
- export type CheckColor = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
33
19
  export type TextColor = 'neutral' | 'info' | 'success' | 'warning' | 'danger'
34
20
 
35
21
  const props = defineProps<Props>()
36
22
 
37
23
  const emit = defineEmits<{
38
- (e: 'update:model-value', value: string | null): void
39
- (e: 'input', value: string | null): void
40
- (e: 'blur', value: string | null): void
41
- (e: 'enter', value: string | null): void
24
+ 'update:model-value': [value: string | null]
25
+ 'input': [value: string | null]
26
+ 'blur': [value: string | null]
27
+ 'enter': [value: string | null]
42
28
  }>()
43
29
 
44
30
  const input = ref<HTMLElement | null>(null)
45
31
  const isFocused = ref(false)
46
32
 
47
33
  const classes = computed(() => [
48
- props.size ?? 'small',
49
34
  props.align ?? 'left',
50
35
  { disabled: props.disabled }
51
36
  ])
@@ -116,6 +101,7 @@ function getValue(e: Event | FocusEvent | KeyboardEvent): string | null {
116
101
  <SInputBase
117
102
  class="SInputText"
118
103
  :class="classes"
104
+ :size="size"
119
105
  :name="name"
120
106
  :label="label"
121
107
  :note="note"
@@ -124,8 +110,10 @@ function getValue(e: Event | FocusEvent | KeyboardEvent): string | null {
124
110
  :check-icon="checkIcon"
125
111
  :check-text="checkText"
126
112
  :check-color="checkColor"
127
- :hide-error="hideError"
128
113
  :validation="validation"
114
+ :warning="warning"
115
+ :hide-error="hideError"
116
+ :hide-warning="hideWarning"
129
117
  >
130
118
  <div class="box" :class="{ focus: isFocused }" @click="focus">
131
119
  <div v-if="$slots['addon-before']" class="addon before">
@@ -349,6 +337,14 @@ function getValue(e: Event | FocusEvent | KeyboardEvent): string | null {
349
337
  }
350
338
  }
351
339
 
340
+ .SInputText.has-warning {
341
+ .box,
342
+ .box:hover,
343
+ .box:focus {
344
+ border-color: var(--input-warning-border-color);
345
+ }
346
+ }
347
+
352
348
  .box {
353
349
  position: relative;
354
350
  display: flex;
@@ -1,42 +1,29 @@
1
1
  <script setup lang="ts">
2
- import { type Component, computed, ref } from 'vue'
3
- import { type Validatable } from '../composables/Validation'
4
- import SInputBase from './SInputBase.vue'
2
+ import { computed, ref } from 'vue'
3
+ import SInputBase, { type Props as BaseProps } from './SInputBase.vue'
5
4
  import SInputSegments from './SInputSegments.vue'
6
5
 
7
- export type Size = 'mini' | 'small' | 'medium'
8
- export type Color = 'neutral' | 'mute' | 'info' | 'success' | 'warning' | 'danger'
9
-
10
- const props = withDefaults(defineProps<{
11
- size?: Size
12
- name?: string
13
- label?: string
14
- info?: string
15
- note?: string
16
- help?: string
17
- checkIcon?: Component
18
- checkText?: string
19
- checkColor?: Color
6
+ export interface Props extends BaseProps {
20
7
  placeholder?: string
21
8
  disabled?: boolean
22
9
  rows?: number | 'fill'
23
10
  autoResize?: boolean | number
24
11
  value?: string | null
25
12
  modelValue?: string | null
26
- hideError?: boolean
27
- validation?: Validatable
28
13
  preview?: (value: string | null) => string
29
14
  previewLabel?: string
30
15
  writeLabel?: string
31
- }>(), {
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
32
19
  size: 'small',
33
20
  rows: 3
34
21
  })
35
22
 
36
23
  const emit = defineEmits<{
37
- (e: 'update:model-value', value: string | null): void
38
- (e: 'input', value: string | null): void
39
- (e: 'blur', value: string | null): void
24
+ 'update:model-value': [value: string | null]
25
+ 'input': [value: string | null]
26
+ 'blur': [value: string | null]
40
27
  }>()
41
28
 
42
29
  const sizePaddingYDict = {
@@ -93,6 +80,7 @@ const isPreview = ref(false)
93
80
  <SInputBase
94
81
  class="SInputTextarea"
95
82
  :class="classes"
83
+ :size="size"
96
84
  :name="name"
97
85
  :label="label"
98
86
  :note="note"
@@ -101,8 +89,10 @@ const isPreview = ref(false)
101
89
  :check-icon="checkIcon"
102
90
  :check-text="checkText"
103
91
  :check-color="checkColor"
104
- :hide-error="hideError"
105
92
  :validation="validation"
93
+ :warning="warning"
94
+ :hide-error="hideError"
95
+ :hide-warning="hideWarning"
106
96
  >
107
97
  <div class="box">
108
98
  <div v-if="preview !== undefined" class="control">
@@ -248,4 +238,10 @@ const isPreview = ref(false)
248
238
  border-color: var(--input-error-border-color);
249
239
  }
250
240
  }
241
+
242
+ .SInputTextarea.has-warning {
243
+ .box {
244
+ border-color: var(--input-warning-border-color);
245
+ }
246
+ }
251
247
  </style>
@@ -143,7 +143,9 @@ function formatProps(props: Record<string, unknown>): string {
143
143
  const ignoreErrors = [
144
144
  /Network Error/,
145
145
  /Non-Error (?:exception|promise rejection) captured/,
146
- /ResizeObserver loop/
146
+ /ResizeObserver loop/,
147
+ /Can't find variable: gmo/,
148
+ /\[Cloudflare Turnstile\] Error: (?:10[2-46]|1106[02]|[36]00)/
147
149
  ]
148
150
 
149
151
  export function useErrorHandler({
@@ -858,6 +858,8 @@
858
858
  --input-focus-border-color: var(--c-border-info-1);
859
859
  --input-error-text-color: var(--c-text-danger-1);
860
860
  --input-error-border-color: var(--c-border-danger-1);
861
+ --input-warning-text-color: var(--c-text-warning-1);
862
+ --input-warning-border-color: var(--c-border-warning-1);
861
863
  --input-disabled-border-color: var(--c-border-mute-1);
862
864
  --input-disabled-value-color: var(--c-text-1);
863
865
  --input-disabled-bg-color: var(--c-bg-mute-1);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@globalbrain/sefirot",
3
3
  "type": "module",
4
- "version": "4.11.0",
4
+ "version": "4.12.0",
5
5
  "packageManager": "pnpm@9.15.3",
6
6
  "description": "Vue Components for Global Brain Design System.",
7
7
  "author": "Kia Ishii <ka.ishii@globalbrains.com>",
@@ -1,175 +0,0 @@
1
- <script setup lang="ts">
2
- import IconX from '~icons/ph/x-bold'
3
- import SAvatar from './SAvatar.vue'
4
-
5
- export type Size = 'mini' | 'small' | 'medium'
6
-
7
- defineProps<{
8
- size: Size
9
- label: string
10
- image?: string | null
11
- value: any
12
- removable: boolean
13
- disabled: boolean
14
- }>()
15
-
16
- defineEmits<{
17
- (e: 'remove', value: any): void
18
- }>()
19
-
20
- const avatarSizeDict = {
21
- mini: 'nano',
22
- small: 'mini',
23
- medium: 'mini'
24
- } as const
25
- </script>
26
-
27
- <template>
28
- <div class="SInputDropdownItemAvatar" :class="[size, { disabled, removable }]">
29
- <div class="user">
30
- <div class="avatar">
31
- <SAvatar :size="avatarSizeDict[size]" :avatar="image" :name="label" />
32
- </div>
33
- <p class="name">{{ label }}</p>
34
- </div>
35
-
36
- <div v-if="!disabled && removable" class="remove" role="button" @click="$emit('remove', value)">
37
- <div class="remove-box">
38
- <IconX class="remove-icon" />
39
- </div>
40
- </div>
41
- </div>
42
- </template>
43
-
44
- <style lang="postcss" scoped>
45
- .SInputDropdownItemAvatar {
46
- display: flex;
47
- align-items: center;
48
- border: 1px solid var(--c-border-mute-1);
49
- background-color: var(--c-bg-mute-1);
50
- }
51
-
52
- .user {
53
- display: flex;
54
- align-items: center;
55
- }
56
-
57
- .avatar {
58
- display: flex;
59
- align-items: center;
60
- }
61
-
62
- .name {
63
- font-size: 12px;
64
- font-weight: 500;
65
- white-space: nowrap;
66
- }
67
-
68
- .remove {
69
- display: flex;
70
- justify-content: center;
71
- align-items: center;
72
- }
73
-
74
- .remove-box {
75
- display: flex;
76
- justify-content: center;
77
- align-items: center;
78
- border-radius: 50%;
79
- color: var(--c-text-2);
80
- transition: color 0.25s, background-color 0.25s;
81
-
82
- .remove:hover & {
83
- color: var(--c-text-1);
84
- background-color: var(--c-bg-mute-2)
85
- }
86
- }
87
-
88
- .SInputDropdownItemAvatar.mini {
89
- gap: 2px;
90
- border-radius: 12px;
91
- padding: 0 8px 0 0;
92
- height: 24px;
93
-
94
- .avatar {
95
- padding: 0 0 0 1px;
96
- }
97
-
98
- .name {
99
- margin-left: 6px;
100
- }
101
-
102
- .remove {
103
- width: 23px;
104
- height: 23px;
105
- }
106
-
107
- .remove-box {
108
- width: 20px;
109
- height: 20px;
110
- }
111
-
112
- .remove-icon {
113
- width: 12px;
114
- height: 12px;
115
- }
116
- }
117
-
118
- .SInputDropdownItemAvatar.small {
119
- border-radius: 14px;
120
- padding: 0 12px 0 0;
121
- height: 28px;
122
-
123
- .avatar {
124
- padding: 0 0 0 1px;
125
- }
126
-
127
- .name {
128
- margin-left: 6px;
129
- }
130
-
131
- .remove {
132
- width: 26px;
133
- height: 26px;
134
- }
135
-
136
- .remove-box {
137
- width: 20px;
138
- height: 20px;
139
- }
140
-
141
- .remove-icon {
142
- width: 12px;
143
- height: 12px;
144
- }
145
- }
146
-
147
- .SInputDropdownItemAvatar.medium {
148
- border-radius: 16px;
149
- padding: 0 12px 0 0;
150
- height: 32px;
151
-
152
- .avatar {
153
- padding: 0 0 0 4px;
154
- }
155
-
156
- .name {
157
- margin-left: 6px;
158
- }
159
-
160
- .remove {
161
- width: 26px;
162
- height: 26px;
163
- }
164
-
165
- .remove-box {
166
- width: 20px;
167
- height: 20px;
168
- }
169
-
170
- .remove-icon {
171
- width: 12px;
172
- height: 12px;
173
- }
174
- }
175
- </style>
@@ -1,154 +0,0 @@
1
- <script setup lang="ts">
2
- import IconX from '~icons/ph/x-bold'
3
-
4
- export type Size = 'mini' | 'small' | 'medium'
5
-
6
- defineProps<{
7
- size: Size
8
- label: string
9
- value: any
10
- removable: boolean
11
- disabled: boolean
12
- }>()
13
-
14
- defineEmits<{
15
- (e: 'remove', value: any): void
16
- }>()
17
- </script>
18
-
19
- <template>
20
- <div class="SInputDropdownItemText" :class="[size, { disabled, removable }]">
21
- <p class="text">{{ label }}</p>
22
-
23
- <div v-if="!disabled && removable" class="remove" role="button" @click.stop="$emit('remove', value)">
24
- <div class="remove-box">
25
- <IconX class="remove-icon" />
26
- </div>
27
- </div>
28
- </div>
29
- </template>
30
-
31
- <style lang="postcss" scoped>
32
- .SInputDropdownItemText {
33
- display: flex;
34
- align-items: center;
35
- border: 1px solid var(--c-border-mute-1);
36
- background-color: var(--c-bg-mute-1);
37
- }
38
-
39
- .text {
40
- margin: 0;
41
- line-height: 20px;
42
- font-size: 12px;
43
- font-weight: 500;
44
- white-space: nowrap;
45
- }
46
-
47
- .remove {
48
- display: flex;
49
- justify-content: center;
50
- align-items: center;
51
- }
52
-
53
- .remove-box {
54
- display: flex;
55
- justify-content: center;
56
- align-items: center;
57
- border-radius: 50%;
58
- color: var(--c-text-2);
59
- transition: color 0.25s, background-color 0.25s;
60
-
61
- .remove:hover & {
62
- color: var(--c-text-1);
63
- background-color: var(--c-bg-mute-3);
64
- }
65
- }
66
-
67
- .SInputDropdownItemText.mini {
68
- gap: 2px;
69
- border-radius: 12px;
70
- padding: 0 8px;
71
- height: 24px;
72
-
73
- &.removable {
74
- padding: 0 0 0 8px;
75
- }
76
-
77
- &.disabled {
78
- padding: 0 10px;
79
- }
80
-
81
- .remove {
82
- width: 23px;
83
- height: 23px;
84
- }
85
-
86
- .remove-box {
87
- width: 20px;
88
- height: 20px;
89
- }
90
-
91
- .remove-icon {
92
- width: 12px;
93
- height: 12px;
94
- }
95
- }
96
-
97
- .SInputDropdownItemText.small {
98
- border-radius: 14px;
99
- padding: 0 12px;
100
- height: 28px;
101
-
102
- &.removable {
103
- padding: 0 0 0 12px;
104
- }
105
-
106
- &.disabled {
107
- padding: 0 12px;
108
- }
109
-
110
- .remove {
111
- width: 26px;
112
- height: 26px;
113
- }
114
-
115
- .remove-box {
116
- width: 20px;
117
- height: 20px;
118
- }
119
-
120
- .remove-icon {
121
- width: 12px;
122
- height: 12px;
123
- }
124
- }
125
-
126
- .SInputDropdownItemText.medium {
127
- border-radius: 16px;
128
- padding: 0 12px;
129
- height: 32px;
130
-
131
- &.removable {
132
- padding: 0 0 0 12px;
133
- }
134
-
135
- &.disabled {
136
- padding: 0 12px;
137
- }
138
-
139
- .remove {
140
- width: 26px;
141
- height: 26px;
142
- }
143
-
144
- .remove-box {
145
- width: 20px;
146
- height: 20px;
147
- }
148
-
149
- .remove-icon {
150
- width: 12px;
151
- height: 12px;
152
- }
153
- }
154
- </style>