diff --git a/frontend/src/components/CustomThemePreview.css b/frontend/src/components/CustomThemePreview.css new file mode 100644 index 0000000..9294f68 --- /dev/null +++ b/frontend/src/components/CustomThemePreview.css @@ -0,0 +1,108 @@ +.custom-theme-preview { + display: flex; + border-radius: var(--radius); + overflow: hidden; + border: 1px solid var(--ds-border); + margin-bottom: var(--sp-3); + font-size: 0.74rem; + /* Isolated from the outer picker grid's own hover/highlight rules -- + each swatch's highlight ring is drawn on itself, not inherited. */ + position: relative; +} + +.ctp-hoverable { + cursor: default; + transition: box-shadow 0.1s ease; +} + +.ctp-highlighted { + box-shadow: 0 0 0 2px #fbbf24, 0 0 8px 1px rgba(251, 191, 36, 0.6); + position: relative; + z-index: 1; +} + +.ctp-sidebar { + width: 96px; + flex: none; + padding: 8px 6px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.ctp-room-row { + padding: 5px 7px; + border-radius: 5px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.ctp-room-row-quiet { + border: 1px solid transparent; + background: transparent; +} + +.ctp-main { + flex: 1; + min-width: 0; + padding: 10px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.ctp-message { + display: flex; + align-items: flex-start; + gap: 6px; +} + +.ctp-avatar { + width: 22px; + height: 22px; + border-radius: 50%; + flex: none; +} + +.ctp-message-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.ctp-mention { + border-radius: 4px; + padding: 0 3px; + font-weight: 700; +} + +.ctp-badge { + flex: none; + align-self: flex-start; + border-radius: var(--radius-pill); + padding: 1px 7px; + font-size: 0.68rem; + font-weight: 800; + color: #07080f; +} + +.ctp-composer { + border: 1px solid transparent; + border-radius: 6px; + padding: 5px 8px; +} + +.ctp-danger-btn { + align-self: flex-start; + background: transparent; + border: 1px solid transparent; + border-radius: 6px; + padding: 4px 8px; + font-size: 0.72rem; + font-weight: 700; + cursor: pointer; +} diff --git a/frontend/src/components/CustomThemePreview.tsx b/frontend/src/components/CustomThemePreview.tsx new file mode 100644 index 0000000..1563f4d --- /dev/null +++ b/frontend/src/components/CustomThemePreview.tsx @@ -0,0 +1,103 @@ +import type { CustomThemeColors } from '../types' +import './CustomThemePreview.css' + +interface CustomThemePreviewProps { + colors: CustomThemeColors + highlightedField: keyof CustomThemeColors | null + onHighlight: (field: keyof CustomThemeColors | null) => void +} + +// A miniature, self-contained mockup of the real chat UI, styled entirely +// from inline styles bound to the draft colors -- deliberately not the +// --ds-* custom properties, since those reflect whatever theme is actually +// active right now, not necessarily the one being edited (see +// ProfileModal.tsx's handleEditColorChange comment). Hovering either a +// swatch here or the matching color input elsewhere highlights both, so +// it's clear at a glance which of the 12 fields controls which part of the +// real UI, in both directions. +export function CustomThemePreview({ colors, highlightedField, onHighlight }: CustomThemePreviewProps) { + function hoverClass(field: keyof CustomThemeColors, extra = ''): string { + const highlighted = highlightedField === field ? ' ctp-highlighted' : '' + return `ctp-hoverable${extra ? ` ${extra}` : ''}${highlighted}` + } + + function hoverHandlers(field: keyof CustomThemeColors) { + return { + onMouseEnter: () => onHighlight(field), + onMouseLeave: () => onHighlight(null), + } + } + + return ( +
+
+
+ # general +
+
+ # random +
+
+
+
+
+
+ + + Alice + {' '} + + 2:30 PM + + +
+ Hey{' '} + + @bob + {' '} + check this out +
+
+ + Owner + +
+
+ Message… +
+ +
+
+ ) +} diff --git a/frontend/src/components/Modal.css b/frontend/src/components/Modal.css index c0b7a69..78a7adb 100644 --- a/frontend/src/components/Modal.css +++ b/frontend/src/components/Modal.css @@ -263,6 +263,20 @@ font-size: 0.8rem; color: var(--ds-text); cursor: pointer; + border-radius: 6px; + padding: 3px 4px; + margin: -3px -4px; + transition: background 0.1s ease; +} + +/* Mirrors CustomThemePreview.css's .ctp-highlighted -- same fixed, theme- + independent color so it stays visible regardless of what's actually + being edited. Set when either this field's input OR the matching swatch + in the live mockup above is hovered/focused, so the mapping works in + both directions. */ +.custom-theme-field-highlighted { + background: rgba(251, 191, 36, 0.15); + box-shadow: 0 0 0 1px #fbbf24; } .custom-theme-field input[type='color'] { diff --git a/frontend/src/components/ProfileModal.tsx b/frontend/src/components/ProfileModal.tsx index c096097..fb0231e 100644 --- a/frontend/src/components/ProfileModal.tsx +++ b/frontend/src/components/ProfileModal.tsx @@ -13,6 +13,7 @@ import { useAuth } from '../context/AuthContext' import { hashIndex } from '../lib/avatar' import { applyTheme, DEFAULT_CUSTOM_COLORS } from '../lib/theme' import type { CustomTheme, CustomThemeColors } from '../types' +import { CustomThemePreview } from './CustomThemePreview' import { UserAvatar } from './UserAvatar' import './Modal.css' @@ -56,6 +57,7 @@ export function ProfileModal({ onClose }: ProfileModalProps) { const [editNameDraft, setEditNameDraft] = useState('') const [editColorsDraft, setEditColorsDraft] = useState(DEFAULT_CUSTOM_COLORS) const [savingThemeEdit, setSavingThemeEdit] = useState(false) + const [highlightedField, setHighlightedField] = useState(null) const [currentPassword, setCurrentPassword] = useState('') const [newPassword, setNewPassword] = useState('') @@ -158,6 +160,7 @@ export function ProfileModal({ onClose }: ProfileModalProps) { setEditNameDraft(theme.name) setEditColorsDraft(theme.colors) setThemeError(null) + setHighlightedField(null) } function closeEditor() { @@ -168,6 +171,7 @@ export function ProfileModal({ onClose }: ProfileModalProps) { applyTheme(user.theme, user.active_custom_theme.colors) } setEditingThemeId(null) + setHighlightedField(null) } function handleEditColorChange(key: keyof CustomThemeColors, value: string) { @@ -393,13 +397,25 @@ export function ProfileModal({ onClose }: ProfileModalProps) { placeholder="Theme name" maxLength={50} /> +
{CUSTOM_COLOR_FIELDS.map((field) => ( -