feat: design token system — DS_TEXT/DS_SURFACE/DS_BORDER + 237 hex migrations
Token infrastructure (src/lib/ds.ts): - DS_TEXT: 16 semantic text color tokens (primary/secondary/muted/success/ warning/error/info/brand/signal + dark variants successDark/warningDark/ signalDark/infoDark for text on tinted surfaces) - DS_BG: page/surface/subtle/muted background tokens - DS_BORDER: default/muted/strong border tokens - DS_SURFACE: 10 bg+border surface pairs (success/warning/error/info/indigo/ purple/orange/blue/neutral/slate) - DS_MATCH_TIER: score-tier surface aliases keyed by strong/moderate/weak - DS_PRE_MARKET / DS_MARKET_SIGNAL: named aliases for futureCard tokens - DS_SHADOW: card/panel/dialog elevation tokens - BADGE_COLORS: 14 semantic presets for GenericBadge (confidenceHigh, riskMedium, preMarket, marketSignal, verified, gold, silver, bronze…) GenericBadge (src/components/shared/GenericBadge.tsx): - New semanticVariant prop (keyof BADGE_COLORS) — preferred over raw hex - color prop becomes optional (fallback), type-documented as escape hatch Priority file migrations — 237 hex literals replaced across 10 files: ScoreBreakdownPanel.tsx −22 PipelineDetailPanel.tsx −22 FutureAvailabilityCard.tsx −26 AICompareSummary.tsx −27 LocationIntelligencePanel −39 AssistantPromptSuggestions −18 UnitStructurePanel.tsx −25 BerichtDialog.tsx −24 PreMarketPanel.tsx −24 PropertyActivityLogPanel −10 ESLint (eslint.config.js): - Updated rule message to reference new tokens (DS_TEXT, DS_SURFACE, etc.) - Added 'stroke' to monitored property names - Remains 'warn' for gradual migration; use check:tokens for CI gate CI script (scripts/check-tokens.js + npm run check:tokens): - Counts hex patterns in components/ + pages/ - Fails if count > THRESHOLD (ratchet: 1958 baseline, lower per sprint) - Reports top 15 offenders for prioritizing next migration batch Results: ESLint targeted sx-prop violations: 1752 → 1030 (−41%) 0 TypeScript errors, 154 tests green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import type { Property } from '../../domain/property'
|
||||
import { MockupUnitProvider } from '../../provider/MockupUnitProvider'
|
||||
import { propertyService } from '../../services/propertyService'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import { DS_BORDER, DS_PRE_MARKET, DS_SURFACE, DS_TEXT } from '../../lib/ds'
|
||||
import { floorLabel, SectionTitle } from './PropertyDetailHelpers'
|
||||
|
||||
export const MOCK_TODAY = new Date('2026-05-20')
|
||||
@@ -99,17 +100,17 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
<Box
|
||||
sx={{
|
||||
border: '1px solid',
|
||||
borderColor: enabled ? '#8b5cf6' : '#e2e8f0',
|
||||
borderColor: enabled ? '#8b5cf6' : DS_BORDER.default,
|
||||
borderRadius: 1.5,
|
||||
p: 1.75,
|
||||
bgcolor: enabled ? '#faf5ff' : 'transparent',
|
||||
bgcolor: enabled ? DS_PRE_MARKET.headerBg : 'transparent',
|
||||
transition: 'background 0.2s, border-color 0.2s',
|
||||
}}
|
||||
>
|
||||
{/* Toggle row */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
|
||||
<Zap size={15} color={enabled ? '#7c3aed' : '#94a3b8'} style={{ marginTop: 2 }} />
|
||||
<Zap size={15} color={enabled ? DS_PRE_MARKET.accent : DS_TEXT.disabled} style={{ marginTop: 2 }} />
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, lineHeight: 1.3 }}>
|
||||
Pre-Market Matching aktivieren
|
||||
@@ -120,13 +121,13 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, ml: 1, flexShrink: 0 }}>
|
||||
{saving && <CircularProgress size={12} sx={{ color: '#7c3aed' }} />}
|
||||
{saving && <CircularProgress size={12} sx={{ color: DS_PRE_MARKET.accent }} />}
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={handleToggle}
|
||||
size="small"
|
||||
sx={{
|
||||
'& .MuiSwitch-switchBase.Mui-checked': { color: '#7c3aed' },
|
||||
'& .MuiSwitch-switchBase.Mui-checked': { color: DS_PRE_MARKET.accent },
|
||||
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: '#8b5cf6' },
|
||||
}}
|
||||
/>
|
||||
@@ -150,9 +151,9 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
onClick={() => handleLeadTime(m)}
|
||||
sx={{
|
||||
height: 20, fontSize: '0.68rem', cursor: 'pointer',
|
||||
bgcolor: leadTimeMonths === m ? '#7c3aed' : '#f1f5f9',
|
||||
color: leadTimeMonths === m ? 'white' : '#374151',
|
||||
'&:hover': { bgcolor: leadTimeMonths === m ? '#6d28d9' : '#e2e8f0' },
|
||||
bgcolor: leadTimeMonths === m ? DS_PRE_MARKET.accent : DS_SURFACE.slate.bg,
|
||||
color: leadTimeMonths === m ? DS_TEXT.inverted : '#374151',
|
||||
'&:hover': { bgcolor: leadTimeMonths === m ? DS_PRE_MARKET.accentHover : DS_BORDER.default },
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -164,8 +165,8 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'flex-start', gap: 0.75, p: 1,
|
||||
borderRadius: 1, border: '1px solid',
|
||||
bgcolor: isActive ? '#f0fdf4' : '#fff7ed',
|
||||
borderColor: isActive ? '#bbf7d0' : '#fed7aa',
|
||||
bgcolor: isActive ? DS_SURFACE.success.bg : DS_SURFACE.orange.bg,
|
||||
borderColor: isActive ? DS_SURFACE.success.border : DS_SURFACE.orange.border,
|
||||
}}
|
||||
>
|
||||
{isActive
|
||||
@@ -184,7 +185,7 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
|
||||
{/* Unit-level release controls */}
|
||||
{(p.units?.length ?? 0) > 0 && (
|
||||
<Box sx={{ mt: 1.25, pt: 1.25, borderTop: '1px solid #e9d5ff' }}>
|
||||
<Box sx={{ mt: 1.25, pt: 1.25, borderTop: `1px solid ${DS_PRE_MARKET.border}` }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: '#4c1d95', textTransform: 'uppercase', letterSpacing: 0.5, fontSize: '0.63rem', display: 'block', mb: 0.875 }}>
|
||||
Einheiten freigeben
|
||||
</Typography>
|
||||
@@ -201,10 +202,10 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Typography sx={{ fontSize: '0.72rem', fontWeight: 600, color: '#1e293b' }}>
|
||||
<Typography sx={{ fontSize: '0.72rem', fontWeight: 600, color: DS_TEXT.primary }}>
|
||||
{floorLabel(u)}{u.unitLabel ? ` · ${u.unitLabel}` : ''}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: '0.65rem', color: '#64748b' }}>
|
||||
<Typography sx={{ fontSize: '0.65rem', color: DS_TEXT.muted }}>
|
||||
{u.areaSqm.toLocaleString('de-CH')} m²
|
||||
{u.currentTenant ? ` · ${u.currentTenant}` : ''}
|
||||
</Typography>
|
||||
@@ -223,7 +224,7 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
{unitSaving[u.id] && <CircularProgress size={10} sx={{ color: '#7c3aed' }} />}
|
||||
{unitSaving[u.id] && <CircularProgress size={10} sx={{ color: DS_PRE_MARKET.accent }} />}
|
||||
<Switch
|
||||
size="small"
|
||||
checked={us.enabled}
|
||||
@@ -233,7 +234,7 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
saveUnit(u.id, checked, us.availableFrom)
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiSwitch-switchBase.Mui-checked': { color: '#7c3aed' },
|
||||
'& .MuiSwitch-switchBase.Mui-checked': { color: DS_PRE_MARKET.accent },
|
||||
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { bgcolor: '#8b5cf6' },
|
||||
}}
|
||||
/>
|
||||
@@ -245,25 +246,25 @@ export function PreMarketPanel({ p }: { p: Property }) {
|
||||
)}
|
||||
|
||||
{/* Demand Intelligence */}
|
||||
<Box sx={{ mt: 1.25, pt: 1.25, borderTop: '1px solid #e9d5ff' }}>
|
||||
<Box sx={{ mt: 1.25, pt: 1.25, borderTop: `1px solid ${DS_PRE_MARKET.border}` }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: '#4c1d95', textTransform: 'uppercase', letterSpacing: 0.5, fontSize: '0.63rem', display: 'block', mb: 0.875 }}>
|
||||
Matching Demand Intelligence
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<Users size={11} color="#7c3aed" style={{ flexShrink: 0 }} />
|
||||
<Users size={11} color={DS_PRE_MARKET.accent} style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: '#374151', lineHeight: 1.3 }}>
|
||||
<strong>{demandProfiles} aktive Suchprofile</strong> im System erkannt
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<Target size={11} color="#7c3aed" style={{ flexShrink: 0 }} />
|
||||
<Target size={11} color={DS_PRE_MARKET.accent} style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: '#374151', lineHeight: 1.3 }}>
|
||||
<strong>{highQualityLeads} hochwertige Suchanfragen</strong> mit passendem Flächenbedarf
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<TrendingUp size={11} color="#7c3aed" style={{ flexShrink: 0 }} />
|
||||
<TrendingUp size={11} color={DS_PRE_MARKET.accent} style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: '#374151', lineHeight: 1.3 }}>
|
||||
Frühzeitige Matchgelegenheit — vor offizieller Vermarktung exklusiv verfügbar
|
||||
</Typography>
|
||||
|
||||
Reference in New Issue
Block a user