feat: govern effective appearance defaults
This commit is contained in:
@@ -27,6 +27,7 @@ import { usePlatformLanguage } from "../../i18n/LanguageContext";
|
||||
import CredentialEnvelopeManager from "../../components/CredentialEnvelopeManager";
|
||||
import DocumentationHelpLink from "../../components/help/DocumentationHelpLink";
|
||||
import WorkspaceLayout from "../../components/WorkspaceLayout";
|
||||
import { AppearancePalettePreview, AppearancePaletteSelect, APPEARANCE_PALETTE_OPTIONS, appearancePaletteLabel } from "../../components/AppearancePaletteControl";
|
||||
|
||||
type SettingsSection = "profile" | "mail-profiles" | "file-connectors" | "interface" | "workspace" | "local-connection" | string;
|
||||
|
||||
@@ -36,7 +37,7 @@ const DEFAULT_UI_PREFERENCES: UserUiPreferences = {
|
||||
reduce_motion: false,
|
||||
sticky_section_sidebars: true,
|
||||
theme: "system",
|
||||
palette: "default"
|
||||
palette: null
|
||||
};
|
||||
|
||||
const UI_THEME_OPTIONS: Array<{ value: UserUiTheme; label: string }> = [
|
||||
@@ -45,13 +46,6 @@ const UI_THEME_OPTIONS: Array<{ value: UserUiTheme; label: string }> = [
|
||||
{ value: "dark", label: "i18n:govoplan-core.dark_theme.164a90d9" }
|
||||
];
|
||||
|
||||
const UI_PALETTE_OPTIONS: Array<{ value: UserUiPalette; label: string }> = [
|
||||
{ value: "default", label: "i18n:govoplan-core.palette_default" },
|
||||
{ value: "civic_blue", label: "i18n:govoplan-core.palette_civic_blue" },
|
||||
{ value: "forest", label: "i18n:govoplan-core.palette_forest" },
|
||||
{ value: "plum", label: "i18n:govoplan-core.palette_plum" }
|
||||
];
|
||||
|
||||
const SETTINGS_DOCUMENTATION = {
|
||||
contextId: "core.settings",
|
||||
documentationType: "user" as const
|
||||
@@ -163,7 +157,7 @@ export default function SettingsPage({
|
||||
const [reduceMotion, setReduceMotion] = useState(currentUiPreferences.reduce_motion);
|
||||
const [stickySections, setStickySections] = useState(currentUiPreferences.sticky_section_sidebars);
|
||||
const [theme, setTheme] = useState<UserUiTheme>(currentUiPreferences.theme);
|
||||
const [palette, setPalette] = useState<UserUiPalette>(currentUiPreferences.palette);
|
||||
const [palette, setPalette] = useState<UserUiPalette | null>(currentUiPreferences.palette);
|
||||
const [navigation, setNavigation] = useState<NavigationPreferences | null>(currentUiPreferences.navigation ?? null);
|
||||
const [uiBusy, setUiBusy] = useState(false);
|
||||
const [uiResult, setUiResult] = useState("");
|
||||
@@ -491,24 +485,18 @@ export default function SettingsPage({
|
||||
label="i18n:govoplan-core.color_palette"
|
||||
help="i18n:govoplan-core.color_palette_help"
|
||||
>
|
||||
<select
|
||||
value={palette}
|
||||
onChange={(event) => setPalette(event.target.value as UserUiPalette)}
|
||||
>
|
||||
{UI_PALETTE_OPTIONS.map((item) => (
|
||||
<option key={item.value} value={item.value}>{item.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<AppearancePaletteSelect value={palette} onChange={setPalette} allowInherit disabled={auth.user.appearance?.locked === true} />
|
||||
</FormField>
|
||||
<div className="button-row compact-actions">
|
||||
<Button onClick={() => setPalette("default")} disabled={palette === "default"}>
|
||||
<Button onClick={() => setPalette(null)} disabled={palette === null || auth.user.appearance?.locked === true}>
|
||||
i18n:govoplan-core.reset_palette
|
||||
</Button>
|
||||
</div>
|
||||
<ThemePreview theme={theme} palette={palette} />
|
||||
<AppearancePalettePreview theme={theme} palette={palette ?? auth.user.appearance?.inherited_palette ?? "default"} />
|
||||
<DescriptionList variant="inline" density="compact">
|
||||
<div><dt>i18n:govoplan-core.theme.a797e309</dt><dd>{themeLabel(theme)}</dd></div>
|
||||
<div><dt>i18n:govoplan-core.accent_color.e49578ed</dt><dd>{paletteLabel(palette)}</dd></div>
|
||||
<div><dt>i18n:govoplan-core.accent_color.e49578ed</dt><dd>{paletteLabel(palette ?? auth.user.appearance?.inherited_palette ?? "default")}</dd></div>
|
||||
<div><dt>i18n:govoplan-core.effective_source</dt><dd>{appearanceSourceLabel(auth.user.appearance?.source)}</dd></div>
|
||||
<div><dt>i18n:govoplan-core.accessibility</dt><dd>i18n:govoplan-core.palette_contrast_validated</dd></div>
|
||||
<div><dt>i18n:govoplan-core.advanced_theme_overrides</dt><dd>i18n:govoplan-core.not_configured</dd></div>
|
||||
<div><dt>i18n:govoplan-core.language.89b86ab0</dt><dd>{languageLabel}</dd></div>
|
||||
@@ -651,42 +639,36 @@ function normalizeUiPreferences(value: Partial<UserUiPreferences> | null | undef
|
||||
reduce_motion: Boolean(value?.reduce_motion ?? DEFAULT_UI_PREFERENCES.reduce_motion),
|
||||
sticky_section_sidebars: Boolean(value?.sticky_section_sidebars ?? DEFAULT_UI_PREFERENCES.sticky_section_sidebars),
|
||||
theme,
|
||||
palette: normalizeUiPalette(value?.palette),
|
||||
palette: normalizeOptionalUiPalette(value?.palette),
|
||||
navigation: value?.navigation ?? null
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeUiPalette(value: UserUiPalette | string | null | undefined): UserUiPalette {
|
||||
return UI_PALETTE_OPTIONS.some((item) => item.value === value)
|
||||
return APPEARANCE_PALETTE_OPTIONS.some((item) => item.value === value)
|
||||
? value as UserUiPalette
|
||||
: "default";
|
||||
}
|
||||
|
||||
function normalizeOptionalUiPalette(value: UserUiPalette | string | null | undefined): UserUiPalette | null {
|
||||
return value == null ? null : normalizeUiPalette(value);
|
||||
}
|
||||
|
||||
function themeLabel(value: UserUiTheme): string {
|
||||
return UI_THEME_OPTIONS.find((item) => item.value === value)?.label ?? UI_THEME_OPTIONS[0].label;
|
||||
}
|
||||
|
||||
function paletteLabel(value: UserUiPalette): string {
|
||||
return UI_PALETTE_OPTIONS.find((item) => item.value === value)?.label ?? UI_PALETTE_OPTIONS[0].label;
|
||||
return appearancePaletteLabel(value);
|
||||
}
|
||||
|
||||
function ThemePreview({ theme, palette }: {theme: UserUiTheme;palette: UserUiPalette;}) {
|
||||
const variants: UserUiTheme[] = theme === "system" ? ["light", "dark"] : [theme];
|
||||
return (
|
||||
<div className="theme-preview-list" aria-label="i18n:govoplan-core.theme.a797e309">
|
||||
{variants.map((variant) =>
|
||||
<div key={variant} className="theme-preview" data-preview-theme={variant} data-preview-palette={palette}>
|
||||
<div className="theme-preview-header">
|
||||
<span>{themeLabel(variant)}</span>
|
||||
<i />
|
||||
</div>
|
||||
<div className="theme-preview-body">
|
||||
<strong>GovOPlaN</strong>
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>);
|
||||
|
||||
function appearanceSourceLabel(value: string | null | undefined): string {
|
||||
const labels: Record<string, string> = {
|
||||
user: "i18n:govoplan-core.appearance_source_user",
|
||||
tenant: "i18n:govoplan-core.appearance_source_tenant",
|
||||
system: "i18n:govoplan-core.appearance_source_system",
|
||||
tenant_lock: "i18n:govoplan-core.appearance_source_tenant_lock",
|
||||
system_lock: "i18n:govoplan-core.appearance_source_system_lock"
|
||||
};
|
||||
return labels[value ?? "system"] ?? labels.system;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user