@saasmakers/ui 2.0.52 → 2.0.53

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,199 +1,24 @@
1
1
  <script lang="ts" setup>
2
- import { NuxtLinkLocale } from '#components'
3
2
  import type { BaseTag } from '../../types/bases'
4
3
 
5
- const props = withDefaults(defineProps<BaseTag>(), {
6
- active: false,
7
- avatar: '',
8
- circle: '',
9
- clickable: true,
10
- color: 'black',
11
- draggable: false,
12
- editable: false,
13
- icon: undefined,
14
- iconSize: undefined,
15
- id: undefined,
16
- isCreation: false,
17
- light: true,
18
- removable: false,
19
- rounded: false,
4
+ withDefaults(defineProps<BaseTag>(), {
5
+ color: 'gray',
20
6
  size: 'base',
21
7
  text: '',
22
- to: undefined,
23
- truncate: false,
24
8
  })
25
-
26
- const emit = defineEmits<{
27
- click: [event: MouseEvent, id?: number | string]
28
- close: [event: MouseEvent, id?: number | string]
29
- inputBlur: [event: FocusEvent, name: string, id?: number | string]
30
- inputSubmit: [event: KeyboardEvent, name: string, id?: number | string]
31
- remove: [event: MouseEvent, id?: number | string]
32
- }>()
33
-
34
- defineSlots<{
35
- default?: () => VNode[]
36
- }>()
37
-
38
- const hovered = ref(false)
39
- const form = reactive({ name: '' })
40
-
41
- onBeforeMount(() => {
42
- if (!props.isCreation) {
43
- initializeFormForExistingTag()
44
- }
45
- })
46
-
47
- function initializeFormForExistingTag() {
48
- form.name = String(props.text)?.slice(1)
49
- }
50
-
51
- function onBlur() {
52
- hovered.value = false
53
- }
54
-
55
- function onClick(event: MouseEvent) {
56
- if (!props.removable) {
57
- emit('click', event, props.id)
58
- }
59
- }
60
-
61
- function onFocus() {
62
- hovered.value = true
63
- }
64
-
65
- function onInputBlur(event: FocusEvent) {
66
- emit('inputBlur', event, form.name.trim(), props.id)
67
- }
68
-
69
- function onInputSubmit(event: KeyboardEvent) {
70
- emit('inputSubmit', event, form.name.trim(), props.id)
71
- }
72
-
73
- function onKeyDown(event: KeyboardEvent) {
74
- if (props.clickable) {
75
- (event.currentTarget as HTMLElement).click()
76
- }
77
- }
78
-
79
- function onMouseEnter() {
80
- hovered.value = true
81
- }
82
-
83
- function onMouseLeave() {
84
- hovered.value = false
85
- }
86
-
87
- function onRemove(event: MouseEvent) {
88
- emit('remove', event, props.id)
89
- }
90
9
  </script>
91
10
 
92
11
  <template>
93
12
  <span
94
- class="flex select-none"
95
- role="button"
96
- :aria-disabled="!clickable"
97
- tabindex="0"
98
- @click.stop="onClick"
99
- @keydown.enter.stop="onKeyDown"
100
- @keydown.space.prevent.stop="onKeyDown"
101
- @mouseenter="onMouseEnter"
102
- @mouseleave="onMouseLeave"
103
- @focusin="onFocus"
104
- @focusout="onBlur"
13
+ class="pointer-events-none inline-flex rounded font-medium tracking-tight"
14
+ :class="{
15
+ 'px-0.5 py-px text-3xs': size === 'xs',
16
+ 'px-1 py-px text-3xs': size === 'sm',
17
+ 'px-1.5 py-0.5 text-2xs': size === 'base',
18
+ 'bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300': color === 'orange',
19
+ 'bg-gray-200 text-gray-600 dark:bg-gray-800 dark:text-gray-400': color === 'gray',
20
+ }"
105
21
  >
106
- <component
107
- :is="to ? NuxtLinkLocale : 'span'"
108
- class="inline-flex items-center border font-medium"
109
- :class="{
110
- 'cursor-pointer': clickable,
111
- 'rounded-md': !rounded,
112
- 'rounded-full': rounded,
113
- 'border-gray-200 dark:border-gray-800 bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200': (color === 'black' && light && !active) || removable,
114
- 'border-gray-900 dark:border-gray-100 bg-gray-900 dark:bg-gray-100 text-white dark:text-black': color === 'black' && (!light || active),
115
- 'border-gray-700 dark:border-gray-300 bg-gray-100 dark:bg-gray-900 text-gray-700 dark:text-gray-300': color === 'gray' && light && !active,
116
- 'border-gray-800 dark:border-gray-200 bg-gray-800 dark:bg-gray-200 text-white dark:text-black': color === 'gray' && (!light || active),
117
- 'border-green-700 dark:border-green-300 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300': color === 'green' && light && !active,
118
- 'border-green-800 dark:border-green-200 bg-green-800 dark:bg-green-200 text-white dark:text-black': color === 'green' && (!light || active),
119
- 'border-indigo-700 dark:border-indigo-300 bg-indigo-100 dark:bg-indigo-900 text-indigo-700 dark:text-indigo-300': color === 'indigo' && light && !active,
120
- 'border-indigo-800 dark:border-indigo-200 bg-indigo-800 dark:bg-indigo-200 text-white dark:text-black': color === 'indigo' && (!light || active),
121
- 'border-orange-700 dark:border-orange-300 bg-white dark:bg-gray-900 text-orange-700 dark:text-orange-300': color === 'orange' && light && !active,
122
- 'border-orange-800 dark:border-orange-200 bg-orange-800 dark:bg-orange-200 text-white dark:text-black': color === 'orange' && (!light || active),
123
- 'border-red-700 dark:border-red-300 bg-white dark:bg-gray-900 text-red-700 dark:text-red-300': color === 'red' && light && !active,
124
- 'border-red-800 dark:border-red-200 bg-red-800 dark:bg-red-200 text-white dark:text-black': color === 'red' && (!light || active),
125
- 'border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200': color === 'white' && light && !active,
126
- 'border-white dark:border-gray-900 text-white dark:text-black': color === 'white' && (!light || active),
127
- 'border-dashed border-gray-400 dark:border-gray-600 bg-white dark:bg-gray-900 text-gray-800 dark:text-gray-200': color === 'white' && light && !active,
128
- 'py-0.5 px-1.5': size === 'xs',
129
- 'py-1.5 px-2.5': size === 'sm',
130
- 'py-2 px-3': size === 'base',
131
- }"
132
- :to="to"
133
- >
134
- <BaseIcon
135
- v-if="draggable"
136
- class="js-drag-handle mr-2 cursor-move"
137
- clickable
138
- color="gray"
139
- :icon="getLayerIcon('drag')"
140
- />
141
-
142
- <span
143
- v-if="avatar || circle || icon"
144
- class="mr-1 flex items-center text-center"
145
- :class="{
146
- 'h-2.5 w-2.5': size === 'xs' || size === 'sm' || iconSize === 2.5,
147
- 'h-3 w-3': size === 'base' || iconSize === 3,
148
- 'h-3.5 w-3.5': iconSize === 3.5,
149
- 'h-4 w-4': iconSize === 4,
150
- }"
151
- >
152
- <BaseAvatar
153
- v-if="avatar"
154
- class="h-full w-full"
155
- :shadow="false"
156
- :src="avatar"
157
- />
158
-
159
- <BaseIcon
160
- v-else-if="icon"
161
- class="h-full w-full"
162
- :icon="icon"
163
- />
164
- </span>
165
-
166
- <span
167
- v-if="$slots.default"
168
- class="mr-1"
169
- >
170
- <slot />
171
- </span>
172
-
173
- <BaseText
174
- v-if="!editable"
175
- class="whitespace-nowrap outline-none"
176
- :class="{ truncate }"
177
- :size="size === 'xs' ? '3xs' : size === 'sm' ? '2xs' : 'xs'"
178
- :text="text"
179
- />
180
-
181
- <FieldInput
182
- v-else
183
- v-model="form.name"
184
- border="none"
185
- class="w-16"
186
- size="sm"
187
- @blur="onInputBlur"
188
- @submit="onInputSubmit"
189
- />
190
-
191
- <BaseIcon
192
- v-if="removable"
193
- class="ml-1.5 text-red-700 dark:text-red-300 hover:text-black dark:hover:text-white"
194
- :icon="getLayerIcon('closeCircle')"
195
- @click.prevent.stop="onRemove"
196
- />
197
- </component>
22
+ <slot>{{ text }}</slot>
198
23
  </span>
