V0.9.21 branding updates

This commit is contained in:
2026-03-15 11:25:00 -04:00
parent d058c9cd5f
commit 8fcadf7f0d
9 changed files with 110 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "jama-frontend",
"version": "0.9.21",
"version": "0.9.22",
"private": true,
"scripts": {
"dev": "vite",

View File

@@ -2,7 +2,8 @@ import { useState, useEffect, useRef, useCallback } from 'react';
import { api } from '../utils/api.js';
import { useToast } from '../contexts/ToastContext.jsx';
const DEFAULT_TITLE_COLOR = '#1a73e8';
const DEFAULT_TITLE_COLOR = '#1a73e8'; // light mode default
const DEFAULT_TITLE_DARK_COLOR = '#60a5fa'; // dark mode default (lighter blue readable on dark bg)
const DEFAULT_PUBLIC_COLOR = '#1a73e8';
const DEFAULT_DM_COLOR = '#a142f4';
@@ -10,6 +11,45 @@ const COLOUR_SUGGESTIONS = [
'#1a73e8', '#a142f4', '#e53935', '#fa7b17', '#fdd835', '#34a853',
];
// ── Title Colour Row — one row per mode ──────────────────────────────────────
function TitleColourRow({ bgColor, bgLabel, textColor, appName, onChange }) {
const [mode, setMode] = useState('idle'); // 'idle' | 'custom'
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
{/* Preview box */}
<div style={{
background: bgColor, borderRadius: 8, padding: '0 14px',
height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center',
border: '1px solid var(--border)', minWidth: 110, flexShrink: 0,
boxShadow: '0 1px 4px rgba(0,0,0,0.1)',
}}>
<span style={{ color: textColor, fontWeight: 700, fontSize: 16, whiteSpace: 'nowrap' }}>
{appName || 'ATC'}
</span>
</div>
{mode === 'idle' && (
<>
<span style={{ fontSize: 12, color: 'var(--text-tertiary)', fontFamily: 'monospace', minWidth: 64 }}>{textColor}</span>
<button className="btn btn-secondary btn-sm" onClick={() => setMode('custom')}>Custom</button>
</>
)}
{mode === 'custom' && (
<div style={{ flex: 1 }}>
<CustomPicker
initial={textColor}
onSet={(hex) => { onChange(hex); setMode('idle'); }}
onBack={() => setMode('idle')}
/>
</div>
)}
</div>
);
}
// ── Colour math helpers ──────────────────────────────────────────────────────
function hexToHsv(hex) {
@@ -278,7 +318,8 @@ export default function BrandingModal({ onClose }) {
const [resetting, setResetting] = useState(false);
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [colourTitle, setColourTitle] = useState(DEFAULT_TITLE_COLOR);
const [colourTitle, setColourTitle] = useState(DEFAULT_TITLE_COLOR);
const [colourTitleDark, setColourTitleDark] = useState(DEFAULT_TITLE_DARK_COLOR);
const [colourPublic, setColourPublic] = useState(DEFAULT_PUBLIC_COLOR);
const [colourDm, setColourDm] = useState(DEFAULT_DM_COLOR);
const [savingColours, setSavingColours] = useState(false);
@@ -288,6 +329,7 @@ export default function BrandingModal({ onClose }) {
setSettings(settings);
setAppName(settings.app_name || 'jama');
setColourTitle(settings.color_title || DEFAULT_TITLE_COLOR);
setColourTitleDark(settings.color_title_dark || DEFAULT_TITLE_DARK_COLOR);
setColourPublic(settings.color_avatar_public || DEFAULT_PUBLIC_COLOR);
setColourDm(settings.color_avatar_dm || DEFAULT_DM_COLOR);
}).catch(() => {});
@@ -329,12 +371,14 @@ export default function BrandingModal({ onClose }) {
try {
await api.updateColors({
colorTitle: colourTitle,
colorTitleDark: colourTitleDark,
colorAvatarPublic: colourPublic,
colorAvatarDm: colourDm,
});
setSettings(prev => ({
...prev,
color_title: colourTitle,
color_title_dark: colourTitleDark,
color_avatar_public: colourPublic,
color_avatar_dm: colourDm,
}));
@@ -355,6 +399,7 @@ export default function BrandingModal({ onClose }) {
setSettings(fresh);
setAppName(fresh.app_name || 'jama');
setColourTitle(DEFAULT_TITLE_COLOR);
setColourTitleDark(DEFAULT_TITLE_DARK_COLOR);
setColourPublic(DEFAULT_PUBLIC_COLOR);
setColourDm(DEFAULT_DM_COLOR);
toast('Settings reset to defaults', 'success');
@@ -453,11 +498,25 @@ export default function BrandingModal({ onClose }) {
{tab === 'colours' && (
<div className="flex-col gap-3">
<ColourPicker
label="App Title Colour"
value={colourTitle}
onChange={setColourTitle}
/>
<div>
<div className="settings-section-label">App Title Colour</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 4 }}>
<TitleColourRow
bgColor="#f1f3f4"
bgLabel="Light mode"
textColor={colourTitle}
appName={appName}
onChange={setColourTitle}
/>
<TitleColourRow
bgColor="#13131f"
bgLabel="Dark mode"
textColor={colourTitleDark}
appName={appName}
onChange={setColourTitleDark}
/>
</div>
</div>
<div style={{ borderTop: '1px solid var(--border)', paddingTop: 20 }}>
<ColourPicker

View File

@@ -5,17 +5,26 @@ import { api } from '../utils/api.js';
export default function GlobalBar({ isMobile, showSidebar }) {
const { connected } = useSocket();
const [settings, setSettings] = useState({ app_name: 'jama', logo_url: '' });
const [isDark, setIsDark] = useState(() => document.documentElement.getAttribute('data-theme') === 'dark');
useEffect(() => {
api.getSettings().then(({ settings }) => setSettings(settings)).catch(() => {});
const handler = () => api.getSettings().then(({ settings }) => setSettings(settings)).catch(() => {});
window.addEventListener('jama:settings-changed', handler);
return () => window.removeEventListener('jama:settings-changed', handler);
// Re-render when theme changes so title colour switches correctly
const themeObserver = new MutationObserver(() => {
setIsDark(document.documentElement.getAttribute('data-theme') === 'dark');
});
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
return () => {
window.removeEventListener('jama:settings-changed', handler);
themeObserver.disconnect();
};
}, []);
const appName = settings.app_name || 'jama';
const logoUrl = settings.logo_url;
const titleColor = settings.color_title || null;
const titleColor = (isDark ? settings.color_title_dark : settings.color_title) || null;
// On mobile: show bar only when sidebar is visible (chat list view)
// On desktop: always show