From 4cfe230c8260e9fb217298cfb02604f3cad77756 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Mon, 17 Aug 2026 13:06:28 -0600 Subject: [PATCH] Open the custom theme builder in a wider dedicated dialog (#46) The theme editor used to expand inline inside ProfileModal, whose .modal is capped at min(380px, 100%) -- too narrow to comfortably see the live CustomThemePreview mockup it's built around. Pulled the editor out into a new ThemeBuilderModal (min(820px, 95vw), two-column layout above 680px) opened on top of the profile modal, same stacked-dialog pattern already used by ImageLightbox/FilePreviewModal. No changes to the theme data model, activation, save, or delete behavior. Co-Authored-By: Claude Sonnet 5 --- frontend/src/components/Modal.css | 8 -- frontend/src/components/ProfileModal.tsx | 81 +++--------- frontend/src/components/ThemeBuilderModal.css | 22 ++++ frontend/src/components/ThemeBuilderModal.tsx | 119 ++++++++++++++++++ 4 files changed, 155 insertions(+), 75 deletions(-) create mode 100644 frontend/src/components/ThemeBuilderModal.css create mode 100644 frontend/src/components/ThemeBuilderModal.tsx diff --git a/frontend/src/components/Modal.css b/frontend/src/components/Modal.css index 78a7adb..a87c9f3 100644 --- a/frontend/src/components/Modal.css +++ b/frontend/src/components/Modal.css @@ -242,14 +242,6 @@ margin-bottom: var(--sp-3) !important; } -.custom-theme-editor { - background: var(--ds-surface-2); - border: 1px solid var(--ds-border); - border-radius: var(--radius); - padding: var(--sp-3); - margin-bottom: var(--sp-4); -} - .custom-theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); diff --git a/frontend/src/components/ProfileModal.tsx b/frontend/src/components/ProfileModal.tsx index fb0231e..7b8f8fd 100644 --- a/frontend/src/components/ProfileModal.tsx +++ b/frontend/src/components/ProfileModal.tsx @@ -13,7 +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 { ThemeBuilderModal } from './ThemeBuilderModal' import { UserAvatar } from './UserAvatar' import './Modal.css' @@ -388,72 +388,19 @@ export function ProfileModal({ onClose }: ProfileModalProps) { {editingTheme && ( -
- setEditNameDraft(e.target.value)} - placeholder="Theme name" - maxLength={50} - /> - -
- {CUSTOM_COLOR_FIELDS.map((field) => ( - - ))} -
-
- Native controls (scrollbars, form inputs) -
- - -
-
-
- - -
-
+ )}
diff --git a/frontend/src/components/ThemeBuilderModal.css b/frontend/src/components/ThemeBuilderModal.css new file mode 100644 index 0000000..423a089 --- /dev/null +++ b/frontend/src/components/ThemeBuilderModal.css @@ -0,0 +1,22 @@ +.theme-builder-modal { + width: min(820px, 95vw); + max-height: 88vh; +} + +.theme-builder-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + gap: var(--sp-5); + align-items: start; + margin-bottom: var(--sp-2); +} + +.theme-builder-preview-col .custom-theme-preview { + margin-bottom: var(--sp-4); +} + +@media (max-width: 680px) { + .theme-builder-layout { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/components/ThemeBuilderModal.tsx b/frontend/src/components/ThemeBuilderModal.tsx new file mode 100644 index 0000000..7d6cac6 --- /dev/null +++ b/frontend/src/components/ThemeBuilderModal.tsx @@ -0,0 +1,119 @@ +import type { CustomThemeColors } from '../types' +import { CustomThemePreview } from './CustomThemePreview' +import './Modal.css' +import './ThemeBuilderModal.css' + +interface ColorField { + key: keyof Omit + label: string +} + +interface ThemeBuilderModalProps { + colorFields: ColorField[] + name: string + onNameChange: (name: string) => void + colors: CustomThemeColors + onColorChange: (key: keyof CustomThemeColors, value: string) => void + highlightedField: keyof CustomThemeColors | null + onHighlight: (field: keyof CustomThemeColors | null) => void + error: string | null + saving: boolean + onSave: () => void + onCancel: () => void +} + +// Same editor as ProfileModal.tsx used to render inline, pulled out into its +// own wider dialog (#46) -- the profile modal's .modal is capped at +// min(380px, 100%), which cramped the CustomThemePreview mockup that's +// supposed to make the color-to-UI mapping easy to see. Nested on top of +// ProfileModal rather than replacing it, same stacked-dialog pattern as +// ImageLightbox/FilePreviewModal opening over MessageList. +export function ThemeBuilderModal({ + colorFields, + name, + onNameChange, + colors, + onColorChange, + highlightedField, + onHighlight, + error, + saving, + onSave, + onCancel, +}: ThemeBuilderModalProps) { + return ( +
+
e.stopPropagation()}> +
+

Edit theme

+ +
+ +
+
+ onNameChange(e.target.value)} + placeholder="Theme name" + maxLength={50} + /> + +
+ Native controls (scrollbars, form inputs) +
+ + +
+
+
+ +
+ {colorFields.map((field) => ( + + ))} +
+
+ + {error &&

{error}

} +
+ + +
+
+
+ ) +}