diff --git a/apps/desktop/src/main/__tests__/badge-converge-contract.test.ts b/apps/desktop/src/main/__tests__/badge-converge-contract.test.ts new file mode 100644 index 0000000000..011472f9d9 --- /dev/null +++ b/apps/desktop/src/main/__tests__/badge-converge-contract.test.ts @@ -0,0 +1,76 @@ +/** + * BADGE-CONVERGE-0 (issue #520 PR9): collapse the coexisting badge surfaces + * onto two canonical primitives, split by UI role: + * - pill `Badge` (packages/ui/src/primitives/badge.tsx) — emphasis markers + * - squared `Chip` (packages/ui/src/primitives/chip.tsx) — dense status rows + * + * Before: four tracks — + * 1. `PrimitiveBadge` (the Base UI primitive, aliased to avoid colliding + * with the legacy) + * 2. legacy `Badge` in `ui.tsx` (hand-written, raw emerald/amber colors) + * 3. `.settingsBadge` span (neutral label chip, CSS class) + * 4. `.settingsConnectionBadge` span (data-tone status chip, CSS class) + * + * After: `PrimitiveBadge` alias and the legacy `ui.tsx` Badge are gone; pill + * badge sites route through ``. The two settings CSS chips (3, 4) route + * through the squared `` primitive instead — see chip-converge-contract. + * Badge and Chip stay separate because settings rows need compact squared + * chips (radius-control), not pill emphasis markers. + */ + +import { strict as assert } from 'node:assert'; +import { readFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { describe, it } from 'node:test'; +import { REPO_ROOT } from './css-test-helpers.js'; + +/** Sites that now route through the pill Badge primitive (was PrimitiveBadge or legacy ui.tsx Badge). */ +const MIGRATED_FILES = [ + 'apps/desktop/src/renderer/settings/health-center-page.tsx', + 'apps/desktop/src/renderer/settings/permission-center-page.tsx', + 'apps/desktop/src/renderer/artifact-pane.tsx', + 'packages/ui/src/plan-reminder-panel.tsx', + 'packages/ui/src/permission-dialog.tsx', +]; + +const BADGE_PRIMITIVE = 'packages/ui/src/primitives/badge.tsx'; +const UI_BARREL = 'packages/ui/src/ui.tsx'; + +const BADGE_IMPORT_RE = + /import\s+\{[^}]*\bBadge\b[^}]*\}\s+from\s+['"][^'"]*?(?:@maka\/ui|primitives\/badge\.js)['"]/; + +describe('badge converge (#520 PR9)', () => { + it('canonical Badge primitive carries data-slot', async () => { + const src = await readFile(resolve(REPO_ROOT, BADGE_PRIMITIVE), 'utf8'); + assert.match(src, /["']?data-slot["']?\s*[:=]\s*["']badge["']/, 'Badge primitive must carry data-slot="badge"'); + assert.match(src, /export function Badge/, 'Badge primitive must export function Badge'); + }); + + it('legacy Badge + badgeVariants are gone from ui.tsx', async () => { + const src = await readFile(resolve(REPO_ROOT, UI_BARREL), 'utf8'); + assert.ok( + !/export function Badge\b/.test(src), + 'ui.tsx must not export the legacy hand-written Badge', + ); + // the legacy badgeVariants (raw emerald/amber cva) must be gone too + assert.ok( + !/emerald-500|amber-500/.test(src), + 'ui.tsx must not carry the legacy raw-color badgeVariants', + ); + }); + + it('migrated sites import Badge from @maka/ui', async () => { + for (const rel of MIGRATED_FILES) { + const src = await readFile(resolve(REPO_ROOT, rel), 'utf8'); + assert.ok(BADGE_IMPORT_RE.test(src), `${rel} must import Badge from @maka/ui`); + } + }); + + it('no remains (aliased name retired)', async () => { + for (const rel of MIGRATED_FILES) { + const src = await readFile(resolve(REPO_ROOT, rel), 'utf8'); + assert.ok(!/, not `); + } + }); + +}); diff --git a/apps/desktop/src/main/__tests__/card-converge-contract.test.ts b/apps/desktop/src/main/__tests__/card-converge-contract.test.ts new file mode 100644 index 0000000000..482390cbdf --- /dev/null +++ b/apps/desktop/src/main/__tests__/card-converge-contract.test.ts @@ -0,0 +1,68 @@ +/** + * CARD-CONVERGE-0 (issue #520 PR9): the hand-written settings card surfaces + * migrate onto a shared Card primitive so the container is the primitive (with + * `data-slot`), not a bare `
` carrying a hand-rolled class. + * + * - settingsRows (row-list container) + settingsMetricCard (metric tile) + + * maka-error-card (crash surface) → Card + * + * Card is intentionally thin (`data-slot="card"` + radius-surface): each site + * keeps its own layout/visual CSS, but the radius now comes from Card and the + * element carries `data-slot="card"`. maka-error-card stays on Card (not Alert) + * because it is a large crash surface with shadow-modal + stack
, not a
+ * small inline callout.
+ *
+ * The usage stats table is NOT on a public Table primitive: with only one HTML
+ *  consumer it was premature abstraction (PR9 review P3), so
+ * SimpleStatsTable keeps its styles inline in usage-settings-page. The table
+ * a11y semantics (aria-label + scope) are locked in settings-usage-contract.
+ */
+
+import { strict as assert } from 'node:assert';
+import { readFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+import { describe, it } from 'node:test';
+import { REPO_ROOT } from './css-test-helpers.js';
+
+/** Sites whose top-level container becomes . */
+const CARD_SITES = [
+  'apps/desktop/src/renderer/settings/settings-rows.tsx',
+  'apps/desktop/src/renderer/settings/settings-metric-card.tsx',
+  'apps/desktop/src/renderer/error-boundary.tsx',
+];
+
+/** Pages that used 
directly — must route through SettingsRows (Card-backed) now. */ +const SETTINGS_ROWS_CONSUMERS = [ + 'apps/desktop/src/renderer/settings/daily-review-settings-page.tsx', + 'apps/desktop/src/renderer/settings/web-search-settings-page.tsx', + 'apps/desktop/src/renderer/settings/memory-settings-page.tsx', +]; + +const CARD_PRIMITIVE = 'packages/ui/src/primitives/card.tsx'; + +const CARD_IMPORT_RE = + /import\s+\{[^}]*\bCard\b[^}]*\}\s+from\s+['"][^'"]*(?:@maka\/ui|primitives\/card)['"]/; + +describe('card converge (#520 PR9)', () => { + it('ships Card primitive with data-slot', async () => { + const card = await readFile(resolve(REPO_ROOT, CARD_PRIMITIVE), 'utf8'); + assert.match(card, /data-slot=["']card["']/, 'Card primitive must carry data-slot="card"'); + }); + + it('card sites import Card', async () => { + for (const rel of CARD_SITES) { + const src = await readFile(resolve(REPO_ROOT, rel), 'utf8'); + assert.ok(CARD_IMPORT_RE.test(src), `${rel} must import Card from @maka/ui`); + } + }); + + it('settingsRows consumer pages no longer use a bare
', async () => { + for (const rel of SETTINGS_ROWS_CONSUMERS) { + const src = await readFile(resolve(REPO_ROOT, rel), 'utf8'); + assert.ok( + !/ readFileSync(join(REPO_ROOT, rel), 'utf8'); + +// #520 PR9 commit 2: settings status chips converge onto a dedicated Chip +// primitive (squared, compact, status-tone), NOT the pill Badge primitive. +// Badge and Chip are two distinct UI roles — pill Badge for emphasis markers +// (health/permission center), squared Chip for dense settings status rows. +// +// This contract locks the role split (Chip radius-control not pill, Badge +// stays pill) AND the user-visible tokens of Chip (neutral bg/text, sm/default +// size geometry, status-tone alphas) so a cva class change that preserves the +// import is still caught. Token values reproduce the retired +// .settingsBadge (sm: 18px/400/0-6px padding/foreground-5) and +// .settingsConnectionBadge (default: 20px/600/2-8px/foreground-5, tone alphas +// /12 /14 /18 /15) CSS so settings visuals do not drift. +test('chip converge (#520 PR9)', async () => { + const chipSrc = read('packages/ui/src/primitives/chip.tsx'); + + // 1. Chip primitive exists + carries data-slot + assert.match(chipSrc, /export function Chip/, 'Chip primitive must be exported'); + assert.match(chipSrc, /["']?data-slot["']?\s*[:=]\s*["']chip["']/, 'Chip must carry data-slot="chip"'); + + // 2. Chip locks radius-control (squared), never pill — role split with Badge + assert.match(chipSrc, /rounded-\[var\(--radius-control\)\]/, 'Chip must use radius-control (squared, not pill)'); + assert.doesNotMatch(chipSrc, /rounded-\[var\(--radius-pill\)\]/, 'Chip must not regress to pill'); + + // 3. index.ts re-exports Chip + const indexSrc = read('packages/ui/src/index.ts'); + assert.match(indexSrc, /export (?:\*|\{[^}]*\bChip\b[^}]*\}) from ['"]\.\/primitives\/chip\.js['"]/, 'index.ts must re-export Chip'); + + // 4. settings CSS chips retired + const botCss = read('apps/desktop/src/renderer/styles/settings/bot.css'); + assert.doesNotMatch(botCss, /\.settingsBadge\s*\{/, '.settingsBadge CSS rule must be retired'); + const connCss = read('apps/desktop/src/renderer/styles/settings/connection.css'); + assert.doesNotMatch(connCss, /\.settingsConnectionBadge\s*[\{[,]/, '.settingsConnectionBadge CSS rule must be retired'); + + // 5. settings chip sites use the Chip primitive (squared status role, not pill Badge) + const CHIP_IMPORT_RE = + /import\s+\{[^}]*\bChip\b[^}]*\}\s+from\s+['"][^'"]*?(?:@maka\/ui|primitives\/chip\.js)['"]/; + const settingsChipFiles = [ + 'apps/desktop/src/renderer/settings/provider-connection-detail.tsx', + 'apps/desktop/src/renderer/settings/provider-add-form.tsx', + 'apps/desktop/src/renderer/settings/web-search-settings-page.tsx', + 'apps/desktop/src/renderer/settings/memory-settings-page.tsx', + 'apps/desktop/src/renderer/settings/account-settings-page.tsx', + 'apps/desktop/src/renderer/settings/provider-oauth-section.tsx', + ]; + for (const rel of settingsChipFiles) { + assert.match(read(rel), CHIP_IMPORT_RE, `${rel} must import Chip`); + } + + // 6. Chip neutral variant tokens — bg = foreground-5 (bg-secondary aliases + // --color-secondary = var(--foreground-5)), text = foreground-secondary + assert.match(chipSrc, /neutral: "bg-secondary text-\[var\(--foreground-secondary\)\]"/, 'Chip neutral bg must be foreground-5 (bg-secondary) and text foreground-secondary'); + + // 7. Chip size tokens — sm reproduces .settingsBadge (18px/400/0-6px), + // default reproduces .settingsConnectionBadge (20px/600/2-8px) + assert.match(chipSrc, /min-h-4\.5 px-\[var\(--space-1-5\)\] py-0 font-normal/, 'Chip sm size must reproduce .settingsBadge (18px / 400 / 0-6px padding)'); + assert.match(chipSrc, /min-h-5 px-\[var\(--space-2\)\] py-\[var\(--space-0-5\)\] font-semibold/, 'Chip default size must reproduce .settingsConnectionBadge (20px / 600 / 2-8px padding)'); + + // 8. Chip status-tone variant tokens — reproduce retired CSS oklch alphas + assert.match(chipSrc, /bg-info\/14/, 'Chip info variant must keep /14 alpha (matches .settingsConnectionBadge info)'); + assert.match(chipSrc, /bg-success\/12/, 'Chip success variant must keep /12 alpha'); + assert.match(chipSrc, /bg-warning\/18/, 'Chip warning variant must keep /18 alpha'); + assert.match(chipSrc, /bg-destructive\/15/, 'Chip destructive variant must keep /15 alpha (not solid red)'); + + // 9. Badge primitive stays pill — dual-track Badge (pill) + Chip (squared) preserved + const badgeSrc = read('packages/ui/src/primitives/badge.tsx'); + assert.match(badgeSrc, /rounded-\[var\(--radius-pill\)\]/, 'Badge stays pill (dual-track with Chip)'); +}); \ No newline at end of file diff --git a/apps/desktop/src/main/__tests__/radius-converge-contract.test.ts b/apps/desktop/src/main/__tests__/radius-converge-contract.test.ts index 7bba65b69c..564e9f7b9f 100644 --- a/apps/desktop/src/main/__tests__/radius-converge-contract.test.ts +++ b/apps/desktop/src/main/__tests__/radius-converge-contract.test.ts @@ -174,7 +174,7 @@ const COMPONENT_RADIUS: ComponentRadiusCheck[] = [ { file: 'packages/ui/src/ui.tsx', name: 'inputClasses', tier: 'control' }, { file: 'packages/ui/src/ui.tsx', name: 'SelectItem', tier: 'control' }, { file: 'packages/ui/src/ui.tsx', name: 'Toggle', tier: 'control' }, - { file: 'packages/ui/src/ui.tsx', name: 'badgeVariants', tier: 'pill' }, + // #520 PR9: legacy ui.tsx badgeVariants retired onto primitives/badge.tsx. // DialogPopup/AlertDialogPopup were merged into createModalContent (PR6 // review P3.1); the modal popup class now lives in MODAL_POPUP_CLASS. { file: 'packages/ui/src/ui.tsx', name: 'MODAL_POPUP_CLASS', tier: 'modal' }, @@ -383,7 +383,6 @@ describe('radius token governance (#406 gap 4)', () => { '.settingsHealthError': '--radius-surface', '.settingsHealthRefresh': '--radius-control', '.settingsBotHero': '--radius-surface', - '.settingsRows': '--radius-surface', '.settingsNotice': '--radius-surface', '.settingsAboutLogo': '--radius-surface', '.settingsAboutPrivacy': '--radius-surface', diff --git a/apps/desktop/src/main/__tests__/settings-form-a11y-contract.test.ts b/apps/desktop/src/main/__tests__/settings-form-a11y-contract.test.ts index 363df01358..ee01e38188 100644 --- a/apps/desktop/src/main/__tests__/settings-form-a11y-contract.test.ts +++ b/apps/desktop/src/main/__tests__/settings-form-a11y-contract.test.ts @@ -42,8 +42,12 @@ describe('Settings form accessibility labels', () => { it('keeps Settings secondary surfaces close to reference implementation card geometry', async () => { const styles = await readRendererContractCss(); const connectionRow = styles.match(/\.settingsConnectionRow\s*\{[\s\S]*?\}/)?.[0] ?? ''; - const connectionBadge = styles.match(/\.settingsConnectionBadge\s*\{[\s\S]*?\}/)?.[0] ?? ''; - const settingsBadge = styles.match(/\.settingsBadge\s*\{[\s\S]*?\}/)?.[0] ?? ''; + // #520 PR9: .settingsConnectionBadge / .settingsBadge CSS chips retired + // onto the squared Chip primitive. The "compact squared, not pill" intent + // now lives on Chip's cva base (rounded-[var(--radius-control)]). + const chipPrimitive = await readRepo('packages/ui/src/primitives/chip.tsx'); + const connectionBadge = chipPrimitive; + const settingsBadge = chipPrimitive; const authContract = styles.match(/\.settingsAuthContract\s*\{[\s\S]*?\}/)?.[0] ?? ''; // PR-DELETE-ORPHAN-CSS: `.providerEmpty` / `.providerCard` were // orphan classes (no TSX consumer); the comma-grouped rule @@ -92,13 +96,13 @@ describe('Settings form accessibility labels', () => { assert.match(providerCatalogBadge, /border-radius:\s*var\(--radius-control\);/, 'Provider catalog badges (category / preview / login) should use compact squared target-layout style corners, not pills'); assert.match(modelTableChip, /border-radius:\s*var\(--radius-control\);/, 'Settings model capability chips should use compact squared target-layout style corners, not pills'); assert.match(modelTableDefaultBadge, /border-radius:\s*var\(--radius-control\);/, 'Settings model default badge should use compact squared target-layout style corners, not pills'); - assert.match(connectionBadge, /border-radius:\s*var\(--radius-control\);/, 'Settings status badges should use compact squared target-layout style corners, not pills'); - assert.match(settingsBadge, /border-radius:\s*var\(--radius-control\);/, 'Generic Settings badges should use compact squared target-layout style corners, not pills'); + assert.match(connectionBadge, /rounded-\[var\(--radius-control\)\]/, 'Settings status badges (Chip primitive) should use compact squared target-layout style corners, not pills'); + assert.match(settingsBadge, /rounded-\[var\(--radius-control\)\]/, 'Generic Settings badges (Chip primitive) should use compact squared target-layout style corners, not pills'); assert.doesNotMatch(providerCatalogBadge, /border-radius:\s*var\(--radius-pill\);/, 'Provider catalog badges must not regress to pill-shaped chrome'); assert.doesNotMatch(modelTableChip, /border-radius:\s*var\(--radius-pill\);/, 'Settings model capability chips must not regress to pill-shaped chrome'); assert.doesNotMatch(modelTableDefaultBadge, /border-radius:\s*var\(--radius-pill\);/, 'Settings model default badge must not regress to pill-shaped chrome'); - assert.doesNotMatch(connectionBadge, /border-radius:\s*var\(--radius-pill\);/, 'Settings connection badges must not regress to pill-shaped chrome'); - assert.doesNotMatch(settingsBadge, /border-radius:\s*var\(--radius-pill\);/, 'Generic Settings badges must not regress to pill-shaped chrome'); + assert.doesNotMatch(connectionBadge, /rounded-\[var\(--radius-pill\)\]/, 'Settings connection badges (Chip primitive) must not regress to pill-shaped chrome'); + assert.doesNotMatch(settingsBadge, /rounded-\[var\(--radius-pill\)\]/, 'Generic Settings badges (Chip primitive) must not regress to pill-shaped chrome'); assert.match(settingsRow, /display:\s*grid;/, 'Settings rows should use a stable label/value grid instead of flex auto sizing'); assert.match(settingsRow, /grid-template-columns:\s*minmax\(150px,\s*0\.36fr\)\s+minmax\(0,\s*1fr\);/, 'Settings rows need a protected label column and shrinkable value column'); assert.match(settingsRowValue, /overflow-wrap:\s*anywhere;/, 'Long Settings values such as workspace paths should wrap in the value column'); diff --git a/apps/desktop/src/main/__tests__/settings-usage-contract.test.ts b/apps/desktop/src/main/__tests__/settings-usage-contract.test.ts index 73055514d8..c4c2498481 100644 --- a/apps/desktop/src/main/__tests__/settings-usage-contract.test.ts +++ b/apps/desktop/src/main/__tests__/settings-usage-contract.test.ts @@ -129,17 +129,17 @@ describe('Settings usage dashboard contract', () => { ); assert.match( simpleStatsTable, - /
/, + /\{props\.headers\.map\(\(header\) =>
\{header\}<\/th>\)\}<\/tr>/, + /\{cell\}<\/th>\s*\) : \(\s*\{cell\}<\/td>\s*\)/, + /cellIndex === 0 \? \(\s* { assert.ok(page, 'Web search settings page block must exist'); assert.match( page![0], - /
/, + //, 'Web search credential controls should sit in the shared grouped Settings card primitive', ); assert.match( page![0], - /
/, + //, 'Web search live-query controls should sit in the shared grouped Settings card primitive', ); for (const rowClass of [ diff --git a/apps/desktop/src/renderer/error-boundary.tsx b/apps/desktop/src/renderer/error-boundary.tsx index 3c47bf6a12..f17e48abf0 100644 --- a/apps/desktop/src/renderer/error-boundary.tsx +++ b/apps/desktop/src/renderer/error-boundary.tsx @@ -8,7 +8,7 @@ import { Component, type ErrorInfo, type ReactNode } from 'react'; import { AlertTriangle, Check, Clipboard, RotateCw } from '@maka/ui/icons'; -import { Button as UiButton, redactSecrets } from '@maka/ui'; +import { Button as UiButton, Card, redactSecrets } from '@maka/ui'; type State = { error: Error | null; @@ -101,7 +101,7 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, State> { return (
-
+ @@ -148,7 +148,7 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, State> {

剪贴板不可用或被系统拒绝;可以手动选择上面的错误摘要。

)}
-
+
); } diff --git a/apps/desktop/src/renderer/settings/account-settings-page.tsx b/apps/desktop/src/renderer/settings/account-settings-page.tsx index 770a400862..78d1303755 100644 --- a/apps/desktop/src/renderer/settings/account-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/account-settings-page.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import type { ConnectionTestResult, LlmConnection } from '@maka/core'; import { deriveProviderAuthContractFromConnection, generalizedErrorMessageChinese } from '@maka/core'; import { PROVIDER_DEFAULTS } from '@maka/core/llm-connections'; -import { Button, RelativeTime, useToast } from '@maka/ui'; +import { Button, Chip, RelativeTime, useToast } from '@maka/ui'; import { deriveAccountAuthActions, presentAccountAuthState, @@ -277,9 +277,9 @@ function AccountConnectionRow(props: {
{subtitle} - + {presentation.label} - +

{presentation.detail}

diff --git a/apps/desktop/src/renderer/settings/bot-chat-settings-page.tsx b/apps/desktop/src/renderer/settings/bot-chat-settings-page.tsx index a3735c85e0..9169b43669 100644 --- a/apps/desktop/src/renderer/settings/bot-chat-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/bot-chat-settings-page.tsx @@ -21,7 +21,6 @@ import { DialogContent, DialogRoot, Input, - PrimitiveBadge, RelativeTime, SettingsSelect, SettingsSwitch as Switch, diff --git a/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx b/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx index 832af9dd4c..61d6a3f853 100644 --- a/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/daily-review-settings-page.tsx @@ -3,6 +3,7 @@ import type { DailyReviewConfig, DailyReviewMode, LlmConnection } from '@maka/co import { Alert, AlertDescription, Button, Input, SettingsSelect, SettingsSwitch as Switch, useToast } from '@maka/ui'; import { buildCatalogDailyReviewModelOptions } from '../model-catalog-choices'; import { settingsActionErrorMessage } from './settings-error-copy'; +import { SettingsRows } from './settings-rows'; /** * PR-DAILY-REVIEW-MVP-0 follow-up: Settings → 每日回顾 is no longer @@ -157,7 +158,7 @@ export function DailyReviewSettingsPage(props: { connections: readonly LlmConnec ) : null} -
+
启用每日回顾 @@ -275,7 +276,7 @@ export function DailyReviewSettingsPage(props: { connections: readonly LlmConnec onChange={() => undefined} />
-
+
{(props.onOpenDailyReview || hasRunOnceIpc) && (
diff --git a/apps/desktop/src/renderer/settings/health-center-page.tsx b/apps/desktop/src/renderer/settings/health-center-page.tsx index 92b2e88913..0c993e7302 100644 --- a/apps/desktop/src/renderer/settings/health-center-page.tsx +++ b/apps/desktop/src/renderer/settings/health-center-page.tsx @@ -7,7 +7,7 @@ import type { HealthSnapshot, } from '@maka/core'; import { HEALTH_SIGNAL_LAYERS } from '@maka/core'; -import { Button, PrimitiveBadge, RelativeTime } from '@maka/ui'; +import { Button, Badge, RelativeTime } from '@maka/ui'; import { settingsActionErrorMessage } from './settings-error-copy'; import { statusBadgeVariant } from './settings-status-badge'; @@ -130,7 +130,7 @@ export function HealthCenterPage() {

- 只读快照 + 只读快照 最近一次读取: @@ -163,14 +163,14 @@ export function HealthCenterPage() { {(blocksSendCount > 0 || blocksCapabilityCount > 0) && (
{blocksSendCount > 0 && ( - + {blocksSendCount} 条健康信号会阻塞发送 - + )} {blocksCapabilityCount > 0 && ( - + {blocksCapabilityCount} 条健康信号会阻塞能力 - + )}
)} @@ -225,7 +225,7 @@ function HealthSignalRow(props: { signal: HealthSignal }) { {signal.label} {HEALTH_SCOPE_LABEL[signal.scope]}
- {statusCopy.label} + {statusCopy.label}

{signal.message}

{signal.detail && {signal.detail}} diff --git a/apps/desktop/src/renderer/settings/memory-settings-page.tsx b/apps/desktop/src/renderer/settings/memory-settings-page.tsx index 993951322c..6e3a280afe 100644 --- a/apps/desktop/src/renderer/settings/memory-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/memory-settings-page.tsx @@ -8,9 +8,10 @@ import { parseLocalMemoryMarkdown, setLocalMemoryEntryStatusDraft, } from '@maka/core'; -import { Button, Input, RelativeTime, SettingsSwitch as Switch, Textarea, redactSecrets, useToast } from '@maka/ui'; +import { Button, Chip, Input, RelativeTime, SettingsSwitch as Switch, Textarea, redactSecrets, useToast } from '@maka/ui'; import { openPathFailureCopy, openPathActionLabel } from '../open-path'; import { settingsActionErrorMessage } from './settings-error-copy'; +import { SettingsRows } from './settings-rows'; export function MemorySettingsPage(props: { settings: AppSettings; @@ -586,15 +587,15 @@ export function MemorySettingsPage(props: { return (
-
+
本地 MEMORY.md 透明 Markdown 文件,保存在当前本机工作区。这里的内容不会自动从聊天里抽取。
- + {memoryStatusLabel(effective.status)} - + void setWorkspaceInstructionsEnabled(enabled)} />
-
+ {workspaceInstructionState && (
diff --git a/apps/desktop/src/renderer/settings/permission-center-page.tsx b/apps/desktop/src/renderer/settings/permission-center-page.tsx index 6abb2d33b9..e03490dbce 100644 --- a/apps/desktop/src/renderer/settings/permission-center-page.tsx +++ b/apps/desktop/src/renderer/settings/permission-center-page.tsx @@ -18,7 +18,7 @@ import type { PermissionSnapshot, } from '@maka/core'; import { OS_PERMISSION_IDS } from '@maka/core'; -import { Button, PrimitiveBadge, RelativeTime, useToast } from '@maka/ui'; +import { Button, Badge, RelativeTime, useToast } from '@maka/ui'; import { settingsActionErrorMessage } from './settings-error-copy'; import { statusBadgeVariant } from './settings-status-badge'; @@ -385,7 +385,7 @@ function CapabilityRow(props: { capability: CapabilitySnapshot }) { {capability.label} {prettyCapabilityId(capability.id)}
- {readinessCopy.label} + {readinessCopy.label}

{readinessCopy.detail}

@@ -514,7 +514,7 @@ function OsPermissionRow(props: {
{label} - {stateCopy.label} + {stateCopy.label}
{purpose} {impact ? ( diff --git a/apps/desktop/src/renderer/settings/provider-add-form.tsx b/apps/desktop/src/renderer/settings/provider-add-form.tsx index 883311228b..de6f655610 100644 --- a/apps/desktop/src/renderer/settings/provider-add-form.tsx +++ b/apps/desktop/src/renderer/settings/provider-add-form.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react'; import { PROVIDER_DEFAULTS, validateSlug, type ProviderType } from '@maka/core'; -import { Button, Input } from '@maka/ui'; +import { Button, Chip, Input } from '@maka/ui'; import { buildCatalogRecommendedDefaultModel } from '../model-catalog-choices'; import { providerDisplay } from './provider-display'; import { @@ -83,7 +83,7 @@ export function AddProviderForm(props: { : isExperimental ? '账号登录暂未接入聊天发送' : `添加 ${display.name}`}

{display.description}

- {categoryLabel(defaults.category)} + {categoryLabel(defaults.category)} {isExperimental && (
diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx index 80e099d457..f4377797a4 100644 --- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx +++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx @@ -15,7 +15,7 @@ import { type ModelInfo, } from '@maka/core'; import { formatRelativeTimestamp } from '@maka/core'; -import { Button, FieldDescription, FieldRoot, Input, Label, useToast } from '@maka/ui'; +import { Button, Chip, FieldDescription, FieldRoot, Input, Label, useToast } from '@maka/ui'; import { PasswordInput } from './password-input'; import { buildCatalogModelChoices } from '../model-catalog-choices'; import { providerDisplay } from './provider-display'; @@ -375,8 +375,8 @@ export function ConnectionDetail(props: {

{display.name}

- {props.isDefault && 默认} - {categoryLabel(defaults.category)} + {props.isDefault && 默认} + {categoryLabel(defaults.category)} diff --git a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx index ad535965f7..f5276a48b3 100644 --- a/apps/desktop/src/renderer/settings/provider-oauth-section.tsx +++ b/apps/desktop/src/renderer/settings/provider-oauth-section.tsx @@ -7,6 +7,7 @@ import { type SubscriptionAccountState, } from '@maka/core'; import { + Chip, Button, Item, ItemActions, @@ -18,6 +19,7 @@ import { Textarea, useToast, } from '@maka/ui'; +import { type StatusTone } from './settings-status-badge'; import { ProviderLogo } from './provider-display'; import { ProviderSheet } from './provider-config-sheet'; @@ -680,7 +682,7 @@ function ClaudeSubscriptionCard() {
无法确认 Claude OAuth 是否可用。没有登录动作会被执行。 - 读取失败 + 读取失败 Claude 登录开关读取失败:{experimentalGateError} @@ -860,7 +862,7 @@ function ClaudeSubscriptionCard() { } // Closed-state render mapping per the runtime state enum. - const presentation = state ? presentSubscriptionState(state) : { label: '加载中…', tone: 'muted', detail: '' }; + const presentation = state ? presentSubscriptionState(state) : { label: '加载中…', tone: 'neutral' as const, detail: '' }; const canStartClaudeLogin = state?.runtimeState === 'not_logged_in' || state?.runtimeState === 'refresh_failed' || @@ -882,9 +884,9 @@ function ClaudeSubscriptionCard() { {state?.profile?.email ? ` · ${state.profile.email}` : ''} - + {presentation.label} - +

{presentation.detail}

{pasteError && !authRequestId && ( @@ -994,14 +996,14 @@ type ClaudeSubscriptionPendingAction = 'login' | 'submit' | 'cancel' | 'logout' interface SubscriptionStatePresentation { label: string; - tone: string; + tone: StatusTone; detail: string; } function presentSubscriptionState(state: SubscriptionAccountState): SubscriptionStatePresentation { switch (state.runtimeState) { case 'not_logged_in': - return { label: '未登录', tone: 'muted', detail: '使用 Claude 订阅配额前需要先登录。' }; + return { label: '未登录', tone: 'neutral', detail: '使用 Claude 订阅配额前需要先登录。' }; case 'authorizing': return { label: '登录中…', tone: 'info', detail: '请在弹出的浏览器窗口完成登录并粘贴授权码。' }; case 'authenticated': @@ -1037,6 +1039,6 @@ function presentSubscriptionState(state: SubscriptionAccountState): Subscription detail: subscriptionResultMessage(state.errorMessage, '订阅端点拒绝了请求,可能需要重新登录。'), }; default: - return { label: '未知状态', tone: 'muted', detail: '' }; + return { label: '未知状态', tone: 'neutral', detail: '' }; } } diff --git a/apps/desktop/src/renderer/settings/settings-metric-card.tsx b/apps/desktop/src/renderer/settings/settings-metric-card.tsx index b242f49e6b..a8e81cb1d8 100644 --- a/apps/desktop/src/renderer/settings/settings-metric-card.tsx +++ b/apps/desktop/src/renderer/settings/settings-metric-card.tsx @@ -1,10 +1,12 @@ +import { Card } from '@maka/ui'; + export function MetricCard(props: { title: string; value: string; detail?: string }) { return ( -
+ {props.title} {props.value} {props.detail && {props.detail}} -
+ ); } diff --git a/apps/desktop/src/renderer/settings/settings-rows.tsx b/apps/desktop/src/renderer/settings/settings-rows.tsx index bfd53de494..b191d2f3e6 100644 --- a/apps/desktop/src/renderer/settings/settings-rows.tsx +++ b/apps/desktop/src/renderer/settings/settings-rows.tsx @@ -1,7 +1,12 @@ import type { ReactNode } from 'react'; +import { Card } from '@maka/ui'; -export function SettingsRows(props: { children: ReactNode }) { - return
{props.children}
; +export function SettingsRows({ className, children }: { className?: string; children: ReactNode }) { + return ( + + {children} + + ); } export function SettingRow(props: { title: string; detail: string; value: string; mono?: boolean }) { diff --git a/apps/desktop/src/renderer/settings/usage-settings-page.tsx b/apps/desktop/src/renderer/settings/usage-settings-page.tsx index c5dd7c654b..f9b9f41f58 100644 --- a/apps/desktop/src/renderer/settings/usage-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/usage-settings-page.tsx @@ -258,21 +258,30 @@ function usageRequestStatusLabel(status: UsageStats['logs'][number]['status']) { } function SimpleStatsTable(props: { ariaLabel: string; headers: string[]; rows: Array>; empty?: string }) { + // Local table styles reproduce the retired Table primitive (now removed — a + // single consumer did not justify a public primitive). Values are inline so + // the stats surface stays self-contained until a second HTML consumer + // appears, at which point this can lift back to packages/ui. + const headClass = "border-b border-border px-[var(--space-2)] py-[var(--space-1)] text-left align-middle font-semibold text-foreground-secondary [font-variant-numeric:tabular-nums]"; + const cellClass = "border-b border-border px-[var(--space-2)] py-[var(--space-1)] text-left align-middle text-foreground-secondary [font-variant-numeric:tabular-nums]"; return ( -
+
- {props.headers.map((header) => )} + {props.headers.map((header) => )} {props.rows.length === 0 ? ( - + ) : props.rows.map((row, rowIndex) => ( {row.map((cell, cellIndex) => ( cellIndex === 0 ? ( - + ) : ( - + ) ))} diff --git a/apps/desktop/src/renderer/settings/web-search-settings-page.tsx b/apps/desktop/src/renderer/settings/web-search-settings-page.tsx index 260c6ae670..7bbef47a7d 100644 --- a/apps/desktop/src/renderer/settings/web-search-settings-page.tsx +++ b/apps/desktop/src/renderer/settings/web-search-settings-page.tsx @@ -1,9 +1,10 @@ import { useEffect, useRef, useState } from 'react'; import type { AppSettings, UpdateAppSettingsResult, WebSearchCredentialStatus } from '@maka/core'; import { normalizeSearchUrl, webSearchCredentialStatusFromResponse } from '@maka/core'; -import { Button, Input, RelativeTime, SettingsSwitch as Switch, redactSecrets, useToast } from '@maka/ui'; +import { Button, Chip, Input, RelativeTime, SettingsSwitch as Switch, redactSecrets, useToast } from '@maka/ui'; import { PasswordInput } from './password-input'; import { settingsActionErrorMessage } from './settings-error-copy'; +import { SettingsRows } from './settings-rows'; /** * PR-WEB-SEARCH-TAVILY-0: Settings → 联网搜索. @@ -236,7 +237,7 @@ export function WebSearchSettingsPage(props: { return (
-
+
启用联网搜索 @@ -244,9 +245,9 @@ export function WebSearchSettingsPage(props: {
- + {statusCopy.label} - + {hasCheckedAt && ( 最近测试 @@ -314,9 +315,9 @@ export function WebSearchSettingsPage(props: { )}
-
+
-
+
真实查询验证 @@ -361,7 +362,7 @@ export function WebSearchSettingsPage(props: { )}
-
+ {liveQueryError && (
diff --git a/apps/desktop/src/renderer/styles/chat-header.css b/apps/desktop/src/renderer/styles/chat-header.css index 7c27299b63..8b1c1e640f 100644 --- a/apps/desktop/src/renderer/styles/chat-header.css +++ b/apps/desktop/src/renderer/styles/chat-header.css @@ -290,7 +290,6 @@ max-width: 520px; padding: var(--space-3) var(--space-4); border: var(--border-width-hairline) solid oklch(from var(--destructive) l c h / 0.3); - border-radius: var(--radius-surface); background: var(--background-elevated); box-shadow: var(--shadow-modal); } diff --git a/apps/desktop/src/renderer/styles/settings/bot.css b/apps/desktop/src/renderer/styles/settings/bot.css index 26e737c1d5..50f3588ead 100644 --- a/apps/desktop/src/renderer/styles/settings/bot.css +++ b/apps/desktop/src/renderer/styles/settings/bot.css @@ -359,7 +359,6 @@ display: grid; align-content: center; gap: var(--space-0-5); - border-radius: var(--radius-surface); background: var(--foreground-5); padding: var(--space-1-5) var(--space-2-5); } @@ -374,28 +373,9 @@ font-size: var(--font-size-ui); } -.settingsStatsTable { - width: 100%; - border-collapse: collapse; - overflow: hidden; - border: var(--border-width-hairline) solid var(--border); - border-radius: var(--radius-surface); - font-size: var(--font-size-caption); -} - -.settingsStatsTable th, -.settingsStatsTable td { - font-variant-numeric: tabular-nums; - border-bottom: var(--border-width-hairline) solid var(--border); - color: var(--foreground-secondary); - padding: var(--space-1) var(--space-2); - text-align: left; -} - -.settingsStatsTable th { - color: var(--foreground-secondary); - font-weight: var(--font-weight-semibold); -} +/* #520 PR9: .settingsStatsTable retired onto the local native + SimpleStatsTable in usage-settings-page.tsx (inline Tailwind classes, + no public primitive — a single HTML
{header}
{header}
{props.empty ?? '暂无请求记录'}
{props.empty ?? '暂无请求记录'}
{cell}{cell}{cell}{cell}
consumer did not justify one). */ .settingsCloseButton { width: 24px; @@ -432,16 +412,9 @@ transition: background var(--duration-base) var(--ease-out-strong); } -.settingsBadge { - display: inline-flex; - align-items: center; - min-height: 18px; - border-radius: var(--radius-control); - background: var(--foreground-5); - color: var(--foreground-secondary); - padding: 0 var(--space-1-5); - font-size: var(--font-size-caption); -} +/* #520 PR9: .settingsBadge retired onto the Chip primitive variant="neutral" + (packages/ui/src/primitives/chip.tsx, size="sm"). Generic settings labels + stay squared (radius-control), not pill Badge. */ /* PR-SETTINGS-GROUPED-CARD-0 (WAWQAQ msg `1abecd66`): same grouped- card pattern as `.settingsStructuredPage` — outer card + hairline @@ -450,7 +423,6 @@ display: grid; gap: 0; border: var(--border-width-hairline) solid oklch(from var(--foreground) l c h / 0.08); - border-radius: var(--radius-surface); overflow: hidden; } diff --git a/apps/desktop/src/renderer/styles/settings/connection.css b/apps/desktop/src/renderer/styles/settings/connection.css index eab2000f3c..0bad1f1bef 100644 --- a/apps/desktop/src/renderer/styles/settings/connection.css +++ b/apps/desktop/src/renderer/styles/settings/connection.css @@ -126,37 +126,10 @@ font-family: var(--font-mono); font-size: var(--font-size-caption); } -.settingsConnectionBadge { - display: inline-flex; - align-items: center; - min-height: 20px; - padding: var(--space-0-5) var(--space-2); - border-radius: var(--radius-control); - font-size: var(--font-size-caption); - font-weight: var(--font-weight-semibold); - letter-spacing: var(--tracking-normal); - background: var(--foreground-5); - color: var(--foreground-secondary); - white-space: nowrap; - } -.settingsConnectionBadge[data-tone="success"] { - background: oklch(from var(--success) l c h / 0.12); - color: var(--success); - } -.settingsConnectionBadge[data-tone="info"] { - background: oklch(from var(--info) l c h / 0.14); - color: var(--info-text); - } -.settingsConnectionBadge[data-tone="warning"] { - background: oklch(from var(--info) l c h / 0.18); - color: var(--info-text); - font-weight: var(--font-weight-bold); - } -.settingsConnectionBadge[data-tone="destructive"] { - background: oklch(from var(--destructive) l c h / 0.15); - color: var(--destructive); - font-weight: var(--font-weight-bold); - } +/* #520 PR9: .settingsConnectionBadge + data-tone retired onto the Chip + primitive (packages/ui/src/primitives/chip.tsx); variants mirror StatusTone + directly, so settings callers pass the tone without statusBadgeVariant. The + warning tone no longer misuses --info. */ .settingsConnectionDetail { margin: 0; color: var(--foreground-secondary); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 9ae2653163..7f50e728b3 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -30,6 +30,7 @@ export * from './utils.js'; export * from './bot-brand.js'; export * from './bot-brand-logo.js'; export * from './primitives/alert.js'; +export * from './primitives/card.js'; // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are // deliberately NOT re-exported here: they are internal styling tables / a // single-consumer dot that the chat call sites apply via relative import, so @@ -91,13 +92,16 @@ export { AccordionPanel as PrimitiveAccordionPanel, AccordionPrimitive as PrimitiveAccordionPrimitive, } from './primitives/accordion.js'; -// PR-USE-SHADCN-BASE-UI-BADGE: the canonical shadcn/base-ui Badge primitive -// (variants: default / destructive / error / info / outline / secondary / -// success / warning). Aliased to PrimitiveBadge so it doesn't collide with -// the legacy `Badge` exported from `ui.tsx`; consumers can pick the version -// they want by import name. -export { - Badge as PrimitiveBadge, - badgeVariants as primitiveBadgeVariants, -} from './primitives/badge.js'; -export type { BadgeProps as PrimitiveBadgeProps } from './primitives/badge.js'; +// PR-USE-SHADCN-BASE-UI-BADGE: the canonical pill Badge primitive. #520 PR9 +// collapsed the legacy ui.tsx Badge onto this one. Badge is the pill emphasis +// marker (health/permission center). Variants: default / destructive / error +// / info / outline / secondary / success / warning. +export { Badge, badgeVariants } from './primitives/badge.js'; +export type { BadgeProps } from './primitives/badge.js'; +// PR-USE-SHADCN-BASE-UI-CHIP: squared compact status label. #520 PR9 collapsed +// .settingsBadge + .settingsConnectionBadge CSS chips onto this one. Chip is +// the squared (radius-control) counterpart to pill Badge, for dense settings +// status rows. Variants mirror StatusTone: neutral / info / success / warning +// / destructive. +export { Chip, chipVariants } from './primitives/chip.js'; +export type { ChipProps } from './primitives/chip.js'; diff --git a/packages/ui/src/permission-dialog.tsx b/packages/ui/src/permission-dialog.tsx index a9ce396d7d..7ea05bf2bb 100644 --- a/packages/ui/src/permission-dialog.tsx +++ b/packages/ui/src/permission-dialog.tsx @@ -4,7 +4,8 @@ import { derivePermissionRequestHealth, formatPermissionRequestWait } from '@mak import { AlertOctagon, AlertTriangle, FileEdit, GitMerge, Globe, HelpCircle, ShieldAlert, Terminal, Wifi } from './icons.js'; import { Alert, AlertDescription } from './primitives/alert.js'; import { Collapsible, CollapsibleTrigger, CollapsiblePanel } from './primitives/collapsible.js'; -import { Badge, Button as UiButton, Checkbox, AlertDialogContent, AlertDialogRoot } from './ui.js'; +import { Button as UiButton, Checkbox, AlertDialogContent, AlertDialogRoot } from './ui.js'; +import { Badge } from './primitives/badge.js'; import { redactSecrets } from './redact.js'; import { formatRedactedJson } from './tool-format.js'; diff --git a/packages/ui/src/plan-reminder-panel.tsx b/packages/ui/src/plan-reminder-panel.tsx index dbb37c8213..34fc911d62 100644 --- a/packages/ui/src/plan-reminder-panel.tsx +++ b/packages/ui/src/plan-reminder-panel.tsx @@ -52,7 +52,6 @@ import { toPlanReminderDateTimeInputValue, } from './plan-reminder-helpers.js'; import { - Badge, Button as UiButton, DialogClose, DialogContent, @@ -65,6 +64,7 @@ import { TabsTrigger, Textarea as UiTextarea, } from './ui.js'; +import { Badge } from './primitives/badge.js'; import { Alert, AlertDescription, AlertTitle } from './primitives/alert.js'; import { Menu, MenuItem, MenuPopup, MenuTrigger } from './primitives/menu.js'; import { EmptyState } from './empty-state.js'; diff --git a/packages/ui/src/primitives/card.tsx b/packages/ui/src/primitives/card.tsx new file mode 100644 index 0000000000..1cfa4f7ec9 --- /dev/null +++ b/packages/ui/src/primitives/card.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { forwardRef } from "react"; +import type { ComponentPropsWithoutRef } from "react"; +import { cn } from "../utils.js"; + +/** + * Card — shared surface container for settings card surfaces (row-list + * containers, metric tiles, the renderer crash surface). Intentionally thin + * (maka's ChoiceCard philosophy): it contributes `data-slot="card"` plus the + * surface radius, and each call site keeps its own layout/visual CSS (grid, + * padding, border, background) via `className`. + * + * Why thin and not shadcn-heavy: settingsRows (row-list), settingsMetricCard + * (metric tile), and maka-error-card (crash surface) share only the surface + * radius; their border / background / padding all differ, so a heavy default + * (`border bg-card shadow`) would have to be overridden at every site. Thin + * keeps each site byte-identical while unifying on the `data-slot` hook that + * the style-hook convention (#520 PR5 item 23) and converge contracts key on. + */ +export type CardProps = ComponentPropsWithoutRef<"div">; + +export const Card = forwardRef(function Card( + { className, ...props }, + ref, +) { + return ( +
+ ); +}); diff --git a/packages/ui/src/primitives/chip.tsx b/packages/ui/src/primitives/chip.tsx new file mode 100644 index 0000000000..d352398ef7 --- /dev/null +++ b/packages/ui/src/primitives/chip.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; +import { cn } from "../utils.js"; +import { cva, type VariantProps } from "class-variance-authority"; +import type React from "react"; + +// Chip is the squared, compact status label for dense information rows +// (settings connection status, capability chips, default markers). It is the +// squared counterpart to the pill Badge primitive: +// - Badge = emphasis marker (pill, radius-pill) — health/permission center +// - Chip = status label (squared, radius-control) — settings rows +// Variants mirror StatusTone so settings callers pass the tone straight +// through with no mapping function. Visual values reproduce the retired +// .settingsConnectionBadge oklch alphas (success /12, info /14, warning /18, +// destructive /15) and the .settingsBadge neutral (foreground-5) base. +export const chipVariants = cva( + "inline-flex items-center whitespace-nowrap rounded-[var(--radius-control)] text-xs outline-none [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + defaultVariants: { + size: "default", + variant: "neutral", + }, + variants: { + size: { + default: + "min-h-5 px-[var(--space-2)] py-[var(--space-0-5)] font-semibold", + sm: "min-h-4.5 px-[var(--space-1-5)] py-0 font-normal", + }, + variant: { + neutral: "bg-secondary text-[var(--foreground-secondary)]", + info: "bg-info/14 text-info-foreground", + success: "bg-success/12 text-success", + warning: "bg-warning/18 text-warning-foreground font-bold", + destructive: "bg-destructive/15 text-destructive font-bold", + }, + }, + }, +); + +export interface ChipProps extends useRender.ComponentProps<"span"> { + variant?: VariantProps["variant"]; + size?: VariantProps["size"]; +} + +export function Chip({ + className, + variant, + size, + render, + ...props +}: ChipProps): React.ReactElement { + const defaultProps = { + className: cn(chipVariants({ className, size, variant })), + "data-slot": "chip", + }; + + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">(defaultProps, props), + render, + }); +} \ No newline at end of file diff --git a/packages/ui/src/ui.tsx b/packages/ui/src/ui.tsx index 78ee88db97..2df7665851 100644 --- a/packages/ui/src/ui.tsx +++ b/packages/ui/src/ui.tsx @@ -114,33 +114,6 @@ export const Button = forwardRef(function Button( ); }); -export const badgeVariants = cva( - 'inline-flex items-center gap-1 rounded-[var(--radius-pill)] border px-2 py-0.5 text-xs font-medium tabular-nums', - { - variants: { - variant: { - default: 'border-transparent bg-accent/10 text-accent', - secondary: 'border-border bg-secondary text-secondary-foreground', - success: 'border-emerald-500/20 bg-emerald-500/10 text-emerald-700', - warning: 'border-amber-500/25 bg-amber-500/10 text-amber-800', - destructive: 'border-destructive/25 bg-destructive/10 text-destructive', - muted: 'border-border bg-muted text-foreground-secondary', - }, - }, - defaultVariants: { - variant: 'default', - }, - }, -); - -export interface BadgeProps - extends React.HTMLAttributes, - VariantProps {} - -export function Badge({ className, variant, ...props }: BadgeProps) { - return ; -} - export const inputClasses = [ 'flex min-h-9 w-full rounded-sm border border-input bg-[oklch(from_var(--foreground)_l_c_h_/_0.02)] px-3 py-2 text-sm text-foreground shadow-sm', 'placeholder:text-foreground-secondary/70', diff --git a/packages/ui/stories/badge.stories.tsx b/packages/ui/stories/badge.stories.tsx index 6bcfdfbf5c..4f5e6b9727 100644 --- a/packages/ui/stories/badge.stories.tsx +++ b/packages/ui/stories/badge.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Badge } from '../src/ui.js'; -import { Badge as PrimitiveBadge } from '../src/primitives/badge.js'; +import { Badge } from '../src/primitives/badge.js'; const meta = { title: 'Primitives/Badge', @@ -13,43 +12,31 @@ export default meta; type Story = StoryObj; -const UI_VARIANTS = ['default', 'secondary', 'success', 'warning', 'destructive', 'muted'] as const; - -export const UiBadgeVariants: Story = { - render: () => ( -
- {UI_VARIANTS.map((variant) => ( -
- {variant} - {variant} - {variant} 12 -
- ))} -
- ), -}; - -const PRIM_VARIANTS = ['default', 'destructive', 'error', 'info', 'outline', 'secondary', 'success', 'warning'] as const; +// #520 PR9: the legacy ui.tsx Badge + .settingsBadge/.settingsConnectionBadge +// CSS chips collapsed onto this one primitive. Variants below cover every +// status tone statusBadgeVariant maps onto (success/warning/destructive/info +// /neutral-as-secondary) plus the rest of the shipped set. +const VARIANTS = ['default', 'destructive', 'error', 'info', 'outline', 'secondary', 'success', 'warning'] as const; const SIZES = ['sm', 'default', 'lg'] as const; -export const PrimitiveBadgeMatrix: Story = { +export const BadgeMatrix: Story = { render: () => (
- {PRIM_VARIANTS.map((v) => ( + {VARIANTS.map((v) => ( {v} ))}
{SIZES.map((size) => (
{size} - {PRIM_VARIANTS.map((variant) => ( - + {VARIANTS.map((variant) => ( + {variant} - + ))}
))}
), -}; \ No newline at end of file +};