frappe-ui 0.1.243 → 0.1.245
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/icons/Icon.vue +28 -0
- package/icons/IconPicker.story.vue +126 -0
- package/icons/IconPicker.vue +221 -0
- package/icons/index.ts +5 -0
- package/icons/spritePlugin.ts +13 -0
- package/package.json +1 -1
- package/src/components/Charts/NumberChart.vue +7 -3
- package/src/components/MultiSelect/MultiSelect.vue +1 -1
- package/src/components/Popover/Popover.vue +2 -1
- package/src/components/Select/Select.vue +6 -7
- package/src/components/Tabs/Tabs.vue +2 -0
- package/src/components/Tabs/types.ts +2 -2
package/icons/Icon.vue
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onMounted } from 'vue';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{ name: string }>()
|
|
5
|
+
|
|
6
|
+
onMounted(() => {
|
|
7
|
+
if (!document.getElementById('lucide-sprite')) {
|
|
8
|
+
console.warn(
|
|
9
|
+
'Lucide sprite not found! Make sure to use the spritePlugin.'
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<svg
|
|
17
|
+
width="24"
|
|
18
|
+
height="24"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
stroke-width="1.5"
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
>
|
|
26
|
+
<use :href="`#${props.name}`" />
|
|
27
|
+
</svg>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, reactive } from 'vue'
|
|
3
|
+
import IconPicker from './IconPicker.vue'
|
|
4
|
+
|
|
5
|
+
const basicValue = ref(null)
|
|
6
|
+
const preselectedValue = ref('star')
|
|
7
|
+
const disabledValue = ref('')
|
|
8
|
+
|
|
9
|
+
const state = reactive({
|
|
10
|
+
disabled: false,
|
|
11
|
+
placeholder: 'Select an icon...',
|
|
12
|
+
openOnClick: true,
|
|
13
|
+
openOnFocus: true,
|
|
14
|
+
placement: 'start',
|
|
15
|
+
})
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<template>
|
|
19
|
+
<Story title="IconPicker" :layout="{ type: 'grid', width: 400 }">
|
|
20
|
+
<Variant title="Basic Usage">
|
|
21
|
+
<div class="p-4">
|
|
22
|
+
<label class="block text-sm font-medium mb-2">Basic Icon Picker</label>
|
|
23
|
+
<IconPicker
|
|
24
|
+
v-model="basicValue"
|
|
25
|
+
:placeholder="state.placeholder"
|
|
26
|
+
:disabled="state.disabled"
|
|
27
|
+
:open-on-click="state.openOnClick"
|
|
28
|
+
:open-on-focus="state.openOnFocus"
|
|
29
|
+
:placement="state.placement"
|
|
30
|
+
/>
|
|
31
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
32
|
+
Selected: {{ basicValue || 'None' }}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</Variant>
|
|
36
|
+
|
|
37
|
+
<Variant title="Subtle Variant (Default)">
|
|
38
|
+
<div class="p-4">
|
|
39
|
+
<label class="block text-sm font-medium mb-2">Subtle Variant</label>
|
|
40
|
+
<IconPicker
|
|
41
|
+
variant="subtle"
|
|
42
|
+
v-model="basicValue"
|
|
43
|
+
:placeholder="state.placeholder"
|
|
44
|
+
:disabled="state.disabled"
|
|
45
|
+
/>
|
|
46
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
47
|
+
Selected: {{ basicValue || 'None' }}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</Variant>
|
|
51
|
+
|
|
52
|
+
<Variant title="Outline Variant">
|
|
53
|
+
<div class="p-4">
|
|
54
|
+
<label class="block text-sm font-medium mb-2">Outline Variant</label>
|
|
55
|
+
<IconPicker
|
|
56
|
+
variant="outline"
|
|
57
|
+
v-model="basicValue"
|
|
58
|
+
:placeholder="state.placeholder"
|
|
59
|
+
:disabled="state.disabled"
|
|
60
|
+
/>
|
|
61
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
62
|
+
Selected: {{ basicValue || 'None' }}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</Variant>
|
|
66
|
+
|
|
67
|
+
<Variant title="Ghost Variant">
|
|
68
|
+
<div class="p-4">
|
|
69
|
+
<label class="block text-sm font-medium mb-2">Ghost Variant</label>
|
|
70
|
+
<IconPicker
|
|
71
|
+
variant="ghost"
|
|
72
|
+
v-model="basicValue"
|
|
73
|
+
:placeholder="state.placeholder"
|
|
74
|
+
:disabled="state.disabled"
|
|
75
|
+
/>
|
|
76
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
77
|
+
Selected: {{ basicValue || 'None' }}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</Variant>
|
|
81
|
+
|
|
82
|
+
<Variant title="Pre-selected Icon">
|
|
83
|
+
<div class="p-4">
|
|
84
|
+
<label class="block text-sm font-medium mb-2">Pre-selected Icon</label>
|
|
85
|
+
<IconPicker
|
|
86
|
+
v-model="preselectedValue"
|
|
87
|
+
:placeholder="state.placeholder"
|
|
88
|
+
:disabled="state.disabled"
|
|
89
|
+
/>
|
|
90
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
91
|
+
Selected: {{ preselectedValue || 'None' }}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</Variant>
|
|
95
|
+
|
|
96
|
+
<Variant title="Disabled State">
|
|
97
|
+
<div class="p-4">
|
|
98
|
+
<label class="block text-sm font-medium mb-2">Disabled Icon Picker</label>
|
|
99
|
+
<IconPicker
|
|
100
|
+
v-model="disabledValue"
|
|
101
|
+
placeholder="This is disabled"
|
|
102
|
+
:disabled="true"
|
|
103
|
+
/>
|
|
104
|
+
<div class="mt-2 text-sm text-gray-600">
|
|
105
|
+
Icon picker is disabled
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</Variant>
|
|
109
|
+
|
|
110
|
+
<template #controls>
|
|
111
|
+
<HstText v-model="state.placeholder" title="Placeholder" />
|
|
112
|
+
<HstCheckbox v-model="state.disabled" title="Disabled" />
|
|
113
|
+
<HstCheckbox v-model="state.openOnClick" title="Open on Click" />
|
|
114
|
+
<HstCheckbox v-model="state.openOnFocus" title="Open on Focus" />
|
|
115
|
+
<HstSelect
|
|
116
|
+
v-model="state.placement"
|
|
117
|
+
title="Placement"
|
|
118
|
+
:options="[
|
|
119
|
+
{ value: 'start', label: 'Start' },
|
|
120
|
+
{ value: 'center', label: 'Center' },
|
|
121
|
+
{ value: 'end', label: 'End' }
|
|
122
|
+
]"
|
|
123
|
+
/>
|
|
124
|
+
</template>
|
|
125
|
+
</Story>
|
|
126
|
+
</template>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
ComboboxAnchor,
|
|
4
|
+
ComboboxContent,
|
|
5
|
+
ComboboxEmpty,
|
|
6
|
+
ComboboxInput,
|
|
7
|
+
ComboboxPortal,
|
|
8
|
+
ComboboxRoot,
|
|
9
|
+
ComboboxTrigger,
|
|
10
|
+
ComboboxViewport,
|
|
11
|
+
} from 'reka-ui'
|
|
12
|
+
import { computed, onMounted, ref, watch } from 'vue'
|
|
13
|
+
import Icon from './Icon.vue'
|
|
14
|
+
|
|
15
|
+
export interface IconPickerProps {
|
|
16
|
+
variant?: 'subtle' | 'outline' | 'ghost'
|
|
17
|
+
modelValue?: string | null
|
|
18
|
+
placeholder?: string
|
|
19
|
+
disabled?: boolean
|
|
20
|
+
openOnFocus?: boolean
|
|
21
|
+
openOnClick?: boolean
|
|
22
|
+
placement?: 'start' | 'center' | 'end'
|
|
23
|
+
maxIcons?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const props = withDefaults(defineProps<IconPickerProps>(), {
|
|
27
|
+
variant: 'subtle',
|
|
28
|
+
openOnClick: true,
|
|
29
|
+
openOnFocus: true,
|
|
30
|
+
maxIcons: 100,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'input'])
|
|
34
|
+
|
|
35
|
+
const searchTerm = ref(getLabel(props.modelValue || ''))
|
|
36
|
+
const internalModelValue = ref(props.modelValue)
|
|
37
|
+
const isOpen = ref(false)
|
|
38
|
+
const iconNames = ref<string[]>([])
|
|
39
|
+
|
|
40
|
+
watch(
|
|
41
|
+
() => props.modelValue,
|
|
42
|
+
(newValue) => {
|
|
43
|
+
internalModelValue.value = newValue
|
|
44
|
+
searchTerm.value = newValue ? getLabel(newValue) : ''
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
const spriteContainer = document.getElementById('lucide-sprite')
|
|
50
|
+
if (!spriteContainer) {
|
|
51
|
+
console.warn('Lucide sprite not found! Make sure to use the spritePlugin.')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const symbols = spriteContainer.getElementsByTagName('symbol')
|
|
56
|
+
const names: string[] = []
|
|
57
|
+
for (let i = 0; i < symbols.length; i++) {
|
|
58
|
+
const symbol = symbols[i]
|
|
59
|
+
names.push(symbol.id)
|
|
60
|
+
}
|
|
61
|
+
iconNames.value = names
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
function getLabel(name: string) {
|
|
65
|
+
return name.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const filteredIcons = computed(() => {
|
|
69
|
+
if (!searchTerm.value) return iconNames.value
|
|
70
|
+
const lowerSearch = searchTerm.value.toLowerCase()
|
|
71
|
+
return iconNames.value.filter((name) =>
|
|
72
|
+
name.replace(/-/g, ' ').toLowerCase().includes(lowerSearch),
|
|
73
|
+
)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const onUpdateModelValue = (value: string | null) => {
|
|
77
|
+
internalModelValue.value = value
|
|
78
|
+
emit('update:modelValue', value)
|
|
79
|
+
searchTerm.value = value ? getLabel(value) : ''
|
|
80
|
+
isOpen.value = false
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const handleInputChange = (event: Event) => {
|
|
84
|
+
const target = event.target as HTMLInputElement
|
|
85
|
+
searchTerm.value = target.value
|
|
86
|
+
|
|
87
|
+
if (searchTerm.value === '') {
|
|
88
|
+
internalModelValue.value = null
|
|
89
|
+
emit('update:modelValue', null)
|
|
90
|
+
}
|
|
91
|
+
emit('input', searchTerm.value)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handleOpenChange = (open: boolean) => {
|
|
95
|
+
isOpen.value = open
|
|
96
|
+
if (!open) {
|
|
97
|
+
searchTerm.value = internalModelValue.value
|
|
98
|
+
? getLabel(internalModelValue.value)
|
|
99
|
+
: ''
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleClick = (event: MouseEvent) => {
|
|
104
|
+
if (props.openOnClick) {
|
|
105
|
+
isOpen.value = true
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const handleFocus = (event: FocusEvent) => {
|
|
110
|
+
if (props.openOnFocus) {
|
|
111
|
+
isOpen.value = true
|
|
112
|
+
}
|
|
113
|
+
emit('focus', event)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const handleBlur = (event: FocusEvent) => {
|
|
117
|
+
emit('blur', event)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const handleIconClick = (iconName: string) => {
|
|
121
|
+
onUpdateModelValue(iconName)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const reset = () => {
|
|
125
|
+
searchTerm.value = ''
|
|
126
|
+
internalModelValue.value = null
|
|
127
|
+
emit('update:modelValue', null)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const variantClasses = computed(() => {
|
|
131
|
+
const borderCss =
|
|
132
|
+
'border focus-within:border-outline-gray-4 focus-within:ring-2 focus-within:ring-outline-gray-3'
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
subtle: `${borderCss} bg-surface-gray-2 hover:bg-surface-gray-3 border-transparent`,
|
|
136
|
+
outline: `${borderCss} border-outline-gray-2`,
|
|
137
|
+
ghost: '',
|
|
138
|
+
}[props.variant]
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
defineExpose({
|
|
142
|
+
reset,
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<div class="relative">
|
|
148
|
+
<ComboboxRoot
|
|
149
|
+
:model-value="internalModelValue"
|
|
150
|
+
@update:modelValue="onUpdateModelValue"
|
|
151
|
+
@update:open="handleOpenChange"
|
|
152
|
+
:ignore-filter="true"
|
|
153
|
+
:open="isOpen"
|
|
154
|
+
>
|
|
155
|
+
<ComboboxAnchor
|
|
156
|
+
class="flex h-7 w-full items-center justify-between gap-2 rounded px-2 py-1 transition-colors"
|
|
157
|
+
:class="{
|
|
158
|
+
'opacity-50 pointer-events-none': disabled,
|
|
159
|
+
[variantClasses]: true,
|
|
160
|
+
}"
|
|
161
|
+
@click="handleClick"
|
|
162
|
+
>
|
|
163
|
+
<div class="flex items-center gap-2 flex-1 overflow-hidden">
|
|
164
|
+
<Icon
|
|
165
|
+
:name="internalModelValue || 'circle-dashed'"
|
|
166
|
+
class="w-4 h-4 flex-shrink-0"
|
|
167
|
+
/>
|
|
168
|
+
<ComboboxInput
|
|
169
|
+
:value="searchTerm"
|
|
170
|
+
@input="handleInputChange"
|
|
171
|
+
@focus="handleFocus"
|
|
172
|
+
@blur="handleBlur"
|
|
173
|
+
class="bg-transparent p-0 focus:outline-0 border-0 focus:border-0 focus:ring-0 text-base text-ink-gray-8 h-full placeholder:text-ink-gray-4 w-full"
|
|
174
|
+
:placeholder="placeholder || 'Select an icon...'"
|
|
175
|
+
:disabled="disabled"
|
|
176
|
+
autocomplete="off"
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
179
|
+
<ComboboxTrigger :disabled="disabled">
|
|
180
|
+
<Icon name="chevron-down" class="h-4 w-4 text-ink-gray-5" />
|
|
181
|
+
</ComboboxTrigger>
|
|
182
|
+
</ComboboxAnchor>
|
|
183
|
+
<ComboboxPortal>
|
|
184
|
+
<ComboboxContent
|
|
185
|
+
class="z-10 w-60 mt-1 bg-surface-modal overflow-hidden rounded-lg shadow-2xl"
|
|
186
|
+
position="popper"
|
|
187
|
+
@openAutoFocus.prevent
|
|
188
|
+
@closeAutoFocus.prevent
|
|
189
|
+
:align="props.placement || 'start'"
|
|
190
|
+
>
|
|
191
|
+
<ComboboxViewport class="max-h-60 overflow-auto p-2">
|
|
192
|
+
<ComboboxEmpty
|
|
193
|
+
v-if="filteredIcons.length === 0"
|
|
194
|
+
class="text-ink-gray-5 text-base text-center py-1.5 px-2.5"
|
|
195
|
+
>
|
|
196
|
+
<template v-if="searchTerm">
|
|
197
|
+
No icons found for "{{ searchTerm }}"
|
|
198
|
+
</template>
|
|
199
|
+
<template v-else> No icons available. </template>
|
|
200
|
+
</ComboboxEmpty>
|
|
201
|
+
<div v-if="filteredIcons.length > 0" class="flex flex-wrap">
|
|
202
|
+
<button
|
|
203
|
+
v-for="iconName in filteredIcons.slice(0, props.maxIcons)"
|
|
204
|
+
:key="iconName"
|
|
205
|
+
@click="handleIconClick(iconName)"
|
|
206
|
+
type="button"
|
|
207
|
+
class="w-8 h-8 flex items-center justify-center rounded hover:bg-surface-gray-3 transition-colors"
|
|
208
|
+
:class="{
|
|
209
|
+
'bg-surface-gray-3': internalModelValue === iconName,
|
|
210
|
+
}"
|
|
211
|
+
:title="getLabel(iconName)"
|
|
212
|
+
>
|
|
213
|
+
<Icon :name="iconName" class="w-4 h-4" />
|
|
214
|
+
</button>
|
|
215
|
+
</div>
|
|
216
|
+
</ComboboxViewport>
|
|
217
|
+
</ComboboxContent>
|
|
218
|
+
</ComboboxPortal>
|
|
219
|
+
</ComboboxRoot>
|
|
220
|
+
</div>
|
|
221
|
+
</template>
|
package/icons/index.ts
CHANGED
|
@@ -8,3 +8,8 @@ export { default as LightningIcon } from './LightningIcon.vue'
|
|
|
8
8
|
export { default as MaximizeIcon } from './MaximizeIcon.vue'
|
|
9
9
|
export { default as MinimizeIcon } from './MinimizeIcon.vue'
|
|
10
10
|
export { default as StepsIcon } from './StepsIcon.vue'
|
|
11
|
+
|
|
12
|
+
// Lucide Icons
|
|
13
|
+
export { default as Icon } from './Icon.vue'
|
|
14
|
+
export { default as IconPicker } from './IconPicker.vue'
|
|
15
|
+
export { default as spritePlugin } from './spritePlugin'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import sprite from 'lucide-static/sprite.svg?raw'
|
|
3
|
+
import type { App } from 'vue'
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
install(app: App) {
|
|
7
|
+
const div = document.createElement('div')
|
|
8
|
+
div.id = 'lucide-sprite'
|
|
9
|
+
div.style.display = 'none'
|
|
10
|
+
div.innerHTML = sprite
|
|
11
|
+
document.body.prepend(div)
|
|
12
|
+
},
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
</slot>
|
|
13
13
|
<slot name="subtitle" v-bind="{ formatValue }">
|
|
14
14
|
<div
|
|
15
|
-
class="flex-1 flex-shrink-0 truncate text-[24px] text-ink-gray-6 font-semibold leading-10"
|
|
15
|
+
class="flex flex-1 items-center gap-0.5 flex-shrink-0 truncate text-[24px] text-ink-gray-6 font-semibold leading-10"
|
|
16
16
|
>
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
<div
|
|
18
|
+
v-if="config.prefix"
|
|
19
|
+
v-html="config.prefix"
|
|
20
|
+
class="size-4 table"
|
|
21
|
+
/>
|
|
22
|
+
{{ formatValue(config.value, 1, true) }}{{ config.suffix }}
|
|
19
23
|
</div>
|
|
20
24
|
</slot>
|
|
21
25
|
<slot name="delta" v-bind="{ formatValue }">
|
|
@@ -29,7 +29,7 @@ const getValues = (arr: String[]) =>
|
|
|
29
29
|
arr.map((x) => props.options.find((y) => y.value === x)?.label);
|
|
30
30
|
|
|
31
31
|
const optionToStr = (options: MultiSelectOption[]) =>
|
|
32
|
-
options.map((x) => x.
|
|
32
|
+
options.map((x) => x.value);
|
|
33
33
|
|
|
34
34
|
const selectedOptions = computed(() => {
|
|
35
35
|
const values = getValues(model.value).join(", ");
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<div
|
|
5
5
|
ref="anchorRef"
|
|
6
6
|
:class="['flex', $attrs.class]"
|
|
7
|
+
:style="($attrs.style as StyleValue)"
|
|
7
8
|
@mouseover="onMouseover"
|
|
8
9
|
@mouseleave="onMouseleave"
|
|
9
10
|
>
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
</template>
|
|
63
64
|
|
|
64
65
|
<script setup lang="ts">
|
|
65
|
-
import { computed, ref, onUnmounted } from 'vue'
|
|
66
|
+
import { computed, ref, onUnmounted, type StyleValue } from 'vue'
|
|
66
67
|
import {
|
|
67
68
|
PopoverAnchor,
|
|
68
69
|
PopoverContent,
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
import type { SelectProps } from './types'
|
|
4
4
|
import LucideChevronDown from '~icons/lucide/chevron-down'
|
|
5
|
+
import LucideCheck from '~icons/lucide/check'
|
|
5
6
|
|
|
6
7
|
import {
|
|
7
8
|
SelectContent,
|
|
8
9
|
SelectItem,
|
|
10
|
+
SelectItemIndicator,
|
|
9
11
|
SelectItemText,
|
|
10
12
|
SelectPortal,
|
|
11
13
|
SelectRoot,
|
|
@@ -90,7 +92,7 @@ const selectOptions = computed(() => {
|
|
|
90
92
|
:disabled="disabled"
|
|
91
93
|
>
|
|
92
94
|
<slot name="prefix" />
|
|
93
|
-
<SelectValue :placeholder="placeholder" class=
|
|
95
|
+
<SelectValue :placeholder="placeholder" class="truncate" />
|
|
94
96
|
<slot name="suffix">
|
|
95
97
|
<LucideChevronDown class="size-4 text-ink-gray-4 ml-auto shrink-0" />
|
|
96
98
|
</slot>
|
|
@@ -107,15 +109,12 @@ const selectOptions = computed(() => {
|
|
|
107
109
|
:key="option.value"
|
|
108
110
|
:value="option.value"
|
|
109
111
|
:class="[sizeClasses, paddingClasses, fontSizeClasses]"
|
|
110
|
-
|
|
111
|
-
text-base text-ink-gray-9 flex items-center relative
|
|
112
|
-
data-[highlighted]:bg-surface-gray-2 border-0 [data-state=checked]:bg-surface-gray-2
|
|
113
|
-
data-[disabled]:text-ink-gray-4 select-none
|
|
114
|
-
"
|
|
112
|
+
class="text-base text-ink-gray-9 flex items-center data-[highlighted]:bg-surface-gray-2 border-0 data-[state=checked]:bg-surface-gray-2 data-[disabled]:text-ink-gray-4 select-none"
|
|
115
113
|
>
|
|
116
114
|
<SelectItemText>
|
|
117
115
|
<slot name="option" v-bind="{ option }">{{ option.label }}</slot>
|
|
118
116
|
</SelectItemText>
|
|
117
|
+
<SelectItemIndicator :as="LucideCheck" class="size-4 ml-auto" />
|
|
119
118
|
</SelectItem>
|
|
120
119
|
<slot name="footer" />
|
|
121
120
|
</SelectViewport>
|
|
@@ -135,7 +134,7 @@ const selectOptions = computed(() => {
|
|
|
135
134
|
@keyframes fadeInScale {
|
|
136
135
|
from {
|
|
137
136
|
opacity: 0;
|
|
138
|
-
transform: scale(0.
|
|
137
|
+
transform: scale(0.9);
|
|
139
138
|
}
|
|
140
139
|
to {
|
|
141
140
|
opacity: 1;
|
|
@@ -11,6 +11,7 @@ import type { TabProps } from './types'
|
|
|
11
11
|
import { h } from 'vue'
|
|
12
12
|
|
|
13
13
|
const props = defineProps<TabProps>()
|
|
14
|
+
const model = defineModel<string | number>({ default: 0 })
|
|
14
15
|
|
|
15
16
|
const indicatorXCss = `left-0 bottom-0 h-[2px] w-[--reka-tabs-indicator-size] transition-[width,transform]
|
|
16
17
|
translate-x-[--reka-tabs-indicator-position] translate-y-[1px]`
|
|
@@ -29,6 +30,7 @@ const Btn = h('button')
|
|
|
29
30
|
class="flex flex-1 overflow-hidden flex-col data-[orientation=vertical]:flex-row"
|
|
30
31
|
:orientation="props.vertical ? 'vertical' : 'horizontal'"
|
|
31
32
|
:default-value="props.tabs[0].label"
|
|
33
|
+
v-model="model"
|
|
32
34
|
>
|
|
33
35
|
<TabsList
|
|
34
36
|
class="relative min-h-fit flex data-[orientation=vertical]:flex-col p-1 border-b data-[orientation=vertical]:border-r gap-5"
|