feat(theme): 专业主题系统重构 — Warp/Linear 设计参考,深色/浅色完整适配
参考设计系统: - Warp: 温暖暗色调、自然层次、克制配色 - Linear: 亮度阶梯层次、半透明边框、精确排版 核心改进: - app-theme.ts: 完整重写深色/浅色调色板 (+surface-hover/active, border-subtle/focus, feedback-bg, placeholder) - useTheme.ts: 修复硬编码 return 'dark',支持 dark/light/system 完整切换 - index.css: 消除硬编码颜色,全部使用 CSS 变量 - tailwind.config.js: 新增 txt/content/surface/border/ph/feedback 语义 token - 44 文件批量迁移: 292 处 text-gray/bg-gray/text-white/placeholder-gray → 主题感知语义 token | 44 files +611/-482 | tsc零错误 | 96/96 tests passing
This commit is contained in:
1 parent
07c4a5b14f
commit
e9a0d21758
44 files changed
+593
-464
No files matched your search
@@ -141,7 +141,7 @@ export default function SettingsView() {
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center bg-dark-900">
|
||||
<div className="text-gray-400">加载中...</div>
|
||||
<div className="text-txt-muted">加载中...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -197,8 +197,8 @@ export default function SettingsView() {
|
||||
onClick={() => setSetting('theme', theme.value as 'dark' | 'light' | 'system')}
|
||||
className={`p-3 rounded-lg border text-sm transition-colors ${
|
||||
settings.theme === theme.value
|
||||
? 'border-accent-blue bg-accent-blue/20 text-accent-blue'
|
||||
: 'border-dark-700/30 hover:border-dark-600 text-gray-300'
|
||||
? 'border-accent-blue bg-surface-baseccent-blue/20 text-accent-blue'
|
||||
: 'border-dark-700/30 hover:border-dark-600 text-content-secondary'
|
||||
}`}
|
||||
>
|
||||
{theme.label}
|
||||
@@ -212,7 +212,7 @@ export default function SettingsView() {
|
||||
<select
|
||||
value={settings.language}
|
||||
onChange={(e) => setSetting('language', e.target.value as 'zh-CN' | 'en-US')}
|
||||
className="w-full mt-3 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-3 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
>
|
||||
<option value="zh-CN">简体中文</option>
|
||||
<option value="en-US">English</option>
|
||||
@@ -229,11 +229,11 @@ export default function SettingsView() {
|
||||
<SettingSectionTitle>字体</SettingSectionTitle>
|
||||
<div className="grid grid-cols-2 gap-4 mt-3">
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">字体族</label>
|
||||
<label className="text-sm text-txt-muted">字体族</label>
|
||||
<select
|
||||
value={settings.fontFamily}
|
||||
onChange={(e) => setSetting('fontFamily', e.target.value)}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
>
|
||||
<option>JetBrains Mono</option>
|
||||
<option>Fira Code</option>
|
||||
@@ -242,11 +242,11 @@ export default function SettingsView() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">字体大小</label>
|
||||
<label className="text-sm text-txt-muted">字体大小</label>
|
||||
<select
|
||||
value={settings.fontSize}
|
||||
onChange={(e) => setSetting('fontSize', parseInt(e.target.value))}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
>
|
||||
{[12, 13, 14, 15, 16, 18, 20].map((size) => (
|
||||
<option key={size} value={size}>{size}px</option>
|
||||
@@ -265,8 +265,8 @@ export default function SettingsView() {
|
||||
onClick={() => setSetting('terminalTheme', key as 'dracula' | 'monokai' | 'solarized' | 'one-dark')}
|
||||
className={`p-3 rounded-lg border text-sm transition-colors ${
|
||||
settings.terminalTheme === key
|
||||
? 'border-accent-blue bg-accent-blue/20 text-accent-blue'
|
||||
: 'border-dark-700/30 hover:border-dark-600 text-gray-300'
|
||||
? 'border-accent-blue bg-surface-baseccent-blue/20 text-accent-blue'
|
||||
: 'border-dark-700/30 hover:border-dark-600 text-content-secondary'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
@@ -299,7 +299,7 @@ export default function SettingsView() {
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={() => keybindingState.resetAll()}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-gray-400 hover:text-accent-blue rounded-lg hover:bg-dark-700/50 transition-colors"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-xs text-txt-muted hover:text-accent-blue rounded-lg hover:bg-dark-700/50 transition-colors"
|
||||
>
|
||||
<RotateCcw className="w-3.5 h-3.5" />
|
||||
重置全部为默认
|
||||
@@ -311,7 +311,7 @@ export default function SettingsView() {
|
||||
const defs = getKeybindingsByCategory(category)
|
||||
return (
|
||||
<SettingCard key={category}>
|
||||
<h3 className="text-sm font-semibold text-gray-300 mb-3">{category}</h3>
|
||||
<h3 className="text-sm font-semibold text-content-secondary mb-3">{category}</h3>
|
||||
<div className="space-y-1">
|
||||
{defs.map((def) => {
|
||||
const currentKeys = allKeys[def.id]
|
||||
@@ -323,14 +323,14 @@ export default function SettingsView() {
|
||||
key={def.id}
|
||||
className={`flex items-center justify-between px-3 py-2 rounded-lg transition-colors ${
|
||||
isRecordingThis
|
||||
? 'bg-accent-blue/10 border border-accent-blue/30'
|
||||
? 'bg-surface-baseccent-blue/10 border border-accent-blue/30'
|
||||
: 'hover:bg-dark-800/60'
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<span className="text-sm text-gray-200">{def.label}</span>
|
||||
<span className="text-sm text-content-primary">{def.label}</span>
|
||||
{isCustom && (
|
||||
<span className="ml-2 text-[10px] text-accent-blue bg-accent-blue/10 px-1.5 py-0.5 rounded">
|
||||
<span className="ml-2 text-[10px] text-accent-blue bg-surface-baseccent-blue/10 px-1.5 py-0.5 rounded">
|
||||
自定义
|
||||
</span>
|
||||
)}
|
||||
@@ -339,12 +339,12 @@ export default function SettingsView() {
|
||||
<div className="flex items-center gap-2">
|
||||
{isRecordingThis ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<kbd className="inline-flex items-center px-3 py-1 rounded bg-accent-blue/20 border border-accent-blue/40 text-xs text-accent-blue font-mono animate-pulse">
|
||||
<kbd className="inline-flex items-center px-3 py-1 rounded bg-surface-baseccent-blue/20 border border-accent-blue/40 text-xs text-accent-blue font-mono animate-pulse">
|
||||
{recordedKeys || '按下快捷键...'}
|
||||
</kbd>
|
||||
<button
|
||||
onClick={() => stopRecording()}
|
||||
className="text-xs text-gray-500 hover:text-gray-300"
|
||||
className="text-xs text-txt-muted hover:text-content-secondary"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
@@ -368,8 +368,8 @@ export default function SettingsView() {
|
||||
<kbd
|
||||
className={`inline-flex items-center px-2.5 py-1 rounded text-xs font-mono transition-colors ${
|
||||
isCustom
|
||||
? 'bg-accent-blue/10 border border-accent-blue/30 text-accent-blue'
|
||||
: 'bg-dark-700 border border-dark-600 text-gray-300'
|
||||
? 'bg-surface-baseccent-blue/10 border border-accent-blue/30 text-accent-blue'
|
||||
: 'bg-dark-700 border border-dark-600 text-content-secondary'
|
||||
} group-hover:border-accent-blue/50`}
|
||||
>
|
||||
{currentKeys}
|
||||
@@ -378,7 +378,7 @@ export default function SettingsView() {
|
||||
{isCustom && (
|
||||
<button
|
||||
onClick={() => keybindingState.resetKeybinding(def.id)}
|
||||
className="text-gray-600 hover:text-gray-400 transition-colors"
|
||||
className="text-txt-faint hover:text-txt-muted transition-colors"
|
||||
title="重置为默认"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
@@ -398,7 +398,7 @@ export default function SettingsView() {
|
||||
{/* 录入确认弹层 */}
|
||||
{recordingId && recordedKeys && (
|
||||
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 z-50 flex items-center gap-3 px-5 py-3 bg-dark-800 border border-dark-600 rounded-xl shadow-2xl">
|
||||
<span className="text-sm text-gray-300">
|
||||
<span className="text-sm text-content-secondary">
|
||||
设置为 <kbd className="px-1.5 py-0.5 rounded bg-dark-700 text-accent-blue font-mono text-xs">{recordedKeys}</kbd>
|
||||
</span>
|
||||
<button
|
||||
@@ -406,20 +406,20 @@ export default function SettingsView() {
|
||||
await keybindingState.setCustomKeybinding(recordingId, recordedKeys)
|
||||
stopRecording()
|
||||
}}
|
||||
className="px-3 py-1 text-xs bg-accent-blue text-white rounded-lg hover:bg-accent-blue/80 transition-colors"
|
||||
className="px-3 py-1 text-xs bg-surface-baseccent-blue text-content-primary rounded-lg hover:bg-surface-baseccent-blue/80 transition-colors"
|
||||
>
|
||||
确认
|
||||
</button>
|
||||
<button
|
||||
onClick={stopRecording}
|
||||
className="px-3 py-1 text-xs text-gray-400 hover:text-gray-200 rounded-lg hover:bg-dark-700 transition-colors"
|
||||
className="px-3 py-1 text-xs text-txt-muted hover:text-content-primary rounded-lg hover:bg-dark-700 transition-colors"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-xs text-gray-600">
|
||||
<p className="text-xs text-txt-faint">
|
||||
点击快捷键可重新绑定 · 按 Esc 取消录入
|
||||
</p>
|
||||
</div>
|
||||
@@ -445,7 +445,7 @@ export default function SettingsView() {
|
||||
type="number"
|
||||
value={settings.defaultPort}
|
||||
onChange={(e) => setSetting('defaultPort', parseInt(e.target.value) || 22)}
|
||||
className="w-32 mt-2 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-32 mt-2 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
</SettingCard>
|
||||
|
||||
@@ -455,7 +455,7 @@ export default function SettingsView() {
|
||||
type="number"
|
||||
value={settings.timeout}
|
||||
onChange={(e) => setSetting('timeout', parseInt(e.target.value) || 30)}
|
||||
className="w-32 mt-2 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-32 mt-2 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
</SettingCard>
|
||||
|
||||
@@ -465,60 +465,60 @@ export default function SettingsView() {
|
||||
<span className="text-accent-blue text-xs font-semibold uppercase tracking-wider">Pro</span>
|
||||
<SettingSectionTitle className="mb-0">连接管理</SettingSectionTitle>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 mb-4">配置连接池行为,优化资源使用和性能</p>
|
||||
<p className="text-sm text-txt-muted mb-4">配置连接池行为,优化资源使用和性能</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">最大连接数</label>
|
||||
<label className="text-sm text-txt-muted">最大连接数</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={50}
|
||||
value={settings.maxConnections || 10}
|
||||
onChange={(e) => setSetting('maxConnections', parseInt(e.target.value) || 10)}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
<div className="text-xs text-gray-600 mt-1">最大并发 SSH 连接数</div>
|
||||
<div className="text-xs text-txt-faint mt-1">最大并发 SSH 连接数</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">最大后台连接</label>
|
||||
<label className="text-sm text-txt-muted">最大后台连接</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={20}
|
||||
value={settings.maxBackgroundConnections || 5}
|
||||
onChange={(e) => setSetting('maxBackgroundConnections', parseInt(e.target.value) || 5)}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
<div className="text-xs text-gray-600 mt-1">非活跃标签保持连接数</div>
|
||||
<div className="text-xs text-txt-faint mt-1">非活跃标签保持连接数</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">空闲超时 (分钟)</label>
|
||||
<label className="text-sm text-txt-muted">空闲超时 (分钟)</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={120}
|
||||
value={Math.floor((settings.idleTimeoutMs || 5 * 60 * 1000) / 60000)}
|
||||
onChange={(e) => setSetting('idleTimeoutMs', (parseInt(e.target.value) || 5) * 60000)}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
<div className="text-xs text-gray-600 mt-1">无操作后进入挂起</div>
|
||||
<div className="text-xs text-txt-faint mt-1">无操作后进入挂起</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm text-gray-500">挂起超时 (分钟)</label>
|
||||
<label className="text-sm text-txt-muted">挂起超时 (分钟)</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={240}
|
||||
value={Math.floor((settings.suspendTimeoutMs || 30 * 60 * 1000) / 60000)}
|
||||
onChange={(e) => setSetting('suspendTimeoutMs', (parseInt(e.target.value) || 30) * 60000)}
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 focus:outline-none focus:border-accent-blue"
|
||||
className="w-full mt-1 px-3 py-2 bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary focus:outline-none focus:border-accent-blue"
|
||||
/>
|
||||
<div className="text-xs text-gray-600 mt-1">挂起后自动断开</div>
|
||||
<div className="text-xs text-txt-faint mt-1">挂起后自动断开</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -571,11 +571,11 @@ export default function SettingsView() {
|
||||
<SettingCard>
|
||||
<div className="flex flex-col items-center py-6">
|
||||
<div className="w-20 h-20 bg-dark-700 rounded-2xl flex items-center justify-center mb-4">
|
||||
<Terminal className="w-10 h-10 text-white" />
|
||||
<Terminal className="w-10 h-10 text-content-primary" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-200 mb-1">OpsXShell</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">版本 {appVersion}</p>
|
||||
<p className="text-sm text-gray-400 max-w-md text-center">
|
||||
<h3 className="text-xl font-semibold text-content-primary mb-1">OpsXShell</h3>
|
||||
<p className="text-sm text-txt-muted mb-4">版本 {appVersion}</p>
|
||||
<p className="text-sm text-txt-muted max-w-md text-center">
|
||||
跨平台 SSH/SFTP 终端工具 · 简洁高效 · 安全可靠 · 操作便捷人性化
|
||||
</p>
|
||||
</div>
|
||||
@@ -595,8 +595,8 @@ export default function SettingsView() {
|
||||
['Lucide React', '0.294+'],
|
||||
].map(([name, ver]) => (
|
||||
<div key={name} className="flex justify-between py-1 border-b border-dark-700/20">
|
||||
<span className="text-gray-400">{name}</span>
|
||||
<span className="text-gray-500 font-mono">{ver}</span>
|
||||
<span className="text-txt-muted">{name}</span>
|
||||
<span className="text-txt-muted font-mono">{ver}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -604,10 +604,10 @@ export default function SettingsView() {
|
||||
|
||||
<SettingCard>
|
||||
<SettingSectionTitle>开源许可</SettingSectionTitle>
|
||||
<p className="text-sm text-gray-400 mt-2">
|
||||
<p className="text-sm text-txt-muted mt-2">
|
||||
OpsXShell 基于 MIT 许可证开源。本项目使用了以下开源组件:
|
||||
</p>
|
||||
<ul className="mt-2 text-sm text-gray-500 list-disc list-inside space-y-1">
|
||||
<ul className="mt-2 text-sm text-txt-muted list-disc list-inside space-y-1">
|
||||
<li>ssh2 — MIT License</li>
|
||||
<li>xterm.js — MIT License</li>
|
||||
<li>zmodem-ts — MIT License</li>
|
||||
@@ -631,18 +631,18 @@ export default function SettingsView() {
|
||||
{/* 搜索框 */}
|
||||
<div className="p-3 border-b border-dark-700/20">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-500" />
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-txt-muted" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索设置..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full pl-8 pr-8 py-1.5 text-sm bg-dark-900 border border-dark-700/30 rounded-lg text-gray-300 placeholder-gray-600 focus:outline-none focus:border-accent-blue/60"
|
||||
className="w-full pl-8 pr-8 py-1.5 text-sm bg-dark-900 border border-dark-700/30 rounded-lg text-content-secondary placeholder-ph focus:outline-none focus:border-accent-blue/60"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-300"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-txt-muted hover:text-content-secondary"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -661,14 +661,14 @@ export default function SettingsView() {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all ${
|
||||
active
|
||||
? 'bg-accent-blue text-white shadow-sm'
|
||||
: 'text-gray-400 hover:bg-dark-700/50 hover:text-gray-200'
|
||||
? 'bg-surface-baseccent-blue text-content-primary shadow-sm'
|
||||
: 'text-txt-muted hover:bg-dark-700/50 hover:text-content-primary'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-4 h-4 flex-shrink-0 ${active ? 'text-white' : ''}`} />
|
||||
<Icon className={`w-4 h-4 flex-shrink-0 ${active ? 'text-content-primary' : ''}`} />
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-medium truncate">{tab.label}</div>
|
||||
<div className={`text-xs truncate ${active ? 'text-white/70' : 'text-gray-600'}`}>
|
||||
<div className={`text-xs truncate ${active ? 'text-content-primary/70' : 'text-txt-faint'}`}>
|
||||
{tab.description}
|
||||
</div>
|
||||
</div>
|
||||
@@ -682,8 +682,8 @@ export default function SettingsView() {
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
{/* Header bar */}
|
||||
<div className="flex-shrink-0 px-8 pt-6 pb-4 border-b border-dark-700/20">
|
||||
<h1 className="text-xl font-semibold text-gray-100">{currentMeta.label}</h1>
|
||||
<p className="text-sm text-gray-500 mt-0.5">{currentMeta.description}</p>
|
||||
<h1 className="text-xl font-semibold text-content-primary">{currentMeta.label}</h1>
|
||||
<p className="text-sm text-txt-muted mt-0.5">{currentMeta.description}</p>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
@@ -727,7 +727,7 @@ function SettingSectionTitle({
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<div className={`text-sm font-semibold text-gray-300 mb-1 ${className}`}>
|
||||
<div className={`text-sm font-semibold text-content-secondary mb-1 ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
@@ -748,13 +748,13 @@ function SettingToggle({
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="mr-4">
|
||||
<div className="font-medium text-gray-200 text-sm">{label}</div>
|
||||
<div className="text-xs text-gray-500 mt-0.5">{description}</div>
|
||||
<div className="font-medium text-content-primary text-sm">{label}</div>
|
||||
<div className="text-xs text-txt-muted mt-0.5">{description}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`relative flex-shrink-0 w-11 h-6 rounded-full transition-colors ${
|
||||
checked ? 'bg-accent-blue' : 'bg-dark-600'
|
||||
checked ? 'bg-surface-baseccent-blue' : 'bg-dark-600'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
|
||||
Reference in new issue
Block a user