mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 10:34:53 +00:00
refactor: introduce PageHeader component (#6629)
* refactor: introduce PageHeader component * feat: split up according to component structure guide * fix: lint * fix: label inconsistencies * feat: refactor PageHeader (again) * refactor: old impls * fix: changes * fix: rescan * fix: prepr * fix: dedupe * fix: rev * fix: lint * fix: lint --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import Avatar from '../../components/base/Avatar.vue'
|
||||
import ButtonStyled from '../../components/base/ButtonStyled.vue'
|
||||
import ContentPageHeader from '../../components/base/ContentPageHeader.vue'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/ContentPageHeader',
|
||||
component: ContentPageHeader,
|
||||
} satisfies Meta<typeof ContentPageHeader>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => ({
|
||||
components: { ContentPageHeader, Avatar, ButtonStyled },
|
||||
template: `
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
<Avatar size="64px" />
|
||||
</template>
|
||||
<template #title>Project Name</template>
|
||||
<template #summary>A brief description of the project goes here.</template>
|
||||
<template #stats>
|
||||
<span>1.2M downloads</span>
|
||||
<span>50K followers</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<ButtonStyled color="brand">
|
||||
<button>Follow</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const WithTitleSuffix: Story = {
|
||||
render: () => ({
|
||||
components: { ContentPageHeader, Avatar, ButtonStyled },
|
||||
template: `
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
<Avatar size="64px" />
|
||||
</template>
|
||||
<template #title>Featured Project</template>
|
||||
<template #title-suffix>
|
||||
<span class="px-2 py-1 bg-brand-highlight text-brand rounded-full text-sm">Featured</span>
|
||||
</template>
|
||||
<template #summary>This project has been featured by the Modrinth team.</template>
|
||||
<template #actions>
|
||||
<ButtonStyled color="brand">
|
||||
<button>Download</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="transparent">
|
||||
<button>Share</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,458 @@
|
||||
import {
|
||||
AffiliateIcon,
|
||||
BoxIcon,
|
||||
CalendarIcon,
|
||||
ClipboardCopyIcon,
|
||||
DownloadIcon,
|
||||
GlobeIcon,
|
||||
HeartIcon,
|
||||
LeftArrowIcon,
|
||||
LinkIcon,
|
||||
MoreVerticalIcon,
|
||||
PlayIcon,
|
||||
SettingsIcon,
|
||||
SlashIcon,
|
||||
StopCircleIcon,
|
||||
TagCategoryGamepad2Icon as Gamepad2Icon,
|
||||
TimerIcon,
|
||||
} from '@modrinth/assets'
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import AutoLink from '../../components/base/AutoLink.vue'
|
||||
import Avatar from '../../components/base/Avatar.vue'
|
||||
import ButtonStyled from '../../components/base/ButtonStyled.vue'
|
||||
import FormattedTag from '../../components/base/FormattedTag.vue'
|
||||
import JoinedButtons from '../../components/base/JoinedButtons.vue'
|
||||
import PageHeader from '../../components/base/page-header/index.vue'
|
||||
import PageHeaderMetadata from '../../components/base/page-header/metadata/index.vue'
|
||||
import PageHeaderMetadataItem from '../../components/base/page-header/metadata/page-header-metadata-item.vue'
|
||||
import PageHeaderMetadataNumberItem from '../../components/base/page-header/metadata/page-header-metadata-number-item.vue'
|
||||
import PageHeaderMetadataTagsItem from '../../components/base/page-header/metadata/page-header-metadata-tags-item.vue'
|
||||
import PageHeaderMetadataTimeItem from '../../components/base/page-header/metadata/page-header-metadata-time-item.vue'
|
||||
import PageHeaderActions from '../../components/base/page-header/page-header-actions.vue'
|
||||
import PageHeaderBadgeItem from '../../components/base/page-header/page-header-badge-item.vue'
|
||||
import TagItem from '../../components/base/TagItem.vue'
|
||||
import TeleportOverflowMenu from '../../components/base/TeleportOverflowMenu.vue'
|
||||
import LoaderIcon from '../../components/servers/icons/LoaderIcon.vue'
|
||||
import ServerIcon from '../../components/servers/icons/ServerIcon.vue'
|
||||
|
||||
const noop = () => undefined
|
||||
const categories = ['adventure', 'magic', 'technology']
|
||||
const joinedDate = new Date(Date.now() - 1000 * 60 * 60 * 24 * 365 * 6)
|
||||
const menuActions = [
|
||||
{
|
||||
id: 'open-folder',
|
||||
label: 'Open folder',
|
||||
icon: GlobeIcon,
|
||||
action: noop,
|
||||
},
|
||||
{
|
||||
id: 'copy-id',
|
||||
label: 'Copy ID',
|
||||
icon: ClipboardCopyIcon,
|
||||
action: noop,
|
||||
},
|
||||
]
|
||||
const joinedActions = [
|
||||
{
|
||||
id: 'stop',
|
||||
label: 'Stop',
|
||||
icon: StopCircleIcon,
|
||||
action: noop,
|
||||
},
|
||||
{
|
||||
id: 'kill_server',
|
||||
label: 'Kill server',
|
||||
icon: SlashIcon,
|
||||
action: noop,
|
||||
},
|
||||
]
|
||||
|
||||
const pageHeaderIcons = {
|
||||
AffiliateIcon,
|
||||
BoxIcon,
|
||||
CalendarIcon,
|
||||
DownloadIcon,
|
||||
Gamepad2Icon,
|
||||
GlobeIcon,
|
||||
HeartIcon,
|
||||
LeftArrowIcon,
|
||||
LinkIcon,
|
||||
MoreVerticalIcon,
|
||||
PlayIcon,
|
||||
SettingsIcon,
|
||||
TimerIcon,
|
||||
}
|
||||
|
||||
const pageHeaderComponents = {
|
||||
AutoLink,
|
||||
Avatar,
|
||||
ButtonStyled,
|
||||
FormattedTag,
|
||||
JoinedButtons,
|
||||
PageHeader,
|
||||
PageHeaderActions,
|
||||
PageHeaderBadgeItem,
|
||||
PageHeaderMetadata,
|
||||
PageHeaderMetadataItem,
|
||||
PageHeaderMetadataNumberItem,
|
||||
PageHeaderMetadataTagsItem,
|
||||
PageHeaderMetadataTimeItem,
|
||||
TagItem,
|
||||
TeleportOverflowMenu,
|
||||
...pageHeaderIcons,
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/PageHeader',
|
||||
component: PageHeader,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
decorators: [
|
||||
(story) => ({
|
||||
components: { story },
|
||||
template: '<div class="w-full"><story /></div>',
|
||||
}),
|
||||
],
|
||||
} satisfies Meta<typeof PageHeader>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const ProjectHeader: Story = {
|
||||
render: () => ({
|
||||
components: pageHeaderComponents,
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
categories,
|
||||
menuActions,
|
||||
noop,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="Fabric API" summary="Lightweight and modular API providing common hooks and intercompatibility measures for Fabric mods.">
|
||||
<template #leading>
|
||||
<Avatar src="" alt="Fabric API" size="96px" tint-by="fabric-api" />
|
||||
</template>
|
||||
|
||||
<template #badges>
|
||||
<PageHeaderBadgeItem :icon="AffiliateIcon">
|
||||
Featured
|
||||
</PageHeaderBadgeItem>
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataNumberItem :icon="DownloadIcon" :value="128452395" label="downloads" />
|
||||
<PageHeaderMetadataNumberItem :icon="HeartIcon" :value="412300" label="followers" />
|
||||
<PageHeaderMetadataTagsItem>
|
||||
<TagItem v-for="category in categories" :key="category" :action="noop">
|
||||
<FormattedTag :tag="category" />
|
||||
</TagItem>
|
||||
</PageHeaderMetadataTagsItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" @click="noop">
|
||||
<DownloadIcon />
|
||||
Download
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large" type="transparent">
|
||||
<TeleportOverflowMenu :options="menuActions" aria-label="More actions">
|
||||
<MoreVerticalIcon />
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const CreatorHeader: Story = {
|
||||
render: () => ({
|
||||
components: pageHeaderComponents,
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
joinedDate,
|
||||
noop,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="Prospector" summary="A Modrinth creator with a handful of popular projects.">
|
||||
<template #leading>
|
||||
<Avatar src="" alt="Prospector" size="96px" tint-by="Prospector" circle />
|
||||
</template>
|
||||
|
||||
<template #badges>
|
||||
<PageHeaderBadgeItem :icon="AffiliateIcon" class="!border-brand-highlight !bg-brand-highlight !text-brand">
|
||||
Affiliate
|
||||
</PageHeaderBadgeItem>
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataNumberItem :icon="BoxIcon" :value="12" label="projects" />
|
||||
<PageHeaderMetadataNumberItem :icon="DownloadIcon" :value="4200000" label="downloads" />
|
||||
<PageHeaderMetadataTimeItem :icon="CalendarIcon" :date="joinedDate" label="Joined" />
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" @click="noop">
|
||||
<HeartIcon />
|
||||
Follow
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const AppInstanceHeader: Story = {
|
||||
render: () => ({
|
||||
components: {
|
||||
...pageHeaderComponents,
|
||||
LoaderIcon,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
LoaderIcon,
|
||||
menuActions,
|
||||
noop,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="Create: Astral">
|
||||
<template #leading>
|
||||
<Avatar src="" alt="Create: Astral" size="64px" tint-by="create-astral" />
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem :icon="Gamepad2Icon" tooltip="Minecraft version">Minecraft 1.20.1</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem :icon="LoaderIcon" :icon-props="{ loader: 'Fabric' }" tooltip="Mod loader">
|
||||
Fabric 0.16.14
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem :icon="TimerIcon" tooltip="Total playtime">12 hours</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" @click="noop">
|
||||
<PlayIcon />
|
||||
Play
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large">
|
||||
<button type="button" aria-label="Instance settings" @click="noop">
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large" type="transparent">
|
||||
<TeleportOverflowMenu :options="menuActions" aria-label="More actions">
|
||||
<MoreVerticalIcon />
|
||||
</TeleportOverflowMenu>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const BrowseHeader: Story = {
|
||||
render: () => ({
|
||||
components: {
|
||||
...pageHeaderComponents,
|
||||
LoaderIcon,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
LoaderIcon,
|
||||
noop,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="Survival SMP" :divider="false" :bottom-padding="false" main-class="items-center" title-class="leading-8" truncate-title>
|
||||
<template #leading>
|
||||
<ButtonStyled circular size="large">
|
||||
<button type="button" aria-label="Back to instance" @click="noop">
|
||||
<LeftArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<Avatar src="" alt="Survival SMP" size="48px" tint-by="survival-smp" />
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem class="!text-primary">Installing content</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem class="!text-primary" :icon="Gamepad2Icon" tooltip="Minecraft version">
|
||||
Minecraft 1.20.1
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem class="!text-primary" :icon="LoaderIcon" :icon-props="{ loader: 'Fabric' }" tooltip="Mod loader">
|
||||
Fabric
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const ServerPanelRootHeader: Story = {
|
||||
render: () => ({
|
||||
components: {
|
||||
...pageHeaderComponents,
|
||||
ServerIcon,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
menuActions,
|
||||
noop,
|
||||
serverImage: undefined,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="Survival SMP">
|
||||
<template #leading>
|
||||
<ServerIcon class="size-16 !rounded-xl" :image="serverImage" />
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem :icon="GlobeIcon" tooltip="Active instance">My World</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem :icon="LinkIcon" tooltip="Copy server address" :action="noop">
|
||||
play.modrinth.gg
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" @click="noop">
|
||||
<PlayIcon />
|
||||
Start server
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled circular size="large">
|
||||
<button type="button" aria-label="Server settings" @click="noop">
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const ServerPanelInstanceHeader: Story = {
|
||||
render: () => ({
|
||||
components: {
|
||||
...pageHeaderComponents,
|
||||
LoaderIcon,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
joinedActions,
|
||||
LoaderIcon,
|
||||
noop,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader title="My World">
|
||||
<template #leading>
|
||||
<ButtonStyled circular size="large">
|
||||
<button type="button" aria-label="All instances" @click="noop">
|
||||
<LeftArrowIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem :icon="Gamepad2Icon" tooltip="Minecraft version">Minecraft 1.20.1</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem :icon="LoaderIcon" :icon-props="{ loader: 'Fabric' }" tooltip="Mod loader">
|
||||
Fabric 0.19.2
|
||||
</PageHeaderMetadataItem>
|
||||
<PageHeaderMetadataItem :icon="TimerIcon" tooltip="Last activity">Last active 2 weeks ago</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<PageHeaderActions>
|
||||
<ButtonStyled color="brand" size="large">
|
||||
<button type="button" @click="noop">
|
||||
<PlayIcon />
|
||||
Start instance
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<JoinedButtons :actions="joinedActions" color="red" size="large" />
|
||||
<ButtonStyled circular size="large">
|
||||
<button type="button" aria-label="Instance settings" @click="noop">
|
||||
<SettingsIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</PageHeaderActions>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const CustomMetadata: Story = {
|
||||
render: () => ({
|
||||
components: pageHeaderComponents,
|
||||
setup() {
|
||||
return {
|
||||
...pageHeaderIcons,
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<PageHeader
|
||||
title="Custom Metadata Project"
|
||||
summary="Custom metadata stays in markup so page-specific components can be composed without expanding PageHeader itself."
|
||||
>
|
||||
<template #leading>
|
||||
<Avatar src="" alt="Custom Metadata Project" size="96px" tint-by="custom-metadata-project" />
|
||||
</template>
|
||||
|
||||
<template #metadata>
|
||||
<PageHeaderMetadata>
|
||||
<PageHeaderMetadataItem>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
1.2M
|
||||
</div>
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
<HeartIcon class="h-6 w-6 text-secondary" />
|
||||
50K
|
||||
</div>
|
||||
</div>
|
||||
</PageHeaderMetadataItem>
|
||||
</PageHeaderMetadata>
|
||||
</template>
|
||||
</PageHeader>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
Reference in New Issue
Block a user