Dark mode was 58 `.dark .bg-white`-style rules patching over components that hardcoded light shades, so every new component silently owed the stylesheet another override — a debt this session kept adding to. Components now name the role of a colour rather than the shade: bg-surface, text-ink, text-muted, border-line, text-danger. Those map through @theme to CSS variables, and light and dark are two sets of values for the same variables. The override block is gone entirely. Solid button fills stay literal; they read correctly on both themes and never had overrides. Verified in the browser in both themes, Perspective included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2HFeU5neCKagTnmA6o9Tu
61 lines
2.0 KiB
JavaScript
61 lines
2.0 KiB
JavaScript
import { useState } from 'react'
|
|
|
|
export default function Login({ onLogin }) {
|
|
const [user, setUser] = useState('')
|
|
const [pass, setPass] = useState('')
|
|
const [error, setError] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
async function handleSubmit(e) {
|
|
e.preventDefault()
|
|
setError('')
|
|
setLoading(true)
|
|
try {
|
|
await onLogin(user, pass)
|
|
} catch {
|
|
setError('Invalid username or password')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center justify-center h-screen bg-raised">
|
|
<div className="bg-surface border border-line rounded-lg p-8 w-80 shadow-sm">
|
|
<h1 className="text-lg font-semibold text-ink mb-6">Dataflow</h1>
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div>
|
|
<label className="block text-xs text-muted mb-1">Username</label>
|
|
<input
|
|
type="text"
|
|
autoFocus
|
|
value={user}
|
|
onChange={e => setUser(e.target.value)}
|
|
className="w-full border border-line rounded px-3 py-2 text-sm focus:outline-none focus:border-accent"
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs text-muted mb-1">Password</label>
|
|
<input
|
|
type="password"
|
|
value={pass}
|
|
onChange={e => setPass(e.target.value)}
|
|
className="w-full border border-line rounded px-3 py-2 text-sm focus:outline-none focus:border-accent"
|
|
required
|
|
/>
|
|
</div>
|
|
{error && <p className="text-xs text-danger">{error}</p>}
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full bg-blue-600 text-white rounded px-3 py-2 text-sm font-medium hover:bg-blue-700 disabled:opacity-50"
|
|
>
|
|
{loading ? 'Signing in…' : 'Sign in'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|