@saasmakers/ui 0.1.59 → 0.1.61

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.
@@ -0,0 +1,291 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldSelect, FieldSelectOption } from '../../types/fields'
3
+ import { vOnClickOutside } from '@vueuse/components'
4
+ import { getIcon } from '../../composables/useIcons'
5
+
6
+ const props = withDefaults(defineProps<FieldSelect>(), {
7
+ border: 'full',
8
+ caret: true,
9
+ columns: () => [],
10
+ description: '',
11
+ direction: 'bottom',
12
+ disabled: false,
13
+ hideError: false,
14
+ label: '',
15
+ labelIcon: undefined,
16
+ maxHeight: 'xs',
17
+ modelValue: undefined,
18
+ openOnHover: false,
19
+ options: () => [],
20
+ padding: true,
21
+ placeholder: '',
22
+ required: false,
23
+ size: 'base',
24
+ validation: undefined,
25
+ })
26
+
27
+ const emit = defineEmits<{
28
+ 'change': [event: MouseEvent, value?: number | string]
29
+ 'click': [event: MouseEvent, value?: number | string]
30
+ 'optionClick': [event: MouseEvent, value?: number | string]
31
+ 'update:modelValue': [value?: number | string]
32
+ }>()
33
+
34
+ const { fadeIn } = useMotion()
35
+
36
+ const opened = ref(false)
37
+ const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
38
+
39
+ const computedColumns = computed(() => {
40
+ if (props.columns.length) {
41
+ return props.columns
42
+ }
43
+
44
+ return [{ options: computedOptions.value }]
45
+ })
46
+
47
+ const computedOptions = computed(() => {
48
+ const options = []
49
+
50
+ // Add index to each option
51
+ if (props.columns.length) {
52
+ for (const column of props.columns) {
53
+ for (const columnOption of column.options) {
54
+ options.push(columnOption)
55
+ }
56
+ }
57
+ }
58
+ else {
59
+ for (const columnOption of props.options) {
60
+ options.push(columnOption)
61
+ }
62
+ }
63
+
64
+ return options
65
+ })
66
+
67
+ const selectedOption = computed(() => {
68
+ return computedOptions.value.find((option) => {
69
+ return option.value === props.modelValue
70
+ })
71
+ })
72
+
73
+ function reset() {
74
+ opened.value = false
75
+ }
76
+
77
+ function selectOption(event: MouseEvent, value?: number | string) {
78
+ emit('change', event, value)
79
+ emit('update:modelValue', value)
80
+
81
+ reset()
82
+ }
83
+
84
+ function onClose() {
85
+ reset()
86
+ }
87
+
88
+ function onContainerClick(event: MouseEvent) {
89
+ if (!props.disabled) {
90
+ opened.value = !opened.value
91
+
92
+ emit('click', event, selectedOption.value?.value)
93
+ }
94
+ }
95
+
96
+ function onContainerKeypress(event: KeyboardEvent) {
97
+ if (['Enter', 'Space'].includes(event.code)) {
98
+ (event.target as HTMLElement)?.click()
99
+ }
100
+ }
101
+
102
+ function onLabelClick() {
103
+ if (!props.disabled) {
104
+ opened.value = !opened.value
105
+ }
106
+ }
107
+
108
+ function onMouseEnter() {
109
+ if (props.openOnHover) {
110
+ opened.value = true
111
+ }
112
+ }
113
+
114
+ function onMouseLeave() {
115
+ if (props.openOnHover) {
116
+ opened.value = false
117
+ }
118
+ }
119
+
120
+ function onOptionClick(event: MouseEvent, option: FieldSelectOption) {
121
+ // Check that the option is not currently selected
122
+ if ((selectedOption.value || {}).value !== option.value) {
123
+ selectOption(event, option.value)
124
+ }
125
+
126
+ else {
127
+ reset()
128
+ }
129
+
130
+ emit('optionClick', event, option.value)
131
+ }
132
+ </script>
133
+
134
+ <template>
135
+ <div class="flex flex-col">
136
+ <FieldLabel
137
+ v-if="label"
138
+ :disabled="disabled"
139
+ :for-field="uuid"
140
+ has-margin-bottom
141
+ :icon="labelIcon"
142
+ :label="label"
143
+ :required="required"
144
+ :size="size"
145
+ @click="onLabelClick"
146
+ />
147
+
148
+ <div
149
+ v-on-click-outside="onClose"
150
+ class="text-left"
151
+ :class="{
152
+ 'cursor-not-allowed': disabled,
153
+ 'cursor-pointer': !disabled,
154
+ }"
155
+ @mouseenter="onMouseEnter"
156
+ @mouseleave="onMouseLeave"
157
+ >
158
+ <div
159
+ class="relative"
160
+ :class="{ 'shadow-sm': border === 'full' }"
161
+ >
162
+ <div
163
+ class="group flex items-center rounded-lg bg-white outline-none dark:bg-gray-900"
164
+ :class="{
165
+ 'border shadow-sm border-gray-200 dark:border-gray-800': border === 'full',
166
+ 'border-b border-gray-200 dark:border-gray-800': border === 'bottom',
167
+ 'border-0': border === 'none',
168
+ 'border-gray-400 dark:border-gray-600': opened,
169
+ 'hover:border-gray-300 dark:hover:border-gray-700': !opened,
170
+ 'focus:border-gray-400 dark:focus:border-gray-600': !disabled,
171
+
172
+ 'text-2xs': size === 'xs',
173
+ 'text-xs': size === 'sm',
174
+ 'text-sm': size === 'base',
175
+ 'text-base': size === 'lg',
176
+
177
+ 'px-4': padding,
178
+
179
+ 'h-8': size === 'xs',
180
+ 'h-10': size === 'sm',
181
+ 'h-12': size === 'base',
182
+ 'h-14': size === 'lg',
183
+ }"
184
+ tabindex="0"
185
+ @click="onContainerClick"
186
+ @keypress.prevent="onContainerKeypress"
187
+ >
188
+ <template v-if="selectedOption">
189
+ <BaseIcon
190
+ v-if="selectedOption.icon"
191
+ class="pointer-events-none mr-2"
192
+ :icon="selectedOption.icon"
193
+ />
194
+
195
+ <span class="flex-1 truncate font-medium uppercase">
196
+ {{ selectedOption.text }}
197
+ </span>
198
+ </template>
199
+
200
+ <span
201
+ v-else-if="placeholder"
202
+ class="flex-1 truncate text-gray-600 font-medium uppercase dark:text-gray-400 group-hover:text-gray-900 dark:group-hover:text-gray-100"
203
+ >
204
+ {{ placeholder }}
205
+ </span>
206
+
207
+ <BaseIcon
208
+ v-if="caret"
209
+ class="ml-2 flex-initial"
210
+ :class="{ 'rotate-180 transform': opened }"
211
+ color="gray"
212
+ :icon="getIcon('arrowDown')"
213
+ />
214
+ </div>
215
+
216
+ <Motion
217
+ v-if="opened"
218
+ :animate="fadeIn.animate"
219
+ as="div"
220
+ class="absolute left-0 right-0 z-10 mt-0.5 cursor-default"
221
+ :class="{
222
+ 'top-0': direction === 'bottom',
223
+ 'bottom-0': direction === 'top',
224
+ }"
225
+ :initial="fadeIn.initial"
226
+ >
227
+ <div
228
+ class="options min-w-xs overflow-y-auto border border-gray-400 rounded-lg bg-white text-gray-600 font-medium dark:border-gray-600 dark:bg-gray-900 dark:text-gray-400"
229
+ :class="{
230
+ 'md:min-w-xl p-1 pb-0': computedColumns.length >= 2,
231
+
232
+ 'mt-8': ['xs', 'sm'].includes(size) && !padding && direction === 'bottom',
233
+ 'mt-10': ((['xs', 'sm'].includes(size) && padding) || (size === 'base' && !padding)) && direction === 'bottom',
234
+ 'mt-12': size === 'base' && padding && direction === 'bottom',
235
+ 'mb-8': ['xs', 'sm'].includes(size) && !padding && direction === 'top',
236
+ 'mb-10': ((['xs', 'sm'].includes(size) && padding) || (size === 'base' && !padding)) && direction === 'top',
237
+ 'mb-12': size === 'base' && padding && direction === 'top',
238
+
239
+ 'max-h-xs': maxHeight === 'xs',
240
+ 'max-h-sm': maxHeight === 'sm',
241
+ 'max-h-md': maxHeight === 'md',
242
+ 'max-h-lg': maxHeight === 'lg',
243
+ }"
244
+ >
245
+ <div
246
+ v-for="(column, columnIndex) in computedColumns"
247
+ :key="columnIndex"
248
+ >
249
+ <BaseIcon
250
+ v-if="column.title"
251
+ class="px-2 py-1 text-gray-900 dark:text-gray-100"
252
+ :text="column.title"
253
+ />
254
+
255
+ <div
256
+ v-for="option in column.options"
257
+ :key="option.value"
258
+ class="group flex cursor-pointer items-center outline-none"
259
+ :class="{
260
+ 'border-b border-gray-200 dark:border-gray-800 px-4 py-3 hover:bg-gray-100 dark:hover:bg-gray-900 last:border-b-0': computedColumns.length < 2,
261
+ 'px-2 py-1 last:mb-2': computedColumns.length >= 2,
262
+
263
+ 'font-medium underline': selectedOption && option.value === selectedOption.value,
264
+ 'text-gray-900 dark:text-gray-100': selectedOption && option.value === selectedOption.value,
265
+ 'bg-white dark:bg-gray-900': selectedOption && option.value !== selectedOption.value,
266
+
267
+ }"
268
+ @click="onOptionClick($event, option)"
269
+ >
270
+ <BaseIcon
271
+ v-if="option.text"
272
+ class="pointer-events-none mr-2 flex-initial"
273
+ :icon="option.icon"
274
+ :size="size"
275
+ :text="option.text"
276
+ />
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </Motion>
281
+ </div>
282
+ </div>
283
+
284
+ <FieldMessage
285
+ :description="description"
286
+ :hide-error="hideError"
287
+ :size="size"
288
+ :validation="validation"
289
+ />
290
+ </div>
291
+ </template>
@@ -0,0 +1,100 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldTabs, FieldTabsAction } from '../../types/fields'
3
+ import { NuxtLinkLocale } from '#components'
4
+
5
+ const props = withDefaults(defineProps<FieldTabs>(), {
6
+ minimizeOnMobile: false,
7
+ modelValue: undefined,
8
+ multiple: false,
9
+ size: 'base',
10
+ tabs: undefined,
11
+ theme: 'rounded',
12
+ })
13
+
14
+ const emit = defineEmits<{
15
+ 'change': [event: MouseEvent, tabValue: number | string, action: FieldTabsAction, activeTabs: Array<number | string>]
16
+ 'click': [event: MouseEvent, tabValue: number | string, activeTabs: Array<number | string>]
17
+ 'update:modelValue': [value?: Array<number | string> | number | string]
18
+ }>()
19
+
20
+ function onTabClick(event: MouseEvent, tabValue: number | string) {
21
+ let activeTabs = [tabValue]
22
+
23
+ // When multiple values are not allowed and tab is not already active
24
+ if (!props.multiple && props.modelValue !== tabValue) {
25
+ emit('change', event, tabValue, 'added', activeTabs)
26
+ }
27
+
28
+ // When multiple values are allowed
29
+ if (props.multiple) {
30
+ // Remove the tab when already active
31
+ if (Array.isArray(props.modelValue) && props.modelValue.includes(tabValue)) {
32
+ activeTabs = props.modelValue.filter(item => item !== tabValue)
33
+
34
+ emit('change', event, tabValue, 'removed', activeTabs)
35
+ }
36
+ // Push the tab when not already active
37
+ else {
38
+ activeTabs = Array.isArray(props.modelValue) ? [...props.modelValue, tabValue] : [tabValue]
39
+ emit('change', event, tabValue, 'added', activeTabs)
40
+ }
41
+ }
42
+
43
+ emit('click', event, tabValue, activeTabs)
44
+ emit('update:modelValue', props.multiple ? activeTabs : tabValue)
45
+ }
46
+ </script>
47
+
48
+ <template>
49
+ <div
50
+ class="item-center flex border border-gray-300 shadow-inner dark:border-gray-700"
51
+ :class="{
52
+ 'gap-0 border-b border-gray-300 dark:border-gray-700 bg-gray-100 dark:bg-gray-900': theme === 'border',
53
+ 'gap-2 rounded-lg bg-gray-200 dark:bg-gray-800 p-0.5 sm:p-1': theme === 'rounded',
54
+ }"
55
+ >
56
+ <component
57
+ :is="tab.to ? NuxtLinkLocale : 'div'"
58
+ v-for="tab in tabs"
59
+ :key="tab.value"
60
+ class="flex cursor-pointer items-center justify-center text-xs"
61
+ :class="{
62
+ 'text-gray-900 dark:text-gray-100': modelValue === tab.value,
63
+ 'text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100': modelValue !== tab.value,
64
+ 'flex-1': !minimizeOnMobile || modelValue !== tab.value,
65
+ 'flex-2 sm:flex-1': minimizeOnMobile && modelValue === tab.value,
66
+
67
+ 'py-1.5 px-2.5': size === 'xs' && theme === 'rounded',
68
+ 'py-2 px-3': size === 'sm' && theme === 'rounded',
69
+ 'py-2.5 px-3.5': size === 'base' && theme === 'rounded' || size === 'xs' && theme === 'border',
70
+ 'py-3 px-4': size === 'lg' && theme === 'rounded' || size === 'sm' && theme === 'border',
71
+ 'py-3.5 px-4.5': size === 'base' && theme === 'border',
72
+ 'py-4 px-5': size === 'lg' && theme === 'border',
73
+
74
+ 'border-b-2 border-gray-300 dark:border-gray-700 transition duration-500': theme === 'border',
75
+ 'border-gray-900 dark:border-gray-100': modelValue === tab.value && theme === 'border',
76
+ 'border-gray-100 dark:border-gray-900': modelValue !== tab.value && theme === 'border',
77
+
78
+ 'rounded-lg': theme === 'rounded',
79
+ 'bg-white dark:bg-gray-900 shadow': modelValue === tab.value && theme === 'rounded',
80
+ 'bg-gray-200 dark:bg-gray-800': modelValue !== tab.value && theme === 'rounded',
81
+ }"
82
+ :to="tab.to"
83
+ @click="onTabClick($event, tab.value)"
84
+ >
85
+ <BaseIcon
86
+ v-if="tab.icon"
87
+ class="mr-1.5 flex-initial"
88
+ :color="modelValue === tab.value ? tab.activeColor : 'black'"
89
+ :icon="tab.icon"
90
+ />
91
+
92
+ <span
93
+ class="flex-initial"
94
+ :class="{ 'hidden sm:inline': minimizeOnMobile && modelValue !== tab.value }"
95
+ >
96
+ {{ tab.label }}
97
+ </span>
98
+ </component>
99
+ </div>
100
+ </template>
@@ -0,0 +1,123 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldTextarea } from '../../types/fields'
3
+
4
+ const props = withDefaults(defineProps<FieldTextarea>(), {
5
+ autofocus: false,
6
+ background: 'gray',
7
+ border: true,
8
+ description: '',
9
+ disabled: false,
10
+ fullWidth: true,
11
+ hideError: false,
12
+ label: '',
13
+ labelIcon: undefined,
14
+ loading: false,
15
+ modelValue: '',
16
+ padding: true,
17
+ placeholder: '',
18
+ required: false,
19
+ resize: false,
20
+ rows: 3,
21
+ size: 'base',
22
+ validation: undefined,
23
+ })
24
+
25
+ const emit = defineEmits<{
26
+ 'blur': [event: FocusEvent, value: string]
27
+ 'click': [event: MouseEvent, value: string]
28
+ 'update:modelValue': [value: string]
29
+ }>()
30
+
31
+ const textarea = ref<HTMLTextAreaElement>()
32
+ const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
33
+
34
+ const { isDesktopBrowser } = useDevice()
35
+
36
+ const value = computed({
37
+ get() {
38
+ return `${props.modelValue}`
39
+ },
40
+ set(value) {
41
+ emit('update:modelValue', value)
42
+ },
43
+ })
44
+
45
+ onMounted(() => {
46
+ if (props.autofocus && textarea.value && isDesktopBrowser.value) {
47
+ textarea.value.focus()
48
+ }
49
+ })
50
+
51
+ function onFieldBlur(event: FocusEvent) {
52
+ const value = textarea.value?.value || ''
53
+
54
+ emit('blur', event, value)
55
+ }
56
+
57
+ function onFieldClick(event: MouseEvent) {
58
+ const value = textarea.value?.value || ''
59
+
60
+ emit('click', event, value)
61
+ }
62
+
63
+ function onFieldInput() {
64
+ const value = textarea.value?.value || ''
65
+
66
+ emit('update:modelValue', value)
67
+ }
68
+ </script>
69
+
70
+ <template>
71
+ <div
72
+ class="relative flex flex-col"
73
+ :class="{ 'w-full': fullWidth }"
74
+ >
75
+ <FieldLabel
76
+ v-if="label"
77
+ :disabled="disabled"
78
+ :for-field="uuid"
79
+ has-margin-bottom
80
+ :icon="labelIcon"
81
+ :label="label"
82
+ :required="required"
83
+ :size="size"
84
+ />
85
+
86
+ <textarea
87
+ :id="uuid"
88
+ ref="textarea"
89
+ v-model="value"
90
+ class="w-full flex-1 appearance-none rounded-lg text-gray-900 font-medium tracking-tight uppercase outline-none dark:text-gray-100 placeholder-gray-600 dark:placeholder-gray-400 focus:placeholder-gray-900 hover:placeholder-gray-900 dark:focus:placeholder-gray-100 dark:hover:placeholder-gray-100"
91
+ :class="{
92
+ 'p-2.5': padding,
93
+ 'p-0': !padding,
94
+ 'resize-none': !resize,
95
+
96
+ 'bg-gray-100 dark:bg-gray-900': background === 'gray',
97
+ 'bg-white dark:bg-gray-900': background === 'white',
98
+
99
+ 'border shadow-sm border-gray-200 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-700 focus:border-gray-400 dark:focus:border-gray-600': border,
100
+ 'border-0': !border,
101
+
102
+ 'text-xs': size === 'xs',
103
+ 'text-sm': size === 'sm',
104
+ 'text-base': size === 'base',
105
+ 'text-lg': size === 'lg',
106
+ }"
107
+ data-enable-grammarly="false"
108
+ :placeholder="placeholder"
109
+ :rows="rows"
110
+ spellcheck="false"
111
+ @blur="onFieldBlur"
112
+ @click="onFieldClick"
113
+ @input="onFieldInput"
114
+ />
115
+
116
+ <FieldMessage
117
+ :description="description"
118
+ :hide-error="hideError"
119
+ :size="size"
120
+ :validation="validation"
121
+ />
122
+ </div>
123
+ </template>
@@ -0,0 +1,73 @@
1
+ <script lang="ts" setup>
2
+ import type { FieldTime } from '../../types/fields'
3
+
4
+ const props = withDefaults(defineProps<FieldTime>(), {
5
+ background: 'gray',
6
+ description: '',
7
+ disabled: false,
8
+ hideError: true,
9
+ icon: undefined,
10
+ id: undefined,
11
+ label: '',
12
+ labelIcon: undefined,
13
+ modelValue: '',
14
+ name: undefined,
15
+ required: false,
16
+ size: 'base',
17
+ validation: undefined,
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ 'blur': [event: FocusEvent, value: string, name?: string]
22
+ 'update:modelValue': [value: string, name?: string]
23
+ }>()
24
+
25
+ const root = ref<HTMLDivElement>()
26
+ const uuid = ref(`${Math.floor((1 + Math.random()) * 0x100000)}`)
27
+
28
+ function onFieldBlur(event: FocusEvent) {
29
+ emit('blur', event, props.modelValue, props.name)
30
+ }
31
+
32
+ function onFieldInput() {
33
+ const value = root.value?.querySelector('input')?.value || ''
34
+
35
+ emit('update:modelValue', value, props.name)
36
+ }
37
+ </script>
38
+
39
+ <template>
40
+ <div ref="root">
41
+ <FieldLabel
42
+ v-if="label"
43
+ :disabled="disabled"
44
+ :for-field="uuid"
45
+ has-margin-bottom
46
+ :icon="labelIcon"
47
+ :label="label"
48
+ :required="required"
49
+ :size="size"
50
+ />
51
+
52
+ <div class="item-center h-[42px] flex overflow-hidden border border-gray-200 rounded-lg pl-3 pr-2 dark:border-gray-800 focus-within:border-gray-400 hover:border-gray-300 dark:focus-within:border-gray-600 dark:hover:border-gray-700">
53
+ <input
54
+ :id="`${id}`"
55
+ class="text-sm outline-none"
56
+ :class="{
57
+ 'bg-gray-100 dark:bg-gray-900': background === 'gray',
58
+ 'bg-white dark:bg-gray-900': background === 'white',
59
+ }"
60
+ type="time"
61
+ @blur="onFieldBlur"
62
+ @input="onFieldInput"
63
+ >
64
+ </div>
65
+
66
+ <FieldMessage
67
+ :description="description"
68
+ :hide-error="hideError"
69
+ :size="size"
70
+ :validation="validation"
71
+ />
72
+ </div>
73
+ </template>
@@ -0,0 +1,11 @@
1
+ export default function useDevice() {
2
+ const isDesktopBrowser = computed(() => {
3
+ if (typeof navigator === 'undefined') {
4
+ return true
5
+ }
6
+
7
+ return !navigator.userAgentData?.mobile
8
+ })
9
+
10
+ return { isDesktopBrowser }
11
+ }
@@ -12,12 +12,16 @@ const icons = {
12
12
  arrowDown: 'solar:alt-arrow-down-bold',
13
13
  arrowRight: 'solar:alt-arrow-right-bold',
14
14
  arrowUp: 'solar:alt-arrow-up-bold',
15
+ back: 'hugeicons:arrow-turn-backward',
15
16
  checkCircle: 'hugeicons:checkmark-circle-02',
16
17
  close: 'hugeicons:cancel-01',
17
18
  closeCircle: 'hugeicons:cancel-circle',
18
19
  default: 'hugeicons:help-circle',
20
+ drag: 'mdi:drag-horizontal-variant',
19
21
  exclamationCircle: 'hugeicons:alert-circle',
20
22
  infoCircle: 'hugeicons:information-circle',
23
+ plus: 'hugeicons:add-01',
24
+ tags: 'hugeicons:tags',
21
25
  } as const
