Skip to content

Commit 35e147a

Browse files
authored
Feat: Add the ability to set Global Name Colors dependent on the theme (dark/light) (#656)
<!-- Please read https://github.com/SableClient/Sable/blob/dev/CONTRIBUTING.md before submitting your pull request --> ### Description <!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. --> This PR proposes the ability to set up different Global Name Colors with the purpose of making names easier to read for everyone independent on which theme they might choose to use. The Global Name Colors options are the General for legacy puroses, Dark and Light Theme. (example of the issue) <img width="620" height="565" alt="image" src="https://github.com/user-attachments/assets/ba3e5abd-b390-4fea-864d-ff290507efd3" /> Here the names of 7w1, Shea, Lunia and potentially Haz are hard to read if one chooses to use a Light theme. (example of the name Shea being corrected for using this PR) <img width="619" height="568" alt="image" src="https://github.com/user-attachments/assets/11720d83-573d-442f-9ee6-139e294089ab" /> This PR also reorganizes the setting for the Global Name Colors location in order to align it more to the style of the other settings: <img width="817" height="696" alt="image" src="https://github.com/user-attachments/assets/ef25c513-9503-4a05-bed9-0ba81a10e188" /> And puts the Save button to the left as to not move the text box and the selector button when it appears: <img width="816" height="699" alt="image" src="https://github.com/user-attachments/assets/9df3e1bc-1951-4d7a-a931-44a05e9156b1" /> #### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings ### AI disclosure: - [ ] Partially AI assisted (clarify which code was AI assisted and briefly explain what it does). - [ ] Fully AI generated (explain what all the generated code does in moderate detail). <!-- Write any explanation required here, but do not generate the explanation using AI!! You must prove you understand what the code in this PR does. --> My anxiety about the future helped me overthink the solution into existence
2 parents f82446a + a556efe commit 35e147a

4 files changed

Lines changed: 139 additions & 86 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: minor
3+
---
4+
5+
Add the ability to set Global Name Colors dependent on the theme (dark/light)

src/app/features/settings/account/NameColorEditor.tsx

Lines changed: 84 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { HexColorPickerPopOut } from '$components/HexColorPickerPopOut';
77
type NameColorEditorProps = {
88
title: string;
99
description?: string;
10+
focusId?: string;
1011
current?: string;
1112
onSave: (color: string | null) => void;
1213
disabled?: boolean;
@@ -15,6 +16,7 @@ type NameColorEditorProps = {
1516
export function NameColorEditor({
1617
title,
1718
description,
19+
focusId,
1820
current,
1921
onSave,
2022
disabled,
@@ -57,89 +59,94 @@ export function NameColorEditor({
5759

5860
return (
5961
<Box direction="Column" gap="100">
60-
<SettingTile title={title} focusId="name-color" description={description} />
61-
<Box
62-
alignItems="Center"
63-
justifyContent="SpaceBetween"
64-
gap="300"
65-
style={{
66-
padding: config.space.S400,
67-
backgroundColor: 'var(--sable-surface-container)',
68-
borderRadius: config.radii.R400,
69-
}}
70-
>
71-
<Box alignItems="Center" gap="300" grow="Yes">
72-
<HexColorPickerPopOut
73-
picker={<HexColorPicker color={tempColor} onChange={handleUpdate} />}
62+
<SettingTile
63+
title={title}
64+
focusId={focusId}
65+
description={description}
66+
after={
67+
<Box
68+
alignItems="Center"
69+
justifyContent="SpaceBetween"
70+
gap="300"
71+
style={{
72+
padding: config.space.S100,
73+
backgroundColor: 'var(--sable-surface-container)',
74+
borderRadius: config.radii.R400,
75+
}}
7476
>
75-
{(onOpen, opened) => (
76-
<Button
77-
onClick={onOpen}
78-
size="400"
79-
variant="Secondary"
80-
fill="None"
81-
radii="300"
82-
disabled={disabled ?? false}
83-
style={{
84-
padding: config.space.S100,
85-
border: `2px solid ${opened ? 'var(--sable-primary-main)' : 'var(--sable-border-focus)'}`,
86-
}}
77+
<Box alignItems="Center" gap="300" grow="Yes">
78+
{hasChanged && (
79+
<Button
80+
variant="Primary"
81+
size="300"
82+
radii="Pill"
83+
onClick={handleSave}
84+
disabled={!/^#[0-9A-F]{6}$/i.test(tempColor)}
85+
>
86+
<Text size="B300">Save</Text>
87+
</Button>
88+
)}
89+
<HexColorPickerPopOut
90+
picker={<HexColorPicker color={tempColor} onChange={handleUpdate} />}
8791
>
88-
<Box
92+
{(onOpen, opened) => (
93+
<Button
94+
onClick={onOpen}
95+
size="400"
96+
variant="Secondary"
97+
fill="None"
98+
radii="300"
99+
disabled={disabled ?? false}
100+
style={{
101+
padding: config.space.S100,
102+
border: `2px solid ${opened ? 'var(--sable-primary-main)' : 'var(--sable-border-focus)'}`,
103+
}}
104+
>
105+
<Box
106+
style={{
107+
width: '32px',
108+
height: '32px',
109+
borderRadius: '50%',
110+
backgroundColor: tempColor,
111+
boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.1)',
112+
}}
113+
/>
114+
</Button>
115+
)}
116+
</HexColorPickerPopOut>
117+
118+
<Box direction="Row" alignItems="Center" gap="100">
119+
<Input
120+
value={tempColor}
121+
onChange={(e) => handleUpdate(e.currentTarget.value)}
122+
placeholder="#FFFFFF"
123+
variant="Background"
124+
size="300"
125+
radii="300"
126+
disabled={disabled ?? false}
89127
style={{
90-
width: '32px',
91-
height: '32px',
92-
borderRadius: '50%',
93-
backgroundColor: tempColor,
94-
boxShadow: 'inset 0 0 0 1px rgba(0,0,0,0.1)',
128+
textTransform: 'uppercase',
129+
fontFamily: 'monospace',
130+
width: '100px',
95131
}}
96132
/>
97-
</Button>
98-
)}
99-
</HexColorPickerPopOut>
100-
101-
<Box direction="Row" alignItems="Center" gap="100">
102-
<Input
103-
value={tempColor}
104-
onChange={(e) => handleUpdate(e.currentTarget.value)}
105-
placeholder="#FFFFFF"
106-
variant="Background"
107-
size="300"
108-
radii="300"
109-
disabled={disabled ?? false}
110-
style={{
111-
textTransform: 'uppercase',
112-
fontFamily: 'monospace',
113-
width: '100px',
114-
}}
115-
/>
116-
{current && (
117-
<IconButton
118-
variant="Secondary"
119-
size="300"
120-
radii="300"
121-
disabled={disabled ?? false}
122-
onClick={handleReset}
123-
title="Reset to default"
124-
>
125-
<Icon src={Icons.Cross} size="100" />
126-
</IconButton>
127-
)}
133+
{current && (
134+
<IconButton
135+
variant="Secondary"
136+
size="300"
137+
radii="300"
138+
disabled={disabled ?? false}
139+
onClick={handleReset}
140+
title="Reset to default"
141+
>
142+
<Icon src={Icons.Cross} size="100" />
143+
</IconButton>
144+
)}
145+
</Box>
146+
</Box>
128147
</Box>
129-
</Box>
130-
131-
{hasChanged && (
132-
<Button
133-
variant="Primary"
134-
size="300"
135-
radii="Pill"
136-
onClick={handleSave}
137-
disabled={!/^#[0-9A-F]{6}$/i.test(tempColor)}
138-
>
139-
<Text size="B300">Save</Text>
140-
</Button>
141-
)}
142-
</Box>
148+
}
149+
/>
143150
</Box>
144151
);
145152
}

src/app/features/settings/account/Profile.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,11 +539,30 @@ function ProfileExtended({ profile, userId }: Readonly<ProfileProps>) {
539539
gap="400"
540540
>
541541
<NameColorEditor
542-
title="Global Name Color"
542+
title="General Global Name Color"
543543
description="Custom name color everywhere names have color!"
544+
focusId="name-color"
544545
current={profile.nameColor || profile.extended?.['moe.sable.app.name_color']}
545546
onSave={(color) => handleSaveField('moe.sable.app.name_color', color)}
546547
/>
548+
<NameColorEditor
549+
title="Dark theme Global Name Color"
550+
description="Your name's color for a dark theme user."
551+
focusId="name-color-dark-theme"
552+
current={
553+
profile.nameColorDark || profile.extended?.['moe.sable.app.name_color_dark_theme']
554+
}
555+
onSave={(color) => handleSaveField('moe.sable.app.name_color_dark_theme', color)}
556+
/>
557+
<NameColorEditor
558+
title="Light theme Global Name Color"
559+
description="Your name's color for a light theme user."
560+
focusId="name-color-light-theme"
561+
current={
562+
profile.nameColorLight || profile.extended?.['moe.sable.app.name_color_light_theme']
563+
}
564+
onSave={(color) => handleSaveField('moe.sable.app.name_color_light_theme', color)}
565+
/>
547566
</SequenceCard>
548567
<SequenceCard
549568
className={SequenceCardStyle}
@@ -658,7 +677,6 @@ export function Profile() {
658677
const mx = useMatrixClient();
659678
const userId = mx.getUserId()!;
660679
const profile = useUserProfile(userId);
661-
662680
return (
663681
<Box direction="Column" gap="700">
664682
<Box direction="Column" gap="100">

src/app/hooks/useUserProfile.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useSetting } from '$state/hooks/settings';
99
import { settingsAtom } from '$state/settings';
1010
import { MSC1767Text } from '$types/matrix/common';
1111
import { useMatrixClient } from './useMatrixClient';
12+
import { ThemeKind, useActiveTheme } from './useTheme';
1213

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

@@ -25,6 +26,8 @@ export type UserProfile = {
2526
status?: string;
2627
bannerUrl?: string;
2728
nameColor?: string;
29+
nameColorDark?: string;
30+
nameColorLight?: string;
2831
isCat?: boolean;
2932
hasCats?: boolean;
3033
extended?: Record<string, any>;
@@ -45,6 +48,8 @@ const normalizeInfo = (info: any): UserProfile => {
4548
'chat.commet.profile_banner',
4649
'chat.commet.profile_status',
4750
'moe.sable.app.name_color',
51+
'moe.sable.app.name_color_dark_theme',
52+
'moe.sable.app.name_color_light_theme',
4853
'kitty.meow.has_cats',
4954
'kitty.meow.is_cat',
5055
]);
@@ -68,6 +73,8 @@ const normalizeInfo = (info: any): UserProfile => {
6873
status: info['chat.commet.profile_status'],
6974
bannerUrl: info['chat.commet.profile_banner'],
7075
nameColor: info['moe.sable.app.name_color'],
76+
nameColorDark: info['moe.sable.app.name_color_dark_theme'],
77+
nameColorLight: info['moe.sable.app.name_color_light_theme'],
7178
isCat: info['kitty.meow.is_cat'] === true,
7279
hasCats: info['kitty.meow.has_cats'] === true,
7380
extended,
@@ -98,6 +105,7 @@ export const useUserProfile = (
98105
const [renderGlobalColors] = useSetting(settingsAtom, 'renderGlobalNameColors');
99106
const [renderRoomColors] = useSetting(settingsAtom, 'renderRoomColors');
100107
const [renderRoomFonts] = useSetting(settingsAtom, 'renderRoomFonts');
108+
const themeKind = useActiveTheme().kind;
101109

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

@@ -202,12 +210,26 @@ export const useUserProfile = (
202210
}
203211
}
204212
const validGlobalVal = isValidHex(data?.nameColor);
213+
const validGlobalValDark = isValidHex(data?.nameColorDark);
214+
const validGlobalValLight = isValidHex(data?.nameColorLight);
205215

206-
const hasGlobalColor = !!validGlobalVal;
207-
const validGlobal =
208-
(renderGlobalColors || userId === mx.getUserId()) && hasGlobalColor
216+
const validGlobalGeneral =
217+
(renderGlobalColors || userId === mx.getUserId()) && !!validGlobalVal
209218
? validGlobalVal
210219
: undefined;
220+
const validGlobalDark =
221+
(renderGlobalColors || userId === mx.getUserId()) &&
222+
themeKind === ThemeKind.Dark &&
223+
!!validGlobalValDark
224+
? validGlobalValDark
225+
: undefined;
226+
const validGlobalLight =
227+
(renderGlobalColors || userId === mx.getUserId()) &&
228+
themeKind === ThemeKind.Light &&
229+
!!validGlobalValLight
230+
? validGlobalValLight
231+
: undefined;
232+
const validGlobal = validGlobalDark ?? validGlobalLight ?? validGlobalGeneral;
211233
const validLocal = localColor && isValidHex(localColor) ? localColor : undefined;
212234
const validSpace = spaceColor && isValidHex(spaceColor) ? spaceColor : undefined;
213235

@@ -237,13 +259,14 @@ export const useUserProfile = (
237259
};
238260
}, [
239261
cached,
262+
initialProfile,
263+
mx,
240264
userId,
241265
room,
242-
mx,
243-
legacyUsernameColor,
244-
renderGlobalColors,
245-
initialProfile,
246266
renderRoomColors,
247267
renderRoomFonts,
268+
renderGlobalColors,
269+
themeKind,
270+
legacyUsernameColor,
248271
]);
249272
};

0 commit comments

Comments
 (0)