Skip to content

Commit 01c9dee

Browse files
authored
refactor: no more vue multiselect (#5523)
* start multiselect component * update styles * small fix * fix padding and styles * add border bottom on sticky items * add border bottom to search as well * fix select all showing line * use multi-select component for languages field * add no options story for empty state * refactor: remove vue-multiselect, replace with either our own combobox and multiselect * pnpm prepr * pnpm prepr * fix combobox in transfer organization
1 parent d50a8ef commit 01c9dee

15 files changed

Lines changed: 172 additions & 240 deletions

File tree

apps/app-frontend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
"vite-svg-loader": "^5.1.0",
4141
"vue": "^3.5.13",
4242
"vue-i18n": "^10.0.0",
43-
"vue-multiselect": "3.0.0",
4443
"vue-router": "^4.6.0",
4544
"vue-virtual-scroller": "v2.0.0-beta.8"
4645
},

apps/app-frontend/src/App.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1599,4 +1599,3 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
15991599
}
16001600
}
16011601
</style>
1602-
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

apps/app-frontend/src/components/ui/install_flow/IncompatibilityWarningModal.vue

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,17 @@
1717
<tr class="content">
1818
<td class="data">{{ instance?.loader }} {{ instance?.game_version }}</td>
1919
<td>
20-
<multiselect
20+
<Combobox
2121
v-if="versions?.length > 1"
22-
v-model="selectedVersion"
23-
:options="versions"
22+
v-model="selectedVersionId"
23+
:options="versionOptions"
2424
:searchable="true"
2525
placeholder="Select version"
26-
open-direction="top"
27-
:show-labels="false"
28-
:custom-label="
29-
(version) =>
30-
`${version?.name} (${version?.loaders
31-
.map((name) => formatLoader(formatMessage, name))
32-
.join(', ')} - ${version?.game_versions.join(', ')})`
33-
"
26+
force-direction="up"
3427
:max-height="150"
3528
/>
3629
<span v-else>
37-
<span>
38-
{{ selectedVersion?.name }} ({{
39-
selectedVersion?.loaders
40-
.map((name) => formatLoader(formatMessage, name))
41-
.join(', ')
42-
}}
43-
- {{ selectedVersion?.game_versions.join(', ') }})
44-
</span>
30+
<span>{{ selectedVersionLabel }}</span>
4531
</span>
4632
</td>
4733
</tr>
@@ -59,9 +45,8 @@
5945

