feat(ui): establish theme tokens and global base styles
This commit is contained in:
parent
83e2b95501
commit
f42bd53cff
|
|
@ -8,7 +8,6 @@ import { ErrorBoundary } from './components/ErrorBoundary';
|
||||||
import { ToastContainer } from './components/Toast';
|
import { ToastContainer } from './components/Toast';
|
||||||
import { useToast } from './hooks/useToast';
|
import { useToast } from './hooks/useToast';
|
||||||
import { createContext, useContext } from 'react';
|
import { createContext, useContext } from 'react';
|
||||||
import { colors, radius } from './theme';
|
|
||||||
|
|
||||||
// Create toast context to share toast functionality across the app
|
// Create toast context to share toast functionality across the app
|
||||||
interface ToastContextType {
|
interface ToastContextType {
|
||||||
|
|
@ -48,10 +47,10 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ToastContext.Provider value={toast}>
|
<ToastContext.Provider value={toast}>
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen">
|
||||||
<ToastContainer messages={toast.messages} onClose={toast.removeToast} />
|
<ToastContainer messages={toast.messages} onClose={toast.removeToast} />
|
||||||
|
|
||||||
<header className="bg-white shadow-sm border-b border-gray-100 ">
|
<header className="sticky top-0 z-20 border-b border-slate-200/70 bg-white/85 shadow-sm backdrop-blur-md dark:border-slate-700/60 dark:bg-slate-900/70">
|
||||||
<div className="max-w-7xl mx-auto px-4">
|
<div className="max-w-7xl mx-auto px-4">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -85,7 +84,7 @@ function App() {
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer className="bg-white border-t border-gray-100 mt-12">
|
<footer className="mt-12 border-t border-slate-200/70 bg-white/70 backdrop-blur-sm dark:border-slate-700/60 dark:bg-slate-900/45">
|
||||||
<div className="max-w-7xl mx-auto py-6 px-4">
|
<div className="max-w-7xl mx-auto py-6 px-4">
|
||||||
<p className="text-center text-sm text-gray-500">
|
<p className="text-center text-sm text-gray-500">
|
||||||
Recipe Manager MVP - Built with React + Vite + TypeScript
|
Recipe Manager MVP - Built with React + Vite + TypeScript
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,39 @@
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--text: #374151;
|
--color-primary: #2563eb;
|
||||||
--text-h: #1e293b;
|
--color-primary-dark: #1d4ed8;
|
||||||
--bg: #fff;
|
--color-primary-light: #dbeafe;
|
||||||
--bg-alt: #f9fafb;
|
--color-accent: #9333ea;
|
||||||
--border: #e5e7eb;
|
|
||||||
--code-bg: #f4f3ec;
|
|
||||||
--accent: #aa3bff;
|
|
||||||
--accent-bg: rgba(170, 59, 255, 0.08);
|
|
||||||
--accent-border: rgba(170, 59, 255, 0.35);
|
|
||||||
--card-shadow: 0 2px 8px 0 rgba(28,30,34,0.08);
|
|
||||||
|
|
||||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
--text: #1f2937;
|
||||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
--text-h: #0f172a;
|
||||||
--mono: ui-monospace, Consolas, monospace;
|
--text-dim: #64748b;
|
||||||
|
|
||||||
|
--bg: #f4f7fb;
|
||||||
|
--bg-alt: #edf2f7;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--surface-muted: #f8fafc;
|
||||||
|
--border: #dbe3ef;
|
||||||
|
--code-bg: #eef2f7;
|
||||||
|
|
||||||
|
--radius-sm: 0.5rem;
|
||||||
|
--radius-md: 0.75rem;
|
||||||
|
--radius-lg: 1rem;
|
||||||
|
|
||||||
|
--shadow-subtle: 0 1px 2px rgba(15, 23, 42, 0.06);
|
||||||
|
--card-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
|
||||||
|
--shadow-hover: 0 14px 34px rgba(15, 23, 42, 0.12);
|
||||||
|
--focus-ring: 0 0 0 3px rgba(37, 99, 235, 0.25);
|
||||||
|
|
||||||
|
--surface-gradient:
|
||||||
|
radial-gradient(1200px 500px at -10% -10%, rgba(147, 51, 234, 0.1), transparent 60%),
|
||||||
|
radial-gradient(900px 420px at 115% -5%, rgba(37, 99, 235, 0.08), transparent 52%),
|
||||||
|
linear-gradient(180deg, #f8fbff 0%, #edf2f7 100%);
|
||||||
|
|
||||||
|
--sans: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
--heading: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
|
@ -26,16 +45,25 @@
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--text: #d1d5db;
|
--text: #e2e8f0;
|
||||||
--text-h: #f3f4f6;
|
--text-h: #f8fafc;
|
||||||
--bg: #16171d;
|
--text-dim: #94a3b8;
|
||||||
--bg-alt: #1a1b20;
|
|
||||||
--border: #2e303a;
|
--bg: #0f172a;
|
||||||
--code-bg: #1f2028;
|
--bg-alt: #111b2e;
|
||||||
--accent: #c084fc;
|
--surface: #132136;
|
||||||
--accent-bg: rgba(192, 132, 252, 0.11);
|
--surface-muted: #172842;
|
||||||
--accent-border: rgba(192, 132, 252, 0.33);
|
--border: #22334d;
|
||||||
--card-shadow: 0 3px 14px 0 rgba(32,34,40,0.21);
|
--code-bg: #1a2942;
|
||||||
|
|
||||||
|
--shadow-subtle: 0 1px 2px rgba(2, 6, 23, 0.35);
|
||||||
|
--card-shadow: 0 10px 30px rgba(2, 6, 23, 0.45);
|
||||||
|
--shadow-hover: 0 14px 34px rgba(2, 6, 23, 0.55);
|
||||||
|
|
||||||
|
--surface-gradient:
|
||||||
|
radial-gradient(900px 400px at 0% -5%, rgba(147, 51, 234, 0.2), transparent 60%),
|
||||||
|
radial-gradient(800px 420px at 105% -10%, rgba(37, 99, 235, 0.18), transparent 55%),
|
||||||
|
linear-gradient(180deg, #0f172a 0%, #111b2e 100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,56 +71,125 @@ body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: var(--sans);
|
font-family: var(--sans);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background: var(--bg-alt);
|
background: var(--surface-gradient);
|
||||||
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: var(--bg-alt);
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, button, textarea, select {
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: var(--heading);
|
||||||
|
color: var(--text-h);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
.button,
|
||||||
|
.btn {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
transition: background-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease,
|
||||||
|
transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover,
|
||||||
|
.button:hover,
|
||||||
|
.btn:hover {
|
||||||
|
box-shadow: var(--shadow-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus-visible,
|
||||||
|
.button:focus-visible,
|
||||||
|
.btn:focus-visible,
|
||||||
|
input:focus-visible,
|
||||||
|
textarea:focus-visible,
|
||||||
|
select:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
color: color-mix(in srgb, var(--text-dim) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card,
|
||||||
|
.shadow-card {
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
.shadow-card {
|
.shadow-card {
|
||||||
box-shadow: var(--card-shadow) !important;
|
box-shadow: var(--card-shadow) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast animation */
|
/* Toast animation */
|
||||||
@keyframes slide-in {
|
@keyframes slide-in {
|
||||||
from { transform: translateX(100%); opacity: 0; }
|
from {
|
||||||
to { transform: translateX(0); opacity: 1; }
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.animate-slide-in {
|
.animate-slide-in {
|
||||||
animation: slide-in 0.3s ease-out;
|
animation: slide-in 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Recipe Manager visual polish */
|
|
||||||
|
|
||||||
::-webkit-input-placeholder { color: #96a3b7; }
|
|
||||||
::-moz-placeholder { color: #96a3b7; }
|
|
||||||
:-ms-input-placeholder { color: #96a3b7; }
|
|
||||||
::placeholder { color: #96a3b7; }
|
|
||||||
|
|
||||||
input:focus, textarea:focus, select:focus { outline: 2px solid #3b82f6; outline-offset: 2px; }
|
|
||||||
|
|
||||||
button, .button, .btn {
|
|
||||||
transition: box-shadow 0.13s, background 0.13s, color 0.13s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card, .shadow-card { border-radius: 1rem; box-shadow: var(--card-shadow); }
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
background: #f3f4f6;
|
background: var(--surface-muted);
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #e5e7eb;
|
background: var(--border);
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.max-w-7xl, .max-w-6xl, .max-w-4xl, .max-w-3xl, .max-w-2xl, .max-w-xl, .max-w-md, .max-w-sm { max-width: 100vw !important; }
|
.max-w-7xl,
|
||||||
.p-8, .p-7, .p-6 { padding: 1rem !important; }
|
.max-w-6xl,
|
||||||
|
.max-w-4xl,
|
||||||
|
.max-w-3xl,
|
||||||
|
.max-w-2xl,
|
||||||
|
.max-w-xl,
|
||||||
|
.max-w-md,
|
||||||
|
.max-w-sm {
|
||||||
|
max-width: 100vw !important;
|
||||||
|
}
|
||||||
|
.p-8,
|
||||||
|
.p-7,
|
||||||
|
.p-6 {
|
||||||
|
padding: 1rem !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,80 @@
|
||||||
/** theme.ts - Defines visual theme tokens and utility styles across the Recipe Manager frontend */
|
/**
|
||||||
|
* Centralized design tokens for the Recipe Manager frontend.
|
||||||
|
* Keep values semantic so UI primitives can evolve without broad refactors.
|
||||||
|
*/
|
||||||
|
|
||||||
export const colors = {
|
export const colors = {
|
||||||
primary: '#2563eb', // Tailwind blue-600
|
primary: '#2563eb',
|
||||||
primaryDark: '#1d4ed8',
|
primaryDark: '#1d4ed8',
|
||||||
primaryLight: '#eff6ff',
|
primaryLight: '#dbeafe',
|
||||||
accent: '#aa3bff',
|
accent: '#9333ea',
|
||||||
success: '#16a34a',
|
success: '#15803d',
|
||||||
warning: '#eab308',
|
warning: '#ca8a04',
|
||||||
error: '#dc2626',
|
error: '#dc2626',
|
||||||
|
|
||||||
bg: '#fff',
|
bg: '#f4f7fb',
|
||||||
bgAlt: '#f9fafb', // Tailwind gray-50
|
bgAlt: '#edf2f7',
|
||||||
surface: '#fcfcff',
|
surface: '#ffffff',
|
||||||
border: '#e5e7eb', // Tailwind gray-200
|
surfaceMuted: '#f8fafc',
|
||||||
text: '#374151', // Tailwind gray-700
|
border: '#dbe3ef',
|
||||||
textDim: '#6b7280',
|
|
||||||
textHeading: '#1e293b',
|
|
||||||
cardShadow: '0 2px 8px 0 rgba(28,30,34,0.08)',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const radius = {
|
text: '#1f2937',
|
||||||
xs: '4px',
|
textDim: '#64748b',
|
||||||
sm: '6px',
|
textHeading: '#0f172a',
|
||||||
md: '10px',
|
|
||||||
lg: '16px',
|
|
||||||
full: '999px',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const spacing = {
|
focusRing: '#2563eb',
|
||||||
xs: '4px',
|
|
||||||
sm: '8px',
|
|
||||||
md: '16px',
|
|
||||||
lg: '24px',
|
|
||||||
xl: '40px',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const shadows = {
|
|
||||||
card: '0 2px 8px 0 rgba(28,30,34,0.08)',
|
|
||||||
hover: '0 4px 20px 0 rgba(28,30,34,0.16)',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const typography = {
|
export const typography = {
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: 'system-ui, Segoe UI, Roboto, sans-serif',
|
sans: "Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
|
||||||
heading: 'system-ui, Segoe UI, Roboto, sans-serif',
|
heading: "Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
|
||||||
mono: 'ui-monospace, Consolas, monospace',
|
mono: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
||||||
|
},
|
||||||
|
fontSize: {
|
||||||
|
xs: '0.75rem',
|
||||||
|
sm: '0.875rem',
|
||||||
|
base: '1rem',
|
||||||
|
lg: '1.125rem',
|
||||||
|
xl: '1.25rem',
|
||||||
|
'2xl': '1.5rem',
|
||||||
|
'3xl': '1.875rem',
|
||||||
|
},
|
||||||
|
lineHeight: {
|
||||||
|
tight: '1.2',
|
||||||
|
normal: '1.5',
|
||||||
|
relaxed: '1.65',
|
||||||
},
|
},
|
||||||
fontWeight: {
|
fontWeight: {
|
||||||
regular: 400,
|
regular: 400,
|
||||||
medium: 500,
|
medium: 500,
|
||||||
|
semibold: 600,
|
||||||
bold: 700,
|
bold: 700,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const spacing = {
|
||||||
|
xxs: '0.25rem',
|
||||||
|
xs: '0.5rem',
|
||||||
|
sm: '0.75rem',
|
||||||
|
md: '1rem',
|
||||||
|
lg: '1.5rem',
|
||||||
|
xl: '2rem',
|
||||||
|
'2xl': '2.5rem',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const radius = {
|
||||||
|
xs: '0.375rem',
|
||||||
|
sm: '0.5rem',
|
||||||
|
md: '0.75rem',
|
||||||
|
lg: '1rem',
|
||||||
|
xl: '1.25rem',
|
||||||
|
full: '9999px',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const shadows = {
|
||||||
|
subtle: '0 1px 2px rgba(15, 23, 42, 0.06)',
|
||||||
|
card: '0 10px 30px rgba(15, 23, 42, 0.08)',
|
||||||
|
hover: '0 14px 34px rgba(15, 23, 42, 0.12)',
|
||||||
|
focus: '0 0 0 3px rgba(37, 99, 235, 0.25)',
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue