feat: 添加模型上下文管理并统一主题组件与用量交互

This commit is contained in:
2026-09-05 21:58:37 +08:00
parent 031ab135d2
commit 7551716e13
33 changed files with 738 additions and 38 deletions
+36
View File
@@ -187,3 +187,39 @@ progress:not([value]) { background: linear-gradient(90deg, var(--color-backgroun
.feature-page { padding: var(--space-lg); }
.split-view { grid-template-columns: 1fr; }
}
/* Shared select and disclosure chrome, including the expanded surface. */
:where(select:not([multiple]):not([size])), .select:not([multiple]):not([size]) {
appearance: none;
box-sizing: border-box;
min-height: 38px;
padding: 0 32px 0 12px;
border: 1px solid var(--color-border-default);
border-radius: var(--radius-md);
color: var(--color-text-primary);
background-color: var(--color-surface-primary);
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: calc(100% - 17px) 50%, calc(100% - 12px) 50%;
background-size: 5px 5px;
background-repeat: no-repeat;
cursor: pointer;
}
.select { padding-right: 32px; }
:where(select option, select optgroup) { color: var(--color-text-primary); background: var(--color-surface-elevated); font: inherit; }
:where(select:disabled) { color: var(--color-text-disabled); cursor: not-allowed; }
@supports (appearance: base-select) {
:where(select:not([multiple]):not([size])), .select:not([multiple]):not([size]), ::picker(select) { appearance: base-select; }
:where(select:not([multiple]):not([size])), .select:not([multiple]):not([size]) { background-image: none; align-items: center; }
select::picker-icon { color: var(--color-text-secondary); transition: transform var(--motion-fast); }
select:open::picker-icon { transform: rotate(180deg); }
::picker(select) { color: var(--color-text-primary); background: var(--color-surface-elevated); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); padding: 6px; box-shadow: var(--shadow-md); max-height: min(320px, 60vh); overflow: auto; }
select option { padding: 8px 12px; min-height: 34px; border-radius: var(--radius-sm); }
select option:is(:hover, :focus-visible) { background: var(--color-background-hover); }
select option:checked { color: var(--color-accent-primary); background: var(--color-accent-soft); }
select option::checkmark { color: var(--color-accent-primary); }
}
.ui-disclosure { border: 1px solid var(--color-border-default); border-radius: var(--radius-md); background: var(--color-surface-primary); overflow-wrap: anywhere; }
.ui-disclosure > summary { min-height: 38px; box-sizing: border-box; line-height: 1.5; border-radius: var(--radius-sm); padding: 6px 8px; }
.ui-disclosure > summary:hover { background: var(--color-background-hover); color: var(--color-accent-primary); }
.ui-disclosure[open] > summary { border-bottom: 1px solid var(--color-border-subtle); border-radius: var(--radius-sm) var(--radius-sm) 0 0; }
+24
View File
@@ -0,0 +1,24 @@
// @vitest-environment happy-dom
import { readFileSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { expect, it } from 'vitest'
import { getCommunityThemePreviewCss, mockCommunityThemes } from '@/services/themePackageService'
const root = join(process.cwd(), 'src')
const files = Object.fromEntries(readdirSync(root, { recursive: true }).map(String).filter(path => /\.(vue|css|ts|theme)$/.test(path)).map(path => [path, readFileSync(join(root, path), 'utf8')]))
it('resolves semantic style token references throughout the component source tree', () => {
const defined = new Set<string>()
const used = new Set<string>()
for (const [path, source] of Object.entries(files)) {
if (path.endsWith('.spec.ts')) continue
for (const match of source.matchAll(/(--[\w-]+)\s*:/g)) defined.add(match[1]!)
for (const match of source.matchAll(/var\((--(?:color|font|space|radius|shadow|motion|line)-[\w-]+)/g)) used.add(match[1]!)
}
expect([...used].filter(name => !defined.has(name))).toEqual([])
})
it.each(mockCommunityThemes)('provides interaction and Markdown colors in $theme_id', theme => {
const css = getCommunityThemePreviewCss(theme.theme_id)
for (const token of ['accent-primary-active', 'accent-soft-hover', 'border-focus', 'text-inverse', 'markdown-grid', 'markdown-marker', 'markdown-table-header']) {
expect(css).toContain(`--color-${token}:`)
}
expect(css).toContain(`color-scheme: ${theme.is_dark ? 'dark' : 'light'}`)
})
+16
View File
@@ -328,3 +328,19 @@ ol {
--sidebar-secondary-width: 224px;
}
}
/* Native form controls share the same surfaces as component controls. */
:where(input:not([type='checkbox']):not([type='radio']):not([type='range']):not([type='color']), textarea, select) {
background-color: var(--color-surface-primary);
border-color: var(--color-border-default);
}
:root { color-scheme: light; }
[data-theme='dark'] { color-scheme: dark; }
[data-theme='sepia'] {
--color-accent-primary-active: #5d3d22;
--color-accent-secondary: #947044;
--color-accent-soft-hover: #e3d1aa;
--color-border-focus: #8a5b32;
--color-border-disabled: #eadfc4;
}