dsh-config-manager 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +216 -0
- package/README.zh-CN.md +218 -0
- package/cordis.patch.yml +45 -0
- package/dsh.bundle.patch +21 -0
- package/dsh.client +39 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/agent-presets.js.map +1 -0
- package/lib/adapters/credentials.d.ts +36 -0
- package/lib/adapters/credentials.js +121 -0
- package/lib/adapters/credentials.js.map +1 -0
- package/lib/adapters/file-collection.d.ts +14 -0
- package/lib/adapters/file-collection.js +102 -0
- package/lib/adapters/file-collection.js.map +1 -0
- package/lib/adapters/index.d.ts +38 -0
- package/lib/adapters/index.js +46 -0
- package/lib/adapters/index.js.map +1 -0
- package/lib/adapters/mcp.d.ts +27 -0
- package/lib/adapters/mcp.js +175 -0
- package/lib/adapters/mcp.js.map +1 -0
- package/lib/adapters/plugin-files.d.ts +15 -0
- package/lib/adapters/plugin-files.js +91 -0
- package/lib/adapters/plugin-files.js.map +1 -0
- package/lib/adapters/plugins.d.ts +13 -0
- package/lib/adapters/plugins.js +177 -0
- package/lib/adapters/plugins.js.map +1 -0
- package/lib/adapters/prompts.d.ts +29 -0
- package/lib/adapters/prompts.js +228 -0
- package/lib/adapters/prompts.js.map +1 -0
- package/lib/adapters/providers.d.ts +33 -0
- package/lib/adapters/providers.js +143 -0
- package/lib/adapters/providers.js.map +1 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/sessions.js.map +1 -0
- package/lib/adapters/settings.d.ts +23 -0
- package/lib/adapters/settings.js +142 -0
- package/lib/adapters/settings.js.map +1 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/skills.js.map +1 -0
- package/lib/adapters/test-helpers.d.ts +99 -0
- package/lib/adapters/test-helpers.js +192 -0
- package/lib/adapters/test-helpers.js.map +1 -0
- package/lib/adapters/ui.d.ts +26 -0
- package/lib/adapters/ui.js +88 -0
- package/lib/adapters/ui.js.map +1 -0
- package/lib/adapters/workspaces.d.ts +12 -0
- package/lib/adapters/workspaces.js +92 -0
- package/lib/adapters/workspaces.js.map +1 -0
- package/lib/client.d.ts +377 -0
- package/lib/client.js +2598 -0
- package/lib/client.js.map +1 -0
- package/lib/core/analyzer.d.ts +35 -0
- package/lib/core/analyzer.js +563 -0
- package/lib/core/analyzer.js.map +1 -0
- package/lib/core/backup.d.ts +29 -0
- package/lib/core/backup.js +184 -0
- package/lib/core/backup.js.map +1 -0
- package/lib/core/exporter.d.ts +37 -0
- package/lib/core/exporter.js +222 -0
- package/lib/core/exporter.js.map +1 -0
- package/lib/core/importer.d.ts +42 -0
- package/lib/core/importer.js +25 -0
- package/lib/core/importer.js.map +1 -0
- package/lib/core/index.d.ts +12 -0
- package/lib/core/index.js +11 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/rollback.d.ts +9 -0
- package/lib/core/rollback.js +136 -0
- package/lib/core/rollback.js.map +1 -0
- package/lib/core/types.d.ts +331 -0
- package/lib/core/types.js +18 -0
- package/lib/core/types.js.map +1 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/validator.js.map +1 -0
- package/lib/index.d.ts +51 -0
- package/lib/index.js +875 -0
- package/lib/index.js.map +1 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/index.js.map +1 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/migrations/v1-to-v2.js.map +1 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/index.js.map +1 -0
- package/lib/profiles/profile-manager.d.ts +102 -0
- package/lib/profiles/profile-manager.js +522 -0
- package/lib/profiles/profile-manager.js.map +1 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +136 -0
- package/lib/schema/config.js.map +1 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/manifest.js.map +1 -0
- package/lib/schema/types.d.ts +169 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/types.js.map +1 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/schema/versions.js.map +1 -0
- package/lib/security/encryption.d.ts +41 -0
- package/lib/security/encryption.js +152 -0
- package/lib/security/encryption.js.map +1 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/index.js.map +1 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/integrity.js.map +1 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/redaction.js.map +1 -0
- package/lib/security/secret-scanner.d.ts +48 -0
- package/lib/security/secret-scanner.js +253 -0
- package/lib/security/secret-scanner.js.map +1 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/security/zip-security.js.map +1 -0
- package/lib/ui/conflict-view.d.ts +28 -0
- package/lib/ui/conflict-view.js +49 -0
- package/lib/ui/conflict-view.js.map +1 -0
- package/lib/ui/errors.d.ts +17 -0
- package/lib/ui/errors.js +76 -0
- package/lib/ui/errors.js.map +1 -0
- package/lib/ui/export-flow.d.ts +62 -0
- package/lib/ui/export-flow.js +79 -0
- package/lib/ui/export-flow.js.map +1 -0
- package/lib/ui/import-wizard.d.ts +67 -0
- package/lib/ui/import-wizard.js +169 -0
- package/lib/ui/import-wizard.js.map +1 -0
- package/lib/ui/path-mapping.d.ts +38 -0
- package/lib/ui/path-mapping.js +75 -0
- package/lib/ui/path-mapping.js.map +1 -0
- package/lib/ui/progress.d.ts +27 -0
- package/lib/ui/progress.js +76 -0
- package/lib/ui/progress.js.map +1 -0
- package/lib/ui/report.d.ts +21 -0
- package/lib/ui/report.js +146 -0
- package/lib/ui/report.js.map +1 -0
- package/lib/ui/test-helpers.d.ts +62 -0
- package/lib/ui/test-helpers.js +147 -0
- package/lib/ui/test-helpers.js.map +1 -0
- package/lib/ui/types.d.ts +129 -0
- package/lib/ui/types.js +21 -0
- package/lib/ui/types.js.map +1 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/hashing.js.map +1 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/json.js.map +1 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/logger.js.map +1 -0
- package/lib/utils/paths.d.ts +31 -0
- package/lib/utils/paths.js +132 -0
- package/lib/utils/paths.js.map +1 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/lib/utils/zip.js.map +1 -0
- package/package.json +109 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +141 -0
- package/src/adapters/file-collection.ts +110 -0
- package/src/adapters/files.test.ts +113 -0
- package/src/adapters/index.ts +69 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +181 -0
- package/src/adapters/plugin-files.ts +96 -0
- package/src/adapters/plugins.test.ts +72 -0
- package/src/adapters/plugins.ts +176 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +226 -0
- package/src/adapters/providers.test.ts +125 -0
- package/src/adapters/providers.ts +161 -0
- package/src/adapters/roundtrip.test.ts +188 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +93 -0
- package/src/adapters/settings.ts +165 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +207 -0
- package/src/adapters/ui.test.ts +56 -0
- package/src/adapters/ui.ts +106 -0
- package/src/adapters/workspaces.test.ts +79 -0
- package/src/adapters/workspaces.ts +95 -0
- package/src/client/CLIENT_DEPENDENCIES.md +101 -0
- package/src/client/ConfigManagerSection.tsx +60 -0
- package/src/client/api.ts +270 -0
- package/src/client/client-types.ts +27 -0
- package/src/client/common/ErrorBanner.tsx +68 -0
- package/src/client/common/ProgressBar.tsx +49 -0
- package/src/client/common/ReportView.tsx +117 -0
- package/src/client/common/ui.tsx +165 -0
- package/src/client/config-manager.module.css +759 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/export/ExportView.tsx +222 -0
- package/src/client/import/ConflictList.tsx +72 -0
- package/src/client/import/ImportWizardView.tsx +406 -0
- package/src/client/import/PathMappingForm.tsx +77 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +182 -0
- package/src/core/analyzer.ts +631 -0
- package/src/core/backup.ts +212 -0
- package/src/core/exporter.ts +246 -0
- package/src/core/importer.ts +66 -0
- package/src/core/index.ts +39 -0
- package/src/core/rollback.ts +140 -0
- package/src/core/smoke.test.ts +717 -0
- package/src/core/types.ts +343 -0
- package/src/core/validator.ts +84 -0
- package/src/index.ts +942 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +280 -0
- package/src/profiles/profile-manager.ts +606 -0
- package/src/schema/config.ts +137 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/types.ts +179 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +180 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +275 -0
- package/src/security/security.test.ts +796 -0
- package/src/security/zip-security.ts +180 -0
- package/src/ui/conflict-view.test.ts +62 -0
- package/src/ui/conflict-view.ts +65 -0
- package/src/ui/errors.test.ts +44 -0
- package/src/ui/errors.ts +95 -0
- package/src/ui/export-flow.test.ts +75 -0
- package/src/ui/export-flow.ts +128 -0
- package/src/ui/import-wizard.test.ts +128 -0
- package/src/ui/import-wizard.ts +208 -0
- package/src/ui/path-mapping.test.ts +67 -0
- package/src/ui/path-mapping.ts +103 -0
- package/src/ui/progress.test.ts +42 -0
- package/src/ui/progress.ts +90 -0
- package/src/ui/report.test.ts +93 -0
- package/src/ui/report.ts +140 -0
- package/src/ui/test-helpers.ts +177 -0
- package/src/ui/types.ts +193 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +122 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结果报告(规范 §21 导出 / §22 导入 / §17 回滚,绑 src/ui/report.ts)。
|
|
3
|
+
*
|
|
4
|
+
* 直接调用 report.ts 的纯文本渲染器(renderExportReport / renderImportReport /
|
|
5
|
+
* renderRollbackReport / suggestedActions / importSectionStats),React 只做外壳:
|
|
6
|
+
* 标题 + 结构化统计徽章 + 文本详情 + 建议动作按钮。
|
|
7
|
+
*
|
|
8
|
+
* 安全约束:所有渲染文本展示前再过 `redact()` 兜底,Secret 不进入 UI。
|
|
9
|
+
*/
|
|
10
|
+
import type { ExportReport, ImportResult, RollbackReport } from '../../core/types.ts'
|
|
11
|
+
import {
|
|
12
|
+
importSectionStats,
|
|
13
|
+
renderExportReport,
|
|
14
|
+
renderImportReport,
|
|
15
|
+
renderRollbackReport,
|
|
16
|
+
suggestedActions,
|
|
17
|
+
} from '../../ui/report.ts'
|
|
18
|
+
import type { ImportResultAction } from '../../ui/types.ts'
|
|
19
|
+
import { redact } from '../../security/redaction.ts'
|
|
20
|
+
import { Badge, Button, type BadgeKind } from './ui.tsx'
|
|
21
|
+
import css from '../config-manager.module.css'
|
|
22
|
+
|
|
23
|
+
export type ReportViewKind = 'export' | 'import'
|
|
24
|
+
|
|
25
|
+
export interface ReportViewProps {
|
|
26
|
+
kind: ReportViewKind
|
|
27
|
+
exportReport?: ExportReport
|
|
28
|
+
importResult?: ImportResult
|
|
29
|
+
/** 结果页动作回调(Fix Issues / View Details / Done) */
|
|
30
|
+
onAction?: (action: ImportResultAction) => void
|
|
31
|
+
/** 下载导出文件的回调(导出报告场景) */
|
|
32
|
+
onDownload?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 导入分区的统计徽章(由 report.importSectionStats 计算) */
|
|
36
|
+
function SectionStatBadges({ result }: { result: ImportResult }) {
|
|
37
|
+
const stats = importSectionStats(result.executed)
|
|
38
|
+
return (
|
|
39
|
+
<div className={css.statRow}>
|
|
40
|
+
{stats.map((s) => {
|
|
41
|
+
let kind: BadgeKind = 'ok'
|
|
42
|
+
if (s.failed > 0) kind = 'error'
|
|
43
|
+
else if (s.skipped > 0) kind = 'warn'
|
|
44
|
+
return (
|
|
45
|
+
<Badge key={s.section} kind={kind}>
|
|
46
|
+
{s.section}: {s.ok}✓{s.skipped > 0 ? ` ${s.skipped}≈` : ''}{s.failed > 0 ? ` ${s.failed}✗` : ''}
|
|
47
|
+
</Badge>
|
|
48
|
+
)
|
|
49
|
+
})}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 导出报告的安全摘要(Included / Excluded / Security 徽章) */
|
|
55
|
+
function ExportSummary({ report }: { report: ExportReport }) {
|
|
56
|
+
const included = report.included.length
|
|
57
|
+
const excluded = report.excluded.length
|
|
58
|
+
return (
|
|
59
|
+
<div className={css.statRow}>
|
|
60
|
+
<Badge kind="ok">{included} included</Badge>
|
|
61
|
+
{excluded > 0 && <Badge kind="warn">{excluded} excluded</Badge>}
|
|
62
|
+
{report.security.containsSecrets && <Badge kind="warn">encrypted</Badge>}
|
|
63
|
+
{!report.security.containsSecrets && <Badge kind="ok">no secrets</Badge>}
|
|
64
|
+
{report.security.redactedHits > 0 && (
|
|
65
|
+
<Badge kind="error">{report.security.redactedHits} redacted</Badge>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 结果报告视图。
|
|
73
|
+
* 文本详情 = report.ts 渲染器的输出(已脱敏),展示前再过 redact() 双保险;
|
|
74
|
+
* 以 <pre> 等宽块呈现保持对齐。
|
|
75
|
+
*/
|
|
76
|
+
export function ReportView({ kind, exportReport, importResult, onAction, onDownload }: ReportViewProps) {
|
|
77
|
+
const actions = kind === 'import' && importResult !== undefined
|
|
78
|
+
? suggestedActions(importResult)
|
|
79
|
+
: []
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className={css.reportView}>
|
|
83
|
+
{kind === 'export' && exportReport !== undefined && (
|
|
84
|
+
<>
|
|
85
|
+
<ExportSummary report={exportReport} />
|
|
86
|
+
<pre className={css.reportText}>{redact(renderExportReport(exportReport))}</pre>
|
|
87
|
+
{onDownload !== undefined && (
|
|
88
|
+
<div className={css.reportFooter}>
|
|
89
|
+
<Button variant="primary" onClick={onDownload}>Download</Button>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
)}
|
|
94
|
+
{kind === 'import' && importResult !== undefined && (
|
|
95
|
+
<>
|
|
96
|
+
<SectionStatBadges result={importResult} />
|
|
97
|
+
<pre className={css.reportText}>{redact(renderImportReport(importResult))}</pre>
|
|
98
|
+
{importResult.rollback !== null && (
|
|
99
|
+
<div className={css.rollbackBox}>
|
|
100
|
+
<strong>Rollback</strong>
|
|
101
|
+
<pre className={css.reportText}>{redact(renderRollbackReport(importResult.rollback as RollbackReport))}</pre>
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
{actions.length > 0 && (
|
|
105
|
+
<div className={css.reportFooter}>
|
|
106
|
+
{actions.map((a) => (
|
|
107
|
+
<Button key={a} variant={a === 'done' ? 'primary' : 'ghost'} onClick={() => onAction?.(a)}>
|
|
108
|
+
{a}
|
|
109
|
+
</Button>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Manager 基础 UI 原语(dsh-ssh 风格,全部走 DSH Design System 的 --dsw-* token,
|
|
3
|
+
* 不另造视觉体系)。仅作薄封装:类名来自 config-manager.module.css,无业务逻辑。
|
|
4
|
+
*/
|
|
5
|
+
import type { ChangeEvent, ReactNode } from 'react'
|
|
6
|
+
import css from '../config-manager.module.css'
|
|
7
|
+
|
|
8
|
+
/* ---------------- Button ---------------- */
|
|
9
|
+
|
|
10
|
+
export type ButtonVariant = 'primary' | 'ghost' | 'danger'
|
|
11
|
+
|
|
12
|
+
export interface ButtonProps {
|
|
13
|
+
variant?: ButtonVariant
|
|
14
|
+
disabled?: boolean
|
|
15
|
+
onClick?: () => void
|
|
16
|
+
children: ReactNode
|
|
17
|
+
title?: string
|
|
18
|
+
className?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 统一按钮(primary=主操作 / ghost=次操作 / danger=危险操作) */
|
|
22
|
+
export function Button({ variant = 'ghost', disabled, onClick, children, title, className }: ButtonProps) {
|
|
23
|
+
const cls =
|
|
24
|
+
variant === 'primary' ? css.primaryButton
|
|
25
|
+
: variant === 'danger' ? css.dangerButton
|
|
26
|
+
: css.ghostButton
|
|
27
|
+
return (
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
className={className !== undefined ? `${cls} ${className}` : cls}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
title={title}
|
|
33
|
+
onClick={onClick}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</button>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ---------------- Badge ---------------- */
|
|
41
|
+
|
|
42
|
+
export type BadgeKind = 'info' | 'ok' | 'warn' | 'error'
|
|
43
|
+
|
|
44
|
+
export interface BadgeProps {
|
|
45
|
+
kind?: BadgeKind
|
|
46
|
+
children: ReactNode
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 状态徽章(info=业务色 / ok=成功 / warn=警告 / error=错误) */
|
|
50
|
+
export function Badge({ kind = 'info', children }: BadgeProps) {
|
|
51
|
+
return <span className={`${css.badge} ${css[`badge${kind[0]!.toUpperCase()}${kind.slice(1)}`] ?? ''}`}>{children}</span>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ---------------- Banner ---------------- */
|
|
55
|
+
|
|
56
|
+
export type BannerKind = 'ok' | 'error' | 'info' | 'warn'
|
|
57
|
+
|
|
58
|
+
export interface BannerProps {
|
|
59
|
+
kind?: BannerKind
|
|
60
|
+
children: ReactNode
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 说明横幅(ok/error/info/warn 四态) */
|
|
64
|
+
export function Banner({ kind = 'info', children }: BannerProps) {
|
|
65
|
+
return <div className={css.banner} data-kind={kind}>{children}</div>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ---------------- Card ---------------- */
|
|
69
|
+
|
|
70
|
+
export interface CardProps {
|
|
71
|
+
children: ReactNode
|
|
72
|
+
className?: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 卡片容器(bg-layer-2 + 圆角 + 细边框) */
|
|
76
|
+
export function Card({ children, className }: CardProps) {
|
|
77
|
+
return <div className={className !== undefined ? `${css.card} ${className}` : css.card}>{children}</div>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ---------------- Spinner ---------------- */
|
|
81
|
+
|
|
82
|
+
export interface SpinnerProps {
|
|
83
|
+
label?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 加载指示(旋转环 + 可选文案) */
|
|
87
|
+
export function Spinner({ label }: SpinnerProps) {
|
|
88
|
+
return (
|
|
89
|
+
<span className={css.spinnerWrap}>
|
|
90
|
+
<span className={css.spinner} aria-hidden="true" />
|
|
91
|
+
{label !== undefined && <span className={css.spinnerLabel}>{label}</span>}
|
|
92
|
+
</span>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ---------------- Field ---------------- */
|
|
97
|
+
|
|
98
|
+
export interface FieldProps {
|
|
99
|
+
label: string
|
|
100
|
+
hint?: string
|
|
101
|
+
children: ReactNode
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 表单字段(标签 + 控件 + 说明) */
|
|
105
|
+
export function Field({ label, hint, children }: FieldProps) {
|
|
106
|
+
return (
|
|
107
|
+
<label className={css.field}>
|
|
108
|
+
<span className={css.fieldLabel}>{label}</span>
|
|
109
|
+
{children}
|
|
110
|
+
{hint !== undefined && <span className={css.hint}>{hint}</span>}
|
|
111
|
+
</label>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* ---------------- SectionTitle ---------------- */
|
|
116
|
+
|
|
117
|
+
export interface SectionTitleProps {
|
|
118
|
+
title: string
|
|
119
|
+
subtitle?: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 区块标题(页面内二级标题 + 可选副标题) */
|
|
123
|
+
export function SectionTitle({ title, subtitle }: SectionTitleProps) {
|
|
124
|
+
return (
|
|
125
|
+
<div className={css.sectionTitleBlock}>
|
|
126
|
+
<h3 className={css.sectionTitle}>{title}</h3>
|
|
127
|
+
{subtitle !== undefined && <p className={css.sectionSubtitle}>{subtitle}</p>}
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ---------------- Empty / Loading ---------------- */
|
|
133
|
+
|
|
134
|
+
export interface EmptyProps {
|
|
135
|
+
children: ReactNode
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** 空状态占位 */
|
|
139
|
+
export function Empty({ children }: EmptyProps) {
|
|
140
|
+
return <div className={css.empty}>{children}</div>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* ---------------- Checkbox ---------------- */
|
|
144
|
+
|
|
145
|
+
export interface CheckboxProps {
|
|
146
|
+
checked: boolean
|
|
147
|
+
onChange: (checked: boolean) => void
|
|
148
|
+
label: ReactNode
|
|
149
|
+
disabled?: boolean
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** 复选框行(勾选 + 标签) */
|
|
153
|
+
export function Checkbox({ checked, onChange, label, disabled }: CheckboxProps) {
|
|
154
|
+
return (
|
|
155
|
+
<label className={css.checkboxRow}>
|
|
156
|
+
<input
|
|
157
|
+
type="checkbox"
|
|
158
|
+
checked={checked}
|
|
159
|
+
disabled={disabled}
|
|
160
|
+
onChange={(event: ChangeEvent<HTMLInputElement>) => { onChange(event.target.checked) }}
|
|
161
|
+
/>
|
|
162
|
+
<span>{label}</span>
|
|
163
|
+
</label>
|
|
164
|
+
)
|
|
165
|
+
}
|