update css

This commit is contained in:
Nico 2026-01-23 22:23:37 -08:00
parent 68976a7683
commit aea07374d9
20 changed files with 653 additions and 734 deletions

View File

@ -2,7 +2,7 @@ import { ROLES } from "../../constants/roles";
export default function UserRoleCard({ user, onRoleChange }) {
return (
<div className="user-card">
<div className="card flex-between p-3 my-2 shadow-sm" style={{ transition: 'var(--transition-base)' }}>
<div className="user-info">
<strong>{user.name}</strong>
<span className="user-username">@{user.username}</span>
@ -10,7 +10,8 @@ export default function UserRoleCard({ user, onRoleChange }) {
<select
onChange={(e) => onRoleChange(user.id, e.target.value)}
value={user.role}
className="role-select"
className="form-select"
style={{ fontSize: 'var(--font-size-sm)' }}
>
<option value={ROLES.VIEWER}>Viewer</option>
<option value={ROLES.EDITOR}>Editor</option>

View File

@ -39,11 +39,11 @@ export default function AddImageModal({ itemName, onClose, onAddImage }) {
};
return (
<div className="add-image-modal-overlay" onClick={onClose}>
<div className="add-image-modal" onClick={(e) => e.stopPropagation()}>
<h2>Add Image</h2>
<p className="add-image-subtitle">
There's no image for <strong>"{itemName}"</strong> yet. Add a new image?
<div className="modal-overlay" onClick={onClose}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2 className="modal-title">Add Image</h2>
<p className="text-center mb-4" style={{ color: 'var(--color-text-secondary)', fontSize: '0.95em' }}>
There's no image for <strong className="text-primary">"{itemName}"</strong> yet. Add a new image?
</p>
{!imagePreview ? (
@ -83,12 +83,12 @@ export default function AddImageModal({ itemName, onClose, onAddImage }) {
style={{ display: "none" }}
/>
<div className="add-image-actions">
<button onClick={onClose} className="add-image-cancel">
<div className="modal-actions">
<button onClick={onClose} className="btn btn-outline flex-1">
Cancel
</button>
{imagePreview && (
<button onClick={handleConfirm} className="add-image-confirm">
<button onClick={handleConfirm} className="btn btn-success flex-1">
Add Image
</button>
)}

View File

@ -10,13 +10,13 @@ export default function ConfirmAddExistingModal({
const newQuantity = currentQuantity + addingQuantity;
return (
<div className="confirm-add-existing-overlay" onClick={onCancel}>
<div className="confirm-add-existing-modal" onClick={(e) => e.stopPropagation()}>
<h2 className="confirm-add-existing-title">
<strong>{itemName}</strong> is already in your list
<div className="modal-overlay" onClick={onCancel}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2 className="text-center text-xl mb-4">
<strong className="text-primary font-semibold">{itemName}</strong> is already in your list
</h2>
<div className="confirm-add-existing-content">
<div className="mb-4">
<div className="confirm-add-existing-qty-info">
<div className="qty-row">
<span className="qty-label">Current quantity:</span>
@ -33,11 +33,11 @@ export default function ConfirmAddExistingModal({
</div>
</div>
<div className="confirm-add-existing-actions">
<button className="confirm-add-existing-btn cancel" onClick={onCancel}>
<div className="modal-actions">
<button className="btn btn-outline flex-1" onClick={onCancel}>
Cancel
</button>
<button className="confirm-add-existing-btn confirm" onClick={onConfirm}>
<button className="btn btn-primary flex-1" onClick={onConfirm}>
Update Quantity
</button>
</div>

View File

@ -17,7 +17,7 @@ export default function ImageModal({ imageUrl, itemName, onClose }) {
<div className="image-modal-overlay" onClick={onClose}>
<div className="image-modal-content" onClick={onClose}>
<img src={imageUrl} alt={itemName} className="image-modal-img" />
<p className="image-modal-caption">{itemName}</p>
<p className="text-center mt-3 text-lg font-semibold">{itemName}</p>
</div>
</div>
);

View File

@ -2,24 +2,24 @@ import "../../styles/SimilarItemModal.css";
export default function SimilarItemModal({ originalName, suggestedName, onCancel, onNo, onYes }) {
return (
<div className="similar-item-modal-overlay" onClick={onCancel}>
<div className="similar-item-modal" onClick={(e) => e.stopPropagation()}>
<h2>Similar Item Found</h2>
<p className="similar-item-question">
Do you mean <strong>"{suggestedName}"</strong>?
<div className="modal-overlay" onClick={onCancel}>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<h2 className="modal-title">Similar Item Found</h2>
<p className="text-center text-lg mb-2">
Do you mean <strong className="text-primary">"{suggestedName}"</strong>?
</p>
<p className="similar-item-clarification">
<p className="text-center text-sm mb-4" style={{ color: 'var(--color-text-secondary)', fontStyle: 'italic' }}>
You entered: "{originalName}"
</p>
<div className="similar-item-actions">
<button onClick={onCancel} className="similar-item-cancel">
<div className="modal-actions">
<button onClick={onCancel} className="btn btn-outline flex-1">
Cancel
</button>
<button onClick={onNo} className="similar-item-no">
<button onClick={onNo} className="btn btn-secondary flex-1">
No, Create New
</button>
<button onClick={onYes} className="similar-item-yes">
<button onClick={onYes} className="btn btn-success flex-1">
Yes, Use Suggestion
</button>
</div>

View File

@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client'
import App from './App'
import './index.css'
import './styles/theme.css'
import './styles/utilities.css'
createRoot(document.getElementById('root')!).render(
<StrictMode>

View File

@ -23,10 +23,10 @@ export default function AdminPanel() {
}
return (
<div className="admin-panel-page">
<div className="admin-panel-container">
<h1 className="admin-panel-title">Admin Panel</h1>
<div className="admin-panel-users">
<div className="p-4" style={{ minHeight: '100vh' }}>
<div className="card" style={{ maxWidth: '800px', margin: '0 auto' }}>
<h1 className="text-center text-3xl font-bold mb-4">Admin Panel</h1>
<div className="mt-4">
{users.map((user) => (
<UserRoleCard
key={user.id}

View File

@ -27,16 +27,16 @@ export default function Login() {
};
return (
<div className="login-wrapper">
<div className="login-box">
<h1 className="login-title">Login</h1>
<div className="flex-center" style={{ minHeight: '100vh', padding: '1em', background: '#f8f9fa' }}>
<div className="card card-elevated" style={{ width: '100%', maxWidth: '360px' }}>
<h1 className="text-center text-2xl mb-3">Login</h1>
<ErrorMessage message={error} />
<form onSubmit={submit}>
<FormInput
type="text"
className="login-input"
className="form-input my-2"
placeholder="Username"
onChange={(e) => setUsername(e.target.value)}
/>
@ -44,7 +44,7 @@ export default function Login() {
<div className="login-password-wrapper">
<FormInput
type={showPassword ? "text" : "password"}
className="login-input"
className="form-input"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
/>
@ -58,11 +58,11 @@ export default function Login() {
</button>
</div>
<button type="submit" className="login-button">Login</button>
<button type="submit" className="btn btn-primary btn-block mt-2">Login</button>
</form>
<p className="login-register">
Need an account? <Link to="/register">Register here</Link>
<p className="text-center mt-3">
Need an account? <Link to="/register" className="text-primary">Register here</Link>
</p>
</div>
</div>

View File

@ -59,7 +59,7 @@ export default function Register() {
return (
<div className="register-container">
<h1>Register</h1>
<h1 className="text-center mb-4 text-2xl font-bold">Register</h1>
<ErrorMessage message={error} />
<ErrorMessage message={success} type="success" />
@ -67,6 +67,7 @@ export default function Register() {
<form className="register-form" onSubmit={submit}>
<FormInput
type="text"
className="form-input"
placeholder="Name"
onChange={(e) => setName(e.target.value)}
required
@ -74,6 +75,7 @@ export default function Register() {
<FormInput
type="text"
className="form-input"
placeholder="Username"
onKeyUp={(e) => setUsername(e.target.value)}
required
@ -81,6 +83,7 @@ export default function Register() {
<FormInput
type="password"
className="form-input"
placeholder="Password"
onChange={(e) => setPassword(e.target.value)}
required
@ -88,18 +91,19 @@ export default function Register() {
<FormInput
type="password"
className="form-input"
placeholder="Confirm Password"
onChange={(e) => setConfirm(e.target.value)}
required
/>
<button disabled={error !== ""} type="submit">
<button disabled={error !== ""} type="submit" className="btn btn-primary btn-block mt-2">
Create Account
</button>
</form>
<p className="register-link">
Already have an account? <Link to="/login">Login here</Link>
<p className="text-center mt-3">
Already have an account? <Link to="/login" className="text-primary font-semibold">Login here</Link>
</p>
</div>
);

View File

@ -37,8 +37,8 @@ export default function Settings() {
return (
<div className="settings-page">
<div className="settings-container">
<h1 className="settings-title">Settings</h1>
<div className="card" style={{ maxWidth: '800px', margin: '0 auto' }}>
<h1 className="text-2xl font-semibold mb-4">Settings</h1>
<div className="settings-tabs">
<button
@ -65,7 +65,7 @@ export default function Settings() {
{/* Appearance Tab */}
{activeTab === "appearance" && (
<div className="settings-section">
<h2 className="settings-section-title">Appearance</h2>
<h2 className="text-xl font-semibold mb-4">Appearance</h2>
<div className="settings-group">
<label className="settings-label">Theme</label>
@ -113,14 +113,14 @@ export default function Settings() {
{/* List Display Tab */}
{activeTab === "list" && (
<div className="settings-section">
<h2 className="settings-section-title">List Display</h2>
<h2 className="text-xl font-semibold mb-4">List Display</h2>
<div className="settings-group">
<label className="settings-label">Default Sort Mode</label>
<select
value={settings.defaultSortMode}
onChange={(e) => handleSelectChange("defaultSortMode", e.target.value)}
className="settings-select"
className="form-select mt-2"
>
<option value="zone">By Zone</option>
<option value="az">A Z</option>
@ -188,7 +188,7 @@ export default function Settings() {
{/* Behavior Tab */}
{activeTab === "behavior" && (
<div className="settings-section">
<h2 className="settings-section-title">Behavior</h2>
<h2 className="text-xl font-semibold mb-4">Behavior</h2>
<div className="settings-group">
<label className="settings-label">
@ -239,8 +239,8 @@ export default function Settings() {
)}
</div>
<div className="settings-actions">
<button onClick={handleReset} className="settings-btn-reset">
<div className="mt-4">
<button onClick={handleReset} className="btn btn-outline">
Reset to Defaults
</button>
</div>

View File

@ -1,44 +1,4 @@
.add-image-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--modal-backdrop-bg);
display: flex;
justify-content: center;
align-items: center;
z-index: var(--z-modal);
animation: fadeIn 0.2s ease-out;
}
.add-image-modal {
background: var(--modal-bg);
padding: var(--spacing-xl);
border-radius: var(--border-radius-xl);
max-width: 500px;
width: 90%;
box-shadow: var(--shadow-xl);
animation: slideUp 0.3s ease-out;
}
.add-image-modal h2 {
margin: 0 0 var(--spacing-sm) 0;
font-size: var(--font-size-2xl);
color: var(--color-text-primary);
text-align: center;
}
.add-image-subtitle {
margin: 0 0 var(--spacing-xl) 0;
color: var(--color-text-secondary);
font-size: 0.95em;
text-align: center;
}
.add-image-subtitle strong {
color: var(--color-primary);
}
/* AddImageModal - custom styles for unique components */
.add-image-options {
display: flex;
@ -121,58 +81,3 @@
.add-image-remove:hover {
background: rgba(255, 0, 0, 1);
}
.add-image-actions {
display: flex;
gap: 1em;
margin-top: 1.5em;
}
.add-image-cancel,
.add-image-confirm {
flex: 1;
padding: 0.8em;
border: none;
border-radius: 6px;
font-size: 1em;
cursor: pointer;
transition: all 0.2s;
}
.add-image-cancel {
background: #f0f0f0;
color: #333;
}
.add-image-cancel:hover {
background: #e0e0e0;
}
.add-image-confirm {
background: #28a745;
color: white;
}
.add-image-confirm:hover {
background: #218838;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

View File

@ -1,3 +1,5 @@
/* ImageModal - specialized full-screen image viewer */
.image-modal-overlay {
position: fixed;
top: 0;
@ -44,30 +46,6 @@
}
}
.image-modal-close {
position: absolute;
top: -15px;
right: -15px;
width: 40px;
height: 40px;
border-radius: 50%;
background: #ff4444;
color: white;
border: 3px solid white;
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
z-index: 1001;
transition: background 0.2s;
}
.image-modal-close:hover {
background: #cc0000;
}
.image-modal-img {
max-width: 100%;
max-height: 70vh;
@ -76,14 +54,6 @@
border-radius: 8px;
}
.image-modal-caption {
text-align: center;
margin-top: 1rem;
font-size: 1.2rem;
font-weight: 600;
color: #333;
}
@media (max-width: 768px) {
.image-modal-overlay {
padding: 1rem;
@ -92,8 +62,5 @@
.image-modal-img {
max-height: 60vh;
}
.image-modal-caption {
font-size: 1rem;
}
}

View File

@ -1,115 +1,3 @@
.similar-item-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--modal-backdrop-bg);
display: flex;
justify-content: center;
align-items: center;
z-index: var(--z-modal);
animation: fadeIn 0.2s ease-out;
}
/* SimilarItemModal - uses utility classes from utilities.css */
/* No custom styles needed - all handled by utilities */
.similar-item-modal {
background: var(--modal-bg);
padding: var(--spacing-xl);
border-radius: var(--border-radius-xl);
max-width: 500px;
width: 90%;
box-shadow: var(--shadow-xl);
animation: slideUp 0.3s ease-out;
}
.similar-item-modal h2 {
margin: 0 0 var(--spacing-md) 0;
font-size: var(--font-size-2xl);
color: var(--color-text-primary);
text-align: center;
}
.similar-item-question {
margin: 0 0 var(--spacing-sm) 0;
font-size: var(--font-size-lg);
color: var(--color-text-primary);
text-align: center;
}
.similar-item-question strong {
color: var(--color-primary);
}
.similar-item-clarification {
margin: 0 0 var(--spacing-xl) 0;
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
text-align: center;
font-style: italic;
}
.similar-item-actions {
display: flex;
gap: 0.8em;
margin-top: 1.5em;
}
.similar-item-cancel,
.similar-item-no,
.similar-item-yes {
flex: 1;
padding: var(--button-padding-y) var(--button-padding-x);
border: none;
border-radius: var(--button-border-radius);
font-size: var(--font-size-base);
cursor: pointer;
transition: var(--transition-base);
font-weight: var(--button-font-weight);
}
.similar-item-cancel {
background: var(--color-gray-200);
color: var(--color-text-primary);
}
.similar-item-cancel:hover {
background: var(--color-gray-300);
}
.similar-item-no {
background: var(--color-secondary);
color: var(--color-text-inverse);
}
.similar-item-no:hover {
background: var(--color-secondary-hover);
}
.similar-item-yes {
background: var(--color-success);
color: var(--color-text-inverse);
}
.similar-item-yes:hover {
background: var(--color-success-hover);
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

View File

@ -1,19 +1,4 @@
.user-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md);
margin: var(--spacing-sm) 0;
background: var(--color-bg-surface);
border-radius: var(--border-radius-lg);
border: var(--border-width-thin) solid var(--color-border-light);
box-shadow: var(--shadow-sm);
transition: var(--transition-base);
}
.user-card:hover {
box-shadow: var(--shadow-md);
}
/* UserRoleCard - custom styles only */
.user-info {
display: flex;
@ -26,28 +11,3 @@
font-size: var(--font-size-sm);
}
.user-info h3 {
color: var(--color-text-primary);
margin: 0;
}
.role-select {
padding: var(--spacing-sm);
border-radius: var(--border-radius-sm);
border: var(--border-width-thin) solid var(--input-border-color);
background: var(--color-bg-surface);
color: var(--color-text-primary);
cursor: pointer;
font-size: var(--font-size-sm);
transition: var(--transition-base);
}
.role-select:hover {
border-color: var(--color-primary);
}
.role-select:focus {
outline: none;
border-color: var(--input-focus-border-color);
box-shadow: var(--input-focus-shadow);
}

View File

@ -1,55 +1,4 @@
/* Confirm Add Existing Modal */
.confirm-add-existing-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--modal-backdrop-bg);
display: flex;
align-items: center;
justify-content: center;
z-index: var(--z-modal);
padding: var(--spacing-md);
}
.confirm-add-existing-modal {
background: var(--modal-bg);
border-radius: var(--modal-border-radius);
padding: var(--modal-padding);
max-width: 400px;
width: 100%;
box-shadow: var(--shadow-xl);
animation: slideIn 0.2s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.confirm-add-existing-title {
margin: 0 0 var(--spacing-lg) 0;
font-size: var(--font-size-xl);
color: var(--color-text-primary);
text-align: center;
font-weight: var(--font-weight-normal);
}
.confirm-add-existing-title strong {
color: var(--color-primary);
font-weight: var(--font-weight-semibold);
}
.confirm-add-existing-content {
margin-bottom: var(--spacing-lg);
}
/* ConfirmAddExistingModal - quantity breakdown box */
.confirm-add-existing-qty-info {
background: var(--color-bg-surface);
@ -90,55 +39,3 @@
font-size: var(--font-size-lg);
}
.confirm-add-existing-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-lg);
}
.confirm-add-existing-btn {
flex: 1;
padding: var(--button-padding-y) var(--button-padding-x);
border: none;
border-radius: var(--button-border-radius);
font-size: var(--font-size-base);
font-weight: var(--button-font-weight);
cursor: pointer;
transition: var(--transition-base);
}
.confirm-add-existing-btn.cancel {
background: var(--color-bg-surface);
color: var(--color-text-primary);
border: var(--border-width-thin) solid var(--color-border-medium);
}
.confirm-add-existing-btn.cancel:hover {
background: var(--color-bg-hover);
border-color: var(--color-border-dark);
}
.confirm-add-existing-btn.confirm {
background: var(--color-primary);
color: var(--color-text-inverse);
}
.confirm-add-existing-btn.confirm:hover {
background: var(--color-primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.confirm-add-existing-btn.confirm:active {
transform: translateY(0);
}
@media (max-width: 480px) {
.confirm-add-existing-modal {
max-width: 90%;
}
.confirm-add-existing-actions {
flex-direction: column;
}
}

View File

@ -1,41 +1,9 @@
/* Admin Panel Page */
.admin-panel-page {
padding: var(--spacing-lg);
min-height: 100vh;
}
/* Admin Panel - uses utility classes */
/* Responsive adjustments only */
.admin-panel-container {
max-width: 800px;
margin: 0 auto;
background: var(--color-bg-surface);
padding: var(--spacing-xl);
border-radius: var(--border-radius-lg);
box-shadow: var(--shadow-card);
}
.admin-panel-title {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--color-text-primary);
margin: 0 0 var(--spacing-xl) 0;
text-align: center;
}
.admin-panel-users {
margin-top: var(--spacing-xl);
}
/* Mobile Responsive */
@media (max-width: 768px) {
.admin-panel-page {
padding: var(--spacing-md);
}
.admin-panel-container {
padding: var(--spacing-lg);
}
.admin-panel-title {
font-size: var(--font-size-2xl);
padding: var(--spacing-md) !important;
}
}

View File

@ -1,37 +1,4 @@
.login-wrapper {
font-family: Arial, sans-serif;
padding: 1em;
background: #f8f9fa;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-box {
width: 100%;
max-width: 360px;
background: white;
padding: 1.5em;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.12);
}
.login-title {
text-align: center;
font-size: 1.6em;
margin-bottom: 1em;
}
.login-input {
width: 100%;
padding: 0.6em;
margin: 0.4em 0;
font-size: 1em;
border-radius: 4px;
border: 1px solid #ccc;
}
/* Login page - custom password toggle only */
.login-password-wrapper {
display: flex;
@ -40,7 +7,7 @@
margin: 0.4em 0;
}
.login-password-wrapper .login-input {
.login-password-wrapper .form-input {
flex: 1;
width: auto;
margin: 0;
@ -67,38 +34,3 @@
background: #e8e8e8;
}
.login-button {
width: 100%;
padding: 0.7em;
margin-top: 0.6em;
background: #007bff;
border: none;
color: white;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
}
.login-button:hover {
background: #0068d1;
}
.login-error {
color: red;
text-align: center;
margin-bottom: 0.6em;
}
.login-register {
text-align: center;
margin-top: 1em;
}
.login-register a {
color: #007bff;
text-decoration: none;
}
.login-register a:hover {
text-decoration: underline;
}

View File

@ -1,18 +1,12 @@
/* Register page - container only */
.register-container {
max-width: 400px;
margin: 50px auto;
padding: 2rem;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
font-family: Arial, sans-serif;
}
.register-container h1 {
text-align: center;
margin-bottom: 1.5rem;
font-size: 1.8rem;
font-weight: bold;
background: var(--color-bg-primary);
box-shadow: var(--shadow-lg);
}
.register-form {
@ -21,64 +15,3 @@
gap: 12px;
}
.register-form input {
padding: 12px 14px;
border: 1px solid #ccc;
border-radius: 8px;
font-size: 1rem;
outline: none;
transition: border-color 0.2s ease;
}
.register-form input:focus {
border-color: #0077ff;
}
.register-form button {
padding: 12px;
border: none;
background: #0077ff;
color: white;
font-size: 1rem;
border-radius: 8px;
cursor: pointer;
margin-top: 10px;
transition: background 0.2s ease;
}
.register-form button:hover:not(:disabled) {
background: #005fcc;
}
.register-form button:disabled {
background: #a8a8a8;
cursor: not-allowed;
}
.error-message {
height: 15px;
color: red;
text-align: center;
margin-bottom: 10px;
}
.success-message {
color: green;
text-align: center;
margin-bottom: 10px;
}
.register-link {
text-align: center;
margin-top: 1rem;
}
.register-link a {
color: #0077ff;
text-decoration: none;
font-weight: bold;
}
.register-link a:hover {
text-decoration: underline;
}

View File

@ -1,4 +1,4 @@
/* Settings Page Styles */
/* Settings Page - custom components only */
.settings-page {
padding: var(--spacing-lg);
@ -6,24 +6,7 @@
margin: 0 auto;
}
.settings-container {
background: var(--color-bg-surface);
border-radius: var(--border-radius-lg);
padding: var(--spacing-xl);
box-shadow: var(--shadow-sm);
}
.settings-title {
font-size: var(--font-size-2xl);
font-weight: 600;
color: var(--color-text-primary);
margin: 0 0 var(--spacing-xl);
}
/* === Tabs === */
/* Tabs */
.settings-tabs {
display: flex;
gap: var(--spacing-sm);
@ -31,7 +14,6 @@
border-bottom: 2px solid var(--color-border-light);
}
.settings-tab {
padding: var(--spacing-md) var(--spacing-lg);
background: none;
@ -45,30 +27,25 @@
margin-bottom: -2px;
}
.settings-tab:hover {
color: var(--color-primary);
background: var(--color-bg-hover);
}
.settings-tab.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
}
/* === Content === */
/* Content */
.settings-content {
min-height: 400px;
}
.settings-section {
animation: fadeIn 0.2s ease-in;
}
@keyframes fadeIn {
from {
opacity: 0;
@ -80,27 +57,16 @@
}
}
.settings-section-title {
font-size: var(--font-size-xl);
font-weight: 600;
color: var(--color-text-primary);
margin: 0 0 var(--spacing-lg);
}
.settings-group {
margin-bottom: var(--spacing-xl);
padding-bottom: var(--spacing-xl);
border-bottom: 1px solid var(--color-border-light);
}
.settings-group:last-child {
border-bottom: none;
}
.settings-label {
display: flex;
align-items: center;
@ -112,14 +78,12 @@
cursor: pointer;
}
.settings-label input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
}
.settings-description {
font-size: var(--font-size-sm);
color: var(--color-text-secondary);
@ -127,15 +91,13 @@
line-height: 1.5;
}
/* === Theme Buttons === */
/* Theme Buttons */
.settings-theme-options {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-sm);
}
.settings-theme-btn {
flex: 1;
padding: var(--spacing-md);
@ -149,40 +111,18 @@
transition: all 0.2s;
}
.settings-theme-btn:hover {
border-color: var(--color-primary);
background: var(--color-primary-light);
}
.settings-theme-btn.active {
border-color: var(--color-primary);
background: var(--color-primary);
color: var(--color-white);
}
/* === Select & Range === */
.settings-select {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border-medium);
border-radius: var(--border-radius-md);
background: var(--color-bg-surface);
color: var(--color-text-primary);
font-size: var(--font-size-base);
cursor: pointer;
margin-top: var(--spacing-sm);
}
.settings-select:focus {
outline: none;
border-color: var(--color-primary);
}
/* Range Slider */
.settings-range {
width: 100%;
height: 6px;
@ -195,7 +135,6 @@
-webkit-appearance: none;
}
.settings-range::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
@ -207,13 +146,11 @@
transition: all 0.2s;
}
.settings-range::-webkit-slider-thumb:hover {
background: var(--color-primary-hover);
transform: scale(1.1);
}
.settings-range::-moz-range-thumb {
width: 20px;
height: 20px;
@ -224,51 +161,17 @@
transition: all 0.2s;
}
.settings-range::-moz-range-thumb:hover {
background: var(--color-primary-hover);
transform: scale(1.1);
}
/* === Actions === */
.settings-actions {
margin-top: var(--spacing-2xl);
padding-top: var(--spacing-xl);
border-top: 2px solid var(--color-border-light);
text-align: center;
}
.settings-btn-reset {
padding: var(--spacing-md) var(--spacing-xl);
border: 2px solid var(--color-danger);
background: transparent;
color: var(--color-danger);
border-radius: var(--border-radius-md);
font-size: var(--font-size-base);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.settings-btn-reset:hover {
background: var(--color-danger);
color: var(--color-white);
}
/* === Responsive === */
/* Responsive */
@media (max-width: 768px) {
.settings-page {
padding: var(--spacing-md);
}
.settings-container {
padding: var(--spacing-lg);
}
.settings-tabs {
flex-wrap: nowrap;
overflow-x: auto;
@ -285,13 +188,3 @@
}
}
@media (max-width: 480px) {
.settings-title {
font-size: var(--font-size-xl);
}
.settings-container {
padding: var(--spacing-md);
}
}

View File

@ -0,0 +1,570 @@
/**
* Reusable Utility Classes
*
* Common patterns extracted from component styles.
* Import this file after theme.css in main.tsx
*/
/* ============================================
LAYOUT UTILITIES
============================================ */
/* Containers */
.container {
max-width: var(--container-max-width);
margin: 0 auto;
padding: var(--container-padding);
}
.container-full {
width: 100%;
padding: var(--spacing-md);
}
/* Centering */
.center-content {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-start {
display: flex;
justify-content: flex-start;
align-items: center;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-1 {
flex: 1;
}
/* ============================================
CARD COMPONENTS
============================================ */
.card {
background: var(--color-bg-surface);
border-radius: var(--card-border-radius);
padding: var(--card-padding);
box-shadow: var(--shadow-card);
}
.card-elevated {
background: var(--color-bg-surface);
border-radius: var(--card-border-radius);
padding: var(--spacing-lg);
box-shadow: var(--shadow-lg);
}
.card-title {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
margin-bottom: var(--spacing-md);
color: var(--color-text-primary);
}
/* ============================================
BUTTON COMPONENTS
============================================ */
.btn {
padding: var(--button-padding-y) var(--button-padding-x);
border: none;
border-radius: var(--button-border-radius);
font-size: var(--font-size-base);
font-weight: var(--button-font-weight);
cursor: pointer;
transition: var(--transition-base);
text-align: center;
display: inline-block;
}
.btn-primary {
background: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary-hover);
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-secondary {
background: var(--color-secondary);
color: var(--color-text-inverse);
}
.btn-secondary:hover:not(:disabled) {
background: var(--color-secondary-hover);
}
.btn-danger {
background: var(--color-danger);
color: var(--color-text-inverse);
}
.btn-danger:hover:not(:disabled) {
background: var(--color-danger-hover);
}
.btn-success {
background: var(--color-success);
color: var(--color-text-inverse);
}
.btn-success:hover:not(:disabled) {
background: var(--color-success-hover);
}
.btn-outline {
background: transparent;
color: var(--color-primary);
border: var(--border-width-thin) solid var(--color-primary);
}
.btn-outline:hover:not(:disabled) {
background: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-ghost {
background: var(--color-bg-surface);
color: var(--color-text-primary);
border: var(--border-width-thin) solid var(--color-border-medium);
}
.btn-ghost:hover:not(:disabled) {
background: var(--color-bg-hover);
border-color: var(--color-border-dark);
}
.btn-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-sm);
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-xl);
font-size: var(--font-size-lg);
}
.btn-block {
width: 100%;
display: block;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* ============================================
FORM COMPONENTS
============================================ */
.form-group {
margin-bottom: var(--spacing-md);
}
.form-label {
display: block;
margin-bottom: var(--spacing-xs);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
color: var(--color-text-primary);
}
.form-input {
width: 100%;
padding: var(--input-padding-y) var(--input-padding-x);
border: var(--border-width-thin) solid var(--input-border-color);
border-radius: var(--input-border-radius);
font-size: var(--font-size-base);
color: var(--color-text-primary);
background: var(--color-bg-surface);
transition: var(--transition-base);
box-sizing: border-box;
}
.form-input:focus {
outline: none;
border-color: var(--input-focus-border-color);
box-shadow: var(--input-focus-shadow);
}
.form-input::placeholder {
color: var(--color-text-muted);
}
.form-select {
width: 100%;
padding: var(--input-padding-y) var(--input-padding-x);
border: var(--border-width-thin) solid var(--input-border-color);
border-radius: var(--input-border-radius);
font-size: var(--font-size-base);
color: var(--color-text-primary);
background: var(--color-bg-surface);
cursor: pointer;
transition: var(--transition-base);
}
.form-select:focus {
outline: none;
border-color: var(--input-focus-border-color);
box-shadow: var(--input-focus-shadow);
}
/* ============================================
MODAL COMPONENTS
============================================ */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--modal-backdrop-bg);
display: flex;
align-items: center;
justify-content: center;
z-index: var(--z-modal);
padding: var(--spacing-md);
}
.modal {
background: var(--modal-bg);
border-radius: var(--modal-border-radius);
padding: var(--modal-padding);
max-width: var(--modal-max-width);
width: 100%;
box-shadow: var(--shadow-xl);
animation: modalSlideIn 0.2s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
margin-bottom: var(--spacing-lg);
}
.modal-title {
margin: 0;
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text-primary);
text-align: center;
}
.modal-content {
margin-bottom: var(--spacing-lg);
}
.modal-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-lg);
}
.modal-actions .btn {
flex: 1;
}
/* ============================================
LIST COMPONENTS
============================================ */
.list-unstyled {
list-style: none;
padding: 0;
margin: 0;
}
.list-item {
padding: var(--spacing-md);
border: var(--border-width-thin) solid var(--color-border-light);
border-radius: var(--border-radius-md);
background: var(--color-bg-surface);
margin-bottom: var(--spacing-sm);
transition: var(--transition-base);
}
.list-item:hover {
background: var(--color-bg-hover);
border-color: var(--color-border-medium);
transform: translateY(-1px);
}
.list-item:last-child {
margin-bottom: 0;
}
/* ============================================
IMAGE COMPONENTS
============================================ */
.image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--color-bg-surface);
border: var(--border-width-medium) dashed var(--color-border-medium);
border-radius: var(--border-radius-md);
color: var(--color-text-muted);
font-size: 2rem;
}
.image-thumbnail {
width: 50px;
height: 50px;
border-radius: var(--border-radius-md);
object-fit: cover;
border: var(--border-width-thin) solid var(--color-border-light);
}
/* ============================================
BADGE COMPONENTS
============================================ */
.badge {
display: inline-block;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--border-radius-full);
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
line-height: 1;
}
.badge-primary {
background: var(--color-primary-light);
color: var(--color-primary);
}
.badge-success {
background: var(--color-success-light);
color: var(--color-success);
}
.badge-danger {
background: var(--color-danger-light);
color: var(--color-danger);
}
.badge-warning {
background: var(--color-warning-light);
color: var(--color-warning);
}
.badge-secondary {
background: var(--color-secondary-light);
color: var(--color-secondary);
}
/* ============================================
DIVIDER
============================================ */
.divider {
border: none;
border-top: var(--border-width-thin) solid var(--color-border-light);
margin: var(--spacing-lg) 0;
}
.divider-thick {
border-top-width: var(--border-width-medium);
}
/* ============================================
SPACING HELPERS
============================================ */
.mt-0 { margin-top: 0 !important; }
.mt-1 { margin-top: var(--spacing-xs) !important; }
.mt-2 { margin-top: var(--spacing-sm) !important; }
.mt-3 { margin-top: var(--spacing-md) !important; }
.mt-4 { margin-top: var(--spacing-lg) !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: var(--spacing-xs) !important; }
.mb-2 { margin-bottom: var(--spacing-sm) !important; }
.mb-3 { margin-bottom: var(--spacing-md) !important; }
.mb-4 { margin-bottom: var(--spacing-lg) !important; }
.ml-auto { margin-left: auto !important; }
.mr-auto { margin-right: auto !important; }
.p-0 { padding: 0 !important; }
.p-1 { padding: var(--spacing-xs) !important; }
.p-2 { padding: var(--spacing-sm) !important; }
.p-3 { padding: var(--spacing-md) !important; }
.p-4 { padding: var(--spacing-lg) !important; }
.px-0 { padding-left: 0 !important; padding-right: 0 !important; }
.px-1 { padding-left: var(--spacing-xs) !important; padding-right: var(--spacing-xs) !important; }
.px-2 { padding-left: var(--spacing-sm) !important; padding-right: var(--spacing-sm) !important; }
.px-3 { padding-left: var(--spacing-md) !important; padding-right: var(--spacing-md) !important; }
.px-4 { padding-left: var(--spacing-lg) !important; padding-right: var(--spacing-lg) !important; }
.py-0 { padding-top: 0 !important; padding-bottom: 0 !important; }
.py-1 { padding-top: var(--spacing-xs) !important; padding-bottom: var(--spacing-xs) !important; }
.py-2 { padding-top: var(--spacing-sm) !important; padding-bottom: var(--spacing-sm) !important; }
.py-3 { padding-top: var(--spacing-md) !important; padding-bottom: var(--spacing-md) !important; }
.py-4 { padding-top: var(--spacing-lg) !important; padding-bottom: var(--spacing-lg) !important; }
/* ============================================
TEXT UTILITIES
============================================ */
.text-xs { font-size: var(--font-size-xs) !important; }
.text-sm { font-size: var(--font-size-sm) !important; }
.text-base { font-size: var(--font-size-base) !important; }
.text-lg { font-size: var(--font-size-lg) !important; }
.text-xl { font-size: var(--font-size-xl) !important; }
.text-2xl { font-size: var(--font-size-2xl) !important; }
.text-center { text-align: center !important; }
.text-left { text-align: left !important; }
.text-right { text-align: right !important; }
.text-primary { color: var(--color-primary) !important; }
.text-secondary { color: var(--color-text-secondary) !important; }
.text-muted { color: var(--color-text-muted) !important; }
.text-danger { color: var(--color-danger) !important; }
.text-success { color: var(--color-success) !important; }
.text-warning { color: var(--color-warning) !important; }
.font-normal { font-weight: var(--font-weight-normal) !important; }
.font-medium { font-weight: var(--font-weight-medium) !important; }
.font-semibold { font-weight: var(--font-weight-semibold) !important; }
.font-bold { font-weight: var(--font-weight-bold) !important; }
.text-uppercase { text-transform: uppercase !important; }
.text-lowercase { text-transform: lowercase !important; }
.text-capitalize { text-transform: capitalize !important; }
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ============================================
DISPLAY & VISIBILITY
============================================ */
.d-none { display: none !important; }
.d-block { display: block !important; }
.d-inline { display: inline !important; }
.d-inline-block { display: inline-block !important; }
.d-flex { display: flex !important; }
.d-grid { display: grid !important; }
.hidden { visibility: hidden !important; }
.visible { visibility: visible !important; }
/* ============================================
BORDER UTILITIES
============================================ */
.border { border: var(--border-width-thin) solid var(--color-border-light) !important; }
.border-0 { border: none !important; }
.border-top { border-top: var(--border-width-thin) solid var(--color-border-light) !important; }
.border-bottom { border-bottom: var(--border-width-thin) solid var(--color-border-light) !important; }
.rounded { border-radius: var(--border-radius-md) !important; }
.rounded-sm { border-radius: var(--border-radius-sm) !important; }
.rounded-lg { border-radius: var(--border-radius-lg) !important; }
.rounded-full { border-radius: var(--border-radius-full) !important; }
/* ============================================
SHADOW UTILITIES
============================================ */
.shadow-none { box-shadow: none !important; }
.shadow-sm { box-shadow: var(--shadow-sm) !important; }
.shadow { box-shadow: var(--shadow-md) !important; }
.shadow-lg { box-shadow: var(--shadow-lg) !important; }
.shadow-xl { box-shadow: var(--shadow-xl) !important; }
/* ============================================
INTERACTION
============================================ */
.cursor-pointer { cursor: pointer !important; }
.cursor-not-allowed { cursor: not-allowed !important; }
.cursor-default { cursor: default !important; }
.pointer-events-none { pointer-events: none !important; }
.user-select-none { user-select: none !important; }
/* ============================================
POSITION
============================================ */
.position-relative { position: relative !important; }
.position-absolute { position: absolute !important; }
.position-fixed { position: fixed !important; }
.position-sticky { position: sticky !important; }
/* ============================================
OVERFLOW
============================================ */
.overflow-hidden { overflow: hidden !important; }
.overflow-auto { overflow: auto !important; }
.overflow-scroll { overflow: scroll !important; }
/* ============================================
WIDTH & HEIGHT
============================================ */
.w-100 { width: 100% !important; }
.w-auto { width: auto !important; }
.h-100 { height: 100% !important; }
.h-auto { height: auto !important; }
.min-h-screen { min-height: 100vh !important; }
/* ============================================
RESPONSIVE UTILITIES
============================================ */
@media (max-width: 480px) {
.mobile-hidden { display: none !important; }
.mobile-block { display: block !important; }
.mobile-text-center { text-align: center !important; }
}
@media (min-width: 481px) {
.desktop-hidden { display: none !important; }
}