fix moderation collapsibles & do a consistency pass (#7330)

* fix moderation collapsibles & do a consistency pass

* extract tech review sources

* improve buttons

* buttn

* improve markdown styling

* fix nested details

* save filters + fix reports reloading

* prepr
This commit is contained in:
Prospector
2026-08-27 18:46:13 +00:00
committed by GitHub
parent 263aaedc8f
commit ffd864a9e2
69 changed files with 3573 additions and 3088 deletions
@@ -0,0 +1,19 @@
<template>
<AutoLink
:to="to"
class="mb-4 flex w-fit items-center gap-2 rounded-lg px-2 py-0.5 pl-0 text-link"
>
<ChevronLeftIcon aria-hidden="true" class="shrink-0" />
<slot />
</AutoLink>
</template>
<script setup lang="ts">
import { ChevronLeftIcon } from '@modrinth/assets'
import AutoLink from './AutoLink.vue'
defineProps<{
to: unknown
}>()
</script>
@@ -2,28 +2,29 @@
<div class="relative overflow-hidden">
<div
class="collapsible-region-content"
:class="{ open: !collapsed }"
:class="{ open: !collapsed || disabled }"
:style="{ '--collapsed-height': collapsedHeight }"
>
<div :class="{ 'pointer-events-none select-none pb-16': collapsed }">
<div :class="{ 'pointer-events-none select-none': collapsed }">
<slot />
</div>
</div>
<div
v-if="collapsed"
class="pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent"
:class="gradientTo"
v-if="!disabled"
class="pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent transition-opacity duration-250"
:class="[gradientTo, { 'opacity-0': !collapsed }]"
/>
<div class="absolute bottom-4 left-1/2 z-20 -translate-x-1/2">
<Button
type="quiet"
class="flex items-center gap-1 text-xs !rounded-full"
@click="collapsed = !collapsed"
>
<ExpandIcon v-if="collapsed" />
<CollapseIcon v-else />
<div v-if="!collapsed && !disabled" class="top-4 right-4 z-20 group absolute">
<Button v-tooltip="collapseText" type="quiet" circular icon-only @click="collapsed = true">
<DropdownIcon class="rotate-180" />
</Button>
</div>
<div v-if="!disabled" class="absolute bottom-4 left-1/2 z-20 -translate-x-1/2">
<Button type="quiet" class="text-xs" @click="collapsed = !collapsed">
<DropdownIcon class="transition-transform" :class="{ 'rotate-180': !collapsed }" />
{{ collapsed ? expandText : collapseText }}
</Button>
</div>
@@ -31,7 +32,7 @@
</template>
<script setup lang="ts">
import { CollapseIcon, ExpandIcon } from '@modrinth/assets'
import { DropdownIcon } from '@modrinth/assets'
import { Button } from '#ui/components/base/buttons'
@@ -41,12 +42,14 @@ withDefaults(
collapseText?: string
collapsedHeight?: string
gradientTo?: string
disabled?: boolean
}>(),
{
expandText: 'Expand',
collapseText: 'Collapse',
collapsedHeight: '8rem',
gradientTo: 'to-surface-2',
disabled: false,
},
)
@@ -56,8 +59,12 @@ const collapsed = defineModel<boolean>('collapsed', { default: true })
<style scoped>
.collapsible-region-content {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s linear;
grid-template-rows: minmax(var(--collapsed-height), 0fr);
transition: grid-template-rows 500ms var(--ease-out-expo);
& > div {
grid-row: 1 / span 2;
}
}
@media (prefers-reduced-motion) {
@@ -67,16 +74,10 @@ const collapsed = defineModel<boolean>('collapsed', { default: true })
}
.collapsible-region-content.open {
grid-template-rows: 1fr;
grid-template-rows: minmax(var(--collapsed-height), 1fr);
}
.collapsible-region-content > div {
overflow: hidden;
min-height: var(--collapsed-height);
transition: min-height 0.3s linear;
}
.collapsible-region-content.open > div {
min-height: 0;
}
</style>
+3 -3
View File
@@ -1,17 +1,17 @@
<template>
<button
class="!m-0 inline-flex w-fit select-text items-center gap-2 rounded-[10px] bg-[var(--color-button-bg)] px-2 py-1 font-mono text-sm text-primary transition-[opacity,filter,transform,outline] duration-200 ease-in-out hover:brightness-[1.25] active:scale-95 active:brightness-[0.8] motion-reduce:transition-none [&>svg]:h-[1em] [&>svg]:w-[1em]"
class="rounded-lg border border-solid border-surface-5 bg-surface-2 text-xs !m-0 inline-flex w-fit select-text items-center gap-2 px-2 py-1 font-mono text-primary transition-[opacity,filter,transform,outline] duration-200 ease-in-out hover:brightness-[1.25] active:scale-95 motion-reduce:transition-none [&>svg]:h-[1em] [&>svg]:w-[1em]"
:title="formatMessage(copiedMessage)"
@click="copyText"
>
<span>{{ displayText ?? text }}</span>
<CheckIcon v-if="copied" />
<ClipboardCopyIcon v-else />
<CopyIcon v-else />
</button>
</template>
<script setup lang="ts">
import { CheckIcon, ClipboardCopyIcon } from '@modrinth/assets'
import { CheckIcon, CopyIcon } from '@modrinth/assets'
import { onBeforeUnmount, ref } from 'vue'
import { defineMessage, useVIntl } from '../../composables/i18n'
@@ -0,0 +1,63 @@
<template>
<IconButton
v-tooltip="label"
:label="label"
:disabled="copied"
class="relative grid place-items-center overflow-hidden"
@click="copyToClipboard"
>
<CheckIcon
class="absolute transition-all ease-in-out"
:class="copied ? 'translate-y-0' : 'translate-y-7'"
/>
<LinkIcon
class="absolute transition-all ease-in-out"
:class="copied ? '-translate-y-7' : 'translate-y-0'"
/>
</IconButton>
</template>
<script setup lang="ts">
import { CheckIcon, LinkIcon } from '@modrinth/assets'
import { computed, onBeforeUnmount, ref } from 'vue'
import { defineMessage, useVIntl } from '../../composables/i18n'
import { commonMessages } from '../../utils/common-messages'
import IconButton from './buttons/IconButton.vue'
const copyLinkMessage = commonMessages.copyLinkButton
const copiedToClipboardMessage = defineMessage({
id: 'button.copied-to-clipboard',
defaultMessage: 'Copied to clipboard',
})
const { formatMessage } = useVIntl()
const props = defineProps<{
url: string
copyLabel?: string
copiedLabel?: string
}>()
const copied = ref(false)
let copiedResetTimeout: ReturnType<typeof setTimeout> | undefined
const label = computed(() => {
if (copied.value) {
return props.copiedLabel ?? formatMessage(copiedToClipboardMessage)
}
return props.copyLabel ?? formatMessage(copyLinkMessage)
})
async function copyToClipboard() {
await navigator.clipboard.writeText(props.url)
copied.value = true
clearTimeout(copiedResetTimeout)
copiedResetTimeout = setTimeout(() => {
copied.value = false
}, 3000)
}
onBeforeUnmount(() => clearTimeout(copiedResetTimeout))
</script>
@@ -67,7 +67,7 @@ defineExpose({ element })
/>
<div
data-anchored-scroll-region
class="flex min-w-48 flex-col p-2 overflow-y-auto"
class="flex flex-col p-2 overflow-y-auto"
:style="{ maxHeight: props.panelStyle.maxHeight }"
>
<slot />
+2
View File
@@ -5,6 +5,7 @@ export { default as AppearingProgressBar } from './AppearingProgressBar.vue'
export { default as AutoBrandIcon } from './AutoBrandIcon.vue'
export { default as AutoLink } from './AutoLink.vue'
export { default as Avatar } from './Avatar.vue'
export { default as BackToParentLink } from './BackToParentLink.vue'
export { default as Badge } from './Badge.vue'
export { default as BaseTerminal } from './BaseTerminal.vue'
export { default as BasicMarkdownText } from './BasicMarkdownText.vue'
@@ -46,6 +47,7 @@ export { default as CollapsibleRegion } from './CollapsibleRegion.vue'
export type { ComboboxOption, ComboboxSearchInputVariant } from './Combobox.vue'
export { default as Combobox } from './Combobox.vue'
export { default as CopyCode } from './CopyCode.vue'
export { default as CopyLinkButton } from './CopyLinkButton.vue'
export { default as DoubleIcon } from './DoubleIcon.vue'
export { default as DropArea } from './DropArea.vue'
export type { DropdownFilterBarCategory, DropdownFilterBarOption } from './DropdownFilterBar.vue'
@@ -43,7 +43,9 @@ async function copyProjectLink() {
</script>
<template>
<div class="bg-surface-3 p-4 rounded-2xl flex flex-col gap-3">
<div
class="bg-surface-3 p-4 rounded-2xl flex flex-col gap-3 border border-solid border-surface-4"
>
<div class="flex gap-4 justify-between">
<div class="flex flex-col gap-2">
<span class="text-contrast font-semibold">{{ title }}</span>
@@ -88,7 +90,9 @@ async function copyProjectLink() {
<div class="font-medium">Notes:</div>
<div>{{ notes ?? 'N/A' }}</div>
</div>
<div class="bg-surface-2 p-4 rounded-2xl flex flex-col gap-3">
<div
class="bg-surface-2 p-4 rounded-2xl flex flex-col gap-3 border border-solid border-surface-4"
>
<span class="text-contrast font-semibold">Files</span>
<span v-if="!(files?.length > 0)" class="text-secondary">
No files available for external project.
+3
View File
@@ -176,6 +176,9 @@
"button.continue": {
"defaultMessage": "Continue"
},
"button.copied-to-clipboard": {
"defaultMessage": "Copied to clipboard"
},
"button.copy-filename": {
"defaultMessage": "Copy filename"
},
@@ -0,0 +1,26 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import BackToParentLink from '../../components/base/BackToParentLink.vue'
const meta = {
title: 'Base/BackToParentLink',
component: BackToParentLink,
render: (args) => ({
components: { BackToParentLink },
setup() {
return { args }
},
template: /*html*/ `
<BackToParentLink v-bind="args">All versions</BackToParentLink>
`,
}),
} satisfies Meta<typeof BackToParentLink>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
to: '/versions',
},
}
@@ -0,0 +1,25 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'
import CopyLinkButton from '../../components/base/CopyLinkButton.vue'
const meta = {
title: 'Base/CopyLinkButton',
component: CopyLinkButton,
} satisfies Meta<typeof CopyLinkButton>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
url: 'https://modrinth.com',
},
}
export const CustomLabels: Story = {
args: {
url: 'https://modrinth.com',
copyLabel: 'Copy project link',
copiedLabel: 'Project link copied',
},
}