@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.
@@ -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>
@@ -0,0 +1,22 @@
1
+ import { type HttpOptions } from '../http/Http'
2
+ import { useSetupHttp } from './Http'
3
+ import { type HasLang, useSetupLang } from './Lang'
4
+ import { type HasTheme, useSetupTheme } from './Theme'
5
+
6
+ export interface SetupAppUser extends HasLang, HasTheme {}
7
+
8
+ export interface SetupAppOptions {
9
+ http?: HttpOptions
10
+ }
11
+
12
+ export function useSetupApp(): (user?: SetupAppUser | null, options?: SetupAppOptions) => void {
13
+ const setupLang = useSetupLang()
14
+ const setupTheme = useSetupTheme()
15
+ const setupHttp = useSetupHttp()
16
+
17
+ return (user, options) => {
18
+ setupLang(user)
19
+ setupTheme(user)
20
+ setupHttp(user, options?.http)
21
+ }
22
+ }
@@ -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({
@@ -0,0 +1,17 @@
1
+ import { type Lang, useBrowserLang } from 'sefirot/composables/Lang'
2
+ import { Http, type HttpOptions } from 'sefirot/http/Http'
3
+
4
+ export interface HasLang {
5
+ lang: Lang
6
+ }
7
+
8
+ export function useSetupHttp(): (user?: HasLang | null, options?: HttpOptions) => void {
9
+ const browserLang = useBrowserLang()
10
+
11
+ return (user, options = {}) => {
12
+ Http.config({
13
+ lang: user?.lang ?? browserLang,
14
+ ...options
15
+ })
16
+ }
17
+ }
@@ -11,8 +11,20 @@ export interface TransMessages<T> {
11
11
  ja: T
12
12
  }
13
13
 
14
+ export interface HasLang {
15
+ lang: Lang
16
+ }
17
+
14
18
  export const SefirotLangKey = 'sefirot-lang-key'
15
19
 
20
+ export function useSetupLang(): (user?: HasLang | null) => void {
21
+ const browserLang = useBrowserLang()
22
+
23
+ return (user) => {
24
+ provideLang(user?.lang ?? browserLang)
25
+ }
26
+ }
27
+
16
28
  export function provideLang(lang: Lang) {
17
29
  provide(SefirotLangKey, lang)
18
30
  }
@@ -23,6 +35,12 @@ export function useLang(): Lang {
23
35
  return inject(SefirotLangKey, 'en') || 'en'
24
36
  }
25
37
 
38
+ export function useBrowserLang(): Lang {
39
+ const lang = navigator.language
40
+
41
+ return lang.split('-')[0] === 'ja' ? 'ja' : 'en'
42
+ }
43
+
26
44
  export function useTrans<T>(messages: TransMessages<T>): Trans<T> {
27
45
  const lang = useLang()
28
46
 
@@ -32,9 +50,3 @@ export function useTrans<T>(messages: TransMessages<T>): Trans<T> {
32
50
  t
33
51
  }
34
52
  }
35
-
36
- export function useBrowserLang(): Lang {
37
- const lang = navigator.language
38
-
39
- return lang.split('-')[0] === 'ja' ? 'ja' : 'en'
40
- }
@@ -0,0 +1,25 @@
1
+ import { useDark } from '@vueuse/core'
2
+ import { type WritableComputedRef, computed } from 'vue'
3
+
4
+ export type Theme = 'light' | 'dark'
5
+
6
+ export interface HasTheme {
7
+ theme: Theme
8
+ }
9
+
10
+ export function useSetupTheme(): (user?: HasTheme | null) => void {
11
+ const theme = useTheme()
12
+
13
+ return (user) => {
14
+ theme.value = user?.theme ?? 'light'
15
+ }
16
+ }
17
+
18
+ export function useTheme(): WritableComputedRef<Theme> {
19
+ const _isDark = useDark()
20
+
21
+ return computed({
22
+ get: () => _isDark.value ? 'dark' : 'light',
23
+ set: (v) => { _isDark.value = v === 'dark' }
24
+ })
25
+ }