199
24
  </template>
@@ -127,6 +127,16 @@ export interface BaseCircle {
127
127
 
128
128
  export type BaseCircleColor = 'amber' | 'green' | 'indigo' | 'red'
129
129
 
130
+ export interface BaseTag {
131
+ color?: BaseTagColor
132
+ size?: BaseTagSize
133
+ text?: BaseTextText
134
+ }
135
+
136
+ export type BaseTagColor = 'gray' | 'orange'
137
+
138
+ export type BaseTagSize = 'base' | 'sm' | 'xs'
139
+
130
140
  export type BaseColor
131
141
  = | 'black'
132
142
  | 'gray'
@@ -327,47 +337,6 @@ export interface BaseSpinner {
327
337
 
328
338
  export type BaseStatus = | 'error' | 'info' | 'success' | 'warning'
329
339
 
330
- export interface BaseTag {
331
- active?: boolean
332
- avatar?: string
333
- circle?: string
334
- clickable?: boolean
335
- color?: BaseColor
336
- draggable?: boolean
337
- editable?: boolean
338
- icon?: string
339
- iconSize?: number
340
- id?: number
341
- isCreation?: boolean
342
- light?: boolean
343
- order?: number
344
- removable?: boolean
345
- rounded?: boolean
346
- size?: BaseTagSize
347
- text: BaseTextText
348
- to?: RouteLocationNamedI18n
349
- truncate?: boolean
350
- }
351
-
352
- export interface BaseTags {
353
- active?: boolean
354
- clickable?: boolean
355
- draggable?: boolean
356
- editable?: boolean
357
- hasBack?: boolean
358
- hasCreation?: boolean
359
- maxTags?: number
360
- modelValue: (number | string)[]
361
- removable?: boolean
362
- selectable?: boolean
363
- selectableUnique?: boolean
364
- size?: BaseTagSize
365
- tags: BaseTag[]
366
- wrap?: boolean
367
- }
368
-
369
- export type BaseTagSize = 'base' | 'sm' | 'xs'
370
-
371
340
  export interface BaseText {
372
341
  alignment?: BaseAlignment
373
342
  background?: BaseBackground
@@ -69,7 +69,7 @@ declare global {
69
69
  type BaseSpinner = import('./bases').BaseSpinner
70
70
  type BaseStatus = import('./bases').BaseStatus
71
71
  type BaseTag = import('./bases').BaseTag
72
- type BaseTags = import('./bases').BaseTags
72
+ type BaseTagColor = import('./bases').BaseTagColor
73
73
  type BaseTagSize = import('./bases').BaseTagSize
74
74
  type BaseText = import('./bases').BaseText
75
75
  type BaseTextText = import('./bases').BaseTextText
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/ui",
3
- "version": "2.0.52",
3
+ "version": "2.0.53",
4
4
  "private": false,
5
5
  "description": "Reusable Nuxt UI components for SaaS Makers projects",
6
6
  "license": "MIT",
@@ -1,289 +0,0 @@
1
- <script lang="ts" setup>
2
- import type { BaseTags } from '../../types/bases'
3
-
4
- const props = withDefaults(defineProps<Omit<BaseTags, 'modelValue'>>(), {
5
- active: false,
6
- clickable: true,
7
- draggable: false,
8
- editable: false,
9
- hasBack: false,
10
- hasCreation: false,
11
- maxTags: Number.POSITIVE_INFINITY,
12
- removable: false,
13
- selectable: false,
14
- selectableUnique: false,
15
- size: 'base',
16
- tags: () => [],
17
- wrap: false,
18
- })
19
-
20
- const emit = defineEmits<{
21
- attach: [event: MouseEvent, tagId?: number | string]
22
- back: [event: MouseEvent]
23
- click: [event: MouseEvent]
24
- create: [event: FocusEvent | KeyboardEvent, name: string]
25
- detach: [event: MouseEvent, tagId?: number | string]
26
- remove: [event: MouseEvent, tagId?: number | string]
27
- update: [event: FocusEvent | KeyboardEvent, name: string, tagId?: number | string]
28
- }>()
29
-
30
- const modelValue = defineModel<BaseTags['modelValue']>({ default: () => [] })
31
- const keyForTagCreation = ref(Date.now())
32
- const keyForTagUpdate = ref(Date.now())
33
- const root = ref<HTMLDivElement>()
34
- const showingAllTags = ref(false)
35
- const showingTagCreationField = ref(false)
36
- const { t } = useI18n()
37
-
38
- const sortedTags = computed({
39
- get() {
40
- return [...props.tags]
41
- .filter((_tag, tagIndex) => tagIndex < props.maxTags || showingAllTags.value)
42
- .toSorted((tagA, tagB) => {
43
- return (tagA.order ?? 0) < (tagB.order ?? 0) ? -1 : 0
44
- })
45
- },
46
-
47
- set(tags) {
48
- const suiteForTags = tags
49
- .map((tag, order) => ({
50
- id: tag.id,
51
- order,
52
- }))
53
- .map(tag => ({
54
- id: tag.id,
55
- order: tag.order,
56
- }))
57
-
58
- return suiteForTags
59
- },
60
- })
61
-
62
- function onCreateTag(event: FocusEvent | KeyboardEvent, name: string) {
63
- showingTagCreationField.value = false
64
- keyForTagCreation.value = Date.now()
65
-
66
- emit('create', event, name)
67
- }
68
-
69
- function onGoBack(event: MouseEvent) {
70
- emit('back', event)
71
- }
72
-
73
- function onRemoveTag(event: MouseEvent, tagId?: number | string) {
74
- emit('remove', event, tagId)
75
- }
76
-
77
- function onShowAllTags() {
78
- showingAllTags.value = true
79
- }
80
-
81
- function onShowTagField() {
82
- if (!showingTagCreationField.value) {
83
- showingTagCreationField.value = true
84
-
85
- nextTick(() => {
86
- const element = root.value?.querySelector<HTMLInputElement>('input[type="text"]')
87
-
88
- element?.focus()
89
- })
90
- }
91
- }
92
-
93
- function onTagClick(event: MouseEvent, tagId?: number | string) {
94
- if (props.selectable && tagId) {
95
- let value = [...modelValue.value]
96
-
97
- if (value.includes(tagId)) {
98
- value.splice(value.indexOf(tagId), 1)
99
- emit('detach', event, tagId)
100
- }
101
- else if (props.selectableUnique) {
102
- value = [tagId]
103
-
104
- emit('attach', event, tagId)
105
- }
106
- else {
107
- value.push(tagId)
108
- emit('attach', event, tagId)
109
- }
110
-
111
- modelValue.value = value
112
- }
113
- }
114
-
115
- function onUpdateTag(event: FocusEvent | KeyboardEvent, name: string, tagId?: number | string) {
116
- if (name) {
117
- emit('update', event, name, tagId)
118
- }
119
-
120
- else {
121
- keyForTagUpdate.value = Date.now()
122
- }
123
- }
124
- </script>
125
-
126
- <template>
127
- <div
128
- ref="root"
129
- class="flex items-center"
130
- :class="{
131
- 'flex-wrap': wrap,
132
- 'w-full overflow-x-auto': !wrap,
133
- '-mt-0.75 -ml-0.75': size === 'sm',
134
- '-mt-1 -ml-1': size === 'base',
135
- }"
136
- >
137
- <BaseTag
138
- v-if="hasBack"
139
- key="BaseTagToGoBack"
140
- :class="{
141
- 'm-0.75': size === 'sm',
142
- 'm-1': size === 'base',
143
- }"
144
- color="indigo"
145
- :icon="getLayerIcon('back')"
146
- :size="size"
147
- :text="t('cancel')"
148
- @click="onGoBack"
149
- />
150
-
151
- <BaseTag
152
- v-if="hasCreation"
153
- :key="keyForTagCreation"
154
- :class="{
155
- 'm-0.75': size === 'sm',
156
- 'm-1': size === 'base',
157
- }"
158
- color="indigo"
159
- :editable="showingTagCreationField"
160
- :icon="showingTagCreationField ? getLayerIcon('tags') : getLayerIcon('plus')"
161
- is-creation
162
- :light="false"
163
- :size="size"
164
- :text="showingTagCreationField ? '' : t('newTag')"
165
- @click="onShowTagField"
166
- @input-blur="onCreateTag"
167
- @input-submit="onCreateTag"
168
- />
169
-
170
- <div class="flex items-center">
171
- <span
172
- v-for="(tag, tagIndex) in sortedTags"
173
- :key="tag.id ? `${tag.id}_${tag.order}_${keyForTagUpdate}` : tagIndex"
174
- class="flex flex-wrap items-center"
175
- :class="{
176
- 'm-0.75': size === 'sm',
177
- 'm-1': size === 'base',
178
- }"
179
- >
180
- <template v-if="tag">
181
- <BaseTag
182
- :id="tag.id || tagIndex"
183
- :key="`${tag.id}_${tagIndex}`"
184
- :active="tag.id && modelValue.includes(tag.id) || tag.active"
185
- :avatar="tag.avatar"
186
- :circle="tag.circle"
187
- :clickable="clickable"
188
- :color="tag.color"
189
- :draggable="draggable"
190
- :editable="editable"
191
- :icon="tag.icon"
192
- :icon-size="tag.iconSize"
193
- :light="tag.light"
194
- :removable="removable"
195
- :size="size"
196
- :text="tag.text"
197
- :to="tag.to"
198
- @click="onTagClick"
199
- @input-blur="onUpdateTag"
200
- @input-submit="onUpdateTag"
201
- @remove="onRemoveTag"
202
- />
203
- </template>
204
- </span>
205
- </div>
206
-
207
- <BaseTag
208
- v-if="tags.length > maxTags && !showingAllTags"
209
- key="showMore"
210
- :class="{
211
- 'm-0.75': size === 'sm',
212
- 'm-1': size === 'base',
213
- }"
214
- :size="size"
215
- :text="t('showMore', { value: tags.length - maxTags })"
216
- @click="onShowAllTags"
217
- />
218
- </div>
219
- </template>
220
-
221
- <i18n lang="json">
222
- {
223
- "de": {
224
- "cancel": "Abbrechen",
225
- "newTag": "Neues Tag",
226
- "showMore": "& {value} weiteres | & {value} weitere"
227
- },
228
- "en": {
229
- "cancel": "Cancel",
230
- "newTag": "New tag",
231
- "showMore": "& {value} other | & {value} others"
232
- },
233
- "es": {
234
- "cancel": "Cancelar",
235
- "newTag": "Nueva etiqueta",
236
- "showMore": "& {value} otro | & {value} otros"
237
- },
238
- "fr": {
239
- "cancel": "Annuler",
240
- "newTag": "Nouveau tag",
241
- "showMore": "& {value} autre | & {value} autres"
242
- },
243
- "id": {
244
- "cancel": "Batal",
245
- "newTag": "Tag baru",
246
- "showMore": "& {value} lainnya | & {value} lainnya"
247
- },
248
- "it": {
249
- "cancel": "Annulla",
250
- "newTag": "Nuovo tag",
251
- "showMore": "& {value} altro | & {value} altri"
252
- },
253
- "ja": {
254
- "cancel": "キャンセル",
255
- "newTag": "新しいタグ",
256
- "showMore": "& {value} その他 | & {value} その他"
257
- },
258
- "ko": {
259
- "cancel": "취소",
260
- "newTag": "새 태그",
261
- "showMore": "& {value}개 더 | & {value}개 더"
262
- },
263
- "nl": {
264
- "cancel": "Annuleren",
265
- "newTag": "Nieuwe tag",
266
- "showMore": "& {value} andere | & {value} anderen"
267
- },
268
- "pl": {
269
- "cancel": "Anuluj",
270
- "newTag": "Nowy tag",
271
- "showMore": "& {value} inny | & {value} innych"
272
- },
273
- "pt": {
274
- "cancel": "Cancelar",
275
- "newTag": "Nova etiqueta",
276
- "showMore": "& {value} outro | & {value} outros"
277
- },
278
- "pt-BR": {
279
- "cancel": "Cancelar",
280
- "newTag": "Nova tag",
281
- "showMore": "& {value} outro | & {value} outros"
282
- },
283
- "vi": {
284
- "cancel": "Hủy",
285
- "newTag": "Thẻ mới",
286
- "showMore": "& {value} mục khác | & {value} mục khác"
287
- }
288
- }
289
- </i18n>