style: 状态胶囊降噪重构——中性底+tone 色点;B2 右对齐标题栏;B3 浮动钮挪到聊天列右上角向下展开
This commit is contained in:
@@ -27,8 +27,8 @@ export function TitleBar({
|
||||
return (
|
||||
<div className="relative z-20 flex h-10 shrink-0 select-none items-center border-b border-line bg-surface pl-4 [-webkit-app-region:drag]">
|
||||
<div className="min-w-0 truncate text-[12.5px] font-medium text-ink">{name || '新会话'}</div>
|
||||
{extra}
|
||||
<div className="ml-auto flex h-full shrink-0 items-center gap-1 pr-1.5 [-webkit-app-region:no-drag]">
|
||||
{extra && <div className="ml-auto flex shrink-0 items-center pr-1 [-webkit-app-region:no-drag]">{extra}</div>}
|
||||
<div className={`flex h-full shrink-0 items-center gap-1 pr-1.5 [-webkit-app-region:no-drag] ${extra ? '' : 'ml-auto'}`}>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={panelOpen ? '关闭侧边面板' : '打开侧边面板'}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* 聊天区浮动胶囊(B3 槽位):与 TitleBar 胶囊(B2)同源数据——主进程单一事实源,
|
||||
* 插件 setStatusItem 后两处同时出现。折叠/展开双态:
|
||||
* - 折叠:右下角单个圆钮(首条图标 + 计数徽标)
|
||||
* - 展开:纵向胶囊堆栈;条目点击行为与 B2 一致(detail → 向上弹层 / openView → 深链)
|
||||
* - 折叠:聊天列右上角单个圆钮(首条图标 + 计数徽标)
|
||||
* - 展开:纵向胶囊堆栈;条目点击行为与 B2 一致(detail → 向下弹层 / openView → 深链)
|
||||
* 无条目不渲染;点外部 / Esc 收起。
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
@@ -10,7 +10,7 @@ import type { ReactNode } from 'react'
|
||||
import { LuPuzzle } from 'react-icons/lu'
|
||||
import type { PluginStatusEntry } from '@shared/types'
|
||||
import { PluginIconValue } from './plugin-icons'
|
||||
import { StatusPopover, TONE_CLASS } from './PluginStatusCapsules'
|
||||
import { StatusPopover, TONE_DOT_CLASS } from './PluginStatusCapsules'
|
||||
|
||||
export function PluginFloatingCapsules({
|
||||
entries,
|
||||
@@ -56,10 +56,7 @@ export function PluginFloatingCapsules({
|
||||
if (entries.length === 0) return null
|
||||
const shown = entries.slice(0, 4)
|
||||
return (
|
||||
<div
|
||||
ref={rootRef}
|
||||
className="absolute bottom-3 right-3 z-30 flex flex-col items-end gap-1.5 [-webkit-app-region:no-drag]"
|
||||
>
|
||||
<div ref={rootRef} className="absolute right-3 top-3 z-30 flex flex-col items-end gap-1.5 [-webkit-app-region:no-drag]">
|
||||
{open &&
|
||||
shown.map(({ pluginId, itemId, item }) => {
|
||||
const tone = item.tone ?? 'default'
|
||||
@@ -80,16 +77,15 @@ export function PluginFloatingCapsules({
|
||||
onOpenView(item.openView.view, item.openView.params)
|
||||
}
|
||||
}}
|
||||
className={`flex max-w-44 shrink-0 items-center gap-1 rounded-full border px-2.5 py-0.5 text-[11px] leading-[18px] shadow-btn transition-transform ${
|
||||
TONE_CLASS[tone] ?? TONE_CLASS.default
|
||||
} ${clickable ? 'hover:opacity-80 active:scale-[0.97]' : 'cursor-default'} bg-surface ${detailOpen ? 'opacity-80' : ''}`}
|
||||
className={`flex max-w-44 shrink-0 items-center gap-1.5 rounded-full border border-line bg-surface px-2 py-0.5 text-[11px] leading-[18px] text-ink-2 shadow-btn transition-colors ${
|
||||
clickable ? 'cursor-pointer hover:border-line-strong hover:text-ink' : 'cursor-default'
|
||||
} ${detailOpen ? 'border-line-strong text-ink' : ''}`}
|
||||
>
|
||||
{item.icon && <PluginIconValue icon={item.icon} size={12} className="shrink-0" />}
|
||||
<span aria-hidden className={`size-1.5 shrink-0 rounded-full ${TONE_DOT_CLASS[tone] ?? TONE_DOT_CLASS.default}`} />
|
||||
{item.icon ? <PluginIconValue icon={item.icon} size={12} className="shrink-0" /> : null}
|
||||
<span className="truncate">{item.text}</span>
|
||||
</button>
|
||||
{detailOpen && item.detail && (
|
||||
<StatusPopover entry={{ pluginId, itemId, item }} onOpenView={onOpenView} direction="up" />
|
||||
)}
|
||||
{detailOpen && item.detail && <StatusPopover entry={{ pluginId, itemId, item }} onOpenView={onOpenView} />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
@@ -102,14 +98,14 @@ export function PluginFloatingCapsules({
|
||||
setOpen((v) => !v)
|
||||
setDetailKey(null)
|
||||
}}
|
||||
className="relative flex size-9 items-center justify-center rounded-full border border-line bg-surface text-ink-2 shadow-raised transition-transform hover:text-ink active:scale-95"
|
||||
className="relative flex size-8 items-center justify-center rounded-full border border-line bg-surface text-ink-2 shadow-btn transition-transform hover:text-ink active:scale-95"
|
||||
>
|
||||
{entries[0]?.item.icon ? (
|
||||
<PluginIconValue icon={entries[0].item.icon} size={16} />
|
||||
<PluginIconValue icon={entries[0].item.icon} size={14} />
|
||||
) : (
|
||||
<LuPuzzle size={16} className="text-ink-3" />
|
||||
<LuPuzzle size={14} className="text-ink-3" />
|
||||
)}
|
||||
<span className="absolute -right-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-accent px-1 text-[10px] font-medium leading-none text-white">
|
||||
<span className="absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-accent px-1 text-[10px] font-medium leading-none text-white">
|
||||
{entries.length}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -11,14 +11,7 @@ import type { ReactNode } from 'react'
|
||||
import type { PluginStatusEntry } from '@shared/types'
|
||||
import { PluginIconValue } from './plugin-icons'
|
||||
|
||||
export const TONE_CLASS: Record<string, string> = {
|
||||
default: 'border-line text-ink-2',
|
||||
accent: 'border-accent/40 text-accent-ink bg-accent-tint',
|
||||
warn: 'border-orange/40 text-orange bg-orange-tint',
|
||||
danger: 'border-red/40 text-red bg-red-tint'
|
||||
}
|
||||
|
||||
const BADGE_TONE_CLASS: Record<string, string> = {
|
||||
export const BADGE_TONE_CLASS: Record<string, string> = {
|
||||
default: 'bg-hover text-ink-3',
|
||||
accent: 'bg-accent-tint text-accent-ink',
|
||||
warn: 'bg-orange-tint text-orange',
|
||||
@@ -87,6 +80,14 @@ export function StatusPopover({
|
||||
)
|
||||
}
|
||||
|
||||
/** 胶囊前导色点:tone 的安静表达(胶囊底保持中性,不整块染色) */
|
||||
export const TONE_DOT_CLASS: Record<string, string> = {
|
||||
default: 'bg-ink-3',
|
||||
accent: 'bg-accent',
|
||||
warn: 'bg-orange',
|
||||
danger: 'bg-red'
|
||||
}
|
||||
|
||||
export function PluginStatusCapsules({
|
||||
entries,
|
||||
onOpenView
|
||||
@@ -122,7 +123,7 @@ export function PluginStatusCapsules({
|
||||
|
||||
if (entries.length === 0) return null
|
||||
return (
|
||||
<div ref={rootRef} className="ml-3 flex min-w-0 items-center gap-1.5 [-webkit-app-region:no-drag]">
|
||||
<div ref={rootRef} className="flex min-w-0 items-center gap-1.5 [-webkit-app-region:no-drag]">
|
||||
{entries.slice(0, 4).map(({ pluginId, itemId, item }) => {
|
||||
const tone = item.tone ?? 'default'
|
||||
const key = `${pluginId}:${itemId}`
|
||||
@@ -138,11 +139,14 @@ export function PluginStatusCapsules({
|
||||
if (item.detail) setOpenKey(open ? null : key)
|
||||
else if (item.openView) onOpenView(item.openView.view, item.openView.params)
|
||||
}}
|
||||
className={`flex max-w-44 shrink-0 items-center gap-1 rounded-full border px-2.5 py-0.5 text-[11px] leading-[18px] transition-transform ${
|
||||
TONE_CLASS[tone] ?? TONE_CLASS.default
|
||||
} ${clickable ? 'hover:opacity-80 active:scale-[0.97]' : 'cursor-default'} ${open ? 'opacity-80' : ''}`}
|
||||
className={`flex max-w-44 shrink-0 items-center gap-1.5 rounded-full border border-line bg-surface px-2 py-0.5 text-[11px] leading-[18px] text-ink-2 transition-colors ${
|
||||
clickable ? 'cursor-pointer hover:border-line-strong hover:text-ink' : 'cursor-default'
|
||||
} ${open ? 'border-line-strong text-ink' : ''}`}
|
||||
>
|
||||
{item.icon && <PluginIconValue icon={item.icon} size={12} className="shrink-0" />}
|
||||
<span aria-hidden className={`size-1.5 shrink-0 rounded-full ${TONE_DOT_CLASS[tone] ?? TONE_DOT_CLASS.default}`} />
|
||||
{item.icon ? (
|
||||
<PluginIconValue icon={item.icon} size={12} className="shrink-0" />
|
||||
) : null}
|
||||
<span className="truncate">{item.text}</span>
|
||||
</button>
|
||||
{open && item.detail && <StatusPopover entry={{ pluginId, itemId, item }} onOpenView={onOpenView} />}
|
||||
|
||||
Reference in New Issue
Block a user