Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 70 additions & 16 deletions src/components/input/MultiSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,41 @@
:max-selected-labels="0"
:pt="pt"
>
<template
v-if="hasSearchBox || showSelectedCount || hasClearButton"
#header
>
<div class="p-2 flex flex-col gap-y-4 pb-0">
<SearchBox
v-if="hasSearchBox"
v-model="searchQuery"
:has-border="true"
:place-holder="searchPlaceholder"
/>
<div class="flex items-center justify-between">
<span
v-if="showSelectedCount"
class="text-sm text-neutral-400 dark-theme:text-zinc-500 px-1"
>
{{
selectedCount > 0
? $t('g.itemsSelected', { selectedCount })
: $t('g.itemSelected', { selectedCount })
}}
</span>
<TextButton
v-if="hasClearButton"
:label="$t('g.clearAll')"
type="transparent"
size="fit-content"
class="text-sm !text-blue-500 !dark-theme:text-blue-600"
@click.stop="selectedItems = []"
/>
</div>
<div class="h-px bg-zinc-200 dark-theme:bg-zinc-700"></div>
</div>
</template>

<!-- Trigger value (keep text scale identical) -->
<template #value>
<span class="text-sm text-zinc-700 dark-theme:text-gray-200">
Expand Down Expand Up @@ -42,7 +77,7 @@
</template>
</MultiSelect>

<!-- Selected count badge (unchanged) -->
<!-- Selected count badge -->
<div
v-if="selectedCount > 0"
class="pointer-events-none absolute -right-2 -top-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-blue-400 dark-theme:bg-blue-500 text-xs font-semibold text-white"
Expand All @@ -58,22 +93,41 @@ import MultiSelect, {
} from 'primevue/multiselect'
import { computed } from 'vue'

const { label, options } = defineProps<{
import SearchBox from '@/components/input/SearchBox.vue'

import TextButton from '../button/TextButton.vue'

type Option = { name: string; value: string }

interface Props {
/** Input label shown on the trigger button */
label?: string
options: { name: string; value: string }[]
}>()
/** Static options for the multiselect (when not using async search) */
options: Option[]
/** Show search box in the panel header */
hasSearchBox?: boolean
/** Show selected count text in the panel header */
showSelectedCount?: boolean
/** Show "Clear all" action in the panel header */
hasClearButton?: boolean
/** Placeholder for the search input */
searchPlaceholder?: string
}
const {
label,
options,
hasSearchBox = false,
showSelectedCount = false,
hasClearButton = false,
searchPlaceholder = 'Search...'
} = defineProps<Props>()

