mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Make job list button visual seperation change
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
<div
|
<div
|
||||||
ref="rowRef"
|
ref="rowRef"
|
||||||
class="relative"
|
class="relative"
|
||||||
@mouseenter="onRowEnter"
|
@mouseenter="handleMouseEnter"
|
||||||
@mouseleave="onRowLeave"
|
@mouseleave="handleMouseLeave"
|
||||||
@contextmenu.stop.prevent="onContextMenu"
|
@contextmenu.stop.prevent="onContextMenu"
|
||||||
>
|
>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
@@ -42,158 +42,119 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
<div
|
|
||||||
class="relative flex items-center justify-between gap-2 overflow-hidden rounded-lg border border-secondary-background bg-secondary-background p-1 text-[12px] text-text-primary transition-colors duration-150 ease-in-out hover:border-secondary-background-hover hover:bg-secondary-background-hover"
|
<div class="flex items-center gap-1">
|
||||||
@mouseenter="isHovered = true"
|
|
||||||
@mouseleave="isHovered = false"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
class="relative flex min-w-0 flex-1 items-center gap-2 overflow-hidden rounded-lg border border-secondary-background bg-secondary-background p-1 text-[12px] text-text-primary transition-colors duration-150 ease-in-out hover:border-secondary-background-hover hover:bg-secondary-background-hover"
|
||||||
props.state === 'running' &&
|
|
||||||
(props.progressTotalPercent !== undefined ||
|
|
||||||
props.progressCurrentPercent !== undefined)
|
|
||||||
"
|
|
||||||
class="absolute inset-0"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="props.progressTotalPercent !== undefined"
|
v-if="
|
||||||
class="pointer-events-none absolute inset-y-0 left-0 h-full bg-interface-panel-job-progress-primary transition-[width]"
|
props.state === 'running' &&
|
||||||
:style="{ width: `${props.progressTotalPercent}%` }"
|
(props.progressTotalPercent !== undefined ||
|
||||||
/>
|
props.progressCurrentPercent !== undefined)
|
||||||
<div
|
"
|
||||||
v-if="props.progressCurrentPercent !== undefined"
|
class="absolute inset-0"
|
||||||
class="pointer-events-none absolute inset-y-0 left-0 h-full bg-interface-panel-job-progress-secondary transition-[width]"
|
>
|
||||||
:style="{ width: `${props.progressCurrentPercent}%` }"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative z-[1] flex items-center gap-1">
|
|
||||||
<div class="relative inline-flex items-center justify-center">
|
|
||||||
<div
|
<div
|
||||||
class="absolute left-1/2 top-1/2 size-10 -translate-x-1/2 -translate-y-1/2"
|
v-if="props.progressTotalPercent !== undefined"
|
||||||
@mouseenter.stop="onIconEnter"
|
class="pointer-events-none absolute inset-y-0 left-0 h-full bg-interface-panel-job-progress-primary transition-[width]"
|
||||||
@mouseleave.stop="onIconLeave"
|
:style="{ width: `${props.progressTotalPercent}%` }"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="inline-flex h-6 w-6 items-center justify-center overflow-hidden rounded-[6px]"
|
v-if="props.progressCurrentPercent !== undefined"
|
||||||
>
|
class="pointer-events-none absolute inset-y-0 left-0 h-full bg-interface-panel-job-progress-secondary transition-[width]"
|
||||||
<img
|
:style="{ width: `${props.progressCurrentPercent}%` }"
|
||||||
v-if="iconImageUrl"
|
/>
|
||||||
:src="iconImageUrl"
|
</div>
|
||||||
class="h-full w-full object-cover"
|
|
||||||
/>
|
<div class="relative z-[1] flex items-center gap-1">
|
||||||
<i
|
<div class="relative inline-flex items-center justify-center">
|
||||||
v-else
|
<div
|
||||||
:class="cn(iconClass, 'size-4', shouldSpin && 'animate-spin')"
|
class="absolute left-1/2 top-1/2 size-10 -translate-x-1/2 -translate-y-1/2"
|
||||||
|
@mouseenter.stop="onIconEnter"
|
||||||
|
@mouseleave.stop="onIconLeave"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
class="inline-flex h-6 w-6 items-center justify-center overflow-hidden rounded-[6px]"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="iconImageUrl"
|
||||||
|
:src="iconImageUrl"
|
||||||
|
class="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
<i
|
||||||
|
v-else
|
||||||
|
:class="cn(iconClass, 'size-4', shouldSpin && 'animate-spin')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="relative z-[1] min-w-0 flex-1">
|
<div class="relative z-[1] min-w-0 flex-1">
|
||||||
<div class="truncate opacity-90" :title="props.title">
|
<div class="truncate opacity-90" :title="props.title">
|
||||||
<slot name="primary">{{ props.title }}</slot>
|
<slot name="primary">{{ props.title }}</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative z-[1] shrink-0 pr-2 text-text-secondary">
|
||||||
|
<slot name="secondary">{{ props.rightText }}</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
<div
|
||||||
TODO: Refactor action buttons to use a declarative config system.
|
v-if="hoverActions.length || alwaysActions.length"
|
||||||
|
class="relative z-[1] flex items-center gap-1 text-text-secondary"
|
||||||
Instead of hardcoding button visibility logic in the template, define an array of
|
>
|
||||||
action button configs with properties like:
|
<div
|
||||||
- icon, label, action, tooltip
|
v-if="isHovered && hoverActions.length"
|
||||||
- visibleStates: JobState[] (which job states show this button)
|
class="flex items-center gap-1"
|
||||||
- alwaysVisible: boolean (show without hover)
|
|
||||||
- destructive: boolean (use destructive styling)
|
|
||||||
|
|
||||||
Then render buttons in two groups:
|
|
||||||
1. Always-visible buttons (outside Transition)
|
|
||||||
2. Hover-only buttons (inside Transition)
|
|
||||||
|
|
||||||
This would eliminate the current duplication where the cancel button exists
|
|
||||||
both outside (for running) and inside (for pending) the Transition.
|
|
||||||
-->
|
|
||||||
<div class="relative z-[1] flex items-center gap-2 text-text-secondary">
|
|
||||||
<Transition
|
|
||||||
mode="out-in"
|
|
||||||
enter-active-class="transition-opacity transition-transform duration-150 ease-out"
|
|
||||||
leave-active-class="transition-opacity transition-transform duration-150 ease-in"
|
|
||||||
enter-from-class="opacity-0 translate-y-0.5"
|
|
||||||
enter-to-class="opacity-100 translate-y-0"
|
|
||||||
leave-from-class="opacity-100 translate-y-0"
|
|
||||||
leave-to-class="opacity-0 translate-y-0.5"
|
|
||||||
>
|
>
|
||||||
<div
|
<template v-for="action in hoverActions" :key="action.key">
|
||||||
v-if="isHovered"
|
|
||||||
key="actions"
|
|
||||||
class="inline-flex items-center gap-2 pr-1"
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="props.state === 'failed' && computedShowClear"
|
v-if="action.type === 'icon'"
|
||||||
v-tooltip.top="deleteTooltipConfig"
|
v-tooltip.top="action.tooltip"
|
||||||
type="transparent"
|
:type="action.buttonType"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="size-6 transform gap-1 rounded bg-destructive-background text-text-primary transition duration-150 ease-in-out hover:-translate-y-px hover:bg-destructive-background-hover hover:opacity-95"
|
:class="getActionButtonClass()"
|
||||||
:aria-label="t('g.delete')"
|
:aria-label="action.ariaLabel"
|
||||||
@click.stop="emit('delete')"
|
@click.stop="action.onClick?.($event)"
|
||||||
>
|
>
|
||||||
<i class="icon-[lucide--trash-2] size-4" />
|
<i :class="cn(action.iconClass, 'size-4')" />
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
v-else-if="
|
|
||||||
props.state !== 'completed' &&
|
|
||||||
props.state !== 'running' &&
|
|
||||||
computedShowClear
|
|
||||||
"
|
|
||||||
v-tooltip.top="cancelTooltipConfig"
|
|
||||||
type="transparent"
|
|
||||||
size="sm"
|
|
||||||
class="size-6 transform gap-1 rounded bg-destructive-background text-text-primary transition duration-150 ease-in-out hover:-translate-y-px hover:bg-destructive-background-hover hover:opacity-95"
|
|
||||||
:aria-label="t('g.cancel')"
|
|
||||||
@click.stop="emit('cancel')"
|
|
||||||
>
|
|
||||||
<i class="icon-[lucide--x] size-4" />
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<TextButton
|
<TextButton
|
||||||
v-else-if="props.state === 'completed'"
|
v-else
|
||||||
class="h-6 transform gap-1 rounded bg-modal-card-button-surface px-2 py-0 text-text-primary transition duration-150 ease-in-out hover:-translate-y-px hover:opacity-95"
|
class="h-8 gap-1 rounded-lg bg-modal-card-button-surface px-3 py-0 text-text-primary transition duration-150 ease-in-out hover:opacity-95"
|
||||||
type="transparent"
|
type="transparent"
|
||||||
:label="t('menuLabels.View')"
|
:label="action.label"
|
||||||
:aria-label="t('menuLabels.View')"
|
:aria-label="action.ariaLabel"
|
||||||
@click.stop="emit('view')"
|
@click.stop="action.onClick?.($event)"
|
||||||
/>
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="alwaysActions.length" class="flex items-center gap-1">
|
||||||
|
<template v-for="action in alwaysActions" :key="action.key">
|
||||||
<IconButton
|
<IconButton
|
||||||
v-if="props.showMenu !== undefined ? props.showMenu : true"
|
v-if="action.type === 'icon'"
|
||||||
v-tooltip.top="moreTooltipConfig"
|
v-tooltip.top="action.tooltip"
|
||||||
type="transparent"
|
:type="action.buttonType"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="size-6 transform gap-1 rounded bg-modal-card-button-surface text-text-primary transition duration-150 ease-in-out hover:-translate-y-px hover:opacity-95"
|
:class="getActionButtonClass()"
|
||||||
:aria-label="t('g.more')"
|
:aria-label="action.ariaLabel"
|
||||||
@click.stop="emit('menu', $event)"
|
@click.stop="action.onClick?.($event)"
|
||||||
>
|
>
|
||||||
<i class="icon-[lucide--more-horizontal] size-4" />
|
<i :class="cn(action.iconClass, 'size-4')" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
<TextButton
|
||||||
<div
|
v-else
|
||||||
v-else-if="props.state !== 'running'"
|
class="h-8 gap-1 rounded-lg bg-modal-card-button-surface px-3 py-0 text-text-primary transition duration-150 ease-in-out hover:opacity-95"
|
||||||
key="secondary"
|
type="transparent"
|
||||||
class="pr-2"
|
:label="action.label"
|
||||||
>
|
:aria-label="action.ariaLabel"
|
||||||
<slot name="secondary">{{ props.rightText }}</slot>
|
@click.stop="action.onClick?.($event)"
|
||||||
</div>
|
/>
|
||||||
</Transition>
|
</template>
|
||||||
<!-- Running job cancel button - always visible -->
|
</div>
|
||||||
<IconButton
|
|
||||||
v-if="props.state === 'running' && computedShowClear"
|
|
||||||
v-tooltip.top="cancelTooltipConfig"
|
|
||||||
type="transparent"
|
|
||||||
size="sm"
|
|
||||||
class="size-6 transform gap-1 rounded bg-destructive-background text-text-primary transition duration-150 ease-in-out hover:-translate-y-px hover:bg-destructive-background-hover hover:opacity-95"
|
|
||||||
:aria-label="t('g.cancel')"
|
|
||||||
@click.stop="emit('cancel')"
|
|
||||||
>
|
|
||||||
<i class="icon-[lucide--x] size-4" />
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,6 +217,9 @@ const { t } = useI18n()
|
|||||||
const cancelTooltipConfig = computed(() => buildTooltipConfig(t('g.cancel')))
|
const cancelTooltipConfig = computed(() => buildTooltipConfig(t('g.cancel')))
|
||||||
const deleteTooltipConfig = computed(() => buildTooltipConfig(t('g.delete')))
|
const deleteTooltipConfig = computed(() => buildTooltipConfig(t('g.delete')))
|
||||||
const moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more')))
|
const moreTooltipConfig = computed(() => buildTooltipConfig(t('g.more')))
|
||||||
|
const viewTooltipConfig = computed(() =>
|
||||||
|
buildTooltipConfig(t('menuLabels.View'))
|
||||||
|
)
|
||||||
|
|
||||||
const rowRef = ref<HTMLDivElement | null>(null)
|
const rowRef = ref<HTMLDivElement | null>(null)
|
||||||
const showDetails = computed(() => props.activeDetailsId === props.jobId)
|
const showDetails = computed(() => props.activeDetailsId === props.jobId)
|
||||||
@@ -324,6 +288,32 @@ const isAnyPopoverVisible = computed(
|
|||||||
() => showDetails.value || (isPreviewVisible.value && canShowPreview.value)
|
() => showDetails.value || (isPreviewVisible.value && canShowPreview.value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ActionVariant = 'neutral' | 'destructive'
|
||||||
|
type ActionMode = 'hover' | 'always'
|
||||||
|
|
||||||
|
type BaseActionConfig = {
|
||||||
|
key: string
|
||||||
|
variant: ActionVariant
|
||||||
|
mode: ActionMode
|
||||||
|
ariaLabel: string
|
||||||
|
tooltip?: ReturnType<typeof buildTooltipConfig>
|
||||||
|
isVisible: () => boolean
|
||||||
|
onClick?: (event?: MouseEvent) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type IconActionConfig = BaseActionConfig & {
|
||||||
|
type: 'icon'
|
||||||
|
iconClass: string
|
||||||
|
buttonType: 'secondary' | 'destructive'
|
||||||
|
}
|
||||||
|
|
||||||
|
type TextActionConfig = BaseActionConfig & {
|
||||||
|
type: 'text'
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionConfig = IconActionConfig | TextActionConfig
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
isAnyPopoverVisible,
|
isAnyPopoverVisible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
@@ -338,6 +328,109 @@ watch(
|
|||||||
|
|
||||||
const isHovered = ref(false)
|
const isHovered = ref(false)
|
||||||
|
|
||||||
|
const computedShowClear = computed(() => {
|
||||||
|
if (props.showClear !== undefined) return props.showClear
|
||||||
|
return props.state !== 'completed'
|
||||||
|
})
|
||||||
|
|
||||||
|
const baseActions = computed<ActionConfig[]>(() => {
|
||||||
|
const showMenu = props.showMenu !== undefined ? props.showMenu : true
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'menu',
|
||||||
|
type: 'icon',
|
||||||
|
variant: 'neutral',
|
||||||
|
buttonType: 'secondary',
|
||||||
|
mode: 'hover',
|
||||||
|
iconClass: 'icon-[lucide--more-horizontal]',
|
||||||
|
ariaLabel: t('g.more'),
|
||||||
|
tooltip: moreTooltipConfig.value,
|
||||||
|
isVisible: () => showMenu,
|
||||||
|
onClick: (event?: MouseEvent) => {
|
||||||
|
if (event) emit('menu', event)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'delete',
|
||||||
|
type: 'icon',
|
||||||
|
variant: 'destructive',
|
||||||
|
buttonType: 'destructive',
|
||||||
|
mode: 'hover',
|
||||||
|
iconClass: 'icon-[lucide--trash-2]',
|
||||||
|
ariaLabel: t('g.delete'),
|
||||||
|
tooltip: deleteTooltipConfig.value,
|
||||||
|
isVisible: () => props.state === 'failed' && computedShowClear.value,
|
||||||
|
onClick: () => emit('delete')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cancel-hover',
|
||||||
|
type: 'icon',
|
||||||
|
variant: 'destructive',
|
||||||
|
buttonType: 'destructive',
|
||||||
|
mode: 'hover',
|
||||||
|
iconClass: 'icon-[lucide--x]',
|
||||||
|
ariaLabel: t('g.cancel'),
|
||||||
|
tooltip: cancelTooltipConfig.value,
|
||||||
|
isVisible: () =>
|
||||||
|
props.state !== 'completed' &&
|
||||||
|
props.state !== 'running' &&
|
||||||
|
props.state !== 'failed' &&
|
||||||
|
computedShowClear.value,
|
||||||
|
onClick: () => emit('cancel')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'view',
|
||||||
|
type: 'icon',
|
||||||
|
variant: 'neutral',
|
||||||
|
buttonType: 'secondary',
|
||||||
|
mode: 'hover',
|
||||||
|
iconClass: 'icon-[lucide--zoom-in]',
|
||||||
|
ariaLabel: t('menuLabels.View'),
|
||||||
|
tooltip: viewTooltipConfig.value,
|
||||||
|
isVisible: () => props.state === 'completed',
|
||||||
|
onClick: () => emit('view')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'cancel-running',
|
||||||
|
type: 'icon',
|
||||||
|
variant: 'destructive',
|
||||||
|
buttonType: 'destructive',
|
||||||
|
mode: 'always',
|
||||||
|
iconClass: 'icon-[lucide--x]',
|
||||||
|
ariaLabel: t('g.cancel'),
|
||||||
|
tooltip: cancelTooltipConfig.value,
|
||||||
|
isVisible: () => props.state === 'running' && computedShowClear.value,
|
||||||
|
onClick: () => emit('cancel')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const hoverActions = computed(() =>
|
||||||
|
baseActions.value.filter(
|
||||||
|
(action) => action.mode === 'hover' && action.isVisible()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const alwaysActions = computed(() =>
|
||||||
|
baseActions.value.filter(
|
||||||
|
(action) => action.mode === 'always' && action.isVisible()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
isHovered.value = true
|
||||||
|
onRowEnter()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
isHovered.value = false
|
||||||
|
onRowLeave()
|
||||||
|
}
|
||||||
|
|
||||||
|
const getActionButtonClass = () =>
|
||||||
|
'h-8 min-w-8 gap-1 rounded-lg text-text-primary transition duration-150 ease-in-out hover:opacity-95'
|
||||||
|
|
||||||
const iconClass = computed(() => {
|
const iconClass = computed(() => {
|
||||||
if (props.iconName) return props.iconName
|
if (props.iconName) return props.iconName
|
||||||
return iconForJobState(props.state)
|
return iconForJobState(props.state)
|
||||||
@@ -350,11 +443,6 @@ const shouldSpin = computed(
|
|||||||
!props.iconImageUrl
|
!props.iconImageUrl
|
||||||
)
|
)
|
||||||
|
|
||||||
const computedShowClear = computed(() => {
|
|
||||||
if (props.showClear !== undefined) return props.showClear
|
|
||||||
return props.state !== 'completed'
|
|
||||||
})
|
|
||||||
|
|
||||||
const onContextMenu = (event: MouseEvent) => {
|
const onContextMenu = (event: MouseEvent) => {
|
||||||
const shouldShowMenu = props.showMenu !== undefined ? props.showMenu : true
|
const shouldShowMenu = props.showMenu !== undefined ? props.showMenu : true
|
||||||
if (shouldShowMenu) emit('menu', event)
|
if (shouldShowMenu) emit('menu', event)
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { cn } from '@comfyorg/tailwind-utils'
|
|||||||
import type { HTMLAttributes } from 'vue'
|
import type { HTMLAttributes } from 'vue'
|
||||||
|
|
||||||
export type ButtonSize = 'full-width' | 'fit-content' | 'sm' | 'md'
|
export type ButtonSize = 'full-width' | 'fit-content' | 'sm' | 'md'
|
||||||
type ButtonType = 'primary' | 'secondary' | 'transparent' | 'accent'
|
type ButtonType =
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'transparent'
|
||||||
|
| 'accent'
|
||||||
|
| 'destructive'
|
||||||
type ButtonBorder = boolean
|
type ButtonBorder = boolean
|
||||||
|
|
||||||
export interface BaseButtonProps {
|
export interface BaseButtonProps {
|
||||||
@@ -33,7 +38,10 @@ export const getButtonTypeClasses = (type: ButtonType = 'primary') => {
|
|||||||
'bg-transparent border-none text-muted-foreground hover:bg-secondary-background-hover'
|
'bg-transparent border-none text-muted-foreground hover:bg-secondary-background-hover'
|
||||||
),
|
),
|
||||||
accent:
|
accent:
|
||||||
'bg-primary-background hover:bg-primary-background-hover border-none text-white font-bold'
|
'bg-primary-background hover:bg-primary-background-hover border-none text-white font-bold',
|
||||||
|
destructive: cn(
|
||||||
|
'bg-destructive-background hover:bg-destructive-background-hover border-none text-base-foreground'
|
||||||
|
)
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
return baseByType[type]
|
return baseByType[type]
|
||||||
@@ -47,14 +55,18 @@ export const getBorderButtonTypeClasses = (type: ButtonType = 'primary') => {
|
|||||||
'bg-transparent text-base-foreground hover:bg-secondary-background-hover'
|
'bg-transparent text-base-foreground hover:bg-secondary-background-hover'
|
||||||
),
|
),
|
||||||
accent:
|
accent:
|
||||||
'bg-primary-background hover:bg-primary-background-hover text-white font-bold'
|
'bg-primary-background hover:bg-primary-background-hover text-white font-bold',
|
||||||
|
destructive: cn(
|
||||||
|
'bg-destructive-background hover:bg-destructive-background-hover text-base-foreground'
|
||||||
|
)
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const borderByType = {
|
const borderByType = {
|
||||||
primary: 'border border-solid border-base-background',
|
primary: 'border border-solid border-base-background',
|
||||||
secondary: 'border border-solid border-base-foreground',
|
secondary: 'border border-solid border-base-foreground',
|
||||||
transparent: 'border border-solid border-base-foreground',
|
transparent: 'border border-solid border-base-foreground',
|
||||||
accent: 'border border-solid border-primary-background'
|
accent: 'border border-solid border-primary-background',
|
||||||
|
destructive: 'border border-solid border-destructive-background'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
return `${baseByType[type]} ${borderByType[type]}`
|
return `${baseByType[type]} ${borderByType[type]}`
|
||||||
|
|||||||
Reference in New Issue
Block a user