feat: standardized page banners (#3610)
* feat: standardized site banners * fix: lint issues * fix: deduplicate SCSS with variant map * feat: color shades + reduced scss * feat: fix theming * chore: Remove shades-generator.ts * fix: lint issues --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
parent
f19643095e
commit
e225bc9f66
@ -162,6 +162,18 @@ html {
|
||||
--landing-green-label-bg: rgba(0, 216, 69, 0.15);
|
||||
|
||||
--landing-raw-bg: #fff;
|
||||
|
||||
--banner-error-bg: #fee2e2;
|
||||
--banner-error-text: #991b1b;
|
||||
--banner-error-border: #ef4444;
|
||||
|
||||
--banner-warning-bg: #ffedd5;
|
||||
--banner-warning-text: #713f12;
|
||||
--banner-warning-border: #f97316;
|
||||
|
||||
--banner-info-bg: #dbeafe;
|
||||
--banner-info-text: #1e3a8a;
|
||||
--banner-info-border: #3b82f6;
|
||||
}
|
||||
|
||||
.dark,
|
||||
@ -286,6 +298,18 @@ html {
|
||||
|
||||
--hover-filter: brightness(120%);
|
||||
--active-filter: brightness(140%);
|
||||
|
||||
--banner-error-bg: #4c1515;
|
||||
--banner-error-text: #fee2e2;
|
||||
--banner-error-border: #7f1d1d;
|
||||
|
||||
--banner-warning-bg: #4a2a0a;
|
||||
--banner-warning-text: #ffe6c0;
|
||||
--banner-warning-border: #b54708;
|
||||
|
||||
--banner-info-bg: #1e2a44;
|
||||
--banner-info-text: #dbeafe;
|
||||
--banner-info-border: #2563eb;
|
||||
}
|
||||
|
||||
.oled-mode {
|
||||
|
||||
@ -27,76 +27,90 @@
|
||||
</div>
|
||||
</div>
|
||||
<div ref="main_page" class="layout" :class="{ 'expanded-mobile-nav': isBrowseMenuOpen }">
|
||||
<div
|
||||
<PagewideBanner
|
||||
v-if="auth.user && !auth.user.email_verified && route.path !== '/auth/verify-email'"
|
||||
class="email-nag"
|
||||
variant="warning"
|
||||
>
|
||||
<template v-if="auth.user.email">
|
||||
<span>{{ formatMessage(verifyEmailBannerMessages.title) }}</span>
|
||||
<button class="btn" @click="resendVerifyEmail">
|
||||
<template #title>
|
||||
<span>
|
||||
{{
|
||||
auth?.user?.email
|
||||
? formatMessage(verifyEmailBannerMessages.title)
|
||||
: formatMessage(addEmailBannerMessages.title)
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<span>
|
||||
{{
|
||||
auth?.user?.email
|
||||
? formatMessage(verifyEmailBannerMessages.description)
|
||||
: formatMessage(addEmailBannerMessages.description)
|
||||
}}
|
||||
</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<button v-if="auth?.user?.email" class="btn" @click="resendVerifyEmail">
|
||||
{{ formatMessage(verifyEmailBannerMessages.action) }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>{{ formatMessage(addEmailBannerMessages.title) }}</span>
|
||||
<nuxt-link class="btn" to="/settings/account">
|
||||
<nuxt-link v-else class="btn" to="/settings/account">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(addEmailBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
</PagewideBanner>
|
||||
<PagewideBanner
|
||||
v-if="
|
||||
user &&
|
||||
user.subscriptions &&
|
||||
user.subscriptions.some((x) => x.status === 'payment-failed') &&
|
||||
route.path !== '/settings/billing'
|
||||
"
|
||||
class="email-nag"
|
||||
variant="error"
|
||||
>
|
||||
<span>{{ formatMessage(subscriptionPaymentFailedBannerMessages.title) }}</span>
|
||||
<nuxt-link class="btn" to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(subscriptionPaymentFailedBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
</div>
|
||||
<div
|
||||
<template #title>
|
||||
<span>{{ formatMessage(subscriptionPaymentFailedBannerMessages.title) }}</span>
|
||||
</template>
|
||||
<template #description>
|
||||
<span>{{ formatMessage(subscriptionPaymentFailedBannerMessages.description) }}</span>
|
||||
</template>
|
||||
<template #actions>
|
||||
<nuxt-link class="btn" to="/settings/billing">
|
||||
<SettingsIcon aria-hidden="true" />
|
||||
{{ formatMessage(subscriptionPaymentFailedBannerMessages.action) }}
|
||||
</nuxt-link>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
<PagewideBanner
|
||||
v-if="
|
||||
config.public.apiBaseUrl.startsWith('https://staging-api.modrinth.com') &&
|
||||
!cosmetics.hideStagingBanner
|
||||
"
|
||||
class="site-banner site-banner--warning [&>*]:z-[6]"
|
||||
variant="warning"
|
||||
>
|
||||
<div class="site-banner__title">
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
<template #title>
|
||||
<span>{{ formatMessage(stagingBannerMessages.title) }}</span>
|
||||
</div>
|
||||
<div class="site-banner__description">
|
||||
</template>
|
||||
<template #description>
|
||||
{{ formatMessage(stagingBannerMessages.description) }}
|
||||
</div>
|
||||
<div class="site-banner__actions">
|
||||
<Button transparent icon-only :action="hideStagingBanner" aria-label="Close banner"
|
||||
><XIcon aria-hidden="true"
|
||||
/></Button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="generatedStateErrors && generatedStateErrors.length > 0"
|
||||
class="site-banner site-banner--warning [&>*]:z-[6]"
|
||||
>
|
||||
<div class="site-banner__title">
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
</template>
|
||||
<template #actions_right>
|
||||
<Button transparent icon-only aria-label="Close" @click="hideStagingBanner">
|
||||
<XIcon aria-hidden="true" />
|
||||
</Button>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
<PagewideBanner v-if="generatedStateErrors?.length" variant="error">
|
||||
<template #title>
|
||||
<span>{{ formatMessage(failedToBuildBannerMessages.title) }}</span>
|
||||
</div>
|
||||
<div class="site-banner__description">
|
||||
</template>
|
||||
<template #description>
|
||||
{{
|
||||
formatMessage(failedToBuildBannerMessages.description, {
|
||||
errors: generatedStateErrors,
|
||||
url: config.public.apiBaseUrl,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PagewideBanner>
|
||||
<header
|
||||
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]"
|
||||
>
|
||||
@ -692,7 +706,14 @@ import {
|
||||
GitHubIcon,
|
||||
ScaleIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { Button, ButtonStyled, OverflowMenu, Avatar, commonMessages } from "@modrinth/ui";
|
||||
import {
|
||||
Button,
|
||||
ButtonStyled,
|
||||
OverflowMenu,
|
||||
PagewideBanner,
|
||||
Avatar,
|
||||
commonMessages,
|
||||
} from "@modrinth/ui";
|
||||
import { isAdmin, isStaff } from "@modrinth/utils";
|
||||
import { errors as generatedStateErrors } from "~/generated/state.json";
|
||||
|
||||
@ -720,8 +741,13 @@ const basePopoutId = useId();
|
||||
|
||||
const verifyEmailBannerMessages = defineMessages({
|
||||
title: {
|
||||
id: "layout.banner.verify-email.title",
|
||||
defaultMessage: "For security purposes, please verify your email address on Modrinth.",
|
||||
id: "layout.banner.account-action",
|
||||
defaultMessage: "Account action required",
|
||||
},
|
||||
description: {
|
||||
id: "layout.banner.verify-email.description",
|
||||
defaultMessage:
|
||||
"For security reasons, Modrinth needs you to verify the email address associated with your account.",
|
||||
},
|
||||
action: {
|
||||
id: "layout.banner.verify-email.action",
|
||||
@ -731,8 +757,13 @@ const verifyEmailBannerMessages = defineMessages({
|
||||
|
||||
const addEmailBannerMessages = defineMessages({
|
||||
title: {
|
||||
id: "layout.banner.add-email.title",
|
||||
defaultMessage: "For security purposes, please enter your email on Modrinth.",
|
||||
id: "layout.banner.account-action",
|
||||
defaultMessage: "Account action required",
|
||||
},
|
||||
description: {
|
||||
id: "layout.banner.add-email.description",
|
||||
defaultMessage:
|
||||
"For security reasons, Modrinth needs you to register an email address to your account.",
|
||||
},
|
||||
action: {
|
||||
id: "layout.banner.add-email.button",
|
||||
@ -743,8 +774,12 @@ const addEmailBannerMessages = defineMessages({
|
||||
const subscriptionPaymentFailedBannerMessages = defineMessages({
|
||||
title: {
|
||||
id: "layout.banner.subscription-payment-failed.title",
|
||||
defaultMessage: "Billing action required.",
|
||||
},
|
||||
description: {
|
||||
id: "layout.banner.subscription-payment-failed.description",
|
||||
defaultMessage:
|
||||
"Your subscription failed to renew. Please update your payment method to prevent losing access.",
|
||||
"One or more subscriptions failed to renew. Please update your payment method to prevent losing access!",
|
||||
},
|
||||
action: {
|
||||
id: "layout.banner.subscription-payment-failed.button",
|
||||
@ -755,7 +790,7 @@ const subscriptionPaymentFailedBannerMessages = defineMessages({
|
||||
const stagingBannerMessages = defineMessages({
|
||||
title: {
|
||||
id: "layout.banner.staging.title",
|
||||
defaultMessage: "You’re viewing Modrinth’s staging environment.",
|
||||
defaultMessage: "You’re viewing Modrinth’s staging environment",
|
||||
},
|
||||
description: {
|
||||
id: "layout.banner.staging.description",
|
||||
@ -1347,72 +1382,6 @@ const footerLinks = [
|
||||
}
|
||||
}
|
||||
|
||||
.email-nag {
|
||||
z-index: 6;
|
||||
position: relative;
|
||||
background-color: var(--color-raised-bg);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.site-banner--warning {
|
||||
// On some pages, there's gradient backgrounds that seep underneath
|
||||
// the banner, so we need to add a solid color underlay.
|
||||
background-color: black;
|
||||
border-bottom: 2px solid var(--color-red);
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template: "title actions" "description actions";
|
||||
padding-block: var(--gap-xl);
|
||||
padding-inline: max(calc((100% - 80rem) / 2 + var(--gap-md)), var(--gap-xl));
|
||||
z-index: 4;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--color-red-bg);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.site-banner__title {
|
||||
grid-area: title;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-contrast);
|
||||
|
||||
svg {
|
||||
color: var(--color-red);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.site-banner__description {
|
||||
grid-area: description;
|
||||
}
|
||||
|
||||
.site-banner__actions {
|
||||
grid-area: actions;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-red);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.app-btn {
|
||||
display: none;
|
||||
|
||||
@ -344,35 +344,38 @@
|
||||
"layout.avatar.alt": {
|
||||
"message": "Your avatar"
|
||||
},
|
||||
"layout.banner.account-action": {
|
||||
"message": "Account action required"
|
||||
},
|
||||
"layout.banner.add-email.button": {
|
||||
"message": "Visit account settings"
|
||||
},
|
||||
"layout.banner.add-email.title": {
|
||||
"message": "For security purposes, please enter your email on Modrinth."
|
||||
},
|
||||
"layout.banner.build-fail.description": {
|
||||
"message": "This deploy of Modrinth's frontend failed to generate state from the API. This may be due to an outage or an error in configuration. Rebuild when the API is available. Error codes: {errors}; Current API URL is: {url}"
|
||||
},
|
||||
"layout.banner.build-fail.title": {
|
||||
"message": "Error generating state from API when building."
|
||||
"message": "Error generating state from API when building"
|
||||
},
|
||||
"layout.banner.staging.description": {
|
||||
"message": "The staging environment is completely separate from the production Modrinth database. This is used for testing and debugging purposes, and may be running in-development versions of the Modrinth backend or frontend newer than the production instance."
|
||||
},
|
||||
"layout.banner.staging.title": {
|
||||
"message": "You’re viewing Modrinth’s staging environment."
|
||||
"message": "You’re viewing Modrinth’s staging environment"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.button": {
|
||||
"message": "Update billing info"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.title": {
|
||||
"message": "Your subscription failed to renew. Please update your payment method to prevent losing access."
|
||||
"message": "Billing action required"
|
||||
},
|
||||
"layout.banner.subscription-payment-failed.description": {
|
||||
"message": "One or more subscriptions failed to renew. Please update your payment method to prevent losing access!"
|
||||
},
|
||||
"layout.banner.verify-email.action": {
|
||||
"message": "Re-send verification email"
|
||||
},
|
||||
"layout.banner.verify-email.title": {
|
||||
"message": "For security purposes, please verify your email address on Modrinth."
|
||||
"layout.banner.verify-email.description": {
|
||||
"message": "For security reasons, Modrinth needs you to verify the email address associated with your account."
|
||||
},
|
||||
"layout.footer.about": {
|
||||
"message": "About"
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
const config = {
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/components/**/*.{js,vue,ts}",
|
||||
"./src/layouts/**/*.vue",
|
||||
@ -36,6 +37,23 @@ const config = {
|
||||
purple: "var(--color-purple-bg)",
|
||||
raised: "var(--color-raised-bg)",
|
||||
},
|
||||
banners: {
|
||||
error: {
|
||||
bg: "var(--banner-error-bg)",
|
||||
text: "var(--banner-error-text)",
|
||||
border: "var(--banner-error-border)",
|
||||
},
|
||||
warning: {
|
||||
bg: "var(--banner-warning-bg)",
|
||||
text: "var(--banner-warning-text)",
|
||||
border: "var(--banner-warning-border)",
|
||||
},
|
||||
info: {
|
||||
bg: "var(--banner-info-bg)",
|
||||
text: "var(--banner-info-text)",
|
||||
border: "var(--banner-info-border)",
|
||||
},
|
||||
},
|
||||
highlight: {
|
||||
DEFAULT: "var(--color-brand-highlight)",
|
||||
red: "var(--color-red-highlight)",
|
||||
@ -126,6 +144,7 @@ const config = {
|
||||
backgroundImage: {
|
||||
mazeBg: "var(--landing-maze-bg)",
|
||||
mazeGradientBg: "var(--landing-maze-gradient-bg)",
|
||||
// @ts-ignore
|
||||
landing: {
|
||||
mazeOuterBg: "var(--landing-maze-outer-bg)",
|
||||
colorHeading: "var(--landing-color-heading)",
|
||||
@ -71,6 +71,7 @@ export { default as Breadcrumbs } from './nav/Breadcrumbs.vue'
|
||||
export { default as NavItem } from './nav/NavItem.vue'
|
||||
export { default as NavRow } from './nav/NavRow.vue'
|
||||
export { default as NavStack } from './nav/NavStack.vue'
|
||||
export { default as PagewideBanner } from './nav/PagewideBanner.vue'
|
||||
|
||||
// Project
|
||||
export { default as NewProjectCard } from './project/NewProjectCard.vue'
|
||||
|
||||
82
packages/ui/src/components/nav/PagewideBanner.vue
Normal file
82
packages/ui/src/components/nav/PagewideBanner.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div
|
||||
:class="['banner-grid relative border-b-2 border-solid border-0', containerClasses[variant]]"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'grid-area-[title] flex items-center gap-2 font-bold text-[var(--font-size-md)]',
|
||||
iconClasses[variant],
|
||||
]"
|
||||
>
|
||||
<IssuesIcon
|
||||
v-if="variant === 'warning' || variant === 'error'"
|
||||
aria-hidden="true"
|
||||
class="w-6 h-6 flex-shrink-0"
|
||||
/>
|
||||
<InfoIcon v-if="variant === 'info'" aria-hidden="true" class="w-6 h-6 flex-shrink-0" />
|
||||
<slot name="title" />
|
||||
</div>
|
||||
|
||||
<div class="grid-area-[description] flex flex-col gap-[var(--gap-md)]">
|
||||
<slot name="description" />
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.actions" class="grid-area-[actions]">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
|
||||
<div v-if="$slots.actions_right" class="grid-area-[actions_right]">
|
||||
<slot name="actions_right" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { InfoIcon, IssuesIcon } from '@modrinth/assets'
|
||||
|
||||
defineProps<{
|
||||
variant: 'error' | 'warning' | 'info'
|
||||
}>()
|
||||
|
||||
const containerClasses = {
|
||||
error: 'bg-banners-error-bg text-banners-error-text border-banners-error-border',
|
||||
warning: 'bg-banners-warning-bg text-banners-warning-text border-banners-warning-border',
|
||||
info: 'bg-banners-info-bg text-banners-info-text border-banners-info-border',
|
||||
}
|
||||
|
||||
const iconClasses = {
|
||||
error: 'text-brand-red',
|
||||
warning: 'text-brand-orange',
|
||||
info: 'text-brand-blue',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.banner-grid {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-areas:
|
||||
'title actions_right'
|
||||
'description actions_right'
|
||||
'actions actions_right';
|
||||
padding-block: var(--gap-xl);
|
||||
padding-inline: max(calc((100% - 80rem) / 2 + var(--gap-md)), var(--gap-xl));
|
||||
}
|
||||
|
||||
.grid-area-\[title\] {
|
||||
grid-area: title;
|
||||
}
|
||||
.grid-area-\[description\] {
|
||||
grid-area: description;
|
||||
}
|
||||
.grid-area-\[actions\] {
|
||||
grid-area: actions;
|
||||
}
|
||||
.grid-area-\[actions_right\] {
|
||||
grid-area: actions_right;
|
||||
}
|
||||
|
||||
.banner-grid a {
|
||||
@apply underline text-current;
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user