6046
<script setup>
6147
import { DownloadIcon, XIcon } from '@modrinth/assets'
62-
import { Button, formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
63-
import { ref } from 'vue'
64-
import Multiselect from 'vue-multiselect'
48+
import { Button, Combobox, formatLoader, injectNotificationManager, useVIntl } from '@modrinth/ui'
49+
import { computed, ref } from 'vue'
6550
6651
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
6752
import { trackEvent } from '@/helpers/analytics'
@@ -79,11 +64,35 @@ const installing = ref(false)
7964
8065
const onInstall = ref(() => {})
8166
67+
const selectedVersionLabel = computed(() => {
68+
if (!selectedVersion.value) return ''
69+
return `${selectedVersion.value.name} (${selectedVersion.value.loaders
70+
.map((name) => formatLoader(formatMessage, name))
71+
.join(', ')} - ${selectedVersion.value.game_versions.join(', ')})`
72+
})
73+
74+
const versionOptions = computed(() =>
75+
(versions.value ?? []).map((version) => ({
76+
value: version.id,
77+
label: `${version.name} (${version.loaders
78+
.map((name) => formatLoader(formatMessage, name))
79+
.join(', ')} - ${version.game_versions.join(', ')})`,
80+
})),
81+
)
82+
83+
const selectedVersionId = computed({
84+
get: () => selectedVersion.value?.id ?? null,
85+
set: (value) => {
86+
if (!value) return
87+
selectedVersion.value = (versions.value ?? []).find((version) => version.id === value) ?? null
88+
},
89+
})
90+
8291
defineExpose({
8392
show: (instanceVal, projectVal, projectVersions, selected, callback) => {
8493
instance.value = instanceVal
85-
versions.value = projectVersions
86-
selectedVersion.value = selected ?? projectVersions[0]
94+
versions.value = projectVersions ?? []
95+
selectedVersion.value = selected ?? projectVersions?.[0] ?? null
8796
8897
project.value = projectVal
8998
@@ -162,9 +171,5 @@ td:first-child {
162171
display: flex;
163172
flex-direction: column;
164173
gap: 1rem;
165-
166-
:deep(.animated-dropdown .options) {
167-
max-height: 13.375rem;
168-
}
169174
}
170175
</style>

apps/frontend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
"semver": "^7.5.4",
7676
"three": "^0.172.0",
7777
"vue-confetti-explosion": "^1.0.2",
78-
"vue-multiselect": "3.0.0-alpha.2",
7978
"vue-typed-virtual-list": "^1.0.10",
8079
"vue3-ace-editor": "^2.2.4",
8180
"vue3-apexcharts": "^1.5.2",

apps/frontend/src/layouts/default.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1593,4 +1593,3 @@ const { cycle: changeTheme } = useTheme()
15931593
}
15941594
}
15951595
</style>
1596-
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

apps/frontend/src/pages/[type]/[id]/settings/members.vue

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -296,27 +296,22 @@
296296
This project is not managed by an organization. If you are the member of any organizations,
297297
you can transfer management to one of them.
298298
</p>
299-
<div v-if="!organization" class="input-group">
300-
<Multiselect
301-
id="organization-picker"
302-
v-model="selectedOrganization"
303-
class="large-multiselect"
304-
track-by="id"
305-
label="name"
306-
open-direction="top"
307-
:close-on-select="true"
308-
:show-labels="false"
309-
:allow-empty="false"
310-
:options="organizations || []"
311-
:disabled="!currentMember?.is_owner || organizations?.length === 0"
299+
<div v-if="!organization" class="flex gap-2">
300+
<Combobox
301+
v-model="selectedOrganizationId"
302+
:options="organizationOptions"
303+
:searchable="true"
304+
search-placeholder="Select organization..."
305+
force-direction="up"
306+
:disabled="!currentMember?.is_owner || organizationOptions.length === 0"
312307
/>
313308
<button
314309
class="btn btn-primary"
315310
:disabled="!selectedOrganization"
316311
@click="openTransferToOrgModal($event)"
317312
>
318313
<CheckIcon />
319-
Transfer management
314+
<span class="w-max"> Transfer management </span>
320315
</button>
321316
</div>
322317
<button v-if="organization" class="btn" @click="$refs.modal_remove.show()">
@@ -561,13 +556,13 @@ import {
561556
Badge,
562557
Card,
563558
Checkbox,
559+
Combobox,
564560
ConfirmModal,
565561
injectNotificationManager,
566562
injectProjectPageContext,
567563
StyledInput,
568564
Toggle,
569565
} from '@modrinth/ui'
570-
import { Multiselect } from 'vue-multiselect'
571566
572567
import ConfirmTransferProjectModal from '~/components/ui/ConfirmTransferProjectModal.vue'
573568
import { removeSelfFromTeam } from '~/helpers/teams.js'
@@ -620,7 +615,7 @@ initMembers()
620615
621616
const currentUsername = ref('')
622617
const openTeamMembers = ref([])
623-
const selectedOrganization = ref(null)
618+
const selectedOrganizationId = ref('')
624619
const transferData = ref(null)
625620
const transferModal = ref(null)
626621
@@ -630,6 +625,17 @@ const { data: organizations } = useAsyncData('organizations', () => {
630625
})
631626
})
632627
628+
const organizationOptions = computed(() =>
629+
(organizations.value ?? []).map((organization) => ({
630+
value: organization.id,
631+
label: organization.name,
632+
})),
633+
)
634+
635+
const selectedOrganization = computed(() =>
636+
(organizations.value ?? []).find((org) => org.id === selectedOrganizationId.value),
637+
)
638+
633639
const UPLOAD_VERSION = 1 << 0
634640
const DELETE_VERSION = 1 << 1
635641
const EDIT_DETAILS = 1 << 2
@@ -642,9 +648,9 @@ const VIEW_ANALYTICS = 1 << 8
642648
const VIEW_PAYOUTS = 1 << 9
643649
644650
const onAddToOrg = useClientTry(async () => {
645-
if (!selectedOrganization.value) return
651+
if (!selectedOrganizationId.value) return
646652
647-
await useBaseFetch(`organization/${selectedOrganization.value.id}/projects`, {
653+
await useBaseFetch(`organization/${selectedOrganizationId.value}/projects`, {
648654
method: 'POST',
649655
body: JSON.stringify({
650656
project_id: project.value.id,
@@ -1002,8 +1008,4 @@ const updateMembers = async () => {
10021008
}
10031009
}
10041010
}
1005-
1006-
.large-multiselect {
1007-
max-width: 24rem;
1008-
}
10091011
</style>

apps/frontend/src/pages/[type]/[id]/version/[version].vue

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,14 @@
2525
The mod loaders you would like to package your data pack for.
2626
</span>
2727
</label>
28-
<multiselect
28+
<MultiSelect
2929
id="package-mod-loaders"
3030
v-model="packageLoaders"
31-
:options="['fabric', 'forge', 'quilt', 'neoforge']"
32-
:custom-label="(value: string) => value.charAt(0).toUpperCase() + value.slice(1)"
33-
:multiple="true"
31+
class="package-loader-select"
32+
:options="packageLoaderOptions"
3433
:searchable="false"
35-
:show-no-results="false"
36-
:show-labels="false"
3734
placeholder="Choose loaders..."
38-
open-direction="top"
35+
force-direction="up"
3936
/>
4037
<div class="button-group">
4138
<ButtonStyled>
@@ -436,11 +433,11 @@ import {
436433
ENVIRONMENTS_COPY,
437434
injectNotificationManager,
438435
injectProjectPageContext,
436+
MultiSelect,
439437
StyledInput,
440438
useFormatDateTime,
441439
} from '@modrinth/ui'
442440
import { formatBytes, renderHighlightedString } from '@modrinth/utils'
443-
import { Multiselect } from 'vue-multiselect'
444441
445442
import Breadcrumbs from '~/components/ui/Breadcrumbs.vue'
446443
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
@@ -504,6 +501,12 @@ const newFiles = ref<File[]>([])
504501
const deleteFiles = ref<string[]>([])
505502
const newFileTypes = ref<Array<{ display: string; value: string } | null>>([])
506503
const packageLoaders = ref(['forge', 'fabric', 'quilt', 'neoforge'])
504+
const packageLoaderOptions = [
505+
{ value: 'fabric', label: 'Fabric' },
506+
{ value: 'forge', label: 'Forge' },
507+
{ value: 'quilt', label: 'Quilt' },
508+
{ value: 'neoforge', label: 'Neoforge' },
509+
]
507510
const showKnownErrors = ref(false)
508511
const shouldPreventActions = ref(false)
509512
const uploadedImageIds = ref<string[]>([])
@@ -1215,11 +1218,6 @@ async function resetProjectVersions() {
12151218
margin-bottom: var(--spacing-card-sm);
12161219
}
12171220
1218-
.multiselect {
1219-
width: 8rem;
1220-
flex-grow: 1;
1221-
}
1222-
12231221
input {
12241222
flex-grow: 2;
12251223
}
@@ -1270,14 +1268,6 @@ async function resetProjectVersions() {
12701268
font-weight: 300;
12711269
}
12721270
1273-
.raised-multiselect {
1274-
display: none;
1275-
margin: 0 0.5rem;
1276-
height: 40px;
1277-
max-height: 40px;
1278-
min-width: 235px;
1279-
}
1280-
12811271
.raised-button {
12821272
margin-left: auto;
12831273
background-color: var(--color-raised-bg);
@@ -1286,13 +1276,6 @@ async function resetProjectVersions() {
12861276
&:not(:nth-child(2)) {
12871277
margin-top: 0.5rem;
12881278
}
1289-
1290-
// TODO: Make file type editing work on mobile
1291-
@media (min-width: 600px) {
1292-
.raised-multiselect {
1293-
display: block;
1294-
}
1295-
}
12961279
}
12971280
12981281
.additional-files {
@@ -1357,7 +1340,7 @@ async function resetProjectVersions() {
13571340
margin-bottom: 1rem;
13581341
}
13591342
1360-
.multiselect {
1343+
.package-loader-select {
13611344
max-width: 20rem;
13621345
}
13631346
}

apps/frontend/src/pages/dashboard/projects.vue

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,11 @@
182182
<div class="push-right">
183183
<div class="labeled-control-row">
184184
Sort by
185-
<Multiselect
185+
<Combobox
186186
v-model="sortBy"
187187
:searchable="false"
188188
class="small-select"
189-
:options="['Name', 'Status', 'Type']"
190-
:close-on-select="true"
191-
:show-labels="false"
192-
:allow-empty="false"
189+
:options="sortOptions"
193190
@update:model-value="projects = updateSort(projects, sortBy, descending)"
194191
/>
195192
<button
@@ -323,6 +320,7 @@ import {
323320
Avatar,
324321
ButtonStyled,
325322
Checkbox,
323+
Combobox,
326324
commonMessages,
327325
CopyCode,
328326
injectNotificationManager,
@@ -332,7 +330,6 @@ import {
332330
useVIntl,
333331
} from '@modrinth/ui'
334332
import { formatProjectType } from '@modrinth/utils'
335-
import { Multiselect } from 'vue-multiselect'
336333
337334
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
338335
import { getProjectTypeForUrl } from '~/helpers/projects.js'
@@ -356,6 +353,11 @@ const projects = ref([])
356353
const projectsWithMigrationWarning = ref([])
357354
const selectedProjects = ref([])
358355
const sortBy = ref('Name')
356+
const sortOptions = [
357+
{ value: 'Name', label: 'Name' },
358+
{ value: 'Status', label: 'Status' },
359+
{ value: 'Type', label: 'Type' },
360+
]
359361
const descending = ref(false)
360362
const editLinks = reactive({
361363
showAffected: false,

0 commit comments

Comments
 (0)