phase 1 styling pass
This commit is contained in:
parent
40838bd134
commit
1f1c6e0bbc
@ -1,158 +1,95 @@
|
||||
.admin-panel {
|
||||
padding: 2rem;
|
||||
max-width: 1400px;
|
||||
padding: var(--spacing-2xl);
|
||||
max-width: var(--max-width-3xl);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.admin-section h2 {
|
||||
margin-bottom: 1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #242424;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
background: var(--color-background-alt);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.users-table th,
|
||||
.users-table td {
|
||||
padding: 12px;
|
||||
padding: var(--spacing-md);
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.users-table th {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-text-inverse);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.users-table tr:hover {
|
||||
background-color: #7f8c8d;
|
||||
background-color: var(--color-dark-light);
|
||||
}
|
||||
|
||||
.auth-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.auth-badge.local {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
background-color: var(--color-dark-light);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.auth-badge.entra {
|
||||
background-color: #0078d4;
|
||||
color: white;
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
padding: var(--spacing-xs) var(--spacing-md);
|
||||
border-radius: var(--radius-xl);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.role-badge.admin {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
background-color: var(--color-danger);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.role-badge.editor {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.role-badge.user {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
background-color: var(--color-dark-light);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.role-select {
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.action-buttons button {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.save-btn:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #c0392b;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.coming-soon {
|
||||
color: #7f8c8d;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.admin-panel {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.users-table {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.users-table th,
|
||||
.users-table td {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
}
|
||||
@ -96,19 +96,27 @@ function AdminPanel({ token, onBack }) {
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className='admin-panel'>Loading...</div>
|
||||
return (
|
||||
<div className='admin-panel'>
|
||||
<div className='loading-container'>
|
||||
<div className='loading-spinner'></div>
|
||||
<div className='loading-text'>Loading users...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='admin-panel'>
|
||||
<div className='admin-header'>
|
||||
<button className='back-button' onClick={onBack}>← Back to Articles</button>
|
||||
<div className='page-header'>
|
||||
<h1 className='page-title'>Admin Panel</h1>
|
||||
<button className='btn btn-ghost' onClick={onBack}>← Back to Articles</button>
|
||||
</div>
|
||||
|
||||
{error && <div className='error-message'>{error}</div>}
|
||||
|
||||
<div className='admin-section'>
|
||||
<h2>User Management</h2>
|
||||
<div className='section'>
|
||||
<h2 className='section-title'>User Management</h2>
|
||||
<table className='users-table'>
|
||||
<thead>
|
||||
<tr>
|
||||
@ -123,7 +131,7 @@ function AdminPanel({ token, onBack }) {
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map(user => (
|
||||
<tr key ={user.id}>
|
||||
<tr key={user.id}>
|
||||
<td>{user.username}</td>
|
||||
<td>{user.email}</td>
|
||||
<td>{user.display_name}</td>
|
||||
@ -152,24 +160,24 @@ function AdminPanel({ token, onBack }) {
|
||||
<td>{new Date(user.created_at).toLocaleDateString()}</td>
|
||||
<td>
|
||||
{editingUserId === user.id ? (
|
||||
<div className='action-buttons'>
|
||||
<div className='btn-group'>
|
||||
<button
|
||||
className='save-btn'
|
||||
className='btn btn-success btn-sm'
|
||||
onClick={() => handleSaveRole(user.id)}
|
||||
>Save</button>
|
||||
<button
|
||||
className='cancel-btn'
|
||||
className='btn btn-secondary btn-sm'
|
||||
onClick={() => handleCancelEdit()}
|
||||
>Cancel</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className='action-buttons'>
|
||||
<div className='btn-group'>
|
||||
<button
|
||||
className='edit-btn'
|
||||
className='btn btn-primary btn-sm'
|
||||
onClick={() => handleRoleChange(user.id, user.role)}
|
||||
>Edit Role</button>
|
||||
<button
|
||||
className='delete-btn'
|
||||
className='btn btn-danger btn-sm'
|
||||
onClick={() => handleDeleteUser(user.id)}
|
||||
>Delete</button>
|
||||
</div>
|
||||
@ -180,8 +188,9 @@ function AdminPanel({ token, onBack }) {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='admin-section'>
|
||||
<h2>Audit Logs</h2>
|
||||
|
||||
<div className='section'>
|
||||
<h2 className='section-title'>Audit Logs</h2>
|
||||
<p className='coming-soon'>Coming Soon...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,105 +1,43 @@
|
||||
.article-detail {
|
||||
padding: 2rem;
|
||||
max-width: 800px;
|
||||
padding: var(--spacing-2xl);
|
||||
max-width: var(--max-width-lg);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
background: none;
|
||||
border: 1px solid #333;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
background: none;
|
||||
border: 1px solid #333;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.edit-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: 1px solid #333;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.ka-number {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.article-content {
|
||||
margin-top: 2rem;
|
||||
line-height: 1.6;
|
||||
margin-top: var(--spacing-2xl);
|
||||
line-height: var(--line-height-relaxed);
|
||||
}
|
||||
|
||||
.article-metadata {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
gap: var(--spacing-lg);
|
||||
margin-top: var(--spacing-lg);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.metadata-label {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.metadata-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-badge {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.article-content video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 1rem 0;
|
||||
margin: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.article-detail {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
}
|
||||
@ -9,16 +9,16 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
|
||||
|
||||
return (
|
||||
<div className='article-detail'>
|
||||
<div className='button-group'>
|
||||
<button className='back-button' onClick={onBack}>
|
||||
<div className='btn-group' style={{ marginBottom: 'var(--spacing-xl)' }}>
|
||||
<button className='btn btn-ghost' onClick={onBack}>
|
||||
← Back
|
||||
</button>
|
||||
{canEdit && (
|
||||
<>
|
||||
<button className='edit-button' onClick={onEdit}>
|
||||
<button className='btn btn-primary' onClick={onEdit}>
|
||||
Edit
|
||||
</button>
|
||||
<button className='delete-button' onClick={onDelete}>
|
||||
<button className='btn btn-danger' onClick={onDelete}>
|
||||
Delete
|
||||
</button>
|
||||
</>
|
||||
@ -26,7 +26,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
|
||||
</div>
|
||||
|
||||
<div className='article-header'>
|
||||
<span className='ka-number'>{article.ka_number}</span>
|
||||
<span className='ka-number-badge'>{article.ka_number}</span>
|
||||
<h1>{article.title}</h1>
|
||||
<p className='article-meta'>
|
||||
Created: {new Date(article.created_at).toLocaleDateString()}
|
||||
@ -36,7 +36,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
|
||||
{article.categories && article.categories.length > 0 && (
|
||||
<div className='article-metadata'>
|
||||
<div className='metadata-label'>Categories:</div>
|
||||
<div className='metadata-badges'>
|
||||
<div className='badge-group'>
|
||||
{article.categories.map(category => (
|
||||
<span key={category} className='badge category-badge'>
|
||||
{category}
|
||||
@ -49,7 +49,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
|
||||
{article.tags && article.tags.length > 0 && (
|
||||
<div className='article-metadata'>
|
||||
<div className='metadata-label'>Tags:</div>
|
||||
<div className='metadata-badges'>
|
||||
<div className='badge-group'>
|
||||
{article.tags.map(tag => (
|
||||
<span key={tag} className='badge tag-badge'>
|
||||
#{tag}
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
.article-editor {
|
||||
padding: 2rem;
|
||||
max-width: 900px;
|
||||
padding: var(--spacing-2xl);
|
||||
max-width: var(--max-width-lg);
|
||||
margin: 0 auto;
|
||||
transition: padding-bottom var(--transition-slow);
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
padding-bottom: var(--spacing-lg);
|
||||
border-bottom: 2px solid var(--color-border-dark);
|
||||
}
|
||||
|
||||
.editor-header h2 {
|
||||
@ -17,166 +21,25 @@
|
||||
.editor-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.editor-form label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.editor-form input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.editor-form input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* React Quill editor styling */
|
||||
.quill {
|
||||
background-color: white;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
min-height: 300px;
|
||||
font-size: 1rem;
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.ql-editor {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.editor-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.editor-buttons button {
|
||||
padding: 0.75rem 2rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.editor-buttons button:first-child {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.editor-buttons button:first-child:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
|
||||
.editor-buttons button:last-child {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.editor-buttons button:last-child:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
/* Form field styling */
|
||||
.form-field {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.form-field input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-field input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
/* Category selector */
|
||||
.category-selector {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-dropdown-btn {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.category-dropdown-btn:hover {
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
color: #666;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.category-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
margin-top: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.category-option:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.category-option input[type="checkbox"] {
|
||||
margin-right: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.no-options {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* Tag selector */
|
||||
/* Category/Tag selector specific styles */
|
||||
.category-selector,
|
||||
.tag-selector {
|
||||
position: relative;
|
||||
}
|
||||
@ -185,142 +48,29 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tag-input:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.tag-suggestions {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.tag-suggestion {
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.tag-suggestion:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.selected-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-badge {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-remove {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.badge-remove:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.create-new-section {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #f9f9f9;
|
||||
padding: var(--spacing-md);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.create-new-section input {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: var(--spacing-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.create-new-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.create-new-buttons button {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.create-new-buttons .create-btn {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.create-new-buttons .create-btn:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
|
||||
.create-new-buttons .cancel-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.create-new-buttons .cancel-btn:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.create-new-button-dropdown {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ddd;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.create-new-button-dropdown:hover {
|
||||
background-color: #229954;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.article-editor {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,34 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
}
|
||||
}, [article]);
|
||||
|
||||
// Add padding and scroll when gallery opens
|
||||
useEffect(() => {
|
||||
const editorElement = document.querySelector('.article-editor');
|
||||
|
||||
if (isGalleryOpen) {
|
||||
// Add padding equal to drawer height (40vh + some extra space)
|
||||
editorElement.style.paddingBottom = 'calc(40vh + 2rem)';
|
||||
|
||||
// Scroll down smoothly so editor is visible above drawer
|
||||
setTimeout(() => {
|
||||
window.scrollTo({
|
||||
top: document.body.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}, 100); // Small delay to let padding apply first
|
||||
} else {
|
||||
// Remove padding when closed
|
||||
editorElement.style.paddingBottom = '';
|
||||
}
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
if (editorElement) {
|
||||
editorElement.style.paddingBottom = '';
|
||||
}
|
||||
};
|
||||
}, [isGalleryOpen]);
|
||||
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:9000/api/categories', {
|
||||
@ -209,7 +237,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
setShowCreateCategory(false);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
alert(data.error || 'Failed to create cateogry');
|
||||
alert(data.error || 'Failed to create category');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create new category:', err);
|
||||
@ -295,7 +323,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
{article.ka_number}
|
||||
</h2>
|
||||
{article.status === 'draft' && (
|
||||
<span className='draft-indicator'>DRAFT</span>
|
||||
<span className='badge draft-badge'>DRAFT</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -304,6 +332,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
<label>Title:</label>
|
||||
<input
|
||||
type='text'
|
||||
className='form-input'
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
@ -314,17 +343,19 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
<div className='category-selector'>
|
||||
<button
|
||||
type='button'
|
||||
className='category-dropdown-btn'
|
||||
className='dropdown-button'
|
||||
onClick={() => setShowCategoryDropdown(!showCategoryDropdown)}
|
||||
>
|
||||
{selectedCategories.length > 0
|
||||
? `${selectedCategories.length} selected`
|
||||
: 'Select categories'}
|
||||
<span>
|
||||
{selectedCategories.length > 0
|
||||
? `${selectedCategories.length} selected`
|
||||
: 'Select categories'}
|
||||
</span>
|
||||
<span className='dropdown-arrow'>{showCategoryDropdown ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
|
||||
{showCategoryDropdown && (
|
||||
<div className='category-dropdown'>
|
||||
<div className='dropdown-menu'>
|
||||
{showCreateCategory ? (
|
||||
<div className='create-new-section'>
|
||||
<input
|
||||
@ -340,17 +371,17 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
<div className='create-new-buttons'>
|
||||
<div className='btn-group gap-sm'>
|
||||
<button
|
||||
type='button'
|
||||
className='create-btn'
|
||||
className='btn btn-success btn-sm'
|
||||
onClick={handleCreateNewCategory}
|
||||
>
|
||||
Create
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className='cancel-btn'
|
||||
className='btn btn-secondary btn-sm'
|
||||
onClick={() => {
|
||||
setShowCreateCategory(false);
|
||||
setNewCategoryInput('');
|
||||
@ -363,7 +394,8 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
) : (
|
||||
<button
|
||||
type='button'
|
||||
className='create-new-button-dropdown'
|
||||
className='btn btn-success'
|
||||
style={{ width: '100%', borderRadius: '0', borderBottom: '1px solid var(--color-border)' }}
|
||||
onClick={() => setShowCreateCategory(true)}
|
||||
>
|
||||
+ Create New Category
|
||||
@ -371,10 +403,10 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
)}
|
||||
|
||||
{availableCategories.length === 0 ? (
|
||||
<div className='no-options'>No categories available</div>
|
||||
<div className='dropdown-empty'>No categories available</div>
|
||||
) : (
|
||||
availableCategories.map(category => (
|
||||
<label key={category} className='category-option'>
|
||||
<label key={category} className='dropdown-item'>
|
||||
<input
|
||||
type='checkbox'
|
||||
checked={selectedCategories.includes(category)}
|
||||
@ -387,9 +419,9 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='selected-badges'>
|
||||
<div className='badge-group mt-md'>
|
||||
{selectedCategories.map(category => (
|
||||
<span key={category} className='badge category-badge'>
|
||||
<span key={category} className='badge category-badge badge-removable'>
|
||||
{category}
|
||||
<button
|
||||
type='button'
|
||||
@ -408,7 +440,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
<div className='tag-input-wrapper'>
|
||||
<input
|
||||
type='text'
|
||||
className='tag-input'
|
||||
className='form-input'
|
||||
placeholder='Type to add tags...'
|
||||
value={tagInput}
|
||||
onChange={(e) => {
|
||||
@ -420,11 +452,11 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
/>
|
||||
|
||||
{showTagSuggestions && filteredTagSuggestions.length > 0 && (
|
||||
<div className='tag-suggestions'>
|
||||
<div className='dropdown-menu'>
|
||||
{filteredTagSuggestions.slice(0, 5).map(tag => (
|
||||
<div
|
||||
key={tag}
|
||||
className='tag-suggestion'
|
||||
className='dropdown-item'
|
||||
onClick={() => addTag(tag)}
|
||||
>
|
||||
{tag}
|
||||
@ -434,9 +466,9 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='selected-badges'>
|
||||
<div className='badge-group mt-md'>
|
||||
{selectedTags.map(tag => (
|
||||
<span key={tag} className='badge tag-badge'>
|
||||
<span key={tag} className='badge tag-badge badge-removable'>
|
||||
#{tag}
|
||||
<button
|
||||
type='button'
|
||||
@ -460,14 +492,14 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='editor-buttons'>
|
||||
<button className='save-draft-btn' onClick={handleSaveDraft}>
|
||||
<div className='btn-group'>
|
||||
<button className='btn btn-secondary' onClick={handleSaveDraft}>
|
||||
Save Draft
|
||||
</button>
|
||||
<button className='publish-btn' onClick={handlePublish}>
|
||||
<button className='btn btn-success' onClick={handlePublish}>
|
||||
Publish
|
||||
</button>
|
||||
<button className='cancel-btn' onClick={onCancel}>
|
||||
<button className='btn btn-ghost' onClick={onCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,56 +1,37 @@
|
||||
.article-item {
|
||||
border: 1px solid #ddd;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.article-item:hover {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
background-color: var(--color-gray-100);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.article-item-header h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
margin: 0 0 var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.article-date {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-md);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.article-item-metadata {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-md);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.metadata-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.3rem 0.6rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tag-badge {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.article-item {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
@ -1,51 +1,27 @@
|
||||
.drafts-list {
|
||||
padding: 2rem;
|
||||
max-width: 1200px;
|
||||
padding: var(--spacing-2xl);
|
||||
max-width: var(--max-width-2xl);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.drafts-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
border-bottom: 2px solid #333;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.drafts-header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.no-drafts {
|
||||
text-align: center;
|
||||
padding: 4rem 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-drafts p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.drafts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
gap: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.draft-card {
|
||||
background: white;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
background: var(--color-background);
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-xl);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.draft-card:hover {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@ -53,43 +29,26 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.draft-badge {
|
||||
background-color: #f39c12;
|
||||
color: white;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ka-number {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.draft-title {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.25rem;
|
||||
color: #2c3e50;
|
||||
margin: 0 0 var(--spacing-lg) 0;
|
||||
font-size: var(--font-size-xl);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.draft-title:empty::before {
|
||||
content: '(Untitled)';
|
||||
color: #999;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.draft-preview {
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-md);
|
||||
line-height: var(--line-height-snug);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
@ -97,33 +56,22 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #eee;
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.draft-date {
|
||||
font-size: 0.85rem;
|
||||
color: #999;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.delete-draft-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.delete-draft-btn:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.drafts-list {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.drafts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@ -57,32 +57,44 @@ function DraftsList({ token, onBack, onSelectDraft }) {
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <div className='drafts-list'>Loading...</div>;
|
||||
return (
|
||||
<div className='drafts-list'>
|
||||
<div className='loading-container'>
|
||||
<div className='loading-spinner'></div>
|
||||
<div className='loading-text'>Loading drafts...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='drafts-list'>
|
||||
<div className='drafts-header'>
|
||||
<h1>My Drafts</h1>
|
||||
<div className='page-header'>
|
||||
<h1 className='page-title'>My Drafts</h1>
|
||||
<button className='btn btn-ghost' onClick={onBack}>
|
||||
← Back to Articles
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (<div className='error-message'>{error}</div>)}
|
||||
|
||||
{drafts.length === 0 ? (
|
||||
<div className='no-drafts'>
|
||||
<p>You do not have any drafts yet.</p>
|
||||
<div className='empty-state'>
|
||||
<div className='empty-state-icon'>📝</div>
|
||||
<h2 className='empty-state-title'>No Drafts Yet</h2>
|
||||
<p className='empty-state-description'>You don't have any drafts. Click "Create New Article" to start writing.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='drafts-grid'>
|
||||
{drafts.map(draft => (
|
||||
<div
|
||||
key={draft.id}
|
||||
className='draft-card'
|
||||
className='draft-card card-hover'
|
||||
onClick={() => onSelectDraft(draft)}
|
||||
>
|
||||
<div className='draft-header'>
|
||||
<span className='draft-badge'>DRAFT</span>
|
||||
<span className='ka-number'>{draft.ka_number}</span>
|
||||
<span className='badge draft-badge'>DRAFT</span>
|
||||
<span className='ka-number-badge'>{draft.ka_number}</span>
|
||||
</div>
|
||||
<h3 className='draft-title'>{draft.title || '(Untitled)'}</h3>
|
||||
<div className='draft-preview'>
|
||||
@ -94,15 +106,14 @@ function DraftsList({ token, onBack, onSelectDraft }) {
|
||||
<div className='draft-footer'>
|
||||
<span className='draft-date'>Created: {new Date(draft.created_at).toLocaleDateString()}</span>
|
||||
<button
|
||||
className='delete-draft-btn'
|
||||
className='btn btn-danger btn-sm'
|
||||
onClick={(e) => handleDeleteDraft(draft.ka_number, e)}
|
||||
>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
.filter-bar {
|
||||
padding: 1rem 2rem;
|
||||
background-color: #f9f9f9;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: var(--spacing-lg) var(--spacing-2xl);
|
||||
background-color: var(--color-background);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@ -14,142 +14,48 @@
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.filter-group label {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
color: #2c3e50;
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.filter-dropdown-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-dropdown-btn {
|
||||
min-width: 180px;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.filter-dropdown-btn:hover {
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.filter-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
margin-top: 4px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.filter-option:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.filter-option input[type="checkbox"] {
|
||||
margin-right: 0.75rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.no-options {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.clear-filters-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.clear-filters-btn:hover {
|
||||
background-color: #c0392b;
|
||||
}
|
||||
|
||||
.active-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid #ddd;
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.active-filters-label {
|
||||
font-weight: bold;
|
||||
color: #2c3e50;
|
||||
font-size: 0.9rem;
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.filter-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 0.75rem;
|
||||
border-radius: 16px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.filter-badge.category-badge {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.filter-badge.tag-badge {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.filter-badge button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.filter-badge button:hover {
|
||||
opacity: 1;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.filter-bar {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.filter-controls {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.filter-dropdown-container {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import './FilterBar.css';
|
||||
|
||||
function FilterBar ({ token, onFilterChange }) {
|
||||
function FilterBar({ token, onFilterChange }) {
|
||||
const [availableCategories, setAvailableCategories] = useState([]);
|
||||
const [availableTags, setAvailableTags] = useState([]);
|
||||
const [selectedCategories, setSelectedCategories] = useState([]);
|
||||
@ -34,7 +34,7 @@ function FilterBar ({ token, onFilterChange }) {
|
||||
setAvailableCategories(data.map(category => category.name));
|
||||
}
|
||||
} catch(err) {
|
||||
console.error('Error fetchign categories:', err);
|
||||
console.error('Error fetching categories:', err);
|
||||
}
|
||||
};
|
||||
|
||||
@ -85,22 +85,24 @@ function FilterBar ({ token, onFilterChange }) {
|
||||
<label>Filter by Category:</label>
|
||||
<div className="filter-dropdown-container">
|
||||
<button
|
||||
className="filter-dropdown-btn"
|
||||
className="dropdown-button"
|
||||
onClick={() => setShowCategoryDropdown(!showCategoryDropdown)}
|
||||
>
|
||||
{selectedCategories.length > 0
|
||||
? `${selectedCategories.length} selected`
|
||||
: 'All Categories'}
|
||||
<span>
|
||||
{selectedCategories.length > 0
|
||||
? `${selectedCategories.length} selected`
|
||||
: 'All Categories'}
|
||||
</span>
|
||||
<span className="dropdown-arrow">{showCategoryDropdown ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
|
||||
{showCategoryDropdown && (
|
||||
<div className="filter-dropdown">
|
||||
<div className="dropdown-menu">
|
||||
{availableCategories.length === 0 ? (
|
||||
<div className="no-options">No categories available</div>
|
||||
<div className="dropdown-empty">No categories available</div>
|
||||
) : (
|
||||
availableCategories.map(category => (
|
||||
<label key={category} className="filter-option">
|
||||
<label key={category} className="dropdown-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCategories.includes(category)}
|
||||
@ -119,22 +121,24 @@ function FilterBar ({ token, onFilterChange }) {
|
||||
<label>Filter by Tag:</label>
|
||||
<div className="filter-dropdown-container">
|
||||
<button
|
||||
className="filter-dropdown-btn"
|
||||
className="dropdown-button"
|
||||
onClick={() => setShowTagDropdown(!showTagDropdown)}
|
||||
>
|
||||
{selectedTags.length > 0
|
||||
? `${selectedTags.length} selected`
|
||||
: 'All Tags'}
|
||||
<span>
|
||||
{selectedTags.length > 0
|
||||
? `${selectedTags.length} selected`
|
||||
: 'All Tags'}
|
||||
</span>
|
||||
<span className="dropdown-arrow">{showTagDropdown ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
|
||||
{showTagDropdown && (
|
||||
<div className="filter-dropdown">
|
||||
<div className="dropdown-menu">
|
||||
{availableTags.length === 0 ? (
|
||||
<div className="no-options">No tags available</div>
|
||||
<div className="dropdown-empty">No tags available</div>
|
||||
) : (
|
||||
availableTags.map(tag => (
|
||||
<label key={tag} className="filter-option">
|
||||
<label key={tag} className="dropdown-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedTags.includes(tag)}
|
||||
@ -150,7 +154,7 @@ function FilterBar ({ token, onFilterChange }) {
|
||||
</div>
|
||||
|
||||
{hasActiveFilters && (
|
||||
<button className="clear-filters-btn" onClick={clearFilters}>
|
||||
<button className="btn btn-danger btn-sm" onClick={clearFilters}>
|
||||
Clear Filters
|
||||
</button>
|
||||
)}
|
||||
@ -160,22 +164,21 @@ function FilterBar ({ token, onFilterChange }) {
|
||||
<div className="active-filters">
|
||||
<span className="active-filters-label">Active Filters:</span>
|
||||
{selectedCategories.map(category => (
|
||||
<span key={category} className="filter-badge category-badge">
|
||||
<span key={category} className="badge category-badge badge-removable">
|
||||
{category}
|
||||
<button onClick={() => toggleCategory(category)}>×</button>
|
||||
<button className="badge-remove" onClick={() => toggleCategory(category)}>×</button>
|
||||
</span>
|
||||
))}
|
||||
{selectedTags.map(tag => (
|
||||
<span key={tag} className="filter-badge tag-badge">
|
||||
<span key={tag} className="badge tag-badge badge-removable">
|
||||
#{tag}
|
||||
<button onClick={() => toggleTag(tag)}>×</button>
|
||||
<button className="badge-remove" onClick={() => toggleTag(tag)}>×</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default FilterBar;
|
||||
@ -3,45 +3,45 @@
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 0.75rem 2rem;
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
padding: var(--spacing-md) var(--spacing-2xl);
|
||||
cursor: pointer;
|
||||
z-index: 1001;
|
||||
z-index: var(--z-drawer-toggle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
gap: var(--spacing-sm);
|
||||
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
|
||||
transition: background-color 0.2s;
|
||||
transition: background-color var(--transition-base);
|
||||
}
|
||||
|
||||
.drawer-toggle:hover {
|
||||
background-color: #2980b9;
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.drawer-toggle.open {
|
||||
background-color: #e74c3c;
|
||||
background-color: var(--color-danger);
|
||||
}
|
||||
|
||||
.drawer-toggle.open:hover {
|
||||
background-color: #c0392b;
|
||||
background-color: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 1.2rem;
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.toggle-text {
|
||||
font-weight: bold;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.media-count {
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.85rem;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-xl);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.media-drawer {
|
||||
@ -50,12 +50,12 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40vh;
|
||||
background-color: white;
|
||||
border-radius: 12px 12px 0 0;
|
||||
background-color: var(--color-background);
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
z-index: var(--z-drawer);
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
transition: transform var(--transition-slow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -65,94 +65,64 @@
|
||||
}
|
||||
|
||||
.drawer-handle {
|
||||
padding: 0.75rem;
|
||||
padding: var(--spacing-md);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 12px 12px 0 0;
|
||||
background-color: var(--color-gray-200);
|
||||
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||
}
|
||||
|
||||
.drawer-handle:hover {
|
||||
background-color: #e0e0e0;
|
||||
background-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.handle-bar {
|
||||
width: 50px;
|
||||
height: 4px;
|
||||
background-color: #bbb;
|
||||
border-radius: 2px;
|
||||
background-color: var(--color-gray-400);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1rem 2rem;
|
||||
padding: var(--spacing-lg) var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.gallery-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
padding-bottom: var(--spacing-lg);
|
||||
border-bottom: 2px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.gallery-header h3 {
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
background-color: #27ae60;
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.upload-button:hover {
|
||||
background-color: #229954;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-media {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.no-media p {
|
||||
margin: 0.5rem 0;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
gap: var(--spacing-lg);
|
||||
padding: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
.media-item {
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem;
|
||||
background: white;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid var(--color-border-light);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--color-background);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.media-item:hover {
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@ -160,22 +130,22 @@
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.video-thumbnail {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
background-color: #34495e;
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-dark-hover);
|
||||
border-radius: var(--radius-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.75rem;
|
||||
color: white;
|
||||
margin-bottom: var(--spacing-md);
|
||||
color: var(--color-text-inverse);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -184,59 +154,33 @@
|
||||
}
|
||||
|
||||
.video-label {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.media-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.media-actions button {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.insert-btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.insert-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #c0392b;
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.media-filename {
|
||||
font-size: 0.75rem;
|
||||
color: #666;
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: #c62828;
|
||||
padding: 0.75rem;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.media-drawer {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.drawer-content {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
@ -60,7 +60,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
||||
setError(data.error || 'Upload failed');
|
||||
}
|
||||
} catch(err) {
|
||||
console.error('Failed to uplaod file:', err);
|
||||
console.error('Failed to upload file:', err);
|
||||
setError('Failed to upload file');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
@ -106,7 +106,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
||||
<div className='drawer-content'>
|
||||
<div className='gallery-header'>
|
||||
<h3>Media Gallery - {kaNumber}</h3>
|
||||
<label className='upload-button'>
|
||||
<label className='btn btn-success'>
|
||||
<input
|
||||
type='file'
|
||||
onChange={handleFileUpload}
|
||||
@ -121,11 +121,17 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
||||
{error && <div className='error-message'>{error}</div>}
|
||||
|
||||
{loading ? (
|
||||
<div className="loading">Loading media...</div>
|
||||
<div className='loading-container'>
|
||||
<div className='loading-spinner'></div>
|
||||
<div className='loading-text'>Loading media...</div>
|
||||
</div>
|
||||
) : media.length === 0 ? (
|
||||
<div className="no-media">
|
||||
<p>No media uploaded yet</p>
|
||||
<p>Click "Upload Media" to add images or videos</p>
|
||||
<div className='empty-state'>
|
||||
<div className='empty-state-icon'>📷</div>
|
||||
<h3 className='empty-state-title'>No Media Yet</h3>
|
||||
<p className='empty-state-description'>
|
||||
Click "Upload Media" to add images or videos to this article.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='media-grid'>
|
||||
@ -144,13 +150,13 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='media-actions'>
|
||||
<div className='btn-group gap-sm'>
|
||||
<button
|
||||
className='insert-btn'
|
||||
className='btn btn-primary btn-sm'
|
||||
onClick={() => onInsertMedia(item)}
|
||||
>Insert</button>
|
||||
<button
|
||||
className='delete-btn'
|
||||
className='btn btn-danger btn-sm'
|
||||
onClick={() => handleDelete(item.filename)}
|
||||
>Delete</button>
|
||||
</div>
|
||||
|
||||
70
client/kb-frontend/src/components/SearchBar.css
Normal file
70
client/kb-frontend/src/components/SearchBar.css
Normal file
@ -0,0 +1,70 @@
|
||||
/* SearchBar Component Styles */
|
||||
|
||||
.search-bar {
|
||||
padding: var(--spacing-xl) var(--spacing-2xl);
|
||||
background-color: var(--color-background);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.search-bar form {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.search-bar input[type="text"] {
|
||||
flex: 1;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
font-size: var(--font-size-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: var(--color-background);
|
||||
transition: border-color var(--transition-base);
|
||||
}
|
||||
|
||||
.search-bar input[type="text"]:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px var(--color-primary-light);
|
||||
}
|
||||
|
||||
.search-bar input[type="text"]::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.search-bar button[type="submit"] {
|
||||
padding: var(--spacing-md) var(--spacing-2xl);
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-base);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.search-bar button[type="submit"]:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.search-bar button[type="submit"]:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.search-bar {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.search-bar form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-bar button[type="submit"] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
//import 'SearchBar.css'
|
||||
import { useState } from 'react';
|
||||
import './SearchBar.css';
|
||||
|
||||
function SearchBar({ onSearch }) {
|
||||
const [query, setQuery] = useState('');
|
||||
@ -16,15 +17,10 @@ function SearchBar({ onSearch }) {
|
||||
type='text'
|
||||
placeholder='Search articles by title, content, or KA number...'
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
|
||||
/* USer this for instant search
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
onSearch(e.target.value);
|
||||
}}
|
||||
*/
|
||||
|
||||
/>
|
||||
<button type='submit'>Search</button>
|
||||
</form>
|
||||
|
||||
@ -1,68 +1,60 @@
|
||||
/* Base Reset & Typography */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1.2;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
font-size: 2rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: color var(--transition-base);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
a:hover {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Remove default button styles - our .btn class will handle it */
|
||||
button {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* App container */
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Override Vite's centered layout */
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -0,0 +1,599 @@
|
||||
/* Cram-A-Lot Knowledge Base - Shared Component Styles */
|
||||
/* Import this in your main.jsx or index.jsx after variables.css */
|
||||
|
||||
/* ===== BUTTONS ===== */
|
||||
|
||||
/* Base button styles */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Button sizes */
|
||||
.btn-sm {
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: var(--spacing-lg) var(--spacing-2xl);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
/* Button variants */
|
||||
.btn-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: var(--color-success);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background-color: var(--color-success-hover);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: var(--color-danger);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background-color: var(--color-danger-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: var(--color-dark-light);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background-color: var(--color-dark-hover);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background-color: transparent;
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-ghost:hover:not(:disabled) {
|
||||
background-color: var(--color-gray-200);
|
||||
border-color: var(--color-gray-400);
|
||||
}
|
||||
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-primary);
|
||||
text-decoration: underline;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.btn-link:hover:not(:disabled) {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
/* Button groups */
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: var(--spacing-lg);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ===== BADGES ===== */
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-2xl);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-sm {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.badge-lg {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
/* Badge variants */
|
||||
.badge-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: var(--color-success);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: var(--color-danger);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background-color: var(--color-dark-light);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.badge-gray {
|
||||
background-color: var(--color-gray-400);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
/* Special badge types for app */
|
||||
.category-badge {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.tag-badge {
|
||||
background-color: var(--color-success);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.draft-badge {
|
||||
background-color: var(--color-warning);
|
||||
color: var(--color-text-inverse);
|
||||
}
|
||||
|
||||
.ka-number-badge {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-text-inverse);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* Badge with remove button */
|
||||
.badge-removable {
|
||||
padding-right: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.badge-remove {
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-size: 1.2rem;
|
||||
cursor: pointer;
|
||||
padding: 0 var(--spacing-xs);
|
||||
line-height: 1;
|
||||
opacity: 0.8;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.badge-remove:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Badge container */
|
||||
.badge-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ===== FORM ELEMENTS ===== */
|
||||
|
||||
.form-field {
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
display: block;
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-md);
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-textarea,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
font-size: var(--font-size-base);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background-color: var(--color-background);
|
||||
transition: border-color var(--transition-base);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-textarea:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.form-input:disabled,
|
||||
.form-textarea:disabled,
|
||||
.form-select:disabled {
|
||||
background-color: var(--color-gray-200);
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Form helper text */
|
||||
.form-helper {
|
||||
display: block;
|
||||
margin-top: var(--spacing-xs);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.form-error {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* ===== MESSAGES & ALERTS ===== */
|
||||
|
||||
.message {
|
||||
padding: var(--spacing-lg);
|
||||
border-radius: var(--radius-sm);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: var(--color-danger-light);
|
||||
color: var(--color-danger-hover);
|
||||
border: 1px solid var(--color-danger);
|
||||
}
|
||||
|
||||
.success-message {
|
||||
background-color: var(--color-success-light);
|
||||
color: var(--color-success-hover);
|
||||
border: 1px solid var(--color-success);
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
background-color: var(--color-warning-light);
|
||||
color: var(--color-warning-hover);
|
||||
border: 1px solid var(--color-warning);
|
||||
}
|
||||
|
||||
.info-message {
|
||||
background-color: var(--color-primary-light);
|
||||
color: var(--color-primary-hover);
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
/* ===== LOADING STATES ===== */
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-3xl) var(--spacing-2xl);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid var(--color-gray-300);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.loading-spinner-sm {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.loading-spinner-lg {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-width: 5px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: var(--spacing-lg);
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== EMPTY STATES ===== */
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-3xl) var(--spacing-2xl);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: var(--font-size-3xl);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.empty-state-description {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* ===== DROPDOWNS ===== */
|
||||
|
||||
.dropdown-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-button {
|
||||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
background-color: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
font-size: var(--font-size-base);
|
||||
transition: border-color var(--transition-base);
|
||||
}
|
||||
|
||||
.dropdown-button:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.dropdown-arrow {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
|
||||
.dropdown-arrow.open {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + var(--spacing-xs));
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
z-index: var(--z-dropdown);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: var(--spacing-md);
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.dropdown-item input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
height: 1px;
|
||||
background-color: var(--color-border);
|
||||
margin: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.dropdown-header {
|
||||
padding: var(--spacing-md);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--font-size-sm);
|
||||
background-color: var(--color-gray-100);
|
||||
}
|
||||
|
||||
.dropdown-empty {
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
/* ===== CARDS ===== */
|
||||
|
||||
.card {
|
||||
background: var(--color-background);
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-xl);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
color: var(--color-text-secondary);
|
||||
line-height: var(--line-height-relaxed);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: var(--spacing-lg);
|
||||
padding-top: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
/* ===== HEADERS & SECTIONS ===== */
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
padding-bottom: var(--spacing-lg);
|
||||
border-bottom: 2px solid var(--color-border-dark);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--font-size-2xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: var(--spacing-3xl);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* ===== UTILITIES ===== */
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
.font-bold {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.mt-0 { margin-top: 0; }
|
||||
.mt-sm { margin-top: var(--spacing-sm); }
|
||||
.mt-md { margin-top: var(--spacing-md); }
|
||||
.mt-lg { margin-top: var(--spacing-lg); }
|
||||
.mt-xl { margin-top: var(--spacing-xl); }
|
||||
.mt-2xl { margin-top: var(--spacing-2xl); }
|
||||
|
||||
.mb-0 { margin-bottom: 0; }
|
||||
.mb-sm { margin-bottom: var(--spacing-sm); }
|
||||
.mb-md { margin-bottom: var(--spacing-md); }
|
||||
.mb-lg { margin-bottom: var(--spacing-lg); }
|
||||
.mb-xl { margin-bottom: var(--spacing-xl); }
|
||||
.mb-2xl { margin-bottom: var(--spacing-2xl); }
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gap-sm { gap: var(--spacing-sm); }
|
||||
.gap-md { gap: var(--spacing-md); }
|
||||
.gap-lg { gap: var(--spacing-lg); }
|
||||
.gap-xl { gap: var(--spacing-xl); }
|
||||
133
client/kb-frontend/src/styles/variables.css
Normal file
133
client/kb-frontend/src/styles/variables.css
Normal file
@ -0,0 +1,133 @@
|
||||
/* Cram-A-Lot Knowledge Base - Design System Variables */
|
||||
|
||||
:root {
|
||||
/* ===== COLORS ===== */
|
||||
|
||||
/* Primary Blue */
|
||||
--color-primary: #3498db;
|
||||
--color-primary-hover: #2980b9;
|
||||
--color-primary-light: #ebf5fb;
|
||||
|
||||
/* Success Green */
|
||||
--color-success: #27ae60;
|
||||
--color-success-hover: #229954;
|
||||
--color-success-light: #e8f6f0;
|
||||
|
||||
/* Danger Red */
|
||||
--color-danger: #e74c3c;
|
||||
--color-danger-hover: #c0392b;
|
||||
--color-danger-light: #ffebee;
|
||||
|
||||
/* Warning Orange */
|
||||
--color-warning: #f39c12;
|
||||
--color-warning-hover: #e67e22;
|
||||
--color-warning-light: #fef5e7;
|
||||
|
||||
/* Neutral Grays */
|
||||
--color-gray-50: #fafafa;
|
||||
--color-gray-100: #f9f9f9;
|
||||
--color-gray-200: #f5f5f5;
|
||||
--color-gray-300: #e0e0e0;
|
||||
--color-gray-400: #bbb;
|
||||
--color-gray-500: #999;
|
||||
--color-gray-600: #666;
|
||||
--color-gray-700: #333;
|
||||
--color-gray-800: #242424;
|
||||
|
||||
/* Dark Colors */
|
||||
--color-dark: #2c3e50;
|
||||
--color-dark-hover: #34495e;
|
||||
--color-dark-light: #7f8c8d;
|
||||
|
||||
/* Semantic Colors */
|
||||
--color-text-primary: #2c3e50;
|
||||
--color-text-secondary: #666;
|
||||
--color-text-muted: #999;
|
||||
--color-text-inverse: #ffffff;
|
||||
|
||||
--color-border: #ddd;
|
||||
--color-border-light: #e0e0e0;
|
||||
--color-border-dark: #333;
|
||||
|
||||
--color-background: #ffffff;
|
||||
--color-background-alt: #f9f9f9;
|
||||
--color-background-dark: #242424;
|
||||
|
||||
/* ===== SPACING SCALE ===== */
|
||||
--spacing-xs: 0.25rem; /* 4px */
|
||||
--spacing-sm: 0.5rem; /* 8px */
|
||||
--spacing-md: 0.75rem; /* 12px */
|
||||
--spacing-lg: 1rem; /* 16px */
|
||||
--spacing-xl: 1.5rem; /* 24px */
|
||||
--spacing-2xl: 2rem; /* 32px */
|
||||
--spacing-3xl: 3rem; /* 48px */
|
||||
--spacing-4xl: 4rem; /* 64px */
|
||||
|
||||
/* ===== TYPOGRAPHY ===== */
|
||||
--font-size-xs: 0.75rem; /* 12px */
|
||||
--font-size-sm: 0.85rem; /* 13.6px */
|
||||
--font-size-base: 1rem; /* 16px */
|
||||
--font-size-md: 0.95rem; /* 15.2px */
|
||||
--font-size-lg: 1.1rem; /* 17.6px */
|
||||
--font-size-xl: 1.25rem; /* 20px */
|
||||
--font-size-2xl: 1.5rem; /* 24px */
|
||||
--font-size-3xl: 2rem; /* 32px */
|
||||
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
--line-height-tight: 1.2;
|
||||
--line-height-snug: 1.4;
|
||||
--line-height-normal: 1.5;
|
||||
--line-height-relaxed: 1.6;
|
||||
|
||||
/* ===== BORDER RADIUS ===== */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-2xl: 16px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* ===== SHADOWS ===== */
|
||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
--shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
|
||||
/* ===== TRANSITIONS ===== */
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-base: 0.2s ease;
|
||||
--transition-slow: 0.3s ease;
|
||||
--transition-slower: 0.4s ease;
|
||||
|
||||
/* ===== Z-INDEX SCALE ===== */
|
||||
--z-base: 1;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 500;
|
||||
--z-fixed: 1000;
|
||||
--z-drawer: 1000;
|
||||
--z-drawer-toggle: 1001;
|
||||
--z-modal-backdrop: 1040;
|
||||
--z-modal: 1050;
|
||||
--z-popover: 1060;
|
||||
--z-tooltip: 1070;
|
||||
|
||||
/* ===== LAYOUT ===== */
|
||||
--max-width-xs: 480px;
|
||||
--max-width-sm: 640px;
|
||||
--max-width-md: 768px;
|
||||
--max-width-lg: 900px;
|
||||
--max-width-xl: 1024px;
|
||||
--max-width-2xl: 1280px;
|
||||
--max-width-3xl: 1400px;
|
||||
|
||||
/* ===== BREAKPOINTS (for reference, use in media queries) ===== */
|
||||
--breakpoint-sm: 640px;
|
||||
--breakpoint-md: 768px;
|
||||
--breakpoint-lg: 1024px;
|
||||
--breakpoint-xl: 1280px;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user