mirror of
https://github.com/modrinth/code.git
synced 2026-09-02 13:05:50 +00:00
680 lines
19 KiB
Vue
680 lines
19 KiB
Vue
<template>
|
|
<div
|
|
class="flex flex-col gap-2"
|
|
:class="[
|
|
props.divider ? 'border-0 border-b border-solid border-divider' : '',
|
|
props.bottomPadding ? 'pb-4' : '',
|
|
props.headerClass,
|
|
]"
|
|
>
|
|
<div class="flex flex-wrap items-start gap-4 max-md:flex-col" :class="props.rowClass">
|
|
<div class="flex min-w-0 flex-1 gap-4" :class="props.mainClass">
|
|
<div v-if="leadingItems.length" class="contents">
|
|
<template v-for="(leadingItem, index) in leadingItems" :key="leadingItem.id ?? index">
|
|
<div
|
|
v-if="leadingItem.type === 'button'"
|
|
:class="
|
|
leadingWrapperClass(
|
|
leadingItem,
|
|
'flex size-16 shrink-0 items-center justify-center',
|
|
)
|
|
"
|
|
>
|
|
<ButtonStyled
|
|
circular
|
|
:color="leadingItem.color ?? 'standard'"
|
|
:size="leadingItem.size ?? 'large'"
|
|
:type="leadingItem.buttonType ?? 'standard'"
|
|
>
|
|
<AutoLink
|
|
v-if="leadingItem.to"
|
|
v-tooltip="leadingItem.tooltip"
|
|
:to="leadingItem.to"
|
|
:aria-label="leadingLabel(leadingItem)"
|
|
>
|
|
<component
|
|
:is="leadingItem.icon"
|
|
v-if="leadingItem.icon"
|
|
aria-hidden="true"
|
|
v-bind="leadingItem.iconProps"
|
|
/>
|
|
</AutoLink>
|
|
<button
|
|
v-else
|
|
v-tooltip="leadingItem.tooltip"
|
|
type="button"
|
|
:aria-label="leadingLabel(leadingItem)"
|
|
@click="leadingItem.onClick"
|
|
>
|
|
<component
|
|
:is="leadingItem.icon"
|
|
v-if="leadingItem.icon"
|
|
aria-hidden="true"
|
|
v-bind="leadingItem.iconProps"
|
|
/>
|
|
</button>
|
|
</ButtonStyled>
|
|
</div>
|
|
<Avatar
|
|
v-else-if="leadingItem.type === 'avatar'"
|
|
:src="leadingItem.src"
|
|
:alt="leadingItem.alt ?? ''"
|
|
:size="leadingItem.avatarSize ?? '64px'"
|
|
:tint-by="leadingItem.tintBy ?? null"
|
|
:circle="leadingItem.circle ?? false"
|
|
:no-shadow="leadingItem.noShadow ?? false"
|
|
:raised="leadingItem.raised ?? false"
|
|
:loading="leadingItem.loading ?? 'eager'"
|
|
:class="leadingItem.class"
|
|
/>
|
|
<component
|
|
:is="leadingItem.component"
|
|
v-else-if="leadingItem.component"
|
|
:class="leadingItem.class"
|
|
v-bind="leadingItem.componentProps"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="flex min-w-0 flex-col gap-2 justify-center">
|
|
<div class="flex flex-col gap-1.5 justify-center">
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<h1
|
|
class="m-0 min-w-0 max-w-full text-2xl font-semibold leading-none text-contrast"
|
|
:class="[props.truncateTitle ? 'truncate' : '', props.titleClass]"
|
|
>
|
|
{{ props.header }}
|
|
</h1>
|
|
<template v-for="badge in props.badges" :key="badge.id">
|
|
<component
|
|
:is="badge.component"
|
|
v-if="badge.component"
|
|
:class="badge.class"
|
|
v-bind="badge.componentProps"
|
|
/>
|
|
<AutoLink
|
|
v-else-if="badge.to"
|
|
v-tooltip="badge.tooltip"
|
|
:to="badge.to"
|
|
:aria-label="badge.ariaLabel ?? badge.label"
|
|
:class="badgeClass(badge, true)"
|
|
:style="badge.style"
|
|
>
|
|
<component
|
|
:is="badge.icon"
|
|
v-if="badge.icon"
|
|
aria-hidden="true"
|
|
v-bind="badge.iconProps"
|
|
/>
|
|
<span>{{ badge.label }}</span>
|
|
</AutoLink>
|
|
<button
|
|
v-else-if="badge.onClick"
|
|
v-tooltip="badge.tooltip"
|
|
type="button"
|
|
:aria-label="badge.ariaLabel ?? badge.label"
|
|
:class="badgeClass(badge, true)"
|
|
:style="badge.style"
|
|
@click="badge.onClick"
|
|
>
|
|
<component
|
|
:is="badge.icon"
|
|
v-if="badge.icon"
|
|
aria-hidden="true"
|
|
v-bind="badge.iconProps"
|
|
/>
|
|
<span>{{ badge.label }}</span>
|
|
</button>
|
|
<div
|
|
v-else
|
|
v-tooltip="badge.tooltip"
|
|
:aria-label="badge.ariaLabel"
|
|
:class="badgeClass(badge)"
|
|
:style="badge.style"
|
|
>
|
|
<component
|
|
:is="badge.icon"
|
|
v-if="badge.icon"
|
|
aria-hidden="true"
|
|
v-bind="badge.iconProps"
|
|
/>
|
|
<span>{{ badge.label }}</span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<p
|
|
v-if="props.summary || $slots.summary"
|
|
class="m-0 max-w-[44rem] empty:hidden"
|
|
:class="[props.disableLineClamp ? '' : 'line-clamp-2']"
|
|
>
|
|
<slot name="summary">{{ props.summary }}</slot>
|
|
</p>
|
|
</div>
|
|
|
|
<div v-if="props.metadata.length" class="flex flex-wrap gap-3 empty:hidden max-md:hidden">
|
|
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
<template v-for="(item, index) in props.metadata" :key="item.id">
|
|
<BulletDivider v-if="index > 0" class="shrink-0" />
|
|
<div v-if="item.type === 'custom'" :class="item.class">
|
|
<slot :name="metadataSlotName(item)" :item="item" />
|
|
</div>
|
|
<AutoLink
|
|
v-else-if="item.to"
|
|
v-tooltip="item.tooltip"
|
|
:to="item.to"
|
|
:aria-label="item.ariaLabel ?? item.label ?? ''"
|
|
:class="metadataClass(item, true)"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</AutoLink>
|
|
<button
|
|
v-else-if="item.onClick"
|
|
v-tooltip="item.tooltip"
|
|
type="button"
|
|
:aria-label="item.ariaLabel ?? item.label ?? ''"
|
|
:class="metadataClass(item, true)"
|
|
@click="item.onClick"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</button>
|
|
<div
|
|
v-else
|
|
v-tooltip="item.tooltip"
|
|
:aria-label="item.ariaLabel"
|
|
:class="metadataClass(item)"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="props.actions.length" class="flex flex-wrap gap-2 items-center">
|
|
<template v-for="action in props.actions" :key="action.id">
|
|
<component
|
|
:is="action.component"
|
|
v-if="action.component"
|
|
:class="action.class"
|
|
v-bind="action.componentProps"
|
|
/>
|
|
<Tooltip
|
|
v-else-if="action.prompt"
|
|
theme="dismissable-prompt"
|
|
:triggers="[]"
|
|
:shown="action.prompt.shown"
|
|
:auto-hide="false"
|
|
:placement="action.prompt.placement ?? 'bottom'"
|
|
>
|
|
<JoinedButtons
|
|
v-if="action.joinedActions?.length"
|
|
:actions="action.joinedActions"
|
|
:color="joinedActionColor(action.color)"
|
|
:size="action.size ?? 'large'"
|
|
:disabled="action.disabled"
|
|
:primary-disabled="action.primaryDisabled"
|
|
:dropdown-disabled="action.dropdownDisabled"
|
|
:primary-muted="action.primaryMuted"
|
|
/>
|
|
<ButtonStyled
|
|
v-else
|
|
:color="action.color ?? 'standard'"
|
|
:size="action.size ?? 'large'"
|
|
:type="action.type ?? 'standard'"
|
|
:circular="action.circular ?? action.labelHidden ?? false"
|
|
>
|
|
<TeleportOverflowMenu
|
|
v-if="action.menuActions?.length"
|
|
:options="action.menuActions"
|
|
:tooltip="action.tooltip"
|
|
:aria-label="actionLabel(action)"
|
|
:disabled="action.disabled"
|
|
@open="dismissActionPrompt(action)"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</TeleportOverflowMenu>
|
|
<AutoLink
|
|
v-else-if="action.to"
|
|
v-tooltip="action.tooltip"
|
|
:to="action.to"
|
|
:aria-label="actionLabel(action)"
|
|
@click="(event) => handleActionClick(action, event)"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</AutoLink>
|
|
<button
|
|
v-else
|
|
v-tooltip="action.tooltip"
|
|
type="button"
|
|
:disabled="action.disabled"
|
|
:aria-label="actionLabel(action)"
|
|
@click="(event) => handleActionClick(action, event)"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</button>
|
|
</ButtonStyled>
|
|
<template #popper>
|
|
<div class="grid grid-cols-[min-content] gap-1">
|
|
<div class="flex min-w-48 items-center justify-between gap-8">
|
|
<h3
|
|
class="m-0 flex items-center gap-2 whitespace-nowrap text-base font-bold text-contrast"
|
|
>
|
|
{{ action.prompt.title }}
|
|
<span
|
|
v-if="action.prompt.badge"
|
|
class="inline-flex items-center rounded-full border border-solid border-brand-highlight bg-brand-highlight px-2 py-1 text-sm font-semibold leading-none text-brand"
|
|
>
|
|
{{ action.prompt.badge }}
|
|
</span>
|
|
</h3>
|
|
<ButtonStyled size="small" circular>
|
|
<button
|
|
v-tooltip="action.prompt.dismissLabel"
|
|
@click="action.prompt.onDismiss?.()"
|
|
>
|
|
<XIcon aria-hidden="true" />
|
|
</button>
|
|
</ButtonStyled>
|
|
</div>
|
|
<p class="m-0 text-wrap text-sm font-medium leading-tight text-secondary">
|
|
{{ action.prompt.description }}
|
|
</p>
|
|
<p v-if="action.prompt.footer" class="m-0 text-wrap text-sm font-bold text-primary">
|
|
{{ action.prompt.footer }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
</Tooltip>
|
|
<template v-else>
|
|
<JoinedButtons
|
|
v-if="action.joinedActions?.length"
|
|
:actions="action.joinedActions"
|
|
:color="joinedActionColor(action.color)"
|
|
:size="action.size ?? 'large'"
|
|
:disabled="action.disabled"
|
|
:primary-disabled="action.primaryDisabled"
|
|
:dropdown-disabled="action.dropdownDisabled"
|
|
:primary-muted="action.primaryMuted"
|
|
/>
|
|
<ButtonStyled
|
|
v-else
|
|
:color="action.color ?? 'standard'"
|
|
:size="action.size ?? 'large'"
|
|
:type="action.type ?? 'standard'"
|
|
:circular="action.circular ?? action.labelHidden ?? false"
|
|
>
|
|
<TeleportOverflowMenu
|
|
v-if="action.menuActions?.length"
|
|
:options="action.menuActions"
|
|
:tooltip="action.tooltip"
|
|
:aria-label="actionLabel(action)"
|
|
:disabled="action.disabled"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</TeleportOverflowMenu>
|
|
<AutoLink
|
|
v-else-if="action.to"
|
|
v-tooltip="action.tooltip"
|
|
:to="action.to"
|
|
:aria-label="actionLabel(action)"
|
|
@click="(event) => handleActionClick(action, event)"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</AutoLink>
|
|
<button
|
|
v-else
|
|
v-tooltip="action.tooltip"
|
|
type="button"
|
|
:disabled="action.disabled"
|
|
:aria-label="actionLabel(action)"
|
|
@click="(event) => handleActionClick(action, event)"
|
|
>
|
|
<component
|
|
:is="action.icon"
|
|
v-if="action.icon"
|
|
:class="action.iconClass"
|
|
aria-hidden="true"
|
|
v-bind="action.iconProps"
|
|
/>
|
|
<span v-if="!action.labelHidden && !action.circular">{{ action.label }}</span>
|
|
</button>
|
|
</ButtonStyled>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="props.metadata.length" class="flex justify-between md:hidden">
|
|
<div class="flex flex-wrap gap-3 empty:hidden">
|
|
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
<template v-for="(item, index) in props.metadata" :key="item.id">
|
|
<BulletDivider v-if="index > 0" class="shrink-0" />
|
|
<div v-if="item.type === 'custom'" :class="item.class">
|
|
<slot :name="metadataSlotName(item)" :item="item" />
|
|
</div>
|
|
<AutoLink
|
|
v-else-if="item.to"
|
|
v-tooltip="item.tooltip"
|
|
:to="item.to"
|
|
:aria-label="item.ariaLabel ?? item.label ?? ''"
|
|
:class="metadataClass(item, true)"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</AutoLink>
|
|
<button
|
|
v-else-if="item.onClick"
|
|
v-tooltip="item.tooltip"
|
|
type="button"
|
|
:aria-label="item.ariaLabel ?? item.label ?? ''"
|
|
:class="metadataClass(item, true)"
|
|
@click="item.onClick"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</button>
|
|
<div
|
|
v-else
|
|
v-tooltip="item.tooltip"
|
|
:aria-label="item.ariaLabel"
|
|
:class="metadataClass(item)"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
v-if="item.icon"
|
|
class="flex size-5 shrink-0 text-current"
|
|
aria-hidden="true"
|
|
v-bind="item.iconProps"
|
|
/>
|
|
<span class="truncate">{{ item.label }}</span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { XIcon } from '@modrinth/assets'
|
|
import { Tooltip } from 'floating-vue'
|
|
import type { Component } from 'vue'
|
|
import { computed } from 'vue'
|
|
import type { RouteLocationRaw } from 'vue-router'
|
|
|
|
import AutoLink from './AutoLink.vue'
|
|
import Avatar from './Avatar.vue'
|
|
import BulletDivider from './BulletDivider.vue'
|
|
import ButtonStyled from './ButtonStyled.vue'
|
|
import JoinedButtons, { type JoinedButtonAction } from './JoinedButtons.vue'
|
|
import TeleportOverflowMenu, {
|
|
type Item as TeleportOverflowMenuItem,
|
|
} from './TeleportOverflowMenu.vue'
|
|
|
|
type PageHeaderTarget = string | RouteLocationRaw
|
|
type ButtonColor =
|
|
| 'standard'
|
|
| 'brand'
|
|
| 'red'
|
|
| 'orange'
|
|
| 'green'
|
|
| 'blue'
|
|
| 'purple'
|
|
| 'medal-promo'
|
|
type ButtonSize = 'standard' | 'large' | 'small'
|
|
type ButtonType =
|
|
| 'standard'
|
|
| 'outlined'
|
|
| 'transparent'
|
|
| 'highlight'
|
|
| 'highlight-colored-text'
|
|
| 'chip'
|
|
|
|
type PageHeaderLeading = {
|
|
id?: string
|
|
type: 'avatar' | 'button' | 'component'
|
|
icon?: Component
|
|
iconProps?: Record<string, unknown>
|
|
component?: Component
|
|
componentProps?: Record<string, unknown>
|
|
src?: string | null
|
|
alt?: string
|
|
tintBy?: string | null
|
|
avatarSize?: string
|
|
circle?: boolean
|
|
noShadow?: boolean
|
|
raised?: boolean
|
|
loading?: 'eager' | 'lazy'
|
|
class?: string
|
|
wrapperClass?: string
|
|
to?: PageHeaderTarget
|
|
onClick?: (event?: MouseEvent) => void | Promise<void>
|
|
tooltip?: string
|
|
ariaLabel?: string
|
|
color?: ButtonColor
|
|
size?: ButtonSize
|
|
buttonType?: ButtonType
|
|
}
|
|
|
|
type PageHeaderBadge = {
|
|
id: string
|
|
label?: string
|
|
icon?: Component
|
|
iconProps?: Record<string, unknown>
|
|
component?: Component
|
|
componentProps?: Record<string, unknown>
|
|
tooltip?: string
|
|
ariaLabel?: string
|
|
to?: PageHeaderTarget
|
|
onClick?: () => void | Promise<void>
|
|
class?: string
|
|
style?: Record<string, string>
|
|
}
|
|
|
|
type PageHeaderMetadataItem = {
|
|
id: string
|
|
type?: 'text' | 'custom'
|
|
label?: string
|
|
icon?: Component
|
|
iconProps?: Record<string, unknown>
|
|
tooltip?: string
|
|
ariaLabel?: string
|
|
to?: PageHeaderTarget
|
|
onClick?: () => void | Promise<void>
|
|
class?: string
|
|
}
|
|
|
|
type PageHeaderActionPrompt = {
|
|
title: string
|
|
description: string
|
|
badge?: string
|
|
footer?: string
|
|
dismissLabel?: string
|
|
shown?: boolean
|
|
placement?: string
|
|
onDismiss?: () => void
|
|
}
|
|
|
|
type PageHeaderAction = {
|
|
id: string
|
|
label: string
|
|
component?: Component
|
|
componentProps?: Record<string, unknown>
|
|
class?: string
|
|
icon?: Component
|
|
iconProps?: Record<string, unknown>
|
|
iconClass?: string
|
|
tooltip?: string
|
|
ariaLabel?: string
|
|
to?: PageHeaderTarget
|
|
onClick?: () => void | Promise<void>
|
|
disabled?: boolean
|
|
labelHidden?: boolean
|
|
circular?: boolean
|
|
color?: ButtonColor
|
|
size?: ButtonSize
|
|
type?: ButtonType
|
|
joinedActions?: JoinedButtonAction[]
|
|
menuActions?: TeleportOverflowMenuItem[]
|
|
primaryDisabled?: boolean
|
|
dropdownDisabled?: boolean
|
|
primaryMuted?: boolean
|
|
prompt?: PageHeaderActionPrompt
|
|
}
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
header: string
|
|
summary?: string | null
|
|
leading?: PageHeaderLeading | PageHeaderLeading[] | null
|
|
badges?: PageHeaderBadge[]
|
|
metadata?: PageHeaderMetadataItem[]
|
|
actions?: PageHeaderAction[]
|
|
headerClass?: string
|
|
rowClass?: string
|
|
mainClass?: string
|
|
titleClass?: string
|
|
truncateTitle?: boolean
|
|
divider?: boolean
|
|
bottomPadding?: boolean
|
|
disableLineClamp?: boolean
|
|
}>(),
|
|
{
|
|
summary: null,
|
|
leading: null,
|
|
badges: () => [],
|
|
metadata: () => [],
|
|
actions: () => [],
|
|
headerClass: '',
|
|
rowClass: '',
|
|
mainClass: '',
|
|
titleClass: '',
|
|
truncateTitle: false,
|
|
divider: true,
|
|
bottomPadding: true,
|
|
disableLineClamp: false,
|
|
},
|
|
)
|
|
|
|
const leadingItems = computed(() => {
|
|
if (!props.leading) return []
|
|
return Array.isArray(props.leading) ? props.leading : [props.leading]
|
|
})
|
|
|
|
function leadingLabel(item: PageHeaderLeading) {
|
|
return item.ariaLabel ?? item.tooltip ?? 'Header action'
|
|
}
|
|
|
|
function leadingWrapperClass(item: PageHeaderLeading, defaultClass: string) {
|
|
return [item.wrapperClass ?? defaultClass]
|
|
}
|
|
|
|
function metadataClass(item: PageHeaderMetadataItem, interactive = false) {
|
|
return [
|
|
'flex min-w-0 items-center gap-2 font-medium text-secondary text-nowrap',
|
|
interactive ? 'm-0 cursor-pointer border-0 bg-transparent p-0 hover:underline' : '',
|
|
item.class,
|
|
]
|
|
}
|
|
|
|
function metadataSlotName(item: PageHeaderMetadataItem) {
|
|
return `metadata-${item.id}`
|
|
}
|
|
|
|
function badgeClass(badge: PageHeaderBadge, interactive = false) {
|
|
return [
|
|
'inline-flex items-center gap-1 rounded-full border border-solid border-surface-5 bg-button-bg px-2 py-1 text-sm font-semibold leading-none text-secondary text-nowrap [&>svg]:size-4 [&>svg]:shrink-0',
|
|
interactive ? 'm-0 cursor-pointer hover:underline' : '',
|
|
badge.class,
|
|
]
|
|
}
|
|
|
|
function actionLabel(action: PageHeaderAction) {
|
|
return action.ariaLabel ?? action.tooltip ?? action.label
|
|
}
|
|
|
|
function dismissActionPrompt(action: PageHeaderAction) {
|
|
if (action.prompt?.shown) {
|
|
action.prompt.onDismiss?.()
|
|
}
|
|
}
|
|
|
|
function handleActionClick(action: PageHeaderAction, event?: MouseEvent) {
|
|
dismissActionPrompt(action)
|
|
void action.onClick?.(event)
|
|
}
|
|
|
|
function joinedActionColor(color?: ButtonColor) {
|
|
return color === 'medal-promo' ? 'standard' : color
|
|
}
|
|
</script>
|