snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
import { VisuallyHidden } from '@react-aria/visually-hidden';
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import type { SelectOption } from '@rocket.chat/fuselage';
|
||||
import { Accordion, AccordionItem, Box, Button, ButtonGroup } from '@rocket.chat/fuselage';
|
||||
import {
|
||||
FieldDescription,
|
||||
Field,
|
||||
FieldGroup,
|
||||
FieldHint,
|
||||
FieldLabel,
|
||||
FieldRow,
|
||||
RadioButton,
|
||||
Select,
|
||||
ToggleSwitch,
|
||||
} from '@rocket.chat/fuselage-forms';
|
||||
import { ExternalLink, Page, PageHeader, PageScrollableContentWithShadow, PageFooter } from '@rocket.chat/ui-client';
|
||||
import { useTranslation, useToastMessageDispatch, useEndpoint, useSetting, useLocationHash } from '@rocket.chat/ui-contexts';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useId, useMemo } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
||||
import { fontSizes } from './fontSizes';
|
||||
import type { AccessibilityPreferencesData } from './hooks/useAcessibilityPreferencesValues';
|
||||
import { useAccessiblityPreferencesValues } from './hooks/useAcessibilityPreferencesValues';
|
||||
import { useCreateFontStyleElement } from './hooks/useCreateFontStyleElement';
|
||||
import { themeItems as themes } from './themeItems';
|
||||
import { getDirtyFields } from '../../../lib/getDirtyFields';
|
||||
import { links } from '../../../lib/links';
|
||||
|
||||
const AccessibilityPage = () => {
|
||||
const t = useTranslation();
|
||||
const dispatchToastMessage = useToastMessageDispatch();
|
||||
const preferencesValues = useAccessiblityPreferencesValues();
|
||||
|
||||
const createFontStyleElement = useCreateFontStyleElement();
|
||||
const displayRolesEnabled = useSetting('UI_DisplayRoles');
|
||||
const shouldExpand = useLocationHash().length > 1;
|
||||
|
||||
const timeFormatOptions = useMemo(
|
||||
(): SelectOption[] => [
|
||||
['0', t('Default')],
|
||||
['1', t('12_Hour')],
|
||||
['2', t('24_Hour')],
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
||||
const pageFormId = useId();
|
||||
const linkListId = useId();
|
||||
|
||||
const {
|
||||
formState: { isDirty, dirtyFields, isSubmitting },
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
watch,
|
||||
} = useForm({
|
||||
defaultValues: preferencesValues,
|
||||
});
|
||||
|
||||
const currentData = watch();
|
||||
|
||||
const setUserPreferencesEndpoint = useEndpoint('POST', '/v1/users.setPreferences');
|
||||
|
||||
const setPreferencesAction = useMutation({
|
||||
mutationFn: setUserPreferencesEndpoint,
|
||||
onSuccess: () => dispatchToastMessage({ type: 'success', message: t('Preferences_saved') }),
|
||||
onError: (error) => dispatchToastMessage({ type: 'error', message: error }),
|
||||
onSettled: (_data, _error, { data: { fontSize } }) => {
|
||||
reset(currentData);
|
||||
dirtyFields.fontSize && fontSize && createFontStyleElement(fontSize);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSaveData = (formData: AccessibilityPreferencesData) => {
|
||||
const data = getDirtyFields(formData, dirtyFields);
|
||||
setPreferencesAction.mutateAsync({ data });
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader title={t('Accessibility_and_Appearance')} />
|
||||
<PageScrollableContentWithShadow>
|
||||
<Box
|
||||
is='form'
|
||||
id={pageFormId}
|
||||
onSubmit={handleSubmit(handleSaveData)}
|
||||
maxWidth='x600'
|
||||
width='full'
|
||||
alignSelf='center'
|
||||
marginBlock={40}
|
||||
marginInline={36}
|
||||
>
|
||||
<Box fontScale='p1' marginBlockEnd={24}>
|
||||
<Box paddingBlock={16} is='p'>
|
||||
{t('Accessibility_activation')}
|
||||
</Box>
|
||||
<p id={linkListId}>{t('Learn_more_about_accessibility')}</p>
|
||||
<ul aria-labelledby={linkListId}>
|
||||
<li>
|
||||
<ExternalLink to={links.go.accessibilityStatement}>{t('Accessibility_statement')}</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink to={links.go.glossary}>{t('Glossary_of_simplified_terms')}</ExternalLink>
|
||||
</li>
|
||||
<li>
|
||||
<ExternalLink to={links.go.accessibilityAndAppearance}>{t('Accessibility_feature_documentation')}</ExternalLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Box>
|
||||
<Accordion>
|
||||
<AccordionItem defaultExpanded={true} title={t('Theme')}>
|
||||
{themes.map(({ id, title, description }, index) => {
|
||||
return (
|
||||
<Field
|
||||
key={id}
|
||||
paddingBlockEnd={themes.length - 1 ? undefined : 'x28'}
|
||||
paddingBlockStart={index === 0 ? undefined : 'x28'}
|
||||
>
|
||||
<FieldRow>
|
||||
<FieldLabel display='flex' alignItems='center'>
|
||||
{t(title)}
|
||||
</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='themeAppearence'
|
||||
render={({ field: { value, onChange, ...field } }) => (
|
||||
<RadioButton id={id} {...field} onChange={() => onChange(id)} checked={value === id} />
|
||||
)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldHint marginBlockStart={12} style={{ whiteSpace: 'break-spaces' }}>
|
||||
{t(description)}
|
||||
</FieldHint>
|
||||
</Field>
|
||||
);
|
||||
})}
|
||||
</AccordionItem>
|
||||
<AccordionItem defaultExpanded={shouldExpand} title={t('Adjustable_layout')}>
|
||||
<FieldGroup>
|
||||
<VisuallyHidden>
|
||||
<legend>{t('Adjustable_layout')}</legend>
|
||||
</VisuallyHidden>
|
||||
<Field>
|
||||
<FieldLabel marginBlockEnd={12}>{t('Font_size')}</FieldLabel>
|
||||
<FieldRow>
|
||||
<Controller control={control} name='fontSize' render={({ field }) => <Select {...field} options={fontSizes(t)} />} />
|
||||
</FieldRow>
|
||||
<FieldDescription marginBlock={12}>{t('Adjustable_font_size_description')}</FieldDescription>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldRow>
|
||||
<FieldLabel>{t('Mentions_with_@_symbol')}</FieldLabel>
|
||||
<Controller
|
||||
control={control}
|
||||
name='mentionsWithSymbol'
|
||||
render={({ field: { value, ...field } }) => <ToggleSwitch {...field} checked={value} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldDescription
|
||||
className={css`
|
||||
white-space: break-spaces;
|
||||
`}
|
||||
marginBlock={12}
|
||||
>
|
||||
{t('Mentions_with_@_symbol_description')}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Field id='clockMode'>
|
||||
<FieldLabel>{t('Message_TimeFormat')}</FieldLabel>
|
||||
<FieldRow>
|
||||
<Controller
|
||||
name='clockMode'
|
||||
control={control}
|
||||
render={({ field: { value, ...field } }) => <Select {...field} value={`${value}`} options={timeFormatOptions} />}
|
||||
/>
|
||||
</FieldRow>
|
||||
</Field>
|
||||
<Field id='hideUsernames'>
|
||||
<FieldRow>
|
||||
<FieldLabel>{t('Show_usernames')}</FieldLabel>
|
||||
<Controller
|
||||
name='hideUsernames'
|
||||
control={control}
|
||||
render={({ field: { value, onChange, ...field } }) => (
|
||||
<ToggleSwitch {...field} checked={!value} onChange={(e) => onChange(!(e.target as HTMLInputElement).checked)} />
|
||||
)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldDescription>{t('Show_or_hide_the_username_of_message_authors')}</FieldDescription>
|
||||
</Field>
|
||||
{displayRolesEnabled && (
|
||||
<Field id='hideRoles'>
|
||||
<FieldRow>
|
||||
<FieldLabel>{t('Show_roles')}</FieldLabel>
|
||||
<Controller
|
||||
name='hideRoles'
|
||||
control={control}
|
||||
render={({ field: { value, onChange, ...field } }) => (
|
||||
<ToggleSwitch {...field} checked={!value} onChange={(e) => onChange(!(e.target as HTMLInputElement).checked)} />
|
||||
)}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldDescription>{t('Show_or_hide_the_user_roles_of_message_authors')}</FieldDescription>
|
||||
</Field>
|
||||
)}
|
||||
</FieldGroup>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Box>
|
||||
</PageScrollableContentWithShadow>
|
||||
<PageFooter isDirty={isDirty}>
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => reset(preferencesValues)}>{t('Cancel')}</Button>
|
||||
<Button primary disabled={!isDirty} loading={isSubmitting} form={pageFormId} type='submit'>
|
||||
{t('Save_changes')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</PageFooter>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccessibilityPage;
|
||||
12
apps/meteor/client/views/account/accessibility/fontSizes.ts
Normal file
12
apps/meteor/client/views/account/accessibility/fontSizes.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { SelectOption } from '@rocket.chat/fuselage';
|
||||
import type { TranslationKey } from '@rocket.chat/ui-contexts';
|
||||
|
||||
type TranslationFunction = (key: TranslationKey) => string;
|
||||
|
||||
export const fontSizes = (t: TranslationFunction): SelectOption[] => [
|
||||
[`${(14 / 16) * 100}%`, t('Font_Small')],
|
||||
['100%', t('Font_Default')],
|
||||
[`${(18 / 16) * 100}%`, t('Font_Medium')],
|
||||
[`${(20 / 16) * 100}%`, t('Font_Large')],
|
||||
[`${(24 / 16) * 100}%`, t('Font_Extra_large')],
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { ThemePreference } from '@rocket.chat/core-typings';
|
||||
import type { FontSize } from '@rocket.chat/rest-typings';
|
||||
import { useUserPreference } from '@rocket.chat/ui-contexts';
|
||||
|
||||
export type AccessibilityPreferencesData = {
|
||||
themeAppearence?: ThemePreference;
|
||||
fontSize?: FontSize;
|
||||
fontSizePreference?: FontSize;
|
||||
mentionsWithSymbol?: boolean;
|
||||
clockMode?: 0 | 1 | 2;
|
||||
hideUsernames?: boolean;
|
||||
hideRoles?: boolean;
|
||||
};
|
||||
|
||||
export const useAccessiblityPreferencesValues = (): AccessibilityPreferencesData => {
|
||||
const themeAppearence = useUserPreference<ThemePreference>('themeAppearence') || 'auto';
|
||||
const fontSize = useUserPreference<FontSize>('fontSize') || '100%';
|
||||
const mentionsWithSymbol = useUserPreference<boolean>('mentionsWithSymbol') || false;
|
||||
const clockMode = useUserPreference<0 | 1 | 2>('clockMode') ?? 0;
|
||||
const hideUsernames = useUserPreference<boolean>('hideUsernames');
|
||||
const hideRoles = useUserPreference<boolean>('hideRoles');
|
||||
|
||||
return {
|
||||
themeAppearence,
|
||||
fontSize,
|
||||
mentionsWithSymbol,
|
||||
clockMode,
|
||||
hideUsernames,
|
||||
hideRoles,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const createStyleElement = (id: string) => {
|
||||
const styleElement = document.getElementById(id);
|
||||
if (styleElement) {
|
||||
return styleElement;
|
||||
}
|
||||
const newStyleElement = document.createElement('style');
|
||||
newStyleElement.setAttribute('id', id);
|
||||
return newStyleElement;
|
||||
};
|
||||
|
||||
export const useCreateFontStyleElement = (): ((fontSize: string) => void) => {
|
||||
return useCallback((fontSize: string) => {
|
||||
const styleElement = createStyleElement('rcx-font-size');
|
||||
const css = `html { font-size: ${fontSize}; }`;
|
||||
styleElement.innerHTML = css;
|
||||
document.head.appendChild(styleElement);
|
||||
}, []);
|
||||
};
|
||||
29
apps/meteor/client/views/account/accessibility/themeItems.ts
Normal file
29
apps/meteor/client/views/account/accessibility/themeItems.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { TranslationKey } from '@rocket.chat/ui-contexts';
|
||||
|
||||
type ThemeItem = {
|
||||
id: string;
|
||||
title: TranslationKey;
|
||||
description: TranslationKey;
|
||||
};
|
||||
export const themeItems: ThemeItem[] = [
|
||||
{
|
||||
id: 'light',
|
||||
title: 'Theme_light',
|
||||
description: 'Theme_light_description',
|
||||
},
|
||||
{
|
||||
id: 'dark',
|
||||
title: 'Theme_dark',
|
||||
description: 'Theme_dark_description',
|
||||
},
|
||||
{
|
||||
id: 'high-contrast',
|
||||
title: 'Theme_high_contrast',
|
||||
description: 'Theme_high_contrast_description',
|
||||
},
|
||||
{
|
||||
id: 'auto',
|
||||
title: 'Theme_match_system',
|
||||
description: 'Theme_match_system_description',
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user