1
0

Fix a number of light mode issues and get rid of scrollbar jumping on menus (#4760)

* Fix DEV-466, Fixes #4692 as well as a bunch of other poor contrast and inconsistency issues in light mode. Adds shadows to buttons and makes scrollbar gutter stable.

* lintttt & only do scrollbar gutter on website

* try to fix following hydration issue

* try another clientonly approach

* fix home page link animation

* lint

* remove dropdown style from checkbox & improve shadow consistency

* liiiint
This commit is contained in:
Prospector
2025-11-13 15:21:43 -08:00
committed by GitHub
parent c27f787c91
commit 94c0003c19
40 changed files with 384 additions and 693 deletions

View File

@@ -765,7 +765,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
>
<LibraryIcon />
</NavButton>
<div class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
<div class="h-px w-6 mx-auto my-2 bg-surface-5"></div>
<suspense>
<QuickInstanceSwitcher />
</suspense>

View File

@@ -284,7 +284,7 @@ onUnmounted(() => {
z-index: 11;
gap: 0.5rem;
padding: 1rem;
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
width: max-content;
user-select: none;
-ms-user-select: none;
@@ -380,7 +380,7 @@ onUnmounted(() => {
text-align: left;
&.expanded {
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
padding: 1rem;
}
}

View File

@@ -119,7 +119,7 @@ onBeforeUnmount(() => {
background-color: var(--color-raised-bg);
border-radius: var(--radius-md);
box-shadow: var(--shadow-floating);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
margin: 0;
position: fixed;
z-index: 1000000;
@@ -163,7 +163,7 @@ onBeforeUnmount(() => {
}
.divider {
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
margin: var(--gap-sm);
pointer-events: none;
}

View File

@@ -34,7 +34,7 @@
</div>
<div class="input-row">
<p class="input-label">Game version</p>
<div class="versions">
<div class="flex gap-4 items-center">
<multiselect
v-model="game_version"
class="selector"
@@ -45,7 +45,7 @@
open-direction="top"
:show-labels="false"
/>
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Show all versions" />
<Checkbox v-model="showSnapshots" class="shrink-0" label="Show all versions" />
</div>
</div>
<div v-if="loader !== 'vanilla'" class="input-row">
@@ -546,12 +546,6 @@ const next = async () => {
font-style: italic;
}
.versions {
display: flex;
flex-direction: row;
gap: 1rem;
}
:deep(button.checkbox) {
border: none;
}

View File

@@ -69,7 +69,7 @@ onUnmounted(() => {
<SpinnerIcon class="animate-spin w-4 h-4" />
</div>
</NavButton>
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-button-bg"></div>
<div v-if="recentInstances.length > 0" class="h-px w-6 mx-auto my-2 bg-divider"></div>
</template>
<style scoped lang="scss"></style>

View File

@@ -293,7 +293,7 @@ onBeforeUnmount(() => {
align-items: center;
gap: 0.5rem;
border-radius: var(--radius-md);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
padding: var(--gap-sm) var(--gap-lg);
}
@@ -356,7 +356,7 @@ onBeforeUnmount(() => {
gap: 1rem;
overflow: auto;
transition: all 0.2s ease-in-out;
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
&.hidden {
transform: translateY(-100%);
@@ -454,7 +454,7 @@ onBeforeUnmount(() => {
flex-direction: column;
overflow: auto;
transition: all 0.2s ease-in-out;
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
padding: var(--gap-md);
&.hidden {

View File

@@ -130,7 +130,7 @@ onUnmounted(() => {
/>
</template>
<div
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised rounded-xl smart-clickable:highlight-on-hover"
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised card-shadow rounded-xl smart-clickable:highlight-on-hover"
>
<Avatar
:src="instanceIcon ? convertFileSrc(instanceIcon) : undefined"

View File

@@ -181,7 +181,7 @@ const messages = defineMessages({
/>
</template>
<div
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised smart-clickable:highlight-on-hover rounded-xl"
class="grid grid-cols-[auto_minmax(0,3fr)_minmax(0,4fr)_auto] items-center gap-2 p-3 bg-bg-raised card-shadow smart-clickable:highlight-on-hover rounded-xl"
:class="{
'world-item-highlighted': highlighted,
}"

View File

@@ -427,7 +427,7 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
<div v-else class="flex items-center justify-center min-h-[50vh] pt-[25%]">
<div
class="bg-bg-raised rounded-lg p-7 flex flex-col gap-5 shadow-md relative max-w-xl w-full mx-auto"
class="bg-bg-raised card-shadow rounded-lg p-7 flex flex-col gap-5 shadow-md relative max-w-xl w-full mx-auto"
>
<img
:src="ExcitedRinthbot"

View File

@@ -1290,3 +1290,7 @@ svg.inline-svg {
}
}
}
.card-shadow {
box-shadow: var(--shadow-card);
}

View File

@@ -37,6 +37,7 @@ html {
--icon-32: 2rem;
interpolate-size: allow-keywords;
scrollbar-gutter: stable;
}
.light-mode {
@@ -89,7 +90,7 @@ html {
--color-hr: var(--color-text);
--color-table-border: #dfe2e5;
--color-table-alternate-row: #f2f4f7;
--color-table-alternate-row: #f0f1f2;
--landing-maze-bg: url('https://cdn.modrinth.com/landing-new/landing-light.webp');
--landing-maze-gradient-bg: url('https://cdn.modrinth.com/landing-new/landing-lower-light.webp');

View File

@@ -1,151 +0,0 @@
<template>
<div
class="checkbox-outer button-within"
:class="{ disabled, checked: modelValue }"
role="presentation"
@click="toggle"
>
<button
class="checkbox"
role="checkbox"
:disabled="disabled"
:class="{ checked: modelValue, collapsing: collapsingToggleStyle }"
:aria-label="description ?? label"
:aria-checked="modelValue"
>
<CheckIcon v-if="modelValue && !collapsingToggleStyle" aria-hidden="true" />
<DropdownIcon v-else-if="collapsingToggleStyle" aria-hidden="true" />
</button>
<!-- aria-hidden is set so screenreaders only use the <button>'s aria-label -->
<p v-if="label" aria-hidden="true">
{{ label }}
</p>
<slot v-else />
</div>
</template>
<script>
import { CheckIcon, DropdownIcon } from '@modrinth/assets'
export default {
components: {
CheckIcon,
DropdownIcon,
},
props: {
label: {
type: String,
required: false,
default: '',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
description: {
type: String,
required: false,
default: null,
},
modelValue: Boolean,
clickEvent: {
type: Function,
required: false,
default: () => {},
},
collapsingToggleStyle: {
type: Boolean,
required: false,
default: false,
},
},
emits: ['update:modelValue'],
methods: {
toggle() {
if (!this.disabled) {
this.$emit('update:modelValue', !this.modelValue)
}
},
},
}
</script>
<style lang="scss" scoped>
.checkbox-outer {
display: flex;
align-items: center;
cursor: pointer;
p {
user-select: none;
padding: 0.2rem 0;
margin: 0;
}
&.disabled {
cursor: not-allowed;
}
&.checked {
outline: 2px solid transparent;
outline-offset: 4px;
border-radius: 0.25rem;
}
}
.checkbox {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
min-width: 1rem;
min-height: 1rem;
padding: 0;
margin: 0 0.5rem 0 0;
color: var(--color-button-text);
background-color: var(--color-button-bg);
border-radius: var(--size-rounded-control);
box-shadow:
var(--shadow-inset-sm),
0 0 0 0 transparent;
&.checked {
background-color: var(--color-brand);
}
svg {
color: var(--color-accent-contrast, var(--color-brand-inverted));
stroke-width: 0.2rem;
height: 0.8rem;
width: 0.8rem;
flex-shrink: 0;
}
&.collapsing {
background-color: transparent !important;
box-shadow: none;
svg {
color: inherit;
height: 1rem;
width: 1rem;
transition: transform 0.25s ease-in-out;
}
&.checked {
svg {
transform: rotate(180deg);
}
}
}
&:disabled {
box-shadow: none;
cursor: not-allowed;
}
}
</style>

View File

@@ -1,6 +1,8 @@
<template>
<nav :aria-label="ariaLabel" class="w-full">
<ul class="m-0 flex list-none flex-col items-start gap-1.5 rounded-2xl bg-bg-raised p-4">
<ul
class="card-shadow m-0 flex list-none flex-col items-start gap-1.5 rounded-2xl bg-bg-raised p-4"
>
<slot v-if="hasSlotContent" />
<template v-else>

View File

@@ -152,7 +152,7 @@ const onSubmitHandler = () => {
border-radius: var(--radius-md);
overflow: hidden;
margin-top: var(--gap-md);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
background-color: var(--color-raised-bg);
.table-row {

View File

@@ -356,7 +356,7 @@ svg {
:deep(.apexcharts-yaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
box-shadow: var(--shadow-floating) !important;
font-size: var(--font-size-nm) !important;
}
@@ -371,7 +371,7 @@ svg {
:deep(.apexcharts-xaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
font-size: var(--font-size-nm) !important;
color: var(--color-base) !important;

View File

@@ -891,7 +891,7 @@ const defaultRanges: RangeObject[] = [
flex-direction: column;
background-color: var(--color-bg);
border-radius: var(--radius-sm);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
gap: var(--gap-md);
padding: var(--gap-md);
margin-top: var(--gap-md);
@@ -920,7 +920,7 @@ const defaultRanges: RangeObject[] = [
width: 100%;
height: 1rem;
background-color: var(--color-raised-bg);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
border-radius: var(--radius-sm);
overflow: hidden;

View File

@@ -159,10 +159,10 @@ defineExpose({
flex-direction: column;
gap: var(--gap-xs);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
border-radius: var(--radius-md);
background-color: var(--color-raised-bg);
box-shadow: var(--shadow-floating);
box-shadow: var(--shadow-card);
color: var(--color-base);
font-size: var(--font-size-nm);
@@ -192,7 +192,7 @@ svg {
:deep(.apexcharts-yaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
box-shadow: var(--shadow-floating) !important;
font-size: var(--font-size-nm) !important;
}

View File

@@ -1,77 +0,0 @@
<template>
<Checkbox
class="filter"
:model-value="activeFilters.includes(facetName)"
:description="displayName"
@update:model-value="toggle()"
>
<div class="filter-text">
<div v-if="icon" aria-hidden="true" class="icon" v-html="icon" />
<div v-else class="icon">
<slot />
</div>
<span aria-hidden="true"> {{ displayName }}</span>
</div>
</Checkbox>
</template>
<script>
import Checkbox from '~/components/ui/Checkbox.vue'
export default {
components: {
Checkbox,
},
props: {
facetName: {
type: String,
default: '',
},
displayName: {
type: String,
default: '',
},
icon: {
type: String,
default: '',
},
activeFilters: {
type: Array,
default() {
return []
},
},
},
emits: ['toggle'],
methods: {
toggle() {
this.$emit('toggle', this.facetName)
},
},
}
</script>
<style lang="scss" scoped>
.filter {
margin-bottom: 0.5rem;
:deep(.filter-text) {
display: flex;
align-items: center;
.icon {
height: 1rem;
svg {
margin-right: 0.25rem;
width: 1rem;
height: 1rem;
}
}
}
span {
user-select: none;
}
}
</style>

View File

@@ -245,13 +245,20 @@ import {
LockOpenIcon,
XIcon,
} from '@modrinth/assets'
import { Admonition, Avatar, ButtonStyled, Combobox, CopyCode, NewModal } from '@modrinth/ui'
import {
Admonition,
Avatar,
ButtonStyled,
Checkbox,
Combobox,
CopyCode,
NewModal,
} from '@modrinth/ui'
import TagItem from '@modrinth/ui/src/components/base/TagItem.vue'
import { formatCategory, formatVersionsForDisplay, type Mod, type Version } from '@modrinth/utils'
import { computed, ref } from 'vue'
import Accordion from '~/components/ui/Accordion.vue'
import Checkbox from '~/components/ui/Checkbox.vue'
import ContentVersionFilter, {
type ListedGameVersion,
type ListedPlatform,

View File

@@ -36,7 +36,7 @@
v-for="(option, index) in filteredOptions"
:key="isDivider(option) ? `divider-${index}` : option.id"
>
<div v-if="isDivider(option)" class="h-px w-full bg-button-bg"></div>
<div v-if="isDivider(option)" class="h-px w-full bg-surface-5"></div>
<ButtonStyled v-else type="transparent" role="menuitem" :color="option.color">
<button
v-if="typeof option.action === 'function'"
@@ -288,19 +288,10 @@ const handleMouseOver = (index: number) => {
// Scrolling is disabled for keyboard navigation
const disableBodyScroll = () => {
// Make opening not shift page when there's a vertical scrollbar
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth
if (scrollBarWidth > 0) {
document.body.style.paddingRight = `${scrollBarWidth}px`
} else {
document.body.style.paddingRight = ''
}
document.body.style.overflow = 'hidden'
}
const enableBodyScroll = () => {
document.body.style.paddingRight = ''
document.body.style.overflow = ''
}

View File

@@ -261,9 +261,14 @@ import {
SendIcon,
XIcon,
} from '@modrinth/assets'
import { CopyCode, injectNotificationManager, MarkdownEditor, OverflowMenu } from '@modrinth/ui'
import {
Checkbox,
CopyCode,
injectNotificationManager,
MarkdownEditor,
OverflowMenu,
} from '@modrinth/ui'
import Checkbox from '~/components/ui/Checkbox.vue'
import Modal from '~/components/ui/Modal.vue'
import ThreadMessage from '~/components/ui/thread/ThreadMessage.vue'
import { useImageUpload } from '~/composables/image-upload.ts'

View File

@@ -220,8 +220,15 @@
class="experimental-styles-within desktop-only relative z-[5] mx-auto grid max-w-[1280px] grid-cols-[1fr_auto] items-center gap-2 px-6 py-4 lg:grid-cols-[auto_1fr_auto]"
>
<div>
<NuxtLink to="/" :aria-label="formatMessage(messages.modrinthHomePage)">
<TextLogo aria-hidden="true" class="h-7 w-auto text-contrast" />
<NuxtLink
to="/"
:aria-label="formatMessage(messages.modrinthHomePage)"
class="group hover:brightness-[--hover-brightness] focus-visible:brightness-[--hover-brightness]"
>
<TextLogo
aria-hidden="true"
class="h-7 w-auto text-contrast transition-transform group-active:scale-[0.98]"
/>
</NuxtLink>
</div>
<div
@@ -369,7 +376,7 @@
formatMessage(navMenuMessages.discoverContent)
}}</span>
<span class="contents md:hidden">{{ formatMessage(navMenuMessages.discover) }}</span>
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
<DropdownIcon aria-hidden="true" class="h-5 w-5" />
<template #mods>
<BoxIcon aria-hidden="true" />

View File

@@ -547,14 +547,8 @@
</div>
</template>
</Tooltip>
<ClientOnly>
<ButtonStyled
size="large"
circular
:color="following ? 'red' : 'standard'"
color-fill="none"
hover-color-fill="background"
>
<ButtonStyled size="large" circular>
<ClientOnly>
<button
v-if="auth.user"
v-tooltip="
@@ -579,94 +573,74 @@
>
<HeartIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<ButtonStyled size="large" circular>
<PopoutMenu
v-if="auth.user"
:tooltip="
collections.some((x) => x.projects.includes(project.id))
? formatMessage(commonMessages.savedLabel)
: formatMessage(commonMessages.saveButton)
"
from="top-right"
:aria-label="formatMessage(commonMessages.saveButton)"
:dropdown-id="`${baseId}-save`"
>
<BookmarkIcon
aria-hidden="true"
:fill="
collections.some((x) => x.projects.includes(project.id))
? 'currentColor'
: 'none'
"
/>
<template #menu>
<input
v-model="displayCollectionsSearch"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
class="search-input menu-search"
/>
<div v-if="collections.length > 0" class="collections-list text-primary">
<Checkbox
v-for="option in collections
.slice()
.sort((a, b) => a.name.localeCompare(b.name))"
:key="option.id"
:model-value="option.projects.includes(project.id)"
class="popout-checkbox"
@update:model-value="() => onUserCollectProject(option, project.id)"
>
{{ option.name }}
</Checkbox>
</div>
<div v-else class="menu-text">
<p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p>
</div>
<button
class="btn collection-button"
@click="(event) => $refs.modal_collection.show(event)"
>
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNewCollection) }}
</button>
</template>
</PopoutMenu>
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
<BookmarkIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<template #fallback>
<ButtonStyled size="large" circular>
<button
v-if="auth.user"
:v-tooltip="formatMessage(commonMessages.followButton)"
:aria-label="formatMessage(commonMessages.followButton)"
@click="userFollowProject(project)"
>
<HeartIcon aria-hidden="true" />
</button>
<template #fallback>
<nuxt-link
v-else
v-tooltip="formatMessage(commonMessages.followButton)"
to="/auth/sign-in"
:aria-label="formatMessage(commonMessages.followButton)"
>
<HeartIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<ButtonStyled size="large" circular>
<nuxt-link
v-tooltip="formatMessage(commonMessages.saveButton)"
to="/auth/sign-in"
:aria-label="formatMessage(commonMessages.saveButton)"
</template>
</ClientOnly>
</ButtonStyled>
<ButtonStyled size="large" circular>
<PopoutMenu
v-if="auth.user"
:tooltip="
collections.some((x) => x.projects.includes(project.id))
? formatMessage(commonMessages.savedLabel)
: formatMessage(commonMessages.saveButton)
"
from="top-right"
:aria-label="formatMessage(commonMessages.saveButton)"
:dropdown-id="`${baseId}-save`"
>
<BookmarkIcon
aria-hidden="true"
:fill="
collections.some((x) => x.projects.includes(project.id))
? 'currentColor'
: 'none'
"
/>
<template #menu>
<input
v-model="displayCollectionsSearch"
type="text"
:placeholder="formatMessage(commonMessages.searchPlaceholder)"
class="search-input menu-search"
/>
<div v-if="collections.length > 0" class="collections-list text-primary">
<Checkbox
v-for="option in collections
.slice()
.sort((a, b) => a.name.localeCompare(b.name))"
:key="option.id"
:model-value="option.projects.includes(project.id)"
class="popout-checkbox"
@update:model-value="() => onUserCollectProject(option, project.id)"
>
{{ option.name }}
</Checkbox>
</div>
<div v-else class="menu-text">
<p class="popout-text">{{ formatMessage(messages.noCollectionsFound) }}</p>
</div>
<button
class="btn collection-button"
@click="(event) => $refs.modal_collection.show(event)"
>
<BookmarkIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
</template>
</ClientOnly>
<PlusIcon aria-hidden="true" />
{{ formatMessage(messages.createNewCollection) }}
</button>
</template>
</PopoutMenu>
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
<BookmarkIcon aria-hidden="true" />
</nuxt-link>
</ButtonStyled>
<ButtonStyled v-if="auth.user && currentMember" size="large" circular>
<nuxt-link
v-tooltip="formatMessage(commonMessages.settingsLabel)"
@@ -1672,10 +1646,12 @@ const projectTypeDisplay = computed(() =>
),
)
const following = computed(
() =>
user.value && user.value.follows && user.value.follows.find((x) => x.id === project.value.id),
)
const following = computed(() => {
if (!user.value?.follows) {
return false
}
return !!user.value.follows.find((x) => x.id === project.value.id)
})
const title = computed(() => `${project.value.title} - Minecraft ${projectTypeDisplay.value}`)
const description = computed(

View File

@@ -36,7 +36,7 @@
</p>
<template v-else>
<template v-for="header in Object.keys(categoryLists)" :key="`categories-${header}`">
<div class="label">
<div class="label mb-3">
<h4>
<span class="label__title">{{ formatCategoryHeader(header) }}</span>
</h4>
@@ -134,6 +134,7 @@
<script setup lang="ts">
import { SaveIcon, StarIcon, TriangleAlertIcon } from '@modrinth/assets'
import { Checkbox } from '@modrinth/ui'
import {
formatCategory,
formatCategoryHeader,
@@ -143,8 +144,6 @@ import {
} from '@modrinth/utils'
import { computed, ref } from 'vue'
import Checkbox from '~/components/ui/Checkbox.vue'
interface Category {
name: string
header: string
@@ -337,7 +336,7 @@ const saveChanges = () => {
margin-bottom: var(--spacing-card-md);
:deep(.category-selector) {
margin-bottom: 0.5rem;
margin-bottom: 0.75rem;
.category-selector__label {
display: flex;

View File

@@ -1,7 +1,7 @@
<template>
<div>
<Modal ref="editLinksModal" header="Edit links">
<div class="universal-modal links-modal">
<NewModal ref="editLinksModal" header="Edit links">
<div class="universal-modal links-modal !p-0">
<p>
Any links you specify below will be overwritten on each of the selected projects. Any you
leave blank will be ignored. You can clear a link from all selected projects using the
@@ -139,10 +139,8 @@
<Checkbox
v-if="selectedProjects.length > 3"
v-model="editLinks.showAffected"
:label="editLinks.showAffected ? 'Less' : 'More'"
description="Show all loaders"
:border="false"
:collapsing-toggle-style="true"
label="Show all projects"
description="Show all projects"
/>
<div class="push-right input-group">
<button class="iconified-button" @click="$refs.editLinksModal.hide()">
@@ -155,7 +153,7 @@
</button>
</div>
</div>
</Modal>
</NewModal>
<ModalCreation ref="modal_creation" />
<section class="universal-card">
<div class="header__row">
@@ -335,13 +333,13 @@ import {
commonMessages,
CopyCode,
injectNotificationManager,
NewModal,
ProjectStatusBadge,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { Multiselect } from 'vue-multiselect'
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
import Modal from '~/components/ui/Modal.vue'
import { getProjectTypeForUrl } from '~/helpers/projects.js'
useHead({ title: 'Projects - Modrinth' })

View File

@@ -40,7 +40,7 @@
<div class="flex flex-col">
<div
class="flex flex-row justify-between border-0 !border-b-[2px] border-solid border-button-bg p-1.5 md:p-2"
class="flex flex-row justify-between border-0 !border-b-[2px] border-solid border-divider p-1.5 md:p-2"
>
<span class="my-auto flex flex-row items-center gap-2 text-sm leading-none md:text-base"
><span
@@ -58,7 +58,7 @@
<div
v-for="(date, i) in dateSegments"
:key="date.date"
class="flex flex-row justify-between border-0 !border-b-[2px] border-solid border-button-bg p-1.5 md:p-2"
class="flex flex-row justify-between border-0 !border-b-[2px] border-solid border-divider p-1.5 md:p-2"
>
<span class="my-auto flex flex-row items-center gap-2 text-sm leading-none md:text-base">
<span
@@ -123,7 +123,7 @@
}}</span>
<div class="grid grid-cols-1 gap-x-4 gap-y-2 md:grid-cols-2 lg:grid-cols-3">
<button
class="relative flex flex-col overflow-hidden rounded-2xl bg-gradient-to-r from-green to-green-700 p-4 text-inverted shadow-md transition-all duration-200 hover:brightness-105 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:brightness-100 md:p-5"
class="relative flex flex-col overflow-hidden rounded-2xl bg-gradient-to-r from-green to-green-700 p-4 text-inverted shadow-md transition-all duration-200 hover:brightness-105 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:brightness-100 md:p-5"
:disabled="hasTinMismatch"
@click="openWithdrawModal"
>

View File

@@ -1,7 +1,7 @@
<template>
<div class="normal-page__content">
<Modal ref="editLinksModal" header="Edit links">
<div class="universal-modal links-modal">
<div>
<NewModal ref="editLinksModal" header="Edit links">
<div class="universal-modal links-modal !p-0">
<p>
Any links you specify below will be overwritten on each of the selected projects. Any you
leave blank will be ignored. You can clear a link from all selected projects using the
@@ -25,16 +25,15 @@
"
maxlength="2048"
/>
<Button
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.issues.clear"
icon-only
@click="editLinks.issues.clear = !editLinks.issues.clear"
>
<TrashIcon />
</Button>
</button>
</div>
<label
for="source-code-input"
@@ -53,15 +52,15 @@
editLinks.source.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
/>
<Button
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.source.clear"
icon-only
@click="editLinks.source.clear = !editLinks.source.clear"
>
<TrashIcon />
</Button>
</button>
</div>
<label
for="wiki-page-input"
@@ -80,15 +79,15 @@
editLinks.wiki.clear ? 'Existing link will be cleared' : 'Enter a valid URL'
"
/>
<Button
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.wiki.clear"
icon-only
@click="editLinks.wiki.clear = !editLinks.wiki.clear"
>
<TrashIcon />
</Button>
</button>
</div>
<label for="discord-invite-input" title="An invitation link to your Discord server.">
<span class="label__title">Discord invite</span>
@@ -106,15 +105,15 @@
: 'Enter a valid Discord invite URL'
"
/>
<Button
<button
v-tooltip="'Clear link'"
aria-label="Clear link"
class="square-button label-button"
:data-active="editLinks.discord.clear"
icon-only
@click="editLinks.discord.clear = !editLinks.discord.clear"
>
<TrashIcon />
</Button>
</button>
</div>
</section>
<p>
@@ -140,35 +139,35 @@
<Checkbox
v-if="selectedProjects.length > 3"
v-model="editLinks.showAffected"
:label="editLinks.showAffected ? 'Less' : 'More'"
description="Show all loaders"
:border="false"
:collapsing-toggle-style="true"
label="Show all projects"
description="Show all projects"
/>
<div class="push-right input-group">
<Button @click="$refs.editLinksModal.hide()">
<button class="iconified-button" @click="$refs.editLinksModal.hide()">
<XIcon />
Cancel
</Button>
<Button color="primary" @click="onBulkEditLinks">
</button>
<button class="iconified-button brand-button" @click="onBulkEditLinks">
<SaveIcon />
Save changes
</Button>
</button>
</div>
</div>
</Modal>
</NewModal>
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
<div class="universal-card">
<h2>Projects</h2>
<div class="input-group">
<Button color="primary" @click="$refs.modal_creation.show()">
<PlusIcon />
{{ formatMessage(commonMessages.createAProjectButton) }}
</Button>
<OrganizationProjectTransferModal
:projects="usersOwnedProjects || []"
@submit="onProjectTransferSubmit"
/>
<section class="universal-card">
<div class="header__row">
<h2 class="header__title text-2xl">Projects</h2>
<div class="input-group">
<button class="iconified-button brand-button" @click="$refs.modal_creation.show()">
<PlusIcon />
{{ formatMessage(commonMessages.createAProjectButton) }}
</button>
<OrganizationProjectTransferModal
:projects="usersOwnedProjects || []"
@submit="onProjectTransferSubmit"
/>
</div>
</div>
<p v-if="sortedProjects.length < 1">
You don't have any projects yet. Click the green button above to begin.
@@ -176,10 +175,14 @@
<template v-else>
<p>You can edit multiple projects at once by selecting them below.</p>
<div class="input-group">
<Button :disabled="selectedProjects.length === 0" @click="$refs.editLinksModal.show()">
<button
class="iconified-button"
:disabled="selectedProjects.length === 0"
@click="$refs.editLinksModal.show()"
>
<EditIcon />
Edit links
</Button>
</button>
<div class="push-right">
<div class="labeled-control-row">
Sort by
@@ -195,21 +198,20 @@
sortedProjects = updateSort(sortedProjects, sortBy, descending)
"
/>
<Button
<button
v-tooltip="descending ? 'Descending' : 'Ascending'"
class="square-button"
icon-only
@click="updateDescending()"
>
<SortDescIcon v-if="descending" />
<SortAscIcon v-else />
</Button>
</button>
</div>
</div>
</div>
<div class="table">
<div class="table-head table-row">
<div class="check-cell table-cell">
<div class="grid-table">
<div class="grid-table__row grid-table__header">
<div>
<Checkbox
:model-value="selectedProjects === sortedProjects"
@update:model-value="
@@ -219,15 +221,19 @@
"
/>
</div>
<div class="table-cell">Icon</div>
<div class="table-cell">Name</div>
<div class="table-cell">ID</div>
<div class="table-cell">Type</div>
<div class="table-cell">Status</div>
<div class="table-cell" />
<div>Icon</div>
<div>Name</div>
<div>ID</div>
<div>Type</div>
<div>Status</div>
<div />
</div>
<div v-for="project in sortedProjects" :key="`project-${project.id}`" class="table-row">
<div class="check-cell table-cell">
<div
v-for="project in sortedProjects"
:key="`project-${project.id}`"
class="grid-table__row"
>
<div>
<Checkbox
:disabled="(project.permissions & EDIT_DETAILS) === EDIT_DETAILS"
:model-value="selectedProjects.includes(project)"
@@ -238,8 +244,13 @@
"
/>
</div>
<div class="table-cell">
<nuxt-link tabindex="-1" :to="`/project/${project.slug ? project.slug : project.id}`">
<div>
<nuxt-link
tabindex="-1"
:to="`/${getProjectTypeForUrl(project.project_types[0] ?? 'project', project.loaders)}/${
project.slug ? project.slug : project.id
}`"
>
<Avatar
:src="project.icon_url"
aria-hidden="true"
@@ -249,7 +260,7 @@
</nuxt-link>
</div>
<div class="table-cell">
<div>
<span class="project-title">
<IssuesIcon
v-if="project.moderator_message"
@@ -258,48 +269,52 @@
<nuxt-link
class="hover-link wrap-as-needed"
:to="`/project/${project.slug ? project.slug : project.id}`"
:to="`/${getProjectTypeForUrl(project.project_types[0] ?? 'project', project.loaders)}/${
project.slug ? project.slug : project.id
}`"
>
{{ project.name }}
</nuxt-link>
</span>
</div>
<div class="table-cell">
<div>
<CopyCode :text="project.id" />
</div>
<div class="table-cell">
<BoxIcon />
<span>{{
<div>
{{
formatProjectType(
$getProjectTypeForDisplay(project.project_types[0] ?? 'project', project.loaders),
getProjectTypeForUrl(project.project_types[0] ?? 'project', project.loaders),
)
}}</span>
}}
</div>
<div class="table-cell">
<Badge v-if="project.status" :type="project.status" class="status" />
<div>
<ProjectStatusBadge v-if="project.status" :status="project.status" />
</div>
<div class="table-cell">
<nuxt-link
class="btn icon-only"
:to="`/project/${project.slug ? project.slug : project.id}/settings`"
>
<SettingsIcon />
</nuxt-link>
<div class="flex !flex-row items-center !justify-end gap-2">
<ButtonStyled circular>
<nuxt-link
v-tooltip="formatMessage(commonMessages.settingsLabel)"
:to="`/${getProjectTypeForUrl(project.project_types[0] ?? 'project', project.loaders)}/${
project.slug ? project.slug : project.id
}/settings`"
>
<SettingsIcon />
</nuxt-link>
</ButtonStyled>
</div>
</div>
</div>
</template>
</div>
</section>
</div>
</template>
<script setup>
import {
BoxIcon,
EditIcon,
IssuesIcon,
PlusIcon,
@@ -312,19 +327,20 @@ import {
} from '@modrinth/assets'
import {
Avatar,
Badge,
Button,
ButtonStyled,
Checkbox,
commonMessages,
CopyCode,
injectNotificationManager,
Modal,
NewModal,
ProjectStatusBadge,
} from '@modrinth/ui'
import { formatProjectType } from '@modrinth/utils'
import { Multiselect } from 'vue-multiselect'
import ModalCreation from '~/components/ui/create/ProjectCreateModal.vue'
import OrganizationProjectTransferModal from '~/components/ui/OrganizationProjectTransferModal.vue'
import { getProjectTypeForUrl } from '~/helpers/projects.js'
import { injectOrganizationContext } from '~/providers/organization-context.ts'
const { addNotification } = injectNotificationManager()
@@ -423,10 +439,12 @@ const updateSort = (inputProjects, sort, descending) => {
break
case 'Type':
sortedArray = inputProjects.slice().sort((a, b) => {
if (a.project_type < b.project_type) {
const aType = a.project_types?.[0] ?? 'project'
const bType = b.project_types?.[0] ?? 'project'
if (aType < bType) {
return -1
}
if (a.project_type > b.project_type) {
if (aType > bType) {
return 1
}
return 0
@@ -456,109 +474,107 @@ watch(
},
)
const emptyLinksData = {
const editLinks = reactive({
showAffected: false,
source: {
val: '',
clear: false,
},
discord: {
val: '',
clear: false,
},
wiki: {
val: '',
clear: false,
},
issues: {
val: '',
clear: false,
},
}
const editLinks = ref(emptyLinksData)
source: { val: '', clear: false },
discord: { val: '', clear: false },
wiki: { val: '', clear: false },
issues: { val: '', clear: false },
})
const updateDescending = () => {
descending.value = !descending.value
sortedProjects.value = updateSort(sortedProjects.value, sortBy.value, descending.value)
}
const onBulkEditLinks = useClientTry(async () => {
const linkData = editLinks.value
const onBulkEditLinks = async () => {
try {
const baseData = {
issues_url: editLinks.value.issues.clear ? null : editLinks.value.issues.val.trim(),
source_url: editLinks.value.source.clear ? null : editLinks.value.source.val.trim(),
wiki_url: editLinks.value.wiki.clear ? null : editLinks.value.wiki.val.trim(),
discord_url: editLinks.value.discord.clear ? null : editLinks.value.discord.val.trim(),
}
const filteredData = Object.fromEntries(Object.entries(baseData).filter(([, v]) => v !== ''))
const baseData = {}
await useBaseFetch(`projects?ids=${JSON.stringify(selectedProjects.value.map((x) => x.id))}`, {
method: 'PATCH',
body: JSON.stringify(filteredData),
})
if (linkData.issues.clear) {
baseData.issues_url = null
} else if (linkData.issues.val.trim().length > 0) {
baseData.issues_url = linkData.issues.val.trim()
editLinksModal.value?.hide()
addNotification({
title: 'Success',
text: "Bulk edited selected project's links.",
type: 'success',
})
selectedProjects.value = []
editLinks.value.issues.val = ''
editLinks.value.source.val = ''
editLinks.value.wiki.val = ''
editLinks.value.discord.val = ''
editLinks.value.issues.clear = false
editLinks.value.source.clear = false
editLinks.value.wiki.clear = false
editLinks.value.discord.clear = false
} catch (e) {
addNotification({
title: 'An error occurred',
text: e?.data?.description || e?.message || e || 'Unknown error',
type: 'error',
})
console.error(e)
}
if (linkData.source.clear) {
baseData.source_url = null
} else if (linkData.source.val.trim().length > 0) {
baseData.source_url = linkData.source.val.trim()
}
if (linkData.wiki.clear) {
baseData.wiki_url = null
} else if (linkData.wiki.val.trim().length > 0) {
baseData.wiki_url = linkData.wiki.val.trim()
}
if (linkData.discord.clear) {
baseData.discord_url = null
} else if (linkData.discord.val.trim().length > 0) {
baseData.discord_url = linkData.discord.val.trim()
}
await useBaseFetch(`projects?ids=${JSON.stringify(selectedProjects.value.map((x) => x.id))}`, {
method: 'PATCH',
body: JSON.stringify(baseData),
})
editLinksModal.value.hide()
addNotification({
title: 'Success',
text: "Bulk edited selected project's links.",
type: 'success',
})
selectedProjects.value = []
editLinks.value = emptyLinksData
})
}
</script>
<style lang="scss" scoped>
.table {
.grid-table {
display: grid;
border-radius: var(--radius-md);
grid-template-columns:
min-content min-content minmax(min-content, 2fr)
minmax(min-content, 1fr) minmax(min-content, 1fr) minmax(min-content, 1fr) min-content;
border-radius: var(--size-rounded-sm);
overflow: hidden;
margin-top: var(--gap-md);
border: 1px solid var(--color-button-bg);
background-color: var(--color-raised-bg);
margin-top: var(--spacing-card-md);
outline: 1px solid transparent;
.table-row {
grid-template-columns: 2.75rem 3.75rem 2fr 1fr 1fr 1fr 3.5rem;
}
.grid-table__row {
display: contents;
.table-cell {
display: flex;
align-items: center;
gap: var(--gap-xs);
padding: var(--gap-md);
padding-left: 0;
}
> div {
display: flex;
flex-direction: column;
justify-content: center;
padding: var(--spacing-card-sm);
.check-cell {
padding-left: var(--gap-md);
&:first-child {
padding-left: var(--spacing-card-bg);
}
&:last-child {
padding-right: var(--spacing-card-bg);
}
}
&:nth-child(2n + 1) > div {
background-color: var(--color-table-alternate-row);
}
&.grid-table__header > div {
background-color: var(--color-bg);
font-weight: bold;
color: var(--color-text-dark);
padding-top: var(--spacing-card-bg);
padding-bottom: var(--spacing-card-bg);
}
}
@media screen and (max-width: 750px) {
display: flex;
flex-direction: column;
.table-row {
.grid-table__row {
display: grid;
grid-template: 'checkbox icon name type settings' 'checkbox icon id status settings';
grid-template-columns:
@@ -596,7 +612,7 @@ const onBulkEditLinks = useClientTry(async () => {
}
}
.table-head {
.grid-table__header {
grid-template: 'checkbox settings';
grid-template-columns: min-content minmax(min-content, 1fr);
@@ -611,7 +627,7 @@ const onBulkEditLinks = useClientTry(async () => {
}
@media screen and (max-width: 560px) {
.table-row {
.grid-table__row {
display: grid;
grid-template: 'checkbox icon name settings' 'checkbox icon id settings' 'checkbox icon type settings' 'checkbox icon status settings';
grid-template-columns: min-content min-content minmax(min-content, 1fr) min-content;
@@ -621,7 +637,7 @@ const onBulkEditLinks = useClientTry(async () => {
}
}
.table-head {
.grid-table__header {
grid-template: 'checkbox settings';
grid-template-columns: min-content minmax(min-content, 1fr);
}
@@ -652,13 +668,13 @@ const onBulkEditLinks = useClientTry(async () => {
flex-direction: row;
min-width: 0;
align-items: center;
gap: var(--gap-sm);
gap: var(--spacing-card-md);
white-space: nowrap;
}
.small-select {
width: fit-content;
width: -moz-fit-content;
width: fit-content;
}
.label-button[data-active='true'] {
@@ -688,16 +704,4 @@ const onBulkEditLinks = useClientTry(async () => {
margin: 0 0 var(--spacing-card-sm) 0;
}
}
h1 {
margin-block: var(--gap-sm) var(--gap-lg);
font-size: 2em;
line-height: 1em;
}
:deep(.checkbox-outer) {
button.checkbox {
border: none;
}
}
</style>

View File

@@ -107,7 +107,7 @@
</div>
</div>
</div>
<div class="flex flex-col gap-4 rounded-xl bg-bg-raised p-6">
<div class="card-shadow flex flex-col gap-4 rounded-xl bg-bg-raised p-6">
<template v-if="!prefilled || !currentItemValid">
<div class="flex flex-col gap-2">
<span class="text-lg font-bold text-contrast">

View File

@@ -88,7 +88,10 @@
</button>
</ButtonStyled>
</div>
<div v-if="server && projectType.id === 'modpack'" class="rounded-2xl bg-bg-raised">
<div
v-if="server && projectType.id === 'modpack'"
class="card-shadow rounded-2xl bg-bg-raised"
>
<div class="flex flex-row items-center gap-2 px-6 py-4 text-contrast">
<h3 class="m-0 text-lg">Options</h3>
</div>
@@ -107,7 +110,10 @@
the selected modpack.
</div>
</div>
<div v-if="server && projectType.id !== 'modpack'" class="rounded-2xl bg-bg-raised p-4">
<div
v-if="server && projectType.id !== 'modpack'"
class="card-shadow rounded-2xl bg-bg-raised p-4"
>
<Checkbox
v-model="serverHideInstalled"
label="Hide installed content"
@@ -126,7 +132,7 @@
:class="
filtersMenuOpen
? 'border-0 border-b-[1px] border-solid border-divider last:border-b-0'
: 'rounded-2xl bg-bg-raised'
: 'card-shadow rounded-2xl bg-bg-raised'
"
button-class="button-animation flex flex-col gap-1 px-6 py-4 w-full bg-transparent cursor-pointer border-none"
content-class="mb-4 mx-3"

View File

@@ -825,7 +825,7 @@ a,
.v-popper--theme-dropdown,
.v-popper--theme-dropdown.v-popper--theme-ribbit-popout {
.v-popper__inner {
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
padding: var(--gap-sm) !important;
width: fit-content !important;
border-radius: var(--radius-md) !important;
@@ -834,7 +834,7 @@ a,
}
.v-popper__arrow-outer {
border-color: var(--color-button-bg) !important;
border-color: var(--color-divider) !important;
}
.v-popper__arrow-inner {

View File

@@ -76,7 +76,7 @@ pre {
padding: 1em 1em 1em 1em;
border-width: 5px;
border-radius: 2em;
border-color: var(--color-button-bg);
border-color: var(--color-divider);
overflow-x: hidden;
code {

View File

@@ -3,7 +3,7 @@
--surface-2: #f5f5f5;
--surface-3: #f8f8f8;
--surface-4: #ffffff;
--surface-5: #e6e6e6;
--surface-5: #dddddd;
--color-red-50: #fef2f2;
--color-red-100: #fee5e7;
@@ -97,8 +97,8 @@
--color-button-border: rgba(161, 161, 161, 0.35);
--color-scrollbar: #96a2b0;
--color-divider: var(--surface-2);
--color-divider-dark: #c8cdd3;
--color-divider: var(--surface-5);
--color-divider-dark: var(--surface-5);
--color-base: var(--color-text-default);
--color-secondary: var(--color-text-tertiary);
@@ -207,6 +207,8 @@ html {
--color-link: var(--color-blue) !important;
--color-link-hover: var(--color-blue) !important; // DEPRECATED, use filters in future
--color-link-active: var(--color-blue) !important; // DEPRECATED, use filters in future
--shadow-button: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px 0 rgba(0, 0, 0, 0.15);
}
.light-mode,

View File

@@ -164,23 +164,31 @@ function setColorFill(
}
const colorVariables = computed(() => {
const defaultShadow =
props.type === 'standard' || props.type === 'highlight' || props.highlighted
? 'var(--shadow-button)'
: 'none'
if (props.highlighted) {
const colors = {
bg:
props.highlightedStyle === 'main-nav-primary'
? 'var(--color-brand-highlight)'
? 'var(--color-button-bg-selected)'
: 'var(--color-button-bg)',
text: 'var(--color-contrast)',
text:
props.highlightedStyle === 'main-nav-primary'
? 'var(--color-button-text-selected)'
: 'var(--color-contrast)',
icon:
props.type === 'chip'
? 'var(--color-contrast)'
: props.highlightedStyle === 'main-nav-primary'
? 'var(--color-brand)'
? 'var(--color-button-text-selected)'
: 'var(--color-contrast)',
}
const hoverColors = JSON.parse(JSON.stringify(colors))
const boxShadow =
props.type === 'chip' && colorVar.value ? `0 0 0 2px ${colorVar.value}` : 'none'
props.type === 'chip' && colorVar.value ? `0 0 0 2px ${colorVar.value}` : defaultShadow
return `--_bg: ${colors.bg}; --_text: ${colors.text}; --_icon: ${colors.icon}; --_hover-bg: ${hoverColors.bg}; --_hover-text: ${hoverColors.text}; --_hover-icon: ${hoverColors.icon}; --_box-shadow: ${boxShadow};`
}
@@ -217,7 +225,8 @@ const colorVariables = computed(() => {
)
}
const boxShadow = props.type === 'chip' && colorVar.value ? `0 0 0 2px ${colorVar.value}` : 'none'
const boxShadow =
props.type === 'chip' && colorVar.value ? `0 0 0 2px ${colorVar.value}` : defaultShadow
return `--_bg: ${colors.bg}; --_text: ${colors.text}; --_hover-bg: ${hoverColors.bg}; --_hover-text: ${hoverColors.text}; --_box-shadow: ${boxShadow};`
})

View File

@@ -1,31 +1,38 @@
<template>
<div
class="checkbox-outer button-within"
:class="{ disabled }"
role="presentation"
<button
class="group bg-transparent border-none p-0 m-0 flex items-center gap-3 checkbox-outer outline-offset-4 text-contrast"
:disabled="disabled"
:class="
disabled
? 'cursor-not-allowed opacity-50'
: 'cursor-pointer hover:brightness-[--hover-brightness] focus-visible:brightness-[--hover-brightness]'
"
:aria-label="description"
:aria-checked="modelValue"
role="checkbox"
@click="toggle"
>
<button
class="checkbox border-none"
role="checkbox"
:disabled="disabled"
:class="{ checked: modelValue, collapsing: collapsingToggleStyle }"
:aria-label="description"
:aria-checked="modelValue"
<span
class="w-5 h-5 rounded-md flex items-center justify-center border-[1px] border-solid"
:class="
(modelValue
? 'bg-brand border-button-border text-brand-inverted'
: 'bg-surface-2 border-surface-5') +
(disabled ? '' : ' checkbox-shadow group-active:scale-95')
"
>
<MinusIcon v-if="indeterminate" aria-hidden="true" />
<CheckIcon v-else-if="modelValue && !collapsingToggleStyle" aria-hidden="true" />
<DropdownIcon v-else-if="collapsingToggleStyle" aria-hidden="true" />
</button>
<MinusIcon v-if="indeterminate" aria-hidden="true" stroke-width="3" />
<CheckIcon v-else-if="modelValue" aria-hidden="true" stroke-width="3" />
</span>
<!-- aria-hidden is set so screenreaders only use the <button>'s aria-label -->
<p v-if="label" aria-hidden="true" class="checkbox-label">
<span v-if="label" aria-hidden="true">
{{ label }}
</p>
</span>
<slot v-else />
</div>
</button>
</template>
<script setup lang="ts">
import { CheckIcon, DropdownIcon, MinusIcon } from '@modrinth/assets'
import { CheckIcon, MinusIcon } from '@modrinth/assets'
const emit = defineEmits<{
'update:modelValue': [boolean]
@@ -38,7 +45,6 @@ const props = withDefaults(
description?: string
modelValue: boolean
clickEvent?: () => void
collapsingToggleStyle?: boolean
indeterminate?: boolean
}>(),
{
@@ -47,7 +53,6 @@ const props = withDefaults(
description: '',
modelValue: false,
clickEvent: () => {},
collapsingToggleStyle: false,
indeterminate: false,
},
)
@@ -60,86 +65,7 @@ function toggle() {
</script>
<style lang="scss" scoped>
.checkbox-outer {
display: flex;
align-items: center;
cursor: pointer;
p {
user-select: none;
padding: 0.2rem 0;
margin: 0;
}
&.disabled {
cursor: not-allowed;
}
}
.checkbox {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
min-width: 1rem;
min-height: 1rem;
padding: 0;
margin: 0 0.5rem 0 0;
color: var(--color-contrast);
background-color: var(--color-button-bg);
border-radius: var(--radius-xs);
box-shadow:
var(--shadow-inset-sm),
0 0 0 0 transparent;
&.checked {
background-color: var(--color-brand);
svg {
color: var(--color-accent-contrast);
}
}
svg {
color: var(--color-secondary);
stroke-width: 0.2rem;
height: 0.8rem;
width: 0.8rem;
flex-shrink: 0;
}
&.collapsing {
background-color: transparent !important;
box-shadow: none;
svg {
color: inherit;
height: 1rem;
width: 1rem;
transition: transform 0.25s ease-in-out;
@media (prefers-reduced-motion) {
transition: none !important;
}
}
&.checked {
svg {
transform: rotate(180deg);
}
}
}
&:disabled {
box-shadow: none;
cursor: not-allowed;
}
}
.checkbox-label {
color: var(--color-base);
.checkbox-shadow {
box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, 0.08);
}
</style>

View File

@@ -891,7 +891,7 @@ function openVideoModal() {
}
.markdown-body-wrapper {
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
border-radius: var(--radius-md);
width: 100%;
padding: var(--radius-md);

View File

@@ -14,7 +14,7 @@
<div
v-if="isDivider(option)"
:key="`divider-${index}`"
class="h-px mx-3 my-2 bg-button-bg"
class="h-px mx-3 my-2 bg-surface-5"
></div>
<Button
v-else

View File

@@ -286,7 +286,7 @@ svg {
:deep(.apexcharts-yaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
box-shadow: var(--shadow-floating) !important;
font-size: var(--font-size-nm) !important;
}
@@ -301,7 +301,7 @@ svg {
:deep(.apexcharts-xaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
font-size: var(--font-size-nm) !important;
color: var(--color-base) !important;

View File

@@ -161,10 +161,10 @@ const chartOptions = ref({
display: flex;
flex-direction: column;
gap: var(--gap-xs);
border: 1px solid var(--color-button-bg);
border: 1px solid var(--color-divider);
border-radius: var(--radius-md);
background-color: var(--color-raised-bg);
box-shadow: var(--shadow-floating);
box-shadow: var(--shadow-card);
color: var(--color-base);
font-size: var(--font-size-nm);
width: 100%;
@@ -190,7 +190,7 @@ svg {
:deep(.apexcharts-yaxistooltip) {
background: var(--color-raised-bg) !important;
border-radius: var(--radius-sm) !important;
border: 1px solid var(--color-button-bg) !important;
border: 1px solid var(--color-divider) !important;
box-shadow: var(--shadow-floating) !important;
font-size: var(--font-size-nm) !important;
}

View File

@@ -151,21 +151,10 @@ const visible = ref(false)
const scrollContainer = ref<HTMLElement | null>(null)
const { showTopFade, showBottomFade, checkScrollState } = useScrollIndicator(scrollContainer)
// make modal opening not shift page when there's a vertical scrollbar
function addBodyPadding() {
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth
if (scrollBarWidth > 0) {
document.body.style.paddingRight = `${scrollBarWidth}px`
} else {
document.body.style.paddingRight = ''
}
}
function show(event?: MouseEvent) {
props.onShow?.()
open.value = true
addBodyPadding()
document.body.style.overflow = 'hidden'
window.addEventListener('mousedown', updateMousePosition)
window.addEventListener('keydown', handleKeyDown)
@@ -184,7 +173,6 @@ function hide() {
props.onHide?.()
visible.value = false
document.body.style.overflow = ''
document.body.style.paddingRight = ''
window.removeEventListener('mousedown', updateMousePosition)
window.removeEventListener('keydown', handleKeyDown)
setTimeout(() => {

View File

@@ -50,7 +50,7 @@
<template v-for="(version, index) in currentVersions" :key="index">
<!-- Row divider -->
<div
class="versions-grid-row h-px w-full bg-button-bg"
class="versions-grid-row h-px w-full bg-surface-5"
:class="{
'max-sm:!hidden': index === 0,
}"