@meistrari/tela-build 1.52.1 → 1.52.2
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.
- package/components/tela/button/button.vue +48 -25
- package/components/tela/combobox/combobox-group.vue +1 -1
- package/components/tela/combobox/combobox-indicator.vue +1 -1
- package/components/tela/combobox/combobox-input.vue +16 -6
- package/components/tela/combobox/combobox-item.vue +1 -1
- package/components/tela/combobox/combobox-label.vue +1 -1
- package/components/tela/combobox/combobox-list.vue +2 -12
- package/components/tela/combobox/combobox-module-selector.vue +6 -16
- package/components/tela/combobox/combobox-trigger.vue +20 -2
- package/components/tela/combobox/combobox.stories.ts +3 -3
- package/components/tela/combobox/combobox.vue +157 -305
- package/components/tela/icon-button/icon-button.vue +10 -10
- package/components/tela/preview/preview-select-variable.vue +10 -22
- package/components/tela/tabs/tabs-trigger.vue +13 -3
- package/components/tela/tabs/tabs.mdx +13 -0
- package/docs/tokens.md +8 -1
- package/package.json +1 -1
- package/unocss.config.ts +4 -4
- package/utils/design-tokens.ts +68 -36
|
@@ -25,40 +25,50 @@ const resolvedVariant = computed(() =>
|
|
|
25
25
|
|| 'primary',
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
+
const slots = useSlots()
|
|
29
|
+
|
|
30
|
+
const hasLeadingIcon = computed(() => !!((props.loading && props.leading) || slots.leading || (props.icon && props.leading)))
|
|
31
|
+
const hasTrailingIcon = computed(() => !!((props.loading && !props.leading) || slots.trailing || (props.icon && !props.leading)))
|
|
32
|
+
|
|
28
33
|
const sizeStyle = computed(() => (({
|
|
29
|
-
sm: 'text-12px leading-16px
|
|
30
|
-
md: 'text-14px leading-18px
|
|
31
|
-
lg: 'text-16px leading-20px
|
|
34
|
+
sm: '[--padding-x:10px] text-[12px] leading-[16px] tracking-normal py-[4px] rounded-[9px]',
|
|
35
|
+
md: '[--padding-x:12px] text-[14px] leading-[18px] -tracking-[0.15px] py-[7px] rounded-[11px]',
|
|
36
|
+
lg: '[--padding-x:16px] text-[16px] leading-[20px] -tracking-[0.15px] py-[10px] rounded-[13px]',
|
|
32
37
|
}) as Record<ButtonSize, string>)[resolvedSize.value] ?? '')
|
|
33
38
|
|
|
39
|
+
const paddingStyle = computed(() => [
|
|
40
|
+
hasLeadingIcon.value ? 'pl-[calc(var(--padding-x)_-_2px)]' : 'pl-[var(--padding-x)]',
|
|
41
|
+
hasTrailingIcon.value ? 'pr-[calc(var(--padding-x)_-_2px)]' : 'pr-[var(--padding-x)]',
|
|
42
|
+
].join(' '))
|
|
43
|
+
|
|
34
44
|
const variantStyle = computed(() => (({
|
|
35
45
|
'primary': fold`
|
|
36
|
-
bg-
|
|
37
|
-
hover:bg-
|
|
38
|
-
active:bg-
|
|
46
|
+
bg-btn-primary text-inverse
|
|
47
|
+
hover:bg-btn-primary-hover
|
|
48
|
+
active:bg-btn-primary-active
|
|
39
49
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
40
50
|
`,
|
|
41
51
|
'secondary': fold`
|
|
42
|
-
bg-
|
|
52
|
+
bg-btn-secondary text-contrast border border-0.5px
|
|
43
53
|
[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]
|
|
44
|
-
hover:bg-
|
|
45
|
-
active:bg-
|
|
54
|
+
hover:bg-btn-secondary-hover hover:border-strong
|
|
55
|
+
active:bg-btn-secondary-active active:border-neutral-400/60
|
|
46
56
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
47
57
|
`,
|
|
48
58
|
'ghost': fold`
|
|
49
|
-
bg-
|
|
50
|
-
hover:bg-
|
|
59
|
+
bg-btn-ghost text-contrast
|
|
60
|
+
hover:bg-btn-ghost-hover
|
|
51
61
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
52
62
|
`,
|
|
53
63
|
'disabled': fold`
|
|
54
|
-
bg-
|
|
64
|
+
bg-btn-disabled text-tertiary !cursor-not-allowed
|
|
55
65
|
`,
|
|
56
66
|
'loading': fold`
|
|
57
|
-
bg-
|
|
67
|
+
bg-btn-loading text-neutral-600
|
|
58
68
|
`,
|
|
59
69
|
'danger': fold`
|
|
60
|
-
bg-
|
|
61
|
-
hover:bg-
|
|
70
|
+
bg-btn-danger text-red-500
|
|
71
|
+
hover:bg-btn-danger-hover
|
|
62
72
|
focus:outline-red-600
|
|
63
73
|
`,
|
|
64
74
|
'ghost-danger': fold`
|
|
@@ -67,14 +77,26 @@ const variantStyle = computed(() => (({
|
|
|
67
77
|
focus:outline-red-600
|
|
68
78
|
`,
|
|
69
79
|
'dark': fold`
|
|
70
|
-
bg-
|
|
71
|
-
hover:bg-
|
|
80
|
+
bg-btn-dark text-white border-0.5px border-white/16
|
|
81
|
+
hover:bg-btn-dark-hover active:bg-btn-dark-active
|
|
72
82
|
focus-visible:ring-0.5px focus-visible:ring-cyan-600
|
|
73
83
|
`,
|
|
74
84
|
}) as Record<ButtonVariant | 'disabled' | 'loading', string>)[resolvedVariant.value] ?? '')
|
|
75
85
|
|
|
86
|
+
const iconColorStyle = computed(() => (({
|
|
87
|
+
'primary': 'icon-inverse',
|
|
88
|
+
'secondary': 'icon-secondary',
|
|
89
|
+
'ghost': 'icon-secondary',
|
|
90
|
+
'disabled': 'icon-subtle',
|
|
91
|
+
'loading': 'icon-tertiary',
|
|
92
|
+
'danger': 'red-500',
|
|
93
|
+
'ghost-danger': 'red-700',
|
|
94
|
+
'dark': 'icon-subtle',
|
|
95
|
+
}) as Record<ButtonVariant | 'disabled' | 'loading', string>)[resolvedVariant.value] ?? '')
|
|
96
|
+
|
|
76
97
|
const resolvedStyle = computed(() => [
|
|
77
98
|
sizeStyle.value,
|
|
99
|
+
paddingStyle.value,
|
|
78
100
|
variantStyle.value,
|
|
79
101
|
].join(' '))
|
|
80
102
|
|
|
@@ -100,19 +122,20 @@ const iconSize = computed(() => {
|
|
|
100
122
|
<component
|
|
101
123
|
:is="tag"
|
|
102
124
|
:to="to"
|
|
103
|
-
:class="
|
|
125
|
+
:class="cn(
|
|
126
|
+
'box-border font-540 select-none flex items-center justify-center gap-6px ease-out duration-40',
|
|
127
|
+
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
|
128
|
+
resolvedStyle,
|
|
129
|
+
)"
|
|
104
130
|
:disabled="disabled"
|
|
105
|
-
:cursor="disabled ? 'not-allowed' : 'pointer'"
|
|
106
131
|
:target="target"
|
|
107
|
-
border-box
|
|
108
|
-
select-none
|
|
109
132
|
>
|
|
110
|
-
<TelaIconSpinner v-if="loading && leading" animate-spin />
|
|
133
|
+
<TelaIconSpinner v-if="loading && leading" class="animate-spin" />
|
|
111
134
|
<slot v-else-if="$slots.leading" name="leading" />
|
|
112
|
-
<TelaIcon v-else-if="icon && leading" :name="icon" :size="iconSize" min-h-14px min-w-14px />
|
|
135
|
+
<TelaIcon v-else-if="icon && leading" :name="icon" :size="iconSize" :color="iconColorStyle" class="min-h-14px min-w-14px" />
|
|
113
136
|
<slot />
|
|
114
|
-
<TelaIconSpinner v-if="loading && !leading" animate-spin
|
|
137
|
+
<TelaIconSpinner v-if="loading && !leading" class="animate-spin min-h-14px min-w-14px" />
|
|
115
138
|
<slot v-else-if="$slots.trailing" name="trailing" />
|
|
116
|
-
<TelaIcon v-else-if="icon && !leading" :name="icon" :size="iconSize" />
|
|
139
|
+
<TelaIcon v-else-if="icon && !leading" :name="icon" :size="iconSize" :color="iconColorStyle" class="min-h-14px min-w-14px" />
|
|
117
140
|
</component>
|
|
118
141
|
</template>
|
|
@@ -15,7 +15,7 @@ const delegatedProps = reactiveOmit(props, 'class')
|
|
|
15
15
|
<template>
|
|
16
16
|
<ComboboxGroup
|
|
17
17
|
v-bind="delegatedProps"
|
|
18
|
-
:class="cn('overflow-hidden
|
|
18
|
+
:class="cn('overflow-hidden p-[4px]', props.class)"
|
|
19
19
|
>
|
|
20
20
|
<slot />
|
|
21
21
|
</ComboboxGroup>
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
} from 'reka-ui'
|
|
9
9
|
import type { ComboboxInputEmits, ComboboxInputProps } from 'reka-ui'
|
|
10
10
|
|
|
11
|
+
defineOptions({ inheritAttrs: false })
|
|
12
|
+
|
|
11
13
|
const props = defineProps<ComboboxInputProps & {
|
|
12
14
|
class?: HTMLAttributes['class']
|
|
13
15
|
}>()
|
|
@@ -20,10 +22,18 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
20
22
|
</script>
|
|
21
23
|
|
|
22
24
|
<template>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
|
|
25
|
+
<div class="relative">
|
|
26
|
+
<span class="pointer-events-none absolute inset-y-0 start-0 flex items-center justify-center pl-[13px]">
|
|
27
|
+
<TelaIcon name="i-ph-magnifying-glass-bold" size="12px" class="text-tertiary" />
|
|
28
|
+
</span>
|
|
29
|
+
<ComboboxInput
|
|
30
|
+
v-bind="{ ...forwarded, ...$attrs }"
|
|
31
|
+
:class="cn('flex h-[36px] w-full bg border-b-[0.5px] border-border pl-32px pr-3 body-14-regular text-primary transition-colors placeholder:text-tertiary focus-visible:bg focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50', $slots.deleteSearchValue && 'pr-30px', props.class)"
|
|
32
|
+
>
|
|
33
|
+
<slot />
|
|
34
|
+
</ComboboxInput>
|
|
35
|
+
<div v-if="$slots.deleteSearchValue" class="absolute inset-y-0 end-0 flex items-center justify-center px-2.5">
|
|
36
|
+
<slot name="deleteSearchValue" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
29
39
|
</template>
|
|
@@ -19,7 +19,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
19
19
|
<template>
|
|
20
20
|
<ComboboxItem
|
|
21
21
|
v-bind="forwarded"
|
|
22
|
-
:class="cn('relative flex cursor-pointer gap-2 select-none justify-between items-center rounded-
|
|
22
|
+
:class="cn('relative flex cursor-pointer gap-2 select-none justify-between items-center rounded-[12px] pl-[10px] pr-[8px] py-[7px] body-14-medium outline-none data-[highlighted]:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)"
|
|
23
23
|
>
|
|
24
24
|
<slot />
|
|
25
25
|
</ComboboxItem>
|
|
@@ -15,7 +15,7 @@ const forwarded = useForwardProps(delegatedProps)
|
|
|
15
15
|
<template>
|
|
16
16
|
<ComboboxLabel
|
|
17
17
|
v-bind="forwarded"
|
|
18
|
-
:class="cn('
|
|
18
|
+
:class="cn('py-[4px] pl-[10px] body-12-semibold text-secondary', props.class)"
|
|
19
19
|
>
|
|
20
20
|
<slot />
|
|
21
21
|
</ComboboxLabel>
|
|
@@ -28,20 +28,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
|
|
28
28
|
</script>
|
|
29
29
|
|
|
30
30
|
<template>
|
|
31
|
-
<
|
|
31
|
+
<ComboboxPortal :disabled="props.disablePortal">
|
|
32
32
|
<ComboboxContent
|
|
33
33
|
v-bind="forwarded"
|
|
34
|
-
:class="cn('ComboboxContent z-[9999] shadow-
|
|
35
|
-
>
|
|
36
|
-
<ComboboxViewport>
|
|
37
|
-
<slot />
|
|
38
|
-
</ComboboxViewport>
|
|
39
|
-
</ComboboxContent>
|
|
40
|
-
</template>
|
|
41
|
-
<ComboboxPortal v-else>
|
|
42
|
-
<ComboboxContent
|
|
43
|
-
v-bind="forwarded"
|
|
44
|
-
:class="cn('ComboboxContent z-[9999] shadow-lg outline-none rounded-xl bg-white-1000 border-[0.5px] border-gray-200 pointer-events-auto', props.class)"
|
|
34
|
+
:class="cn('ComboboxContent z-[9999] [box-shadow:0_4px_16px_0_rgba(85,91,98,0.14)] overflow-hidden outline-none rounded-[16px] bg border-0.5px border pointer-events-auto', props.class)"
|
|
45
35
|
>
|
|
46
36
|
<ComboboxViewport>
|
|
47
37
|
<slot />
|
|
@@ -263,24 +263,14 @@ watch(innerValue, (value) => {
|
|
|
263
263
|
@pointer-down-outside="closeOnOutsideClick ? undefined : $event.preventDefault()"
|
|
264
264
|
@escape-key-down="closeOnEscape ? undefined : $event.preventDefault()"
|
|
265
265
|
>
|
|
266
|
-
<
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<ComboboxInput
|
|
271
|
-
v-model="search" class="pl-7 rounded-b-none"
|
|
272
|
-
:placeholder="t('models.labels.search')"
|
|
273
|
-
/>
|
|
274
|
-
<div v-if="search.length > 0" class="absolute inset-y-0 end-0 flex items-center justify-center px-2.5">
|
|
275
|
-
<button class="flex items-center justify-center" @click="handleDeleteSearchValue">
|
|
276
|
-
<TelaIcon name="i-ph-x-circle" size="16px" class="text-gray-400" />
|
|
277
|
-
</button>
|
|
278
|
-
</div>
|
|
279
|
-
</div>
|
|
266
|
+
<ComboboxInput
|
|
267
|
+
v-model="search"
|
|
268
|
+
:placeholder="t('models.labels.search')"
|
|
269
|
+
/>
|
|
280
270
|
<Tabs v-model="selectedTab" :default-value="selectedTab">
|
|
281
271
|
<TabsList class="sticky z-10 top-0 shrink-0 flex gap-2 w-full">
|
|
282
272
|
<TabsIndicator />
|
|
283
|
-
<TabsTrigger v-for="tab in tabs" :key="tab.tab" :value="tab.tab">
|
|
273
|
+
<TabsTrigger v-for="tab in tabs" :key="tab.tab" :value="tab.tab" size="sm">
|
|
284
274
|
{{ tab.label }}
|
|
285
275
|
</TabsTrigger>
|
|
286
276
|
</TabsList>
|
|
@@ -315,7 +305,7 @@ watch(innerValue, (value) => {
|
|
|
315
305
|
</TelaButton>
|
|
316
306
|
</ComboboxEmpty>
|
|
317
307
|
<template v-for="group in groupedOptions" :key="group.heading">
|
|
318
|
-
<ComboboxGroup :heading="group.heading"
|
|
308
|
+
<ComboboxGroup :heading="group.heading">
|
|
319
309
|
<ComboboxLabel v-if="!search.trim()" class="py-0 tracking-normal">
|
|
320
310
|
{{ group.heading }}
|
|
321
311
|
</ComboboxLabel>
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'vue'
|
|
3
|
+
import { reactiveOmit } from '@vueuse/core'
|
|
2
4
|
import { ComboboxTrigger } from 'reka-ui'
|
|
3
5
|
import type { ComboboxTriggerProps } from 'reka-ui'
|
|
4
6
|
|
|
5
|
-
const props = defineProps<ComboboxTriggerProps>()
|
|
7
|
+
const props = defineProps<ComboboxTriggerProps & { class?: HTMLAttributes['class'] }>()
|
|
8
|
+
|
|
9
|
+
const delegatedProps = reactiveOmit(props, 'class')
|
|
6
10
|
</script>
|
|
7
11
|
|
|
8
12
|
<template>
|
|
9
|
-
<ComboboxTrigger
|
|
13
|
+
<ComboboxTrigger
|
|
14
|
+
v-bind="delegatedProps"
|
|
15
|
+
:class="cn(
|
|
16
|
+
'group select-none flex items-center justify-between body-14-semibold gap-3 rounded-10px pl-11px pr-10px py-7px bg-white border-0.5px border transition hover:border-strong hover:bg-subtle',
|
|
17
|
+
'data-[state=open]:text-secondary data-[state=open]:border-strong data-[state=open]:bg-subtle',
|
|
18
|
+
'[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]',
|
|
19
|
+
props.class,
|
|
20
|
+
)"
|
|
21
|
+
>
|
|
10
22
|
<slot />
|
|
23
|
+
<TelaIcon
|
|
24
|
+
name="i-ph-caret-down-bold"
|
|
25
|
+
size="15px"
|
|
26
|
+
color="icon-secondary"
|
|
27
|
+
class="shrink-0 transition-transform ease-out duration-150 group-data-[state=open]:rotate-180"
|
|
28
|
+
/>
|
|
11
29
|
</ComboboxTrigger>
|
|
12
30
|
</template>
|
|
@@ -93,7 +93,7 @@ const meta: Meta<typeof Combobox> = {
|
|
|
93
93
|
|
|
94
94
|
export default meta
|
|
95
95
|
|
|
96
|
-
export const Default: StoryObj<{ options: Array<{ value: string
|
|
96
|
+
export const Default: StoryObj<{ options: Array<{ value: string, label: string }> }> = {
|
|
97
97
|
render: args => ({
|
|
98
98
|
components: { ComboboxRoot, ComboboxAnchor, ComboboxTrigger, TelaIcon, TelaButton, ComboboxInput, ComboboxList, ComboboxEmpty, ComboboxGroup, ComboboxItem, ComboboxItemIndicator, ComboboxLabel },
|
|
99
99
|
setup() {
|
|
@@ -105,14 +105,14 @@ export const Default: StoryObj<{ options: Array<{ value: string; label: string }
|
|
|
105
105
|
return selectedValue.value
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
return args.options.find(
|
|
108
|
+
return args.options.find(option => option.value === selectedValue.value)
|
|
109
109
|
})
|
|
110
110
|
|
|
111
111
|
const filteredOptions = computed(() => {
|
|
112
112
|
if (!search.value.trim()) {
|
|
113
113
|
return args.options
|
|
114
114
|
}
|
|
115
|
-
return args.options.filter(
|
|
115
|
+
return args.options.filter(option =>
|
|
116
116
|
option.label.toLowerCase().includes(search.value.toLowerCase()),
|
|
117
117
|
)
|
|
118
118
|
})
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, computed } from 'vue'
|
|
3
2
|
import type { Component, ComponentPublicInstance, Ref } from 'vue'
|
|
4
3
|
import { useMagicKeys } from '@vueuse/core'
|
|
5
4
|
|
|
6
|
-
import ComboboxRoot from './combobox-root.vue'
|
|
7
|
-
import ComboboxAnchor from './combobox-anchor.vue'
|
|
8
|
-
import ComboboxEmpty from './combobox-empty.vue'
|
|
9
|
-
import ComboboxGroup from './combobox-group.vue'
|
|
10
|
-
import ComboboxInput from './combobox-input.vue'
|
|
11
|
-
import ComboboxItem from './combobox-item.vue'
|
|
12
|
-
import ComboboxItemIndicator from './combobox-indicator.vue'
|
|
13
|
-
import ComboboxList from './combobox-list.vue'
|
|
14
|
-
import ComboboxLabel from './combobox-label.vue'
|
|
15
|
-
import ComboboxTrigger from './combobox-trigger.vue'
|
|
16
|
-
|
|
17
5
|
interface ComboboxOption {
|
|
18
6
|
value: string
|
|
19
7
|
label: string
|
|
@@ -200,6 +188,8 @@ const groupedOptions = computed(() => {
|
|
|
200
188
|
})).filter(group => group.children.length > 0)
|
|
201
189
|
})
|
|
202
190
|
|
|
191
|
+
const isEmpty = computed(() => groupedOptions.value.every(group => group.children.length === 0))
|
|
192
|
+
|
|
203
193
|
const handleDeleteSearchValue = () => search.value = ''
|
|
204
194
|
|
|
205
195
|
const keys = useMagicKeys()
|
|
@@ -324,17 +314,15 @@ watch(innerValue, (raw) => {
|
|
|
324
314
|
</script>
|
|
325
315
|
|
|
326
316
|
<template>
|
|
327
|
-
<
|
|
317
|
+
<TelaComboboxRoot
|
|
328
318
|
v-model="innerValue" by="value" :open="isOpen" :disabled="readOnly" @update:open="onOpenChange"
|
|
329
319
|
@update:model-value="handleSelect"
|
|
330
320
|
>
|
|
331
|
-
<
|
|
332
|
-
<
|
|
321
|
+
<TelaComboboxAnchor>
|
|
322
|
+
<TelaComboboxTrigger
|
|
333
323
|
as="button"
|
|
334
324
|
tabindex="0"
|
|
335
325
|
:class="cn(
|
|
336
|
-
'group select-none flex items-center justify-between gap-3 rounded-10px px-8px py-7px bg-white border-0.5px border transition hover:border-strong hover:bg-subtle data-[state=open]:border-strong data-[state=open]:bg-subtle',
|
|
337
|
-
'[box-shadow:0_1px_6px_0_rgba(103,127,148,0.05)]',
|
|
338
326
|
!compact && 'w-full !py-2.5 pl-3 pr-3',
|
|
339
327
|
props.triggerClass,
|
|
340
328
|
)"
|
|
@@ -342,324 +330,188 @@ watch(innerValue, (raw) => {
|
|
|
342
330
|
<div :class="cn('flex items-center', !compact ? 'gap-2' : 'gap-1')">
|
|
343
331
|
<div v-if="currentOption?.icon && labelIconVisible" :class="cn('group-data-[state=open]:opacity-70', props.iconClass)">
|
|
344
332
|
<div v-if="currentOption?.icon && !(currentOption?.value === '')">
|
|
345
|
-
<TelaIcon v-if="typeof currentOption.icon === 'string'" :name="currentOption.icon" :color="colorIcon" />
|
|
333
|
+
<TelaIcon v-if="typeof currentOption.icon === 'string'" :name="currentOption.icon" :color="colorIcon" class="shrink-0" />
|
|
346
334
|
<Component
|
|
347
335
|
:is="currentOption.icon"
|
|
348
336
|
v-else
|
|
349
|
-
:class="cn((currentOption?.label.startsWith('DeepSeek') || currentOption?.label.startsWith('Gemini')) && 'scale-75')"
|
|
337
|
+
:class="cn('shrink-0', (currentOption?.label.startsWith('DeepSeek') || currentOption?.label.startsWith('Gemini')) && 'scale-75')"
|
|
350
338
|
/>
|
|
351
339
|
</div>
|
|
352
340
|
<div v-if="!currentOption?.icon && currentOption?.externalIconSrc">
|
|
353
|
-
<img :src="currentOption.externalIconSrc" :alt="labelModelIconAlt || currentOption?.label" w-16px h-16px>
|
|
341
|
+
<img :src="currentOption.externalIconSrc" :alt="labelModelIconAlt || currentOption?.label" class="w-16px h-16px">
|
|
354
342
|
</div>
|
|
355
343
|
</div>
|
|
356
|
-
<span
|
|
357
|
-
:class="cn('truncate text-primary group-data-[state=open]:text-secondary',
|
|
358
|
-
compact ? 'text-sm font-580' : 'text-body-14-regular', props.labelClass)"
|
|
359
|
-
>
|
|
344
|
+
<span :class="cn('truncate text-primary group-data-[state=open]:text-secondary', props.labelClass)">
|
|
360
345
|
{{ currentOption?.triggerLabel || currentOption?.label }}
|
|
361
346
|
</span>
|
|
362
347
|
</div>
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
</span>
|
|
399
|
-
<span v-if="search.trim()" class="inline-flex items-center gap-1.5 text-md text-secondary">
|
|
400
|
-
{{ labelPress }}
|
|
401
|
-
<div class="flex items-center gap-1">
|
|
402
|
-
<div class="p-2px bg-lowered rounded">
|
|
403
|
-
<TelaIcon name="i-ph-command" size="16px" />
|
|
404
|
-
</div>
|
|
405
|
-
<div class="p-2px bg-lowered rounded">
|
|
406
|
-
<TelaIcon name="i-ph-backspace" size="16px" />
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
{{ labelToClear }}
|
|
410
|
-
</span>
|
|
348
|
+
</TelaComboboxTrigger>
|
|
349
|
+
</TelaComboboxAnchor>
|
|
350
|
+
<TelaComboboxList :class="cn('z-999', compact ? 'w-440px' : 'w-327px', props.contentClass)" :disable-portal="props.disablePortal" align="start">
|
|
351
|
+
<TelaComboboxInput
|
|
352
|
+
v-if="hasSearchbar"
|
|
353
|
+
v-model="search"
|
|
354
|
+
:placeholder="inputPlaceholder || labelSearchPlaceholder"
|
|
355
|
+
/>
|
|
356
|
+
<TelaTabsRoot v-if="hasTabs" v-model="selectedTab">
|
|
357
|
+
<TelaTabsList class="sticky z-10 top-0 shrink-0 flex gap-2 w-full">
|
|
358
|
+
<TelaTabsIndicator />
|
|
359
|
+
<TelaTabsTrigger v-for="tab in tabs" :key="tab.tab" :value="tab.tab" size="sm">
|
|
360
|
+
{{ tab.label }}
|
|
361
|
+
</TelaTabsTrigger>
|
|
362
|
+
</TelaTabsList>
|
|
363
|
+
</TelaTabsRoot>
|
|
364
|
+
<div :class="cn('max-h-262px overflow-y-auto no-scrollbar', hasTabs && 'pt-2')">
|
|
365
|
+
<TelaComboboxEmpty
|
|
366
|
+
v-if="isEmpty"
|
|
367
|
+
class="flex flex-col items-center justify-center gap-5 px-7"
|
|
368
|
+
>
|
|
369
|
+
<div class="flex flex-col items-center justify-center gap-3">
|
|
370
|
+
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-tertiary" />
|
|
371
|
+
<span class="text-xl font-medium leading-none">
|
|
372
|
+
{{ search.trim() && labelNoResultsWithSearch ? labelNoResultsWithSearch(search) : labelNoResults }}
|
|
373
|
+
</span>
|
|
374
|
+
<span v-if="search.trim()" class="inline-flex items-center gap-1.5 text-md text-secondary">
|
|
375
|
+
{{ labelPress }}
|
|
376
|
+
<div class="flex items-center gap-1">
|
|
377
|
+
<div class="p-2px bg-lowered rounded">
|
|
378
|
+
<TelaIcon name="i-ph-command" size="16px" />
|
|
379
|
+
</div>
|
|
380
|
+
<div class="p-2px bg-lowered rounded">
|
|
381
|
+
<TelaIcon name="i-ph-backspace" size="16px" />
|
|
382
|
+
</div>
|
|
411
383
|
</div>
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
384
|
+
{{ labelToClear }}
|
|
385
|
+
</span>
|
|
386
|
+
</div>
|
|
387
|
+
<TelaButton
|
|
388
|
+
v-if="search.trim()" variant="secondary" size="sm"
|
|
389
|
+
@click="handleDeleteSearchValue"
|
|
390
|
+
>
|
|
391
|
+
{{ labelClear }}
|
|
392
|
+
</TelaButton>
|
|
393
|
+
</TelaComboboxEmpty>
|
|
394
|
+
<template v-for="group in groupedOptions" :key="group.heading">
|
|
395
|
+
<TelaComboboxGroup :heading="group.heading">
|
|
396
|
+
<TelaComboboxLabel v-if="!search.trim() && hasGroupingLabels">
|
|
397
|
+
{{ group.heading }}
|
|
398
|
+
</TelaComboboxLabel>
|
|
399
|
+
<template v-for="item in group.children" :key="item.value">
|
|
400
|
+
<TelaComboboxItem
|
|
401
|
+
:ref="(el) => item.children?.length && setNestedItemRef(item.value, el)"
|
|
402
|
+
:value="item"
|
|
403
|
+
:class="cn('group py-2', !compact && '!px-1.5')"
|
|
404
|
+
@mouseenter="handleItemMouseEnter(item)"
|
|
405
|
+
@select="(e) => item.children?.length && e.preventDefault()"
|
|
415
406
|
>
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
<TelaIcon
|
|
429
|
-
v-if="typeof item.icon === 'string'" :name="item.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
430
|
-
:background-class="iconWithBackground?.backgroundClass"
|
|
431
|
-
/>
|
|
432
|
-
<Component
|
|
433
|
-
:is="item.icon"
|
|
434
|
-
v-else
|
|
435
|
-
:class="cn((item?.label.startsWith('DeepSeek') || item?.label.startsWith('Gemini')) && 'scale-75')"
|
|
436
|
-
/>
|
|
437
|
-
</div>
|
|
438
|
-
<div v-if="!item.icon && item?.externalIconSrc">
|
|
439
|
-
<img :src="item.externalIconSrc" :alt="item.label" class="w-5 h-5">
|
|
440
|
-
</div>
|
|
441
|
-
<div class="flex flex-col">
|
|
442
|
-
<div class="flex items-center gap-1">
|
|
443
|
-
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
444
|
-
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-muted" text-class="leading-none">
|
|
445
|
-
{{ labelMultimodal }}
|
|
446
|
-
</TelaBadge>
|
|
447
|
-
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
448
|
-
<span class="text-primary">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
449
|
-
<span class="text-neutral-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
450
|
-
</span>
|
|
451
|
-
</div>
|
|
452
|
-
<span v-if="item.description" :class="cn('font-normal text-sm text-balance leading-none text-secondary', descriptionClass)">
|
|
453
|
-
{{ item.description }}
|
|
454
|
-
</span>
|
|
455
|
-
<slot name="tags" :option="item" />
|
|
456
|
-
</div>
|
|
407
|
+
<div class="flex items-center">
|
|
408
|
+
<div :class="cn('flex items-center w-270px', compact ? 'gap-2' : 'gap-1.5')">
|
|
409
|
+
<div v-if="item.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
|
|
410
|
+
<TelaIcon
|
|
411
|
+
v-if="typeof item.icon === 'string'" :name="item.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
412
|
+
:background-class="iconWithBackground?.backgroundClass"
|
|
413
|
+
/>
|
|
414
|
+
<Component
|
|
415
|
+
:is="item.icon"
|
|
416
|
+
v-else
|
|
417
|
+
:class="cn((item?.label.startsWith('DeepSeek') || item?.label.startsWith('Gemini')) && 'scale-75')"
|
|
418
|
+
/>
|
|
457
419
|
</div>
|
|
458
|
-
<div
|
|
459
|
-
<
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
<span class="
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
{{
|
|
470
|
-
</span>
|
|
471
|
-
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
472
|
-
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
420
|
+
<div v-if="!item.icon && item?.externalIconSrc">
|
|
421
|
+
<img :src="item.externalIconSrc" :alt="item.label" class="w-5 h-5">
|
|
422
|
+
</div>
|
|
423
|
+
<div class="flex flex-col">
|
|
424
|
+
<div class="flex items-center gap-1">
|
|
425
|
+
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
426
|
+
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-muted" text-class="leading-none">
|
|
427
|
+
{{ labelMultimodal }}
|
|
428
|
+
</TelaBadge>
|
|
429
|
+
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
430
|
+
<span class="text-primary">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
431
|
+
<span class="text-neutral-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
473
432
|
</span>
|
|
474
433
|
</div>
|
|
434
|
+
<span v-if="item.description" :class="cn('font-normal text-sm text-balance leading-none text-secondary', descriptionClass)">
|
|
435
|
+
{{ item.description }}
|
|
436
|
+
</span>
|
|
437
|
+
<slot name="tags" :option="item" />
|
|
475
438
|
</div>
|
|
476
439
|
</div>
|
|
477
|
-
<div class="flex gap-
|
|
478
|
-
<
|
|
479
|
-
<
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
<div class="flex flex-col items-center justify-center gap-3">
|
|
495
|
-
<TelaIcon name="i-ph-smiley-sad" size="24px" class="text-tertiary" />
|
|
496
|
-
<span class="text-xl font-medium leading-none">
|
|
497
|
-
{{ search.trim() && labelNoResultsWithSearch ? labelNoResultsWithSearch(search) : labelNoResults }}
|
|
498
|
-
</span>
|
|
499
|
-
<span v-if="search.trim()" class="inline-flex gap-1.5 text-md text-secondary">
|
|
500
|
-
{{ labelPress }}
|
|
501
|
-
<div class="flex items-center gap-1">
|
|
502
|
-
<div class="p-2px bg-lowered rounded">
|
|
503
|
-
<TelaIcon name="i-ph-command" size="16px" />
|
|
504
|
-
</div>
|
|
505
|
-
<div class="p-2px bg-lowered rounded">
|
|
506
|
-
<TelaIcon name="i-ph-backspace" size="16px" />
|
|
440
|
+
<div v-if="item.maxInputTokens || item.maxOutputTokens" class="flex gap-3">
|
|
441
|
+
<div v-if="item.maxInputTokens" class="flex flex-col gap-1.5">
|
|
442
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
443
|
+
{{ labelInputMax }}
|
|
444
|
+
</span>
|
|
445
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
446
|
+
{{ handleFormatNumber(item.maxInputTokens) }}
|
|
447
|
+
</span>
|
|
448
|
+
</div>
|
|
449
|
+
<div v-if="item.maxOutputTokens" class="flex flex-col gap-1.5">
|
|
450
|
+
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
451
|
+
{{ labelOutputMax }}
|
|
452
|
+
</span>
|
|
453
|
+
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
454
|
+
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
455
|
+
</span>
|
|
456
|
+
</div>
|
|
507
457
|
</div>
|
|
508
458
|
</div>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
<template v-for="item in group.children" :key="item.value">
|
|
525
|
-
<ComboboxItem
|
|
526
|
-
:ref="(el) => item.children?.length && setNestedItemRef(item.value, el)"
|
|
527
|
-
:value="item"
|
|
528
|
-
:class="cn('group/item py-2', !compact && '!px-1.5')"
|
|
529
|
-
@mouseenter="handleItemMouseEnter(item)"
|
|
530
|
-
@select="(e) => item.children?.length && e.preventDefault()"
|
|
459
|
+
<div class="flex gap-2 items-center">
|
|
460
|
+
<TelaIcon v-if="item.children?.length" name="i-ph-caret-right" size="14px" color="icon-tertiary" />
|
|
461
|
+
<TelaComboboxIndicator v-else>
|
|
462
|
+
<TelaIcon name="i-ph-check" size="14px" />
|
|
463
|
+
</TelaComboboxIndicator>
|
|
464
|
+
</div>
|
|
465
|
+
</TelaComboboxItem>
|
|
466
|
+
<!-- Nested submenu -->
|
|
467
|
+
<Teleport v-if="item.children?.length" to="body">
|
|
468
|
+
<div
|
|
469
|
+
v-if="expandedItem === item.value"
|
|
470
|
+
:style="submenuStyle"
|
|
471
|
+
class="min-w-200px bg border-0.5px border rounded-12px shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_-4px_rgba(0,0,0,0.1)] pointer-events-auto"
|
|
472
|
+
@mouseenter="expandedItem = item.value"
|
|
473
|
+
@pointerdown.stop
|
|
531
474
|
>
|
|
532
|
-
<div class="
|
|
533
|
-
<div
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
<div class="flex items-center gap-1">
|
|
550
|
-
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ item.label }}</span>
|
|
551
|
-
<TelaBadge v-if="item.isMultiModal" variant="filled" class="py-[2px] bg-muted" text-class="leading-none">
|
|
552
|
-
{{ labelMultimodal }}
|
|
553
|
-
</TelaBadge>
|
|
554
|
-
<span v-if="item.cost !== undefined" class="text-10px font-580 ml-1px">
|
|
555
|
-
<span class="text-primary">{{ renderCostIndicator(item.cost)?.blackSymbols }}</span>
|
|
556
|
-
<span class="text-neutral-300">{{ renderCostIndicator(item.cost)?.graySymbols }}</span>
|
|
557
|
-
</span>
|
|
475
|
+
<div class="p-4px">
|
|
476
|
+
<div
|
|
477
|
+
v-for="child in item.children"
|
|
478
|
+
:key="child.value"
|
|
479
|
+
:class="cn(
|
|
480
|
+
'relative flex cursor-pointer select-none justify-between items-center rounded-lg px-2 py-2 text-sm font-medium text-primary outline-none hover:bg-muted',
|
|
481
|
+
!compact && '!px-1.5',
|
|
482
|
+
)"
|
|
483
|
+
@click.stop="handleChildSelect(child, $event)"
|
|
484
|
+
>
|
|
485
|
+
<div :class="cn('flex items-center', compact ? 'gap-2' : 'gap-1.5')">
|
|
486
|
+
<div v-if="child.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
|
|
487
|
+
<TelaIcon
|
|
488
|
+
v-if="typeof child.icon === 'string'" :name="child.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
489
|
+
:background-class="iconWithBackground?.backgroundClass"
|
|
490
|
+
/>
|
|
491
|
+
<Component :is="child.icon" v-else />
|
|
558
492
|
</div>
|
|
559
|
-
<
|
|
560
|
-
{{
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
</div>
|
|
565
|
-
<div v-if="item.maxInputTokens || item.maxOutputTokens" class="flex gap-3">
|
|
566
|
-
<div class="flex flex-col gap-1.5">
|
|
567
|
-
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
568
|
-
{{ labelInputMax }}
|
|
569
|
-
</span>
|
|
570
|
-
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
571
|
-
{{ handleFormatNumber(item.maxInputTokens) }}
|
|
572
|
-
</span>
|
|
573
|
-
</div>
|
|
574
|
-
<div class="flex flex-col gap-1.5">
|
|
575
|
-
<span class="text-tertiary leading-none text-9px uppercase tracking-wider font-semibold">
|
|
576
|
-
{{ labelOutputMax }}
|
|
577
|
-
</span>
|
|
578
|
-
<span class="text-tertiary leading-none text-11px uppercase font-semibold group-data-[highlighted]:text-neutral-700">
|
|
579
|
-
{{ handleFormatNumber(item.maxOutputTokens) }}
|
|
580
|
-
</span>
|
|
581
|
-
</div>
|
|
582
|
-
</div>
|
|
583
|
-
</div>
|
|
584
|
-
<div class="flex gap-2 items-center">
|
|
585
|
-
<TelaIcon v-if="item.children?.length" name="i-ph-caret-right" size="14px" color="icon-tertiary" />
|
|
586
|
-
<ComboboxItemIndicator v-else>
|
|
587
|
-
<TelaIcon name="i-ph-check" size="14px" />
|
|
588
|
-
</ComboboxItemIndicator>
|
|
589
|
-
</div>
|
|
590
|
-
</ComboboxItem>
|
|
591
|
-
<!-- Nested submenu -->
|
|
592
|
-
<Teleport v-if="item.children?.length" to="body">
|
|
593
|
-
<div
|
|
594
|
-
v-if="expandedItem === item.value"
|
|
595
|
-
:style="submenuStyle"
|
|
596
|
-
class="min-w-200px bg-white border border-strong rounded-12px shadow-[0_10px_15px_-3px_rgba(0,0,0,0.1),0_4px_6px_-4px_rgba(0,0,0,0.1)] pointer-events-auto"
|
|
597
|
-
@mouseenter="expandedItem = item.value"
|
|
598
|
-
@pointerdown.stop
|
|
599
|
-
>
|
|
600
|
-
<div p-4px>
|
|
601
|
-
<div
|
|
602
|
-
v-for="child in item.children"
|
|
603
|
-
:key="child.value"
|
|
604
|
-
:class="cn(
|
|
605
|
-
'relative flex cursor-pointer select-none justify-between items-center rounded-lg px-2 py-2 text-sm font-medium text-primary outline-none hover:bg-muted',
|
|
606
|
-
!compact && '!px-1.5',
|
|
607
|
-
)"
|
|
608
|
-
@click.stop="handleChildSelect(child, $event)"
|
|
609
|
-
>
|
|
610
|
-
<div :class="cn('flex items-center', compact ? 'gap-2' : 'gap-1.5')">
|
|
611
|
-
<div v-if="child.icon" class="w-5 h-5 shrink-0 flex items-center justify-center">
|
|
612
|
-
<TelaIcon
|
|
613
|
-
v-if="typeof child.icon === 'string'" :name="child.icon" :color="colorIcon" :size="iconWithBackground?.size"
|
|
614
|
-
:background-class="iconWithBackground?.backgroundClass"
|
|
615
|
-
/>
|
|
616
|
-
<Component :is="child.icon" v-else />
|
|
617
|
-
</div>
|
|
618
|
-
<div class="flex flex-col">
|
|
619
|
-
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ child.label }}</span>
|
|
620
|
-
<span v-if="child.description" :class="cn('font-normal text-sm leading-none text-secondary', descriptionClass)">
|
|
621
|
-
{{ child.description }}
|
|
622
|
-
</span>
|
|
623
|
-
</div>
|
|
493
|
+
<div class="flex flex-col">
|
|
494
|
+
<span :class="cn('font-medium truncate max-w-140px', compact ? 'text-sm font-580' : 'text-body-14-regular', labelItemClass)">{{ child.label }}</span>
|
|
495
|
+
<span v-if="child.description" :class="cn('font-normal text-sm leading-none text-secondary', descriptionClass)">
|
|
496
|
+
{{ child.description }}
|
|
497
|
+
</span>
|
|
624
498
|
</div>
|
|
625
|
-
<TelaIcon v-if="child.value === (modelValue || innerValue)" name="i-ph-check" size="14px" />
|
|
626
499
|
</div>
|
|
500
|
+
<TelaIcon v-if="child.value === (modelValue || innerValue)" name="i-ph-check" size="14px" />
|
|
627
501
|
</div>
|
|
628
502
|
</div>
|
|
629
|
-
</
|
|
630
|
-
</
|
|
631
|
-
</
|
|
632
|
-
</
|
|
633
|
-
</
|
|
634
|
-
</
|
|
635
|
-
</
|
|
636
|
-
</
|
|
503
|
+
</div>
|
|
504
|
+
</Teleport>
|
|
505
|
+
</template>
|
|
506
|
+
</TelaComboboxGroup>
|
|
507
|
+
</template>
|
|
508
|
+
</div>
|
|
509
|
+
</TelaComboboxList>
|
|
510
|
+
</TelaComboboxRoot>
|
|
637
511
|
</template>
|
|
638
512
|
|
|
639
513
|
<style scoped>
|
|
640
514
|
.nuxt-icon {
|
|
641
515
|
margin-bottom: 0;
|
|
642
516
|
}
|
|
643
|
-
|
|
644
|
-
.no-scrollbar {
|
|
645
|
-
-ms-overflow-style: none;
|
|
646
|
-
scrollbar-width: none;
|
|
647
|
-
|
|
648
|
-
&::-webkit-scrollbar {
|
|
649
|
-
display: none;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
</style>
|
|
653
|
-
|
|
654
|
-
<style>
|
|
655
|
-
@keyframes combobox-submenuFadeIn {
|
|
656
|
-
from {
|
|
657
|
-
opacity: 0;
|
|
658
|
-
transform: translateX(-4px);
|
|
659
|
-
}
|
|
660
|
-
to {
|
|
661
|
-
opacity: 1;
|
|
662
|
-
transform: translateX(0);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
517
|
</style>
|
|
@@ -29,28 +29,28 @@ const emit = defineEmits<{
|
|
|
29
29
|
}>()
|
|
30
30
|
|
|
31
31
|
const style = computed(() => {
|
|
32
|
-
const base = 'cursor-pointer flex items-center justify-center
|
|
32
|
+
const base = 'cursor-pointer flex items-center justify-center p-0.5em ease-out duration-40'
|
|
33
33
|
const size = ({
|
|
34
|
-
'3xs': 'text-6px p-1',
|
|
35
|
-
'2xs': 'text-8px p-4px', // 16px
|
|
36
|
-
'xs': 'text-12px p-6px', // 24px
|
|
37
|
-
'sm': 'text-16px p-0.4em', // 32px
|
|
38
|
-
'md': 'text-20px p-0.4em', // 44px
|
|
34
|
+
'3xs': 'text-6px p-1 rounded-8px',
|
|
35
|
+
'2xs': 'text-8px p-4px rounded-4px', // 16px
|
|
36
|
+
'xs': 'text-12px p-6px rounded-6px', // 24px
|
|
37
|
+
'sm': 'text-16px p-0.4em rounded-8px', // 32px
|
|
38
|
+
'md': 'text-20px p-0.4em rounded-10px', // 44px
|
|
39
39
|
} as Record<typeof props.size, string>)[props.size]
|
|
40
40
|
|
|
41
41
|
const color = ({
|
|
42
|
-
primary: 'bg-
|
|
42
|
+
primary: 'bg-btn-primary !text-inverse hover:bg-btn-primary-hover active:bg-btn-primary-active',
|
|
43
43
|
secondary: 'bg-transparent text-icon-tertiary hover:bg-muted hover:text-primary active:text-primary disabled:bg-muted disabled:!text-subtle',
|
|
44
44
|
} as Record<typeof props.color, string>)[props.color]
|
|
45
45
|
|
|
46
46
|
const disabled = props.disabled && 'cursor-not-allowed !bg-muted !text-subtle !b-0'
|
|
47
47
|
|
|
48
48
|
const outline = props.outline && ({
|
|
49
|
-
primary: '
|
|
50
|
-
secondary: '
|
|
49
|
+
primary: 'border-0.5px !border-neutral-800',
|
|
50
|
+
secondary: 'border-0.5px !border',
|
|
51
51
|
} as Record<typeof props.color, string>)[props.color]
|
|
52
52
|
|
|
53
|
-
const active = props.active && '!bg-
|
|
53
|
+
const active = props.active && '!bg-muted'
|
|
54
54
|
|
|
55
55
|
return [
|
|
56
56
|
base,
|
|
@@ -164,32 +164,20 @@ function iconSrc(option: PreviewVariableOption): string {
|
|
|
164
164
|
</p>
|
|
165
165
|
</div>
|
|
166
166
|
</div>
|
|
167
|
-
<div flex-shrink-0>
|
|
168
|
-
<TelaIcon
|
|
169
|
-
name="i-ph-caret-down-bold"
|
|
170
|
-
size="16px"
|
|
171
|
-
color="icon-secondary"
|
|
172
|
-
:class="cn('ease transition-transform duration-200', isOpen && 'rotate-180')"
|
|
173
|
-
/>
|
|
174
|
-
</div>
|
|
175
167
|
</TelaComboboxTrigger>
|
|
176
168
|
</TelaComboboxAnchor>
|
|
177
169
|
<TelaComboboxList :class="cn('z-999 w-full max-w-[var(--reka-combobox-trigger-width)]', popoverClass)" :disable-portal="true" align="center">
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
:class="cn('rounded-b-none pl-30px')"
|
|
185
|
-
:placeholder="labels.searchPlaceholder"
|
|
186
|
-
/>
|
|
187
|
-
<div v-if="search.length > 0" class="absolute inset-y-0 end-0 flex items-center justify-center px-3">
|
|
170
|
+
<TelaComboboxInput
|
|
171
|
+
v-model="search"
|
|
172
|
+
class="rounded-b-none"
|
|
173
|
+
:placeholder="labels.searchPlaceholder"
|
|
174
|
+
>
|
|
175
|
+
<template v-if="search.length > 0" #deleteSearchValue>
|
|
188
176
|
<button class="flex items-center justify-center" @mousedown.prevent @click.stop="handleDeleteSearchValue">
|
|
189
177
|
<TelaIcon name="i-ph-x-circle" size="16px" class="text-gray-400" />
|
|
190
178
|
</button>
|
|
191
|
-
</
|
|
192
|
-
</
|
|
179
|
+
</template>
|
|
180
|
+
</TelaComboboxInput>
|
|
193
181
|
<div class="max-h-262px overflow-y-auto no-scrollbar">
|
|
194
182
|
<TelaComboboxEmpty
|
|
195
183
|
v-if="groupedOptions.length === 0 || groupedOptions.every(g => g.children.length === 0)"
|
|
@@ -248,9 +236,9 @@ function iconSrc(option: PreviewVariableOption): string {
|
|
|
248
236
|
</p>
|
|
249
237
|
</div>
|
|
250
238
|
<div v-if="props.modelValue === item.value" class="flex gap-2 items-center">
|
|
251
|
-
<
|
|
239
|
+
<TelaComboboxIndicator>
|
|
252
240
|
<TelaIcon name="i-ph-check-bold" size="14px" color="icon-secondary" />
|
|
253
|
-
</
|
|
241
|
+
</TelaComboboxIndicator>
|
|
254
242
|
</div>
|
|
255
243
|
</div>
|
|
256
244
|
</TelaComboboxItem>
|
|
@@ -4,18 +4,28 @@ import { reactiveOmit } from '@vueuse/core'
|
|
|
4
4
|
import { TabsTrigger, useForwardProps } from 'reka-ui'
|
|
5
5
|
import type { TabsTriggerProps } from 'reka-ui'
|
|
6
6
|
|
|
7
|
-
const props = defineProps<TabsTriggerProps & {
|
|
7
|
+
const props = withDefaults(defineProps<TabsTriggerProps & {
|
|
8
|
+
class?: HTMLAttributes['class']
|
|
9
|
+
textClass?: HTMLAttributes['class']
|
|
10
|
+
size?: 'sm' | 'md'
|
|
11
|
+
}>(), { size: 'md' })
|
|
8
12
|
|
|
9
|
-
const delegatedProps = reactiveOmit(props, 'class', 'textClass')
|
|
13
|
+
const delegatedProps = reactiveOmit(props, 'class', 'textClass', 'size')
|
|
10
14
|
|
|
11
15
|
const forwardedProps = useForwardProps(delegatedProps)
|
|
16
|
+
|
|
17
|
+
const sizeStyle = computed(() => (({
|
|
18
|
+
sm: 'px-[6px] py-1.5 body-12-semibold',
|
|
19
|
+
md: 'px-[6px] py-2 body-14-semibold',
|
|
20
|
+
}) as Record<'sm' | 'md', string>)[props.size])
|
|
12
21
|
</script>
|
|
13
22
|
|
|
14
23
|
<template>
|
|
15
24
|
<TabsTrigger
|
|
16
25
|
v-bind="forwardedProps"
|
|
17
26
|
:class="cn(
|
|
18
|
-
'inline-flex cursor-pointer items-center justify-center whitespace-nowrap
|
|
27
|
+
'inline-flex cursor-pointer items-center justify-center whitespace-nowrap transition-all focus-visible:outline-none disabled:pointer-events-none text-tertiary hover:text-primary disabled:opacity-50 data-[state=active]:text-primary',
|
|
28
|
+
sizeStyle,
|
|
19
29
|
props.class,
|
|
20
30
|
)"
|
|
21
31
|
>
|
|
@@ -111,6 +111,19 @@ type TabsProps = {
|
|
|
111
111
|
|
|
112
112
|
- `content` - Tab panel content area
|
|
113
113
|
|
|
114
|
+
## Sizes
|
|
115
|
+
|
|
116
|
+
`TelaTabsTrigger` supports two sizes via the `size` prop:
|
|
117
|
+
|
|
118
|
+
- `sm` — use in small components, e.g. inside a card or a table
|
|
119
|
+
- `md` (default) — use in bigger layouts, e.g. `TelaDetails`
|
|
120
|
+
|
|
121
|
+
```vue
|
|
122
|
+
<TelaTabsTrigger value="overview" size="sm">
|
|
123
|
+
Overview
|
|
124
|
+
</TelaTabsTrigger>
|
|
125
|
+
```
|
|
126
|
+
|
|
114
127
|
## Components
|
|
115
128
|
|
|
116
129
|
The Tabs system consists of these reka-ui components:
|
package/docs/tokens.md
CHANGED
|
@@ -52,6 +52,12 @@ Surfaces stack. Use the right background token for each layer — not by feel, b
|
|
|
52
52
|
- **`bg-subtle`** — One level up. Secondary panels, sidebars. (`DT.colors.background.subtle`)
|
|
53
53
|
- **`bg-muted`** — Inputs, cards, contained areas. (`DT.colors.background.muted`)
|
|
54
54
|
- **`bg-lowered`** — Below the base. Subtle separators, recessed areas. (`DT.colors.background.lowered`)
|
|
55
|
+
- **`bg-btn-primary`** / **`-hover`** / **`-active`** — Primary button background at rest, hover, pressed. (`DT.colors.background['btn-primary*']`)
|
|
56
|
+
- **`bg-btn-secondary`** / **`-hover`** / **`-active`** — Secondary button background states. (`DT.colors.background['btn-secondary*']`)
|
|
57
|
+
- **`bg-btn-ghost`** / **`-hover`** — Ghost button background states. (`DT.colors.background['btn-ghost*']`)
|
|
58
|
+
- **`bg-btn-danger`** / **`-hover`** — Danger button background states. (`DT.colors.background['btn-danger*']`)
|
|
59
|
+
- **`bg-btn-dark`** / **`-hover`** / **`-active`** — Dark (inverse surface) button background states. (`DT.colors.background['btn-dark*']`)
|
|
60
|
+
- **`bg-btn-disabled`**, **`bg-btn-loading`** — Button backgrounds for disabled and loading states. (`DT.colors.background['btn-disabled']`, `['btn-loading']`)
|
|
55
61
|
- **`bg-success`** — Success state backgrounds in badges and alerts. (`DT.colors.background.success`)
|
|
56
62
|
- **`bg-error`** — Error state backgrounds in badges and alerts. (`DT.colors.background.error`)
|
|
57
63
|
- **`bg-warning`** — Warning state backgrounds in badges and alerts. (`DT.colors.background.warning`)
|
|
@@ -64,7 +70,8 @@ For typography, default to `text-primary` (content) and `text-secondary` (suppor
|
|
|
64
70
|
- **`text-secondary`** — Supporting text, metadata, labels. (`DT.colors.text.secondary`)
|
|
65
71
|
- **`text-tertiary`** — Placeholders, disabled states — not "quieter body text". (`DT.colors.text.tertiary`)
|
|
66
72
|
- **`text-subtle`** — Very low emphasis, over muted surfaces only. (`DT.colors.text.subtle`)
|
|
67
|
-
- **`text-contrast`** —
|
|
73
|
+
- **`text-contrast`** — Darkest text on light surfaces — e.g. labels on secondary/ghost buttons. Not for copy; primary is already the default heading color. (`DT.colors.text.contrast`)
|
|
74
|
+
- **`text-inverse`** — Text on dark/inverted surfaces — e.g. labels on primary/dark buttons. (`DT.colors.text.inverse`)
|
|
68
75
|
- **`text-icon`** — Icon color applied via text utility. (`DT.colors.text.icon`)
|
|
69
76
|
- **`text-success`** — Success messaging and status indicators. (`DT.colors.text.success`)
|
|
70
77
|
- **`text-error`** — Error messaging, destructive action labels. (`DT.colors.text.error`)
|
package/package.json
CHANGED
package/unocss.config.ts
CHANGED
|
@@ -77,11 +77,11 @@ export default defineConfig({
|
|
|
77
77
|
|
|
78
78
|
return `${sizeStyle} font-${weight} ${isCaption ? 'uppercase tracking-0.05em!' : ''}`
|
|
79
79
|
}],
|
|
80
|
-
// Catch for `text-(primary|secondary|tertiary|subtle|contrast|success|error|warning|pending)`
|
|
81
|
-
[/text-(primary|secondary|tertiary|subtle|contrast|success|error|warning|pending)/, ([_, v]) => `text-text-${v}`],
|
|
82
|
-
// Catch for `bg-(subtle|muted|lowered|success|error|warning)`
|
|
80
|
+
// Catch for `text-(primary|secondary|tertiary|subtle|contrast|inverse|success|error|warning|pending)`
|
|
81
|
+
[/text-(primary|secondary|tertiary|subtle|contrast|inverse|success|error|warning|pending)/, ([_, v]) => `text-text-${v}`],
|
|
82
|
+
// Catch for `bg-(subtle|muted|lowered|btn-*|success|error|warning)`
|
|
83
83
|
// Handle bg, bg-subtle, etc. Use bg-background if just 'bg'
|
|
84
|
-
[/^bg(?:-(subtle|muted|lowered|success|error|warning))?$/, ([_, v]) => v ? `bg-background-${v}` : 'bg-background'],
|
|
84
|
+
[/^bg(?:-(subtle|muted|lowered|btn(?:-[a-z]+)*|success|error|warning))?$/, ([_, v]) => v ? `bg-background-${v}` : 'bg-background'],
|
|
85
85
|
// Catch for `border-(subtle|strong)`
|
|
86
86
|
[/^border(?:-(subtle|strong))?$/, ([_, v]) => v ? `border-border-${v.toLowerCase()}` : 'border-border'],
|
|
87
87
|
],
|
package/utils/design-tokens.ts
CHANGED
|
@@ -290,44 +290,76 @@ const LEGACY_COLORS = {
|
|
|
290
290
|
},
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
const BTN_COLORS = {
|
|
294
|
+
'btn-primary': BASE_COLORS.neutral[900],
|
|
295
|
+
'btn-primary-hover': BASE_COLORS.neutral[800],
|
|
296
|
+
'btn-primary-active': BASE_COLORS.neutral[700],
|
|
297
|
+
'btn-secondary': BASE_COLORS.white.DEFAULT,
|
|
298
|
+
'btn-secondary-hover': BASE_COLORS.neutral[50],
|
|
299
|
+
'btn-secondary-active': BASE_COLORS.neutral[100],
|
|
300
|
+
'btn-ghost': BASE_COLORS.transparent,
|
|
301
|
+
'btn-ghost-hover': BASE_COLORS.neutral[100],
|
|
302
|
+
'btn-danger': BASE_COLORS.red[100],
|
|
303
|
+
'btn-danger-hover': BASE_COLORS.red[50],
|
|
304
|
+
'btn-disabled': BASE_COLORS.neutral[100],
|
|
305
|
+
'btn-loading': BASE_COLORS.neutral[200],
|
|
306
|
+
'btn-dark': 'rgba(255, 255, 255, 0.04)',
|
|
307
|
+
'btn-dark-hover': 'rgba(255, 255, 255, 0.08)',
|
|
308
|
+
'btn-dark-active': 'rgba(255, 255, 255, 0.1)',
|
|
309
|
+
} as const
|
|
310
|
+
|
|
311
|
+
const BACKGROUND_COLORS = {
|
|
312
|
+
DEFAULT: BASE_COLORS.white.DEFAULT,
|
|
313
|
+
subtle: BASE_COLORS.neutral[50],
|
|
314
|
+
muted: BASE_COLORS.neutral[100],
|
|
315
|
+
lowered: BASE_COLORS.neutral[200],
|
|
316
|
+
success: BASE_COLORS.green[200],
|
|
317
|
+
error: BASE_COLORS.red[200],
|
|
318
|
+
warning: BASE_COLORS.amber[200],
|
|
319
|
+
...BTN_COLORS,
|
|
320
|
+
} as const
|
|
321
|
+
|
|
322
|
+
const TEXT_COLORS = {
|
|
323
|
+
primary: BASE_COLORS.neutral[800],
|
|
324
|
+
secondary: BASE_COLORS.neutral[500],
|
|
325
|
+
tertiary: BASE_COLORS.neutral[400],
|
|
326
|
+
subtle: BASE_COLORS.neutral[300],
|
|
327
|
+
contrast: BASE_COLORS.neutral[900],
|
|
328
|
+
inverse: BASE_COLORS.white.DEFAULT,
|
|
329
|
+
success: BASE_COLORS.green[600],
|
|
330
|
+
error: BASE_COLORS.red[600],
|
|
331
|
+
warning: BASE_COLORS.amber[600],
|
|
332
|
+
pending: BASE_COLORS.blue[600],
|
|
333
|
+
} as const
|
|
334
|
+
|
|
335
|
+
const BORDER_COLORS = {
|
|
336
|
+
DEFAULT: BASE_COLORS.neutral[200],
|
|
337
|
+
subtle: BASE_COLORS.neutral[100],
|
|
338
|
+
strong: BASE_COLORS.neutral[300],
|
|
339
|
+
accent: BASE_COLORS.neutral[400],
|
|
340
|
+
inverse: BASE_COLORS.white.DEFAULT,
|
|
341
|
+
} as const
|
|
342
|
+
|
|
343
|
+
const ICON_COLORS = {
|
|
344
|
+
DEFAULT: BASE_COLORS.neutral[700],
|
|
345
|
+
secondary: BASE_COLORS.neutral[500],
|
|
346
|
+
tertiary: BASE_COLORS.neutral[400],
|
|
347
|
+
subtle: BASE_COLORS.neutral[300],
|
|
348
|
+
inverse: BASE_COLORS.white.DEFAULT,
|
|
349
|
+
} as const
|
|
350
|
+
|
|
351
|
+
const COLORS = {
|
|
352
|
+
...BASE_COLORS,
|
|
353
|
+
...LEGACY_COLORS,
|
|
354
|
+
background: BACKGROUND_COLORS,
|
|
355
|
+
text: TEXT_COLORS,
|
|
356
|
+
border: BORDER_COLORS,
|
|
357
|
+
icon: ICON_COLORS,
|
|
358
|
+
} as const
|
|
359
|
+
|
|
293
360
|
export const DT = {
|
|
294
361
|
colors: {
|
|
295
|
-
...
|
|
296
|
-
...LEGACY_COLORS,
|
|
297
|
-
background: {
|
|
298
|
-
DEFAULT: BASE_COLORS.white.DEFAULT,
|
|
299
|
-
subtle: BASE_COLORS.neutral[50],
|
|
300
|
-
muted: BASE_COLORS.neutral[100],
|
|
301
|
-
lowered: BASE_COLORS.neutral[200],
|
|
302
|
-
success: BASE_COLORS.green[200],
|
|
303
|
-
error: BASE_COLORS.red[200],
|
|
304
|
-
warning: BASE_COLORS.amber[200],
|
|
305
|
-
},
|
|
306
|
-
text: {
|
|
307
|
-
primary: BASE_COLORS.neutral[800],
|
|
308
|
-
secondary: BASE_COLORS.neutral[500],
|
|
309
|
-
tertiary: BASE_COLORS.neutral[400],
|
|
310
|
-
subtle: BASE_COLORS.neutral[300],
|
|
311
|
-
contrast: BASE_COLORS.neutral[900],
|
|
312
|
-
success: BASE_COLORS.green[600],
|
|
313
|
-
error: BASE_COLORS.red[600],
|
|
314
|
-
warning: BASE_COLORS.amber[600],
|
|
315
|
-
pending: BASE_COLORS.blue[600],
|
|
316
|
-
},
|
|
317
|
-
border: {
|
|
318
|
-
DEFAULT: BASE_COLORS.neutral[200],
|
|
319
|
-
subtle: BASE_COLORS.neutral[100],
|
|
320
|
-
strong: BASE_COLORS.neutral[300],
|
|
321
|
-
accent: BASE_COLORS.neutral[400],
|
|
322
|
-
inverse: BASE_COLORS.white.DEFAULT,
|
|
323
|
-
},
|
|
324
|
-
icon: {
|
|
325
|
-
DEFAULT: BASE_COLORS.neutral[700],
|
|
326
|
-
secondary: BASE_COLORS.neutral[500],
|
|
327
|
-
tertiary: BASE_COLORS.neutral[400],
|
|
328
|
-
subtle: BASE_COLORS.neutral[300],
|
|
329
|
-
inverse: BASE_COLORS.white.DEFAULT,
|
|
330
|
-
},
|
|
362
|
+
...COLORS,
|
|
331
363
|
},
|
|
332
364
|
fontSize: {
|
|
333
365
|
// headers (you should use <h.> tags for these sizes)
|