mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 22:40:29 +03:00
Add a setting for user pronouns
This commit is contained in:
parent
c7f6e33a2b
commit
c5b59ea122
1 changed files with 138 additions and 6 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
import React, {
|
import React, {
|
||||||
ChangeEventHandler,
|
ChangeEventHandler,
|
||||||
|
FormEventHandler,
|
||||||
|
KeyboardEventHandler,
|
||||||
|
MouseEventHandler,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
|
@ -26,9 +29,14 @@ import {
|
||||||
Dialog,
|
Dialog,
|
||||||
Header,
|
Header,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
Chip,
|
||||||
|
PopOut,
|
||||||
|
RectCords,
|
||||||
|
Menu,
|
||||||
} from 'folds';
|
} from 'folds';
|
||||||
import FocusTrap from 'focus-trap-react';
|
import FocusTrap from 'focus-trap-react';
|
||||||
import { UserEvent } from 'matrix-js-sdk';
|
import { UserEvent } from 'matrix-js-sdk';
|
||||||
|
import { isKeyHotkey } from 'is-hotkey';
|
||||||
import { SequenceCard } from '../../../components/sequence-card';
|
import { SequenceCard } from '../../../components/sequence-card';
|
||||||
import { SettingTile } from '../../../components/setting-tile';
|
import { SettingTile } from '../../../components/setting-tile';
|
||||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||||
|
|
@ -203,7 +211,6 @@ function ProfileTextField<K extends keyof FilterByValues<ExtendedProfile, string
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
radii="300"
|
radii="300"
|
||||||
style={{ paddingRight: config.space.S200 }}
|
|
||||||
readOnly={disabled}
|
readOnly={disabled}
|
||||||
after={
|
after={
|
||||||
hasChanges &&
|
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() {
|
function ProfileTimezone() {
|
||||||
const { busy, value, setValue } = useProfileField('us.cloke.msc4175.tz');
|
const { busy, value, setValue } = useProfileField('us.cloke.msc4175.tz');
|
||||||
const disabled = !useProfileFieldAllowed('us.cloke.msc4175.tz') || busy;
|
const disabled = !useProfileFieldAllowed('us.cloke.msc4175.tz') || busy;
|
||||||
|
|
@ -392,7 +523,7 @@ export function Profile() {
|
||||||
const extendedProfile =
|
const extendedProfile =
|
||||||
extendedProfileState.status === AsyncStatus.Success ? extendedProfileState.data : undefined;
|
extendedProfileState.status === AsyncStatus.Success ? extendedProfileState.data : undefined;
|
||||||
|
|
||||||
const [fieldDefaults, setFieldDefaults] = useState<ExtendedProfile>({})
|
const [fieldDefaults, setFieldDefaults] = useState<ExtendedProfile>({});
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (extendedProfile !== undefined) {
|
if (extendedProfile !== undefined) {
|
||||||
setFieldDefaults(extendedProfile);
|
setFieldDefaults(extendedProfile);
|
||||||
|
|
@ -471,14 +602,15 @@ export function Profile() {
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
direction="Column"
|
direction="Column"
|
||||||
gap="300"
|
gap="300"
|
||||||
radii='0'
|
radii="0"
|
||||||
outlined
|
outlined
|
||||||
style={{ borderLeftWidth: "0", borderRightWidth: "0", borderBottomWidth: "0" }}
|
style={{ borderLeftWidth: '0', borderRightWidth: '0', borderBottomWidth: '0' }}
|
||||||
>
|
>
|
||||||
<ProfileAvatar />
|
<ProfileAvatar />
|
||||||
<ProfileTextField field="displayname" label="Display Name" />
|
<ProfileTextField field="displayname" label="Display Name" />
|
||||||
|
<ProfilePronouns />
|
||||||
<ProfileTimezone />
|
<ProfileTimezone />
|
||||||
<Box gap="300" alignItems='Center'>
|
<Box gap="300" alignItems="Center">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
size="300"
|
size="300"
|
||||||
|
|
@ -500,7 +632,7 @@ export function Profile() {
|
||||||
radii="300"
|
radii="300"
|
||||||
onClick={reset}
|
onClick={reset}
|
||||||
disabled={!hasChanges || busy}
|
disabled={!hasChanges || busy}
|
||||||
>
|
>
|
||||||
<Text size="B300">Cancel</Text>
|
<Text size="B300">Cancel</Text>
|
||||||
</Button>
|
</Button>
|
||||||
{saving && <Spinner size="300" />}
|
{saving && <Spinner size="300" />}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue