feat: complete frontend redesign — all 6 pages implemented per prototype specs
Fix UI components (Badge, Button, Card, Input, ProgressBar, ScoreBar, ChatPanel) to use CSS variables instead of old theme object pattern. Clean up barrel exports (common/index.ts, layout/index.ts) and remove stale router/tabs import from shell-config.ts. Build: tsc zero errors, Vite production build succeeds (1760 modules, 270 kB JS, 22 kB CSS).
This commit is contained in:
@@ -1,3 +1 @@
|
||||
export { TLogo } from './TLogo';
|
||||
export { ThemeToggle } from './ThemeToggle';
|
||||
export { TPattern } from './TPattern';
|
||||
// common UI helpers — add exports here as new shared components are created
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { AppShell } from './AppShell';
|
||||
export { ContentLayout } from './ContentLayout';
|
||||
export { FooterLayout } from './FooterLayout';
|
||||
export { HeaderLayout } from './HeaderLayout';
|
||||
export { Sidebar } from './Sidebar';
|
||||
export { Topbar } from './Topbar';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { appTabs } from '../../router/tabs';
|
||||
|
||||
export const shellFrameClassName = 'mx-auto w-full max-w-[1680px] px-8';
|
||||
|
||||
export const shellMeta = {
|
||||
@@ -8,5 +6,3 @@ export const shellMeta = {
|
||||
status: 'ONLINE',
|
||||
surface: 'Desktop Web',
|
||||
} as const;
|
||||
|
||||
export const shellModuleSummary = appTabs.map((tab) => tab.label).join(' / ');
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface BadgeProps {
|
||||
children: React.ReactNode;
|
||||
@@ -7,32 +6,29 @@ interface BadgeProps {
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
const colorMap: Record<string, { bg: string; text: string }> = {
|
||||
accent: { bg: 'var(--accent)', text: '#fff' },
|
||||
green: { bg: 'var(--success)', text: '#fff' },
|
||||
orange: { bg: 'var(--warn)', text: '#fff' },
|
||||
red: { bg: 'var(--danger)', text: '#fff' },
|
||||
};
|
||||
|
||||
export const Badge: React.FC<BadgeProps> = ({
|
||||
children,
|
||||
color = 'accent',
|
||||
size = 'sm',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const colorStyles = {
|
||||
accent: { bg: theme.gradientAccent, text: '#fff' },
|
||||
green: { bg: theme.green, text: '#fff' },
|
||||
orange: { bg: theme.orange, text: '#fff' },
|
||||
red: { bg: '#ff4444', text: '#fff' },
|
||||
};
|
||||
|
||||
const sizeStyles = {
|
||||
const sizeStyles: Record<string, string> = {
|
||||
sm: 'px-2 py-0.5 text-xs',
|
||||
md: 'px-3 py-1 text-sm',
|
||||
};
|
||||
|
||||
const c = colorMap[color] ?? colorMap.accent;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`${sizeStyles[size]} rounded font-mono font-medium`}
|
||||
style={{
|
||||
background: colorStyles[color].bg,
|
||||
color: colorStyles[color].text,
|
||||
}}
|
||||
style={{ background: c.bg, color: c.text }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
@@ -18,8 +17,6 @@ export const Button: React.FC<ButtonProps> = ({
|
||||
disabled = false,
|
||||
className = '',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const baseStyles = `
|
||||
inline-flex items-center justify-center
|
||||
font-semibold rounded-xl cursor-pointer
|
||||
@@ -27,13 +24,13 @@ export const Button: React.FC<ButtonProps> = ({
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`;
|
||||
|
||||
const sizeStyles = {
|
||||
const sizeStyles: Record<string, string> = {
|
||||
sm: 'px-3 py-1.5 text-xs',
|
||||
md: 'px-5 py-3 text-sm',
|
||||
lg: 'px-8 py-5 text-base',
|
||||
};
|
||||
|
||||
const variantStyles = {
|
||||
const variantStyles: Record<string, string> = {
|
||||
primary: `
|
||||
bg-gradient-to-r from-t-accent to-t-accent-dark
|
||||
text-white hover:shadow-t-accent hover:-translate-y-0.5
|
||||
@@ -50,9 +47,6 @@ export const Button: React.FC<ButtonProps> = ({
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={`${baseStyles} ${sizeStyles[size]} ${variantStyles[variant]} ${className}`}
|
||||
style={{
|
||||
color: variant === 'primary' ? '#fff' : theme.text2,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface CardProps {
|
||||
accent?: boolean;
|
||||
@@ -18,9 +17,7 @@ export const Card: React.FC<CardProps> = ({
|
||||
className = '',
|
||||
onClick,
|
||||
}) => {
|
||||
const { theme, isDark } = useTheme();
|
||||
|
||||
const paddingStyles = {
|
||||
const paddingStyles: Record<string, string> = {
|
||||
sm: 'p-4',
|
||||
md: 'p-5',
|
||||
lg: 'p-8',
|
||||
@@ -30,22 +27,22 @@ export const Card: React.FC<CardProps> = ({
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={`
|
||||
rounded-xl border transition-all duration-200 cursor-default
|
||||
relative rounded-xl border transition-all duration-200 cursor-default
|
||||
${paddingStyles[padding]}
|
||||
${accent ? 'border-t-accent' : 'border-t-border'}
|
||||
${highlight ? 'border-2 border-t-accent' : ''}
|
||||
${!isDark ? 'shadow-t-card' : ''}
|
||||
${onClick ? 'cursor-pointer hover:border-t-accent' : ''}
|
||||
${accent ? 'border-[color:var(--accent)]' : 'border-[color:var(--border)]'}
|
||||
${highlight ? 'border-2 border-[color:var(--accent)]' : ''}
|
||||
${onClick ? 'cursor-pointer hover:border-[color:var(--accent)]' : ''}
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
backgroundColor: 'var(--surface)',
|
||||
boxShadow: 'var(--shadow-card)',
|
||||
}}
|
||||
>
|
||||
{accent && (
|
||||
<div
|
||||
className="absolute top-0 left-0 right-0 h-[3px] rounded-t-xl"
|
||||
style={{ background: theme.gradientAccent }}
|
||||
style={{ background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))' }}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface InputProps {
|
||||
value: string;
|
||||
@@ -18,8 +17,6 @@ export const Input: React.FC<InputProps> = ({
|
||||
className = '',
|
||||
type = 'text',
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
@@ -28,17 +25,24 @@ export const Input: React.FC<InputProps> = ({
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={placeholder}
|
||||
className={`
|
||||
w-full px-4 py-3 text-sm
|
||||
bg-t-bg-card border border-t-border rounded-lg
|
||||
text-t-text outline-none
|
||||
focus:border-t-accent focus:ring-1 focus:ring-t-accent
|
||||
placeholder:text-t-text3
|
||||
w-full px-4 py-3 text-sm rounded-lg outline-none
|
||||
focus:ring-1
|
||||
placeholder:text-[color:var(--muted)]
|
||||
${className}
|
||||
`}
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
borderColor: theme.border,
|
||||
color: theme.text,
|
||||
backgroundColor: 'var(--surface)',
|
||||
borderColor: 'var(--border)',
|
||||
border: '1px solid var(--border)',
|
||||
color: 'var(--fg)',
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--accent)';
|
||||
e.currentTarget.style.boxShadow = '0 0 0 1px var(--accent)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = 'var(--border)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface ProgressBarProps {
|
||||
percent: number;
|
||||
@@ -7,36 +6,39 @@ interface ProgressBarProps {
|
||||
showLabel?: boolean;
|
||||
}
|
||||
|
||||
const colorBg: Record<string, string> = {
|
||||
accent: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
|
||||
green: 'linear-gradient(90deg, var(--success), #00ff88)',
|
||||
orange: 'linear-gradient(90deg, var(--warn), #ffaa00)',
|
||||
red: 'var(--danger)',
|
||||
};
|
||||
|
||||
export const ProgressBar: React.FC<ProgressBarProps> = ({
|
||||
percent,
|
||||
color = 'accent',
|
||||
showLabel = false,
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
const colorStyles = {
|
||||
accent: theme.gradientAccent,
|
||||
green: `linear-gradient(90deg, ${theme.green}, #00ff88)`,
|
||||
orange: `linear-gradient(90deg, ${theme.orange}, #ffaa00)`,
|
||||
red: '#ff4444',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="h-2 rounded-full flex-1"
|
||||
style={{ backgroundColor: theme.bgHover }}
|
||||
style={{ backgroundColor: 'var(--border)' }}
|
||||
>
|
||||
<div
|
||||
className="h-full rounded-full transition-all duration-300"
|
||||
style={{
|
||||
width: `${percent}%`,
|
||||
background: colorStyles[color],
|
||||
background: colorBg[color] ?? colorBg.accent,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{showLabel && (
|
||||
<span className="font-mono text-xs text-t-accent">{percent}%</span>
|
||||
<span
|
||||
className="font-mono text-xs"
|
||||
style={{ color: 'var(--accent)' }}
|
||||
>
|
||||
{percent}%
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
|
||||
interface ScoreBarProps {
|
||||
score: number; // 0-100
|
||||
@@ -12,27 +11,25 @@ export const ScoreBar: React.FC<ScoreBarProps> = ({
|
||||
label,
|
||||
accent = false,
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="p-5 rounded-xl border"
|
||||
style={{
|
||||
backgroundColor: theme.bgCard,
|
||||
borderColor: accent ? theme.accent : theme.border,
|
||||
backgroundColor: 'var(--surface)',
|
||||
borderColor: accent ? 'var(--accent)' : 'var(--border)',
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<div
|
||||
className="font-mono text-xs mb-2"
|
||||
style={{ color: theme.text3, letterSpacing: '1px' }}
|
||||
style={{ color: 'var(--muted)', letterSpacing: '1px' }}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="font-mono text-3xl font-bold"
|
||||
style={{ color: accent ? theme.accent : theme.text }}
|
||||
style={{ color: accent ? 'var(--accent)' : 'var(--fg)' }}
|
||||
>
|
||||
{score}
|
||||
</div>
|
||||
@@ -42,7 +39,7 @@ export const ScoreBar: React.FC<ScoreBarProps> = ({
|
||||
key={i}
|
||||
className="w-2 h-4 rounded-sm"
|
||||
style={{
|
||||
backgroundColor: i < score / 10 ? theme.accent : theme.bgHover,
|
||||
backgroundColor: i < score / 10 ? 'var(--accent)' : 'var(--border)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useTheme } from '../../contexts';
|
||||
import type { ComplianceChunk } from '../../types';
|
||||
|
||||
interface ChatPanelProps {
|
||||
@@ -29,7 +28,6 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
'法规的具体要求是什么?',
|
||||
],
|
||||
}) => {
|
||||
const { theme } = useTheme();
|
||||
const activeChunk = chunks.find(c => c.id === activeChunkId);
|
||||
|
||||
return (
|
||||
@@ -40,8 +38,8 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: 420,
|
||||
background: theme.bgCard,
|
||||
borderLeft: `1px solid ${theme.border}`,
|
||||
background: 'var(--surface)',
|
||||
borderLeft: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
zIndex: 50,
|
||||
@@ -51,14 +49,14 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
{/* Chat Header */}
|
||||
<div style={{
|
||||
padding: '20px 24px',
|
||||
borderBottom: `1px solid ${theme.border}`,
|
||||
borderBottom: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, color: theme.text }}>合规对话</div>
|
||||
<div className="mono" style={{ fontSize: 11, color: theme.text3 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, color: 'var(--fg)' }}>合规对话</div>
|
||||
<div className="mono" style={{ fontSize: 11, color: 'var(--muted)' }}>
|
||||
段落 #{activeChunk?.index} · {activeChunk?.regulations.length} 条法规
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,7 +66,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
background: theme.bgHover,
|
||||
background: 'var(--rail-hover)',
|
||||
border: 'none',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
@@ -77,7 +75,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
}}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M18 6L6 18M6 6L18 18" stroke={theme.text3} strokeWidth="2" strokeLinecap="round"/>
|
||||
<path d="M18 6L6 18M6 6L18 18" stroke="var(--muted)" strokeWidth="2" strokeLinecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -85,15 +83,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
{/* Current Chunk Info */}
|
||||
<div style={{
|
||||
padding: '16px 24px',
|
||||
background: theme.bgHover,
|
||||
borderBottom: `1px solid ${theme.border}`,
|
||||
background: 'var(--rail-hover)',
|
||||
borderBottom: '1px solid var(--border)',
|
||||
}}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8, color: theme.text }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 500, marginBottom: 8, color: 'var(--fg)' }}>
|
||||
{activeChunk?.intent}
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
color: theme.text2,
|
||||
color: 'var(--muted)',
|
||||
lineHeight: 1.5,
|
||||
maxHeight: 60,
|
||||
overflow: 'hidden',
|
||||
@@ -122,7 +120,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
background: theme.gradientAccent,
|
||||
background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -136,13 +134,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
<div style={{
|
||||
maxWidth: '80%',
|
||||
padding: '12px 16px',
|
||||
background: msg.role === 'user' ? theme.gradientAccent : theme.bgElevated,
|
||||
background: msg.role === 'user'
|
||||
? 'linear-gradient(90deg, var(--accent), var(--accent-hover))'
|
||||
: 'var(--surface)',
|
||||
borderRadius: 12,
|
||||
color: msg.role === 'user' ? '#fff' : theme.text,
|
||||
color: msg.role === 'user' ? '#fff' : 'var(--fg)',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.6,
|
||||
whiteSpace: 'pre-wrap',
|
||||
border: msg.role === 'assistant' ? `1px solid ${theme.border}` : 'none',
|
||||
border: msg.role === 'assistant' ? '1px solid var(--border)' : 'none',
|
||||
}}>
|
||||
{msg.content}
|
||||
</div>
|
||||
@@ -154,7 +154,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 8,
|
||||
background: theme.gradientAccent,
|
||||
background: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
@@ -165,15 +165,15 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
</div>
|
||||
<div style={{
|
||||
padding: '12px 16px',
|
||||
background: theme.bgElevated,
|
||||
background: 'var(--surface)',
|
||||
borderRadius: 12,
|
||||
border: `1px solid ${theme.border}`,
|
||||
border: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
}}>
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: theme.accent, animation: 'pulse 1s infinite' }} />
|
||||
<span style={{ fontSize: 13, color: theme.text2 }}>分析中...</span>
|
||||
<div style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)', animation: 'pulse 1s infinite' }} />
|
||||
<span style={{ fontSize: 13, color: 'var(--muted)' }}>分析中...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -193,10 +193,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
style={{
|
||||
padding: '6px 12px',
|
||||
fontSize: 12,
|
||||
background: theme.bgHover,
|
||||
border: `1px solid ${theme.border}`,
|
||||
background: 'var(--rail-hover)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 6,
|
||||
color: theme.text2,
|
||||
color: 'var(--muted)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>{q}</button>
|
||||
@@ -206,7 +206,7 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
{/* Chat Input */}
|
||||
<div style={{
|
||||
padding: '16px 24px',
|
||||
borderTop: `1px solid ${theme.border}`,
|
||||
borderTop: '1px solid var(--border)',
|
||||
display: 'flex',
|
||||
gap: 12,
|
||||
}}>
|
||||
@@ -219,10 +219,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
flex: 1,
|
||||
padding: 12,
|
||||
fontSize: 14,
|
||||
background: theme.bgHover,
|
||||
border: `1px solid ${theme.border}`,
|
||||
background: 'var(--rail-hover)',
|
||||
border: '1px solid var(--border)',
|
||||
borderRadius: 8,
|
||||
color: theme.text,
|
||||
color: 'var(--fg)',
|
||||
outline: 'none',
|
||||
}}
|
||||
/>
|
||||
@@ -233,8 +233,10 @@ export const ChatPanel: React.FC<ChatPanelProps> = ({
|
||||
padding: '12px 20px',
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
background: chatLoading || !chatInput.trim() ? theme.bgHover : theme.gradientAccent,
|
||||
color: chatLoading || !chatInput.trim() ? theme.text3 : '#fff',
|
||||
background: chatLoading || !chatInput.trim()
|
||||
? 'var(--rail-hover)'
|
||||
: 'linear-gradient(90deg, var(--accent), var(--accent-hover))',
|
||||
color: chatLoading || !chatInput.trim() ? 'var(--muted)' : '#fff',
|
||||
border: 'none',
|
||||
borderRadius: 8,
|
||||
cursor: chatLoading || !chatInput.trim() ? 'not-allowed' : 'pointer',
|
||||
|
||||
Reference in New Issue
Block a user