Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/feat-add-theme-specific-colors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
default: minor
---

Add the ability to set Global Name Colors dependent on the theme (dark/light)
161 changes: 84 additions & 77 deletions src/app/features/settings/account/NameColorEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { HexColorPickerPopOut } from '$components/HexColorPickerPopOut';
type NameColorEditorProps = {
title: string;
description?: string;
focusId?: string;
current?: string;
onSave: (color: string | null) => void;
disabled?: boolean;
Expand All @@ -15,6 +16,7 @@ type NameColorEditorProps = {
export function NameColorEditor({
title,
description,
focusId,
current,
onSave,
disabled,
Expand Down Expand Up @@ -57,89 +59,94 @@ export function NameColorEditor({

return (
<Box direction="Column" gap="100">
<SettingTile title={title} focusId="name-color" description={description} />
<Box
alignItems="Center"
justifyContent="SpaceBetween"
gap="300"
style={{
padding: config.space.S400,
backgroundColor: 'var(--sable-surface-container)',
borderRadius: config.radii.R400,
}}
>
<Box alignItems="Center" gap="300" grow="Yes">
<HexColorPickerPopOut
picker={<HexColorPicker color={tempColor} onChange={handleUpdate} />}
<SettingTile
title={title}
focusId={focusId}
description={description}
after={
<Box
alignItems="Center"
justifyContent="SpaceBetween"
gap="300"
style={{
padding: config.space.S100,
backgroundColor: 'var(--sable-surface-container)',
borderRadius: config.radii.R400,
}}
>
{(onOpen, opened) => (
<Button
onClick={onOpen}
size="400"
variant="Secondary"
fill="None"
radii="300"
disabled={disabled ?? false}
style={{
padding: config.space.S100,
border: `2px solid ${opened ? 'var(--sable-primary-main)' : 'var(--sable-border-focus)'}`,
}}
<Box alignItems="Center" gap="300" grow="Yes">
{hasChanged && (
<Button
variant="Primary"
size="300"
radii="Pill"
onClick={handleSave}
disabled={!/^#[0-9A-F]{6}$/i.test(tempColor)}
>
<Text size="B300">Save</Text>
</Button>
)}
<HexColorPickerPopOut
picker={<HexColorPicker color={tempColor} onChange={handleUpdate} />}
>
<Box
{(onOpen, opened) => (
<Button
onClick={onOpen}
size="400"
variant="Secondary"
fill="None"
radii="300"
disabled={disabled ?? false}
style={{
padding: config.space.S100,
border: `2px solid ${opened ? 'var(--sable-primary-main)' : 'var(--sable-border-focus)'}`,
}}
>
<Box
style={{
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: tempColor,
boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.1)',
}}
/>
</Button>
)}
</HexColorPickerPopOut>

<Box direction="Row" alignItems="Center" gap="100">
<Input
value={tempColor}
onChange={(e) => handleUpdate(e.currentTarget.value)}
placeholder="#FFFFFF"
variant="Background"
size="300"
radii="300"
disabled={disabled ?? false}
style={{
width: '32px',
height: '32px',
borderRadius: '50%',
backgroundColor: tempColor,
boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.1)',
textTransform: 'uppercase',
fontFamily: 'monospace',
width: '100px',
}}
/>
</Button>
)}
</HexColorPickerPopOut>

<Box direction="Row" alignItems="Center" gap="100">
<Input
value={tempColor}
onChange={(e) => handleUpdate(e.currentTarget.value)}
placeholder="#FFFFFF"
variant="Background"
size="300"
radii="300"
disabled={disabled ?? false}
style={{
textTransform: 'uppercase',
fontFamily: 'monospace',
width: '100px',
}}
/>
{current && (
<IconButton
variant="Secondary"
size="300"
radii="300"
disabled={disabled ?? false}
onClick={handleReset}
title="Reset to default"
>
<Icon src={Icons.Cross} size="100" />
</IconButton>
)}
{current && (
<IconButton
variant="Secondary"
size="300"
radii="300"
disabled={disabled ?? false}
onClick={handleReset}
title="Reset to default"
>
<Icon src={Icons.Cross} size="100" />
</IconButton>
)}
</Box>
</Box>
</Box>
</Box>

{hasChanged && (
<Button
variant="Primary"
size="300"
radii="Pill"
onClick={handleSave}
disabled={!/^#[0-9A-F]{6}$/i.test(tempColor)}
>
<Text size="B300">Save</Text>
</Button>
)}
</Box>
}
/>
</Box>
);
}
22 changes: 20 additions & 2 deletions src/app/features/settings/account/Profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -539,11 +539,30 @@ function ProfileExtended({ profile, userId }: Readonly<ProfileProps>) {
gap="400"
>
<NameColorEditor
title="Global Name Color"
title="General Global Name Color"
description="Custom name color everywhere names have color!"
focusId="name-color"
current={profile.nameColor || profile.extended?.['moe.sable.app.name_color']}
onSave={(color) => handleSaveField('moe.sable.app.name_color', color)}
/>
<NameColorEditor
title="Dark theme Global Name Color"
description="Your name's color for a dark theme user."
focusId="name-color-dark-theme"
current={
profile.nameColorDark || profile.extended?.['moe.sable.app.name_color_dark_theme']
}
onSave={(color) => handleSaveField('moe.sable.app.name_color_dark_theme', color)}
/>
<NameColorEditor
title="Light theme Global Name Color"
description="Your name's color for a light theme user."
focusId="name-color-light-theme"
current={
profile.nameColorLight || profile.extended?.['moe.sable.app.name_color_light_theme']
}
onSave={(color) => handleSaveField('moe.sable.app.name_color_light_theme', color)}
/>
</SequenceCard>
<SequenceCard
className={SequenceCardStyle}
Expand Down Expand Up @@ -658,7 +677,6 @@ export function Profile() {
const mx = useMatrixClient();
const userId = mx.getUserId()!;
const profile = useUserProfile(userId);

return (
<Box direction="Column" gap="700">
<Box direction="Column" gap="100">
Expand Down
37 changes: 30 additions & 7 deletions src/app/hooks/useUserProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useSetting } from '$state/hooks/settings';
import { settingsAtom } from '$state/settings';
import { MSC1767Text } from '$types/matrix/common';
import { useMatrixClient } from './useMatrixClient';
import { ThemeKind, useActiveTheme } from './useTheme';

const inFlightProfiles = new Map<string, Promise<any>>();

Expand All @@ -25,6 +26,8 @@ export type UserProfile = {
status?: string;
bannerUrl?: string;
nameColor?: string;
nameColorDark?: string;
nameColorLight?: string;
isCat?: boolean;
hasCats?: boolean;
extended?: Record<string, any>;
Expand All @@ -45,6 +48,8 @@ const normalizeInfo = (info: any): UserProfile => {
'chat.commet.profile_banner',
'chat.commet.profile_status',
'moe.sable.app.name_color',
'moe.sable.app.name_color_dark_theme',
'moe.sable.app.name_color_light_theme',
'kitty.meow.has_cats',
'kitty.meow.is_cat',
]);
Expand All @@ -68,6 +73,8 @@ const normalizeInfo = (info: any): UserProfile => {
status: info['chat.commet.profile_status'],
bannerUrl: info['chat.commet.profile_banner'],
nameColor: info['moe.sable.app.name_color'],
nameColorDark: info['moe.sable.app.name_color_dark_theme'],
nameColorLight: info['moe.sable.app.name_color_light_theme'],
isCat: info['kitty.meow.is_cat'] === true,
hasCats: info['kitty.meow.has_cats'] === true,
extended,
Expand Down Expand Up @@ -98,6 +105,7 @@ export const useUserProfile = (
const [renderGlobalColors] = useSetting(settingsAtom, 'renderGlobalNameColors');
const [renderRoomColors] = useSetting(settingsAtom, 'renderRoomColors');
const [renderRoomFonts] = useSetting(settingsAtom, 'renderRoomFonts');
const themeKind = useActiveTheme().kind;

const userSelector = useMemo(() => selectAtom(profilesCacheAtom, (db) => db[userId]), [userId]);

Expand Down Expand Up @@ -202,12 +210,26 @@ export const useUserProfile = (
}
}
const validGlobalVal = isValidHex(data?.nameColor);
const validGlobalValDark = isValidHex(data?.nameColorDark);
const validGlobalValLight = isValidHex(data?.nameColorLight);

const hasGlobalColor = !!validGlobalVal;
const validGlobal =
(renderGlobalColors || userId === mx.getUserId()) && hasGlobalColor
const validGlobalGeneral =
(renderGlobalColors || userId === mx.getUserId()) && !!validGlobalVal
? validGlobalVal
: undefined;
const validGlobalDark =
(renderGlobalColors || userId === mx.getUserId()) &&
themeKind === ThemeKind.Dark &&
!!validGlobalValDark
? validGlobalValDark
: undefined;
const validGlobalLight =
(renderGlobalColors || userId === mx.getUserId()) &&
themeKind === ThemeKind.Light &&
!!validGlobalValLight
? validGlobalValLight
: undefined;
const validGlobal = validGlobalDark ?? validGlobalLight ?? validGlobalGeneral;
const validLocal = localColor && isValidHex(localColor) ? localColor : undefined;
const validSpace = spaceColor && isValidHex(spaceColor) ? spaceColor : undefined;

Expand Down Expand Up @@ -237,13 +259,14 @@ export const useUserProfile = (
};
}, [
cached,
initialProfile,
mx,
userId,
room,
mx,
legacyUsernameColor,
renderGlobalColors,
initialProfile,
renderRoomColors,
renderRoomFonts,
renderGlobalColors,
themeKind,
legacyUsernameColor,
]);
};
Loading