22
26
 
23
27
  export function getIcon(attribute: keyof typeof icons) {
@@ -1,5 +1,12 @@
1
1
  import numbroLib from 'numbro'
2
2
 
3
+ export function normalizeText(text: string) {
4
+ return text
5
+ .toLowerCase()
6
+ .normalize('NFD')
7
+ .replace(/[\u0300-\u036F]/g, '')
8
+ }
9
+
3
10
  export function numbro(number: '∞' | number | undefined, format?: string) {
4
11
  if (!number && number !== 0) {
5
12
  return ''
@@ -262,6 +262,48 @@ export interface BaseSpinner {
262
262
  uppercase?: boolean
263
263
  }
264
264
 
265
+ export interface BaseTag {
266
+ active?: boolean
267
+ avatar?: string
268
+ circle?: string
269
+ clickable?: boolean
270
+ color?: BaseColor
271
+ draggable?: boolean
272
+ editable?: boolean
273
+ icon?: BaseIconValue
274
+ iconSize?: number
275
+ id?: number
276
+ isCreation?: boolean
277
+ light?: boolean
278
+ order?: number
279
+ removable?: boolean
280
+ rounded?: boolean
281
+ size?: BaseTagSize
282
+ text: BaseTextText
283
+ to?: RouteLocationNamedI18n
284
+ truncate?: boolean
285
+ uppercase?: boolean
286
+ }
287
+
288
+ export type BaseTagSize = 'base' | 'sm'
289
+
290
+ export interface BaseTags {
291
+ active?: boolean
292
+ clickable?: boolean
293
+ draggable?: boolean
294
+ editable?: boolean
295
+ hasBack?: boolean
296
+ hasCreation?: boolean
297
+ maxTags?: number
298
+ removable?: boolean
299
+ selectable?: boolean
300
+ selectableUnique?: boolean
301
+ size?: BaseTagSize
302
+ tags: BaseTag[]
303
+ value?: (number | string)[]
304
+ wrap?: boolean
305
+ }
306
+
265
307
  export interface BaseText {
266
308
  background?: BaseTextBackground
267
309
  bold?: boolean