Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions apps/desktop/src/renderer/settings/permission-center-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type {
PermissionSnapshot,
} from '@maka/core';
import { OS_PERMISSION_IDS } from '@maka/core';
import { Button, Badge, RelativeTime, PageHeader, StatTile, useToast } from '@maka/ui';
import { Button, Badge, RelativeTime, PageHeader, SectionHeader, StatTile, useToast } from '@maka/ui';
import { settingsActionErrorMessage } from './settings-error-copy';
import { statusBadgeVariant } from './settings-status-badge';
import { SettingsSkeletonStack } from './settings-skeleton';
Expand Down Expand Up @@ -248,21 +248,23 @@ export function PermissionCenterPage() {
</section>

<section aria-label="功能能力" className="settingsPermissionSection">
<header className="settingsPermissionSectionHeader">
<div>
<h4>功能能力</h4>
<small>每个能力的就绪状态由「功能开关 · 配置 · 系统权限 · 运行态探测」共同决定。</small>
</div>
<Button
type="button"
variant="secondary"
size="sm"
onClick={() => setDiagnosticsOpen((open) => !open)}
aria-expanded={diagnosticsOpen}
>
{diagnosticsOpen ? '收起详情' : '展开详情'}
</Button>
</header>
<SectionHeader
className="settingsPermissionSectionHeader"
as="h4"
title="功能能力"
subtitle="每个能力的就绪状态由「功能开关 · 配置 · 系统权限 · 运行态探测」共同决定。"
action={
<Button
type="button"
variant="secondary"
size="sm"
onClick={() => setDiagnosticsOpen((open) => !open)}
aria-expanded={diagnosticsOpen}
>
{diagnosticsOpen ? '收起详情' : '展开详情'}
</Button>
}
/>
<ul className="settingsCapabilityList" aria-label="功能能力列表" data-diagnostics-open={diagnosticsOpen ? 'true' : undefined}>
{capabilities.capabilities.map((capability) => (
<CapabilityRow key={capability.id} capability={capability} />
Expand Down
7 changes: 0 additions & 7 deletions apps/desktop/src/renderer/styles/settings/permission.css
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,6 @@
/* Diagnostic 5-column capability detail collapsed by default so the
page reads as "system permissions" first. Expand on click. */

.settingsPermissionSectionHeader {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--space-3);
}

.settingsCapabilityList:not([data-diagnostics-open="true"]) .settingsCapabilityLayers,
.settingsCapabilityList:not([data-diagnostics-open="true"]) .settingsCapabilityGuidance,
.settingsCapabilityList:not([data-diagnostics-open="true"]) .settingsCapabilityAuditSlot,
Expand Down
12 changes: 8 additions & 4 deletions packages/ui/src/daily-review-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { SettingsSegmented } from './primitives/settings-segmented.js';
import { Alert, AlertAction, AlertDescription } from './primitives/alert.js';
import { EmptyState } from './empty-state.js';
import { StatTile } from './primitives/stat-tile.js';
import { SectionHeader } from './primitives/section-header.js';
import type { DailyReviewBridge, DailyReviewMarkdownActionInput } from './module-panel-types.js';
import { RelativeTime } from './relative-time.js';
import { Markdown } from './markdown.js';
Expand Down Expand Up @@ -366,10 +367,13 @@ export function DailyReviewPanel(props: {
)}
{canLoadArchives && (
<section className="maka-daily-review-archives" aria-label="已生成报告">
<div className="maka-daily-review-archives-header">
<h4 className="maka-daily-review-section-title">已生成报告</h4>
<span className="maka-daily-review-archive-count">{archives.length} 份</span>
</div>
<SectionHeader
className="maka-daily-review-archives-header"
as="h4"
accent
title="已生成报告"
count={<span className="maka-daily-review-archive-count">{archives.length} 份</span>}
/>
{archiveError && (
<Alert variant="warning" className="maka-daily-review-alert">
<AlertDescription>回顾报告读取失败:{archiveError}</AlertDescription>
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export * from './primitives/kbd.js';
export * from './primitives/menu.js';
export * from './primitives/dialog-header.js';
export * from './primitives/stat-tile.js';
export * from './primitives/section-header.js';
export * from './primitives/choice-card.js';
export * from './primitives/settings-segmented.js';
export * from './primitives/settings-select.js';
Expand Down
77 changes: 77 additions & 0 deletions packages/ui/src/primitives/section-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// packages/ui/src/primitives/section-header.tsx
//
// Convergence round 5: the ONE "section title (+ count / subtitle) +
// trailing action" header. Before this, three dialects were hand-rolled:
// skills' span-row (label + N 个), daily-review's h4 with a colored
// accent bar, and permission's h4 + small + trailing button.
//
// Styled with Tailwind utilities (portable, like DialogHeader/StatTile);
// call sites keep their wrapper classes for page-pinned CSS placement.

import type { ReactNode } from 'react';
import { cn } from '../utils.js';

export interface SectionHeaderProps {
title: ReactNode;
/** Muted trailing count (e.g. "3 个" / "2 份"). */
count?: ReactNode;
/** One quiet line under the title (permission's layer explainer). */
subtitle?: ReactNode;
/** Right-aligned action cluster (button / filter pills). */
action?: ReactNode;
/** Leading accent bar (daily-review's section marker). */
accent?: boolean;
as?: 'h4' | 'span';
className?: string;
}

export function SectionHeader({
title,
count,
subtitle,
action,
accent = false,
as: Tag = 'span',
className,
}: SectionHeaderProps) {
return (
<div
className={cn('flex min-w-0 items-center justify-between gap-3', className)}
data-slot="section-header"
>
<div className="min-w-0">
<Tag
className={cn(
'm-0 inline-flex items-center gap-2 text-[length:var(--font-size-ui)] font-semibold text-foreground',
accent &&
'before:inline-block before:h-3 before:w-[3px] before:rounded-[var(--radius-pill)] before:bg-[oklch(from_var(--status-running)_l_c_h_/_0.85)] before:content-[""]',
)}
data-slot="section-header-title"
>
{title}
{count != null && (
<small
className="text-[length:var(--font-size-caption)] font-normal text-[color:var(--muted-foreground)] [font-variant-numeric:tabular-nums]"
data-slot="section-header-count"
>
{count}
</small>
)}
</Tag>
{subtitle != null && (
<small
className="mt-0.5 block text-[length:var(--font-size-caption)] font-normal text-[color:var(--foreground-secondary)]"
data-slot="section-header-subtitle"
>
{subtitle}
</small>
)}
</div>
{action != null && (
<div className="flex shrink-0 items-center gap-2" data-slot="section-header-action">
{action}
</div>
)}
</div>
);
}
50 changes: 28 additions & 22 deletions packages/ui/src/skills-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { PageHeader } from './primitives/page-header.js';
import { Input } from './primitives/input.js';
import { SettingsSelect, type SettingsSelectOption } from './primitives/settings-select.js';
import { EmptyState } from './empty-state.js';
import { SectionHeader } from './primitives/section-header.js';
import { CapabilityAuditStrip } from './capability-audit-strip.js';
import type { ManagedSkillCategory, ManagedSkillSourceEntry, ManagedSkillUpdatePreview, SkillEntry } from './module-panel-types.js';

Expand Down Expand Up @@ -225,20 +226,23 @@ function SkillLibraryPanel(props: {

const market = (
<section className="maka-skill-market" aria-label="技能市场">
<div className="maka-skill-section-row">
<span className="maka-skill-section-label">官方精选</span>
<div className="maka-skill-filter-actions" aria-label="来源库操作">
<UiButton
type="button"
variant="secondary"
className="maka-skill-filter-pill"
onClick={props.onImportManagedSkillSource}
disabled={!props.onImportManagedSkillSource || props.actionBusy}
>
导入本地 Skill
</UiButton>
</div>
</div>
<SectionHeader
className="maka-skill-section-row"
title={<span className="maka-skill-section-label">官方精选</span>}
action={
<div className="maka-skill-filter-actions" aria-label="来源库操作">
<UiButton
type="button"
variant="secondary"
className="maka-skill-filter-pill"
onClick={props.onImportManagedSkillSource}
disabled={!props.onImportManagedSkillSource || props.actionBusy}
>
导入本地 Skill
</UiButton>
</div>
}
/>
{allManagedSources.length === 0 ? (
<EmptyState
Icon={BookOpen}
Expand Down Expand Up @@ -320,10 +324,11 @@ function SkillLibraryPanel(props: {
/>
) : (
<>
<div className="maka-skill-section-row">
<span className="maka-skill-section-label">{label}</span>
<small>{list.length} 个</small>
</div>
<SectionHeader
className="maka-skill-section-row"
title={<span className="maka-skill-section-label">{label}</span>}
count={`${list.length} 个`}
/>
<ul className="maka-skill-library-list" aria-label="技能列表">
{list.map((skill) => {
const tools = skill.declaredTools ?? [];
Expand Down Expand Up @@ -415,10 +420,11 @@ function SkillLibraryPanel(props: {

const updateReview = updatePreview ? (
<section className="maka-skill-governance-review" aria-label="Skill 更新审查">
<div className="maka-skill-section-row">
<span className="maka-skill-section-label">更新审查</span>
<small>{formatSkillStatusLabel(updatePreview.skill)}</small>
</div>
<SectionHeader
className="maka-skill-section-row"
title={<span className="maka-skill-section-label">更新审查</span>}
count={formatSkillStatusLabel(updatePreview.skill)}
/>
<div className="maka-skill-governance-summary">
<span>{updatePreview.skill.name}</span>
<span>{updatePreview.skill.managedSourceId ? `来源 ${updatePreview.skill.managedSourceId}` : '受管理来源'}</span>
Expand Down
Loading