const selectedItems = defineModel<{ name: string; value: string }[]>({
const selectedItems = defineModel<Option[]>({
required: true
})

const searchQuery = defineModel<string>('searchQuery')
const selectedCount = computed(() => selectedItems.value.length)

/**
* Pure unstyled mode using only the PrimeVue PT API.
* All PrimeVue built-in checkboxes/headers are hidden via PT (no :deep hacks).
* Visual output matches the previous version exactly.
*/
const pt = computed(() => ({
root: ({ props }: MultiSelectPassThroughMethodOptions) => ({
class: [
Expand All @@ -97,19 +151,19 @@ const pt = computed(() => ({
dropdown: {
class: 'flex shrink-0 cursor-pointer items-center justify-center px-3'
},
header: { class: 'hidden' },

header: () => ({
class:
hasSearchBox || showSelectedCount || hasClearButton ? 'block' : 'hidden'
}),
// Overlay & list visuals unchanged
overlay:
'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100',
'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100 dark-theme:border-zinc-700',
list: {
class: 'flex flex-col gap-1 p-0 list-none border-none text-xs'
},

// Option row hover tone identical
option:
'flex gap-1 items-center p-2 hover:bg-neutral-100/50 dark-theme:hover:bg-zinc-700/50',

// Hide built-in checkboxes entirely via PT (no :deep)
pcHeaderCheckbox: {
root: { class: 'hidden' },
Expand Down
21 changes: 15 additions & 6 deletions src/components/input/SearchBox.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<template>
<div
class="flex w-full items-center rounded-lg px-2 py-1.5 gap-2 bg-white dark-theme:bg-zinc-800"
>
<i-lucide:search class="text-neutral" />
<div :class="wrapperStyle">
<i-lucide:search :class="iconColorStyle" />
<InputText
v-model="searchQuery"
:placeholder="placeHolder || 'Search...'"
Expand All @@ -15,10 +13,21 @@

<script setup lang="ts">
import InputText from 'primevue/inputtext'
import { defineModel } from 'vue'
import { computed, defineModel } from 'vue'

const { placeHolder } = defineProps<{
const { placeHolder, hasBorder = false } = defineProps<{
placeHolder?: string
hasBorder?: boolean
}>()
const searchQuery = defineModel<string>('')

const wrapperStyle = computed(() => {
return hasBorder
? 'flex w-full items-center rounded gap-2 bg-white dark-theme:bg-zinc-800 p-1 border border-solid border-zinc-200 dark-theme:border-zinc-700'
: 'flex w-full items-center rounded px-2 py-1.5 gap-2 bg-white dark-theme:bg-zinc-800'
})

const iconColorStyle = computed(() => {
return !hasBorder ? 'text-neutral' : 'text-zinc-300 dark-theme:text-zinc-700'
})
</script>
2 changes: 1 addition & 1 deletion src/components/input/SingleSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const pt = computed(() => ({
overlay: {
class: [
// dropdown panel
'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg'
'mt-2 bg-white dark-theme:bg-zinc-800 text-neutral dark-theme:text-white rounded-lg border border-solid border-zinc-100 dark-theme:border-zinc-700'
]
},
list: {
Expand Down
4 changes: 4 additions & 0 deletions src/components/widget/ModelSelector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@
<div class="relative px-6 pt-2 pb-4 flex gap-2">
<MultiSelect
v-model="selectedFrameworks"
class="w-[250px]"
:has-search-box="true"
:show-selected-count="true"
:has-clear-button="true"
label="Select Frameworks"
:options="frameworkOptions"
/>
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ar/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "الفئة",
"choose_file_to_upload": "اختر ملفاً للرفع",
"clear": "مسح",
"clearAll": "مسح الكل",
"clearFilters": "مسح الفلاتر",
"close": "إغلاق",
"color": "اللون",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "مثبت",
"installing": "جارٍ التثبيت",
"interrupted": "تمت المقاطعة",
"itemSelected": "تم تحديد عنصر واحد",
"itemsSelected": "تم تحديد {selectedCount} عناصر",
"keybinding": "اختصار لوحة المفاتيح",
"keybindingAlreadyExists": "الاختصار موجود بالفعل في",
"learnMore": "اعرف المزيد",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,11 @@
"copy": "Copy",
"imageUrl": "Image URL",
"clear": "Clear",
"clearAll": "Clear all",
"copyURL": "Copy URL",
"releaseTitle": "{package} {version} Release",
"itemSelected": "{selectedCount} item selected",
"itemsSelected": "{selectedCount} items selected",
"progressCountOf": "of",
"keybindingAlreadyExists": "Keybinding already exists on",
"startRecording": "Start Recording",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/es/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "Categoría",
"choose_file_to_upload": "elige archivo para subir",
"clear": "Limpiar",
"clearAll": "Borrar todo",
"clearFilters": "Borrar filtros",
"close": "Cerrar",
"color": "Color",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "Instalado",
"installing": "Instalando",
"interrupted": "Interrumpido",
"itemSelected": "{selectedCount} elemento seleccionado",
"itemsSelected": "{selectedCount} elementos seleccionados",
"keybinding": "Combinación de teclas",
"keybindingAlreadyExists": "La combinación de teclas ya existe en",
"learnMore": "Aprende más",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "Catégorie",
"choose_file_to_upload": "choisissez le fichier à télécharger",
"clear": "Effacer",
"clearAll": "Tout effacer",
"clearFilters": "Effacer les filtres",
"close": "Fermer",
"color": "Couleur",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "Installé",
"installing": "Installation",
"interrupted": "Interrompu",
"itemSelected": "{selectedCount} élément sélectionné",
"itemsSelected": "{selectedCount} éléments sélectionnés",
"keybinding": "Raccourci clavier",
"keybindingAlreadyExists": "Le raccourci clavier existe déjà",
"learnMore": "En savoir plus",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ja/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "カテゴリ",
"choose_file_to_upload": "アップロードするファイルを選択",
"clear": "クリア",
"clearAll": "すべてクリア",
"clearFilters": "フィルターをクリア",
"close": "閉じる",
"color": "色",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "インストール済み",
"installing": "インストール中",
"interrupted": "中断されました",
"itemSelected": "{selectedCount}件選択済み",
"itemsSelected": "{selectedCount}件選択済み",
"keybinding": "キーバインディング",
"keybindingAlreadyExists": "このキー割り当てはすでに存在します",
"learnMore": "詳細を学ぶ",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ko/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "카테고리",
"choose_file_to_upload": "업로드할 파일 선택",
"clear": "지우기",
"clearAll": "모두 지우기",
"clearFilters": "필터 지우기",
"close": "닫기",
"color": "색상",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "설치됨",
"installing": "설치 중",
"interrupted": "중단됨",
"itemSelected": "{selectedCount}개 선택됨",
"itemsSelected": "{selectedCount}개 선택됨",
"keybinding": "키 바인딩",
"keybindingAlreadyExists": "단축키가 이미 존재합니다",
"learnMore": "더 알아보기",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/ru/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "Категория",
"choose_file_to_upload": "выберите файл для загрузки",
"clear": "Очистить",
"clearAll": "Очистить всё",
"clearFilters": "Сбросить фильтры",
"close": "Закрыть",
"color": "Цвет",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "Установлено",
"installing": "Установка",
"interrupted": "Прервано",
"itemSelected": "Выбран {selectedCount} элемент",
"itemsSelected": "Выбрано {selectedCount} элементов",
"keybinding": "Привязка клавиш",
"keybindingAlreadyExists": "Горячая клавиша уже существует",
"learnMore": "Узнать больше",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/zh-TW/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "分類",
"choose_file_to_upload": "選擇要上傳的檔案",
"clear": "清除",
"clearAll": "全部清除",
"clearFilters": "清除篩選",
"close": "關閉",
"color": "顏色",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "已安裝",
"installing": "安裝中",
"interrupted": "已中斷",
"itemSelected": "已選取 {selectedCount} 項",
"itemsSelected": "已選取 {selectedCount} 項",
"keybinding": "快捷鍵",
"keybindingAlreadyExists": "快捷鍵已存在於",
"learnMore": "了解更多",
Expand Down
3 changes: 3 additions & 0 deletions src/locales/zh/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@
"category": "类别",
"choose_file_to_upload": "选择要上传的文件",
"clear": "清除",
"clearAll": "全部清除",
"clearFilters": "清除筛选",
"close": "关闭",
"color": "颜色",
Expand Down Expand Up @@ -327,6 +328,8 @@
"installed": "已安装",
"installing": "正在安装",
"interrupted": "已中断",
"itemSelected": "已选择 {selectedCount} 项",
"itemsSelected": "已选择 {selectedCount} 项",
"keybinding": "按键绑定",
"keybindingAlreadyExists": "快捷键已存在",
"learnMore": "了解更多",
Expand Down
4 changes: 3 additions & 1 deletion src/types/buttonTypes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { HTMLAttributes } from 'vue'

export interface BaseButtonProps {
size?: 'sm' | 'md'
size?: 'fit-content' | 'sm' | 'md'
type?: 'primary' | 'secondary' | 'transparent'
class?: HTMLAttributes['class']
}

export const getButtonSizeClasses = (size: BaseButtonProps['size'] = 'md') => {
const sizeClasses = {
'fit-content': '',
sm: 'px-2 py-1.5 text-xs',
md: 'px-2.5 py-2 text-sm'
}
Expand All @@ -31,6 +32,7 @@ export const getIconButtonSizeClasses = (
size: BaseButtonProps['size'] = 'md'
) => {
const sizeClasses = {
'fit-content': 'w-auto h-auto',
sm: 'w-6 h-6 text-xs !rounded-md',
md: 'w-8 h-8 text-sm'
}
Expand Down