feat(web): add CookieBanner component
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
.cookie-banner-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(28, 25, 23, 0.5);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
.cookie-banner {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
padding: 1.5rem 1.25rem 1.25rem;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.15);
|
||||
animation: slideUp 0.25s ease;
|
||||
}
|
||||
.cookie-banner-title { font-size: 1.1rem; font-weight: 700; color: var(--text-main); margin: 0 0 0.5rem; }
|
||||
.cookie-banner-desc { font-size: 0.85rem; color: var(--text-muted); line-height: 1.5; margin: 0 0 1rem; }
|
||||
.cookie-categories { display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem; }
|
||||
.cookie-category { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.75rem; background: var(--surface-muted); border-radius: var(--radius); }
|
||||
.cookie-category-info { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; }
|
||||
.cookie-category-name { font-size: 0.9rem; font-weight: 600; color: var(--text-main); }
|
||||
.cookie-category-desc { font-size: 0.78rem; color: var(--text-muted); line-height: 1.4; }
|
||||
.cookie-toggle { background: none; border: none; cursor: pointer; padding: 0; }
|
||||
.cookie-toggle-track { display: block; width: 44px; height: 24px; background: var(--border); border-radius: 12px; position: relative; transition: background 0.2s; }
|
||||
.cookie-toggle--on .cookie-toggle-track { background: var(--primary); }
|
||||
.cookie-toggle-thumb { display: block; width: 20px; height: 20px; background: white; border-radius: 50%; position: absolute; top: 2px; left: 2px; transition: transform 0.2s; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); }
|
||||
.cookie-toggle--on .cookie-toggle-thumb { transform: translateX(20px); }
|
||||
.cookie-toggle--locked .cookie-toggle-track { background: var(--primary); opacity: 0.6; cursor: not-allowed; }
|
||||
.cookie-banner-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
||||
.cookie-btn { flex: 1; min-width: 0; padding: 0.6rem 1rem; border-radius: 999px; border: none; font-size: 0.85rem; font-weight: 600; cursor: pointer; transition: opacity 0.15s; white-space: nowrap; }
|
||||
.cookie-btn:hover { opacity: 0.85; }
|
||||
.cookie-btn--primary { background: var(--primary); color: var(--on-primary); }
|
||||
.cookie-btn--secondary { background: var(--surface-muted); color: var(--text-main); border: 1px solid var(--border); }
|
||||
.cookie-btn--tertiary { background: transparent; color: var(--primary); border: 1px solid var(--primary); }
|
||||
.cookie-more-info { display: block; width: 100%; margin-top: 0.75rem; padding: 0; background: none; border: none; color: var(--text-muted); font-size: 0.8rem; cursor: pointer; text-align: center; text-decoration: underline; }
|
||||
.cookie-more-info:hover { color: var(--text-main); }
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
|
||||
@media (min-width: 769px) {
|
||||
.cookie-banner-overlay { align-items: center; padding: 1rem; }
|
||||
.cookie-banner { border-radius: var(--radius-lg); max-width: 420px; }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from '../i18n';
|
||||
import './CookieBanner.css';
|
||||
|
||||
function CookieBanner({ onConsent, onPrivacyClick }) {
|
||||
const { t } = useTranslation();
|
||||
const [categories, setCategories] = useState({
|
||||
analytics: false,
|
||||
preferences: false,
|
||||
health_data: false,
|
||||
});
|
||||
|
||||
function toggleCategory(cat) {
|
||||
setCategories(prev => ({ ...prev, [cat]: !prev[cat] }));
|
||||
}
|
||||
|
||||
function handleAcceptAll() {
|
||||
onConsent({ essential: true, analytics: true, preferences: true, health_data: true });
|
||||
}
|
||||
|
||||
function handleRejectOptional() {
|
||||
onConsent({ essential: true, analytics: false, preferences: false, health_data: false });
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
onConsent({ essential: true, ...categories });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="cookie-banner-overlay">
|
||||
<div className="cookie-banner">
|
||||
<h2 className="cookie-banner-title">{t('cookie_banner.title')}</h2>
|
||||
<p className="cookie-banner-desc">{t('cookie_banner.description')}</p>
|
||||
<div className="cookie-categories">
|
||||
<div className="cookie-category">
|
||||
<div className="cookie-category-info">
|
||||
<span className="cookie-category-name">{t('cookie_banner.category.essential')}</span>
|
||||
<span className="cookie-category-desc">{t('cookie_banner.category.essential_desc')}</span>
|
||||
</div>
|
||||
<div className="cookie-toggle cookie-toggle--locked">
|
||||
<span className="cookie-toggle-track"><span className="cookie-toggle-thumb" /></span>
|
||||
</div>
|
||||
</div>
|
||||
{['analytics', 'preferences', 'health_data'].map(cat => (
|
||||
<div className="cookie-category" key={cat}>
|
||||
<div className="cookie-category-info">
|
||||
<span className="cookie-category-name">{t(`cookie_banner.category.${cat}`)}</span>
|
||||
<span className="cookie-category-desc">{t(`cookie_banner.category.${cat}_desc`)}</span>
|
||||
</div>
|
||||
<button type="button" className={`cookie-toggle ${categories[cat] ? 'cookie-toggle--on' : ''}`} onClick={() => toggleCategory(cat)} aria-pressed={categories[cat]}>
|
||||
<span className="cookie-toggle-track"><span className="cookie-toggle-thumb" /></span>
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="cookie-banner-actions">
|
||||
<button type="button" className="cookie-btn cookie-btn--primary" onClick={handleAcceptAll}>{t('cookie_banner.accept_all')}</button>
|
||||
<button type="button" className="cookie-btn cookie-btn--secondary" onClick={handleRejectOptional}>{t('cookie_banner.reject_optional')}</button>
|
||||
<button type="button" className="cookie-btn cookie-btn--tertiary" onClick={handleSave}>{t('cookie_banner.save')}</button>
|
||||
</div>
|
||||
<button type="button" className="cookie-more-info" onClick={onPrivacyClick}>{t('cookie_banner.more_info')} →</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CookieBanner;
|
||||
Reference in New Issue
Block a user