Add a setting for user pronouns

This commit is contained in:
Ginger 2025-09-15 14:46:08 -04:00
parent c7f6e33a2b
commit c5b59ea122
No known key found for this signature in database

View file

@ -1,5 +1,8 @@
import React, {
ChangeEventHandler,
FormEventHandler,
KeyboardEventHandler,
MouseEventHandler,
ReactNode,
useCallback,
useEffect,
@ -26,9 +29,14 @@ import {
Dialog,
Header,
MenuItem,
Chip,
PopOut,
RectCords,
Menu,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { UserEvent } from 'matrix-js-sdk';
import { isKeyHotkey } from 'is-hotkey';
import { SequenceCard } from '../../../components/sequence-card';
import { SettingTile } from '../../../components/setting-tile';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
@ -203,7 +211,6 @@ function ProfileTextField<K extends keyof FilterByValues<ExtendedProfile, string
onChange={handleChange}
variant="Secondary"
radii="300"
style={{ paddingRight: config.space.S200 }}
readOnly={disabled}
after={
hasChanges &&
@ -227,6 +234,130 @@ function ProfileTextField<K extends keyof FilterByValues<ExtendedProfile, string
);
}
function ProfilePronouns() {
const { busy, value, setValue } = useProfileField('io.fsky.nyx.pronouns');
const disabled = !useProfileFieldAllowed('io.fsky.nyx.pronouns') || busy;
const [menuCords, setMenuCords] = useState<RectCords>();
const [pendingPronoun, setPendingPronoun] = useState('');
const handleRemovePronoun = (index: number) => {
const newPronouns = [...(value ?? [])];
newPronouns.splice(index, 1);
if (newPronouns.length > 0) {
setValue(newPronouns);
} else {
setValue(undefined);
}
};
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
evt.preventDefault();
setMenuCords(undefined);
if (pendingPronoun.length > 0) {
setValue([...(value ?? []), { language: 'en', summary: pendingPronoun }]);
}
};
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (evt) => {
if (isKeyHotkey('escape', evt)) {
evt.stopPropagation();
setMenuCords(undefined);
}
};
const handleOpenMenu: MouseEventHandler<HTMLSpanElement> = (evt) => {
setPendingPronoun('');
setMenuCords(evt.currentTarget.getBoundingClientRect());
};
return (
<SettingTile
title={
<Text as="span" size="L400">
Pronouns
</Text>
}
>
<Box alignItems="Center" gap="200" wrap="Wrap">
{value?.map(({ summary }, index) => (
<Chip
// eslint-disable-next-line react/no-array-index-key
key={index}
variant="Secondary"
radii="Pill"
after={<Icon src={Icons.Cross} size="100" />}
onClick={() => handleRemovePronoun(index)}
disabled={disabled}
>
<Text size="T200" truncate>
{summary}
</Text>
</Chip>
))}
<Chip
variant="Secondary"
radii="Pill"
disabled={disabled}
after={<Icon src={menuCords ? Icons.ChevronRight : Icons.Plus} size="100" />}
onClick={handleOpenMenu}
>
<Text size="T200">Add</Text>
</Chip>
</Box>
<PopOut
anchor={menuCords}
offset={5}
position="Right"
align="Center"
content={
<FocusTrap
focusTrapOptions={{
onDeactivate: () => setMenuCords(undefined),
clickOutsideDeactivates: true,
isKeyForward: (evt: KeyboardEvent) =>
evt.key === 'ArrowDown' || evt.key === 'ArrowRight',
isKeyBackward: (evt: KeyboardEvent) =>
evt.key === 'ArrowUp' || evt.key === 'ArrowLeft',
escapeDeactivates: stopPropagation,
}}
>
<Menu
variant="SurfaceVariant"
style={{
padding: config.space.S200,
}}
>
<Box as="form" onSubmit={handleSubmit} direction="Row" gap="200">
<Input
variant="Secondary"
placeholder="they/them"
inputSize={10}
radii="300"
size="300"
outlined
value={pendingPronoun}
onChange={(evt) => setPendingPronoun(evt.currentTarget.value)}
onKeyDown={handleKeyDown}
/>
<Button
type="submit"
size="300"
variant="Success"
radii="300"
before={<Icon size="100" src={Icons.Plus} />}
>
<Text size="B300">Add</Text>
</Button>
</Box>
</Menu>
</FocusTrap>
}
/>
</SettingTile>
);
}
function ProfileTimezone() {
const { busy, value, setValue } = useProfileField('us.cloke.msc4175.tz');
const disabled = !useProfileFieldAllowed('us.cloke.msc4175.tz') || busy;
@ -392,7 +523,7 @@ export function Profile() {
const extendedProfile =
extendedProfileState.status === AsyncStatus.Success ? extendedProfileState.data : undefined;
const [fieldDefaults, setFieldDefaults] = useState<ExtendedProfile>({})
const [fieldDefaults, setFieldDefaults] = useState<ExtendedProfile>({});
useLayoutEffect(() => {
if (extendedProfile !== undefined) {
setFieldDefaults(extendedProfile);
@ -471,14 +602,15 @@ export function Profile() {
variant="SurfaceVariant"
direction="Column"
gap="300"
radii='0'
radii="0"
outlined
style={{ borderLeftWidth: "0", borderRightWidth: "0", borderBottomWidth: "0" }}
style={{ borderLeftWidth: '0', borderRightWidth: '0', borderBottomWidth: '0' }}
>
<ProfileAvatar />
<ProfileTextField field="displayname" label="Display Name" />
<ProfilePronouns />
<ProfileTimezone />
<Box gap="300" alignItems='Center'>
<Box gap="300" alignItems="Center">
<Button
type="submit"
size="300"