From f8bd7fb2b95ed8d0fe74847b41300c85d97e1427 Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:02:00 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=B8=20Improve=20accessibility=20(#980)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚸 Improve accessibility * 🌐 Add missing translations --- .../settings/customization/accessibility.json | 11 +++ .../en/settings/customization/general.json | 6 +- .../Dashboard/Tiles/Apps/AppPing.tsx | 73 ++++++++++++++--- .../Accessibility/AccessibilitySettings.tsx | 80 +++++++++++++++++++ .../Customization/CustomizationAccordeon.tsx | 10 ++- .../Customization/Layout/LayoutSelector.tsx | 2 +- src/tools/config/migrateConfig.ts | 4 + src/tools/server/translation-namespaces.ts | 1 + src/types/settings.ts | 6 ++ 9 files changed, 181 insertions(+), 12 deletions(-) create mode 100644 public/locales/en/settings/customization/accessibility.json create mode 100644 src/components/Settings/Customization/Accessibility/AccessibilitySettings.tsx diff --git a/public/locales/en/settings/customization/accessibility.json b/public/locales/en/settings/customization/accessibility.json new file mode 100644 index 000000000..ce1086664 --- /dev/null +++ b/public/locales/en/settings/customization/accessibility.json @@ -0,0 +1,11 @@ +{ + "disablePulse": { + "label": "Disable ping pulse", + "description": "By default, ping indicators in Homarr will pulse. This may be irritating. This slider will deactivate the animation" + }, + "replaceIconsWithDots": { + "label": "Replace ping dots with icons", + "description": "For colorblind users, ping dots may be unrecognizable. This will replace indicators with icons" + }, + "alert": "Are you missing something? We'll gladly extend the accessibility of Homarr" +} \ No newline at end of file diff --git a/public/locales/en/settings/customization/general.json b/public/locales/en/settings/customization/general.json index 24610a4bb..358b5158b 100644 --- a/public/locales/en/settings/customization/general.json +++ b/public/locales/en/settings/customization/general.json @@ -16,6 +16,10 @@ "appereance": { "name": "Appearance", "description": "Customize the background, colors and apps appearance" + }, + "accessibility": { + "name": "Accessibility", + "description": "Configure Homarr for disabled and handicapped users" } } -} \ No newline at end of file +} diff --git a/src/components/Dashboard/Tiles/Apps/AppPing.tsx b/src/components/Dashboard/Tiles/Apps/AppPing.tsx index a0ce9f9a5..ca12c424b 100644 --- a/src/components/Dashboard/Tiles/Apps/AppPing.tsx +++ b/src/components/Dashboard/Tiles/Apps/AppPing.tsx @@ -1,5 +1,7 @@ -import { Indicator, Tooltip } from '@mantine/core'; +import { Box, Indicator, Tooltip } from '@mantine/core'; +import { IconCheck, IconCheckbox, IconDownload, IconLoader, IconX } from '@tabler/icons-react'; import Consola from 'consola'; +import { TargetAndTransition, Transition, motion } from 'framer-motion'; import { useTranslation } from 'next-i18next'; import { api } from '~/utils/api'; @@ -16,6 +18,7 @@ export const AppPing = ({ app }: AppPingProps) => { const active = (config?.settings.customization.layout.enabledPing && app.network.enabledStatusChecker) ?? false; + const { data, isLoading, isFetching, isSuccess } = api.app.ping.useQuery(app.id, { retry: false, enabled: active, @@ -32,8 +35,37 @@ export const AppPing = ({ app }: AppPingProps) => { if (!active) return null; + const isOnline = data?.state === 'online'; + + const disablePulse = config?.settings.customization.accessibility?.disablePingPulse ?? false; + const replaceDotWithIcon = + config?.settings.customization.accessibility?.replacePingDotsWithIcons ?? false; + + const scaleAnimation = isOnline ? [1, 0.7, 1] : 1; + const animate: TargetAndTransition | undefined = disablePulse + ? undefined + : { + scale: scaleAnimation, + }; + const transition: Transition | undefined = disablePulse + ? undefined + : { + repeat: Infinity, + duration: 2.5, + ease: 'easeInOut', + }; + return ( -
+ { : `${data?.statusText} ${data?.status}` } > - + {config?.settings.customization.accessibility?.replacePingDotsWithIcons ? ( + + + + ) : ( + + )} -
+ ); }; +const AccessibleIndicatorPing = ({ + isLoading, + isOnline, +}: { + isOnline: boolean; + isLoading: boolean; +}) => { + if (isOnline) { + return ; + } + + if (isLoading) { + return ; + } + + return ; +}; + export const getIsOk = (app: AppType, status: number) => { if (app.network.okStatus === undefined || app.network.statusCodes.length >= 1) { Consola.log('Using new status codes'); diff --git a/src/components/Settings/Customization/Accessibility/AccessibilitySettings.tsx b/src/components/Settings/Customization/Accessibility/AccessibilitySettings.tsx new file mode 100644 index 000000000..74d28a6b0 --- /dev/null +++ b/src/components/Settings/Customization/Accessibility/AccessibilitySettings.tsx @@ -0,0 +1,80 @@ +import { Alert, Stack, Switch } from '@mantine/core'; +import { IconInfoCircle } from '@tabler/icons-react'; +import { BaseSyntheticEvent } from 'react'; +import { useConfigStore } from '../../../../config/store'; +import { useConfigContext } from '../../../../config/provider'; +import { useTranslation } from 'react-i18next'; + +export const AccessibilitySettings = () => { + const { t } = useTranslation('settings/customization/accessibility'); + const { updateConfig } = useConfigStore(); + const { config, name: configName } = useConfigContext(); + + return ( + + { + if (!configName) { + return; + } + + updateConfig( + configName, + (previousConfig) => ({ + ...previousConfig, + settings: { + ...previousConfig.settings, + customization: { + ...previousConfig.settings.customization, + accessibility: { + ...previousConfig.settings.customization.accessibility, + disablePingPulse: value.target.checked, + }, + }, + }, + }), + false, + true + ); + }} + /> + + { + if (!configName) { + return; + } + + updateConfig( + configName, + (previousConfig) => ({ + ...previousConfig, + settings: { + ...previousConfig.settings, + customization: { + ...previousConfig.settings.customization, + accessibility: { + ...previousConfig.settings.customization.accessibility, + replacePingDotsWithIcons: value.target.checked, + }, + }, + }, + }), + false, + true + ); + }} + /> + + } color="blue"> + {t('alert')} + + + ); +}; diff --git a/src/components/Settings/Customization/CustomizationAccordeon.tsx b/src/components/Settings/Customization/CustomizationAccordeon.tsx index f5d18551b..39315ee49 100644 --- a/src/components/Settings/Customization/CustomizationAccordeon.tsx +++ b/src/components/Settings/Customization/CustomizationAccordeon.tsx @@ -1,5 +1,5 @@ import { Accordion, Checkbox, Grid, Group, Stack, Text } from '@mantine/core'; -import { IconBrush, IconChartCandle, IconCode, IconDragDrop, IconLayout } from '@tabler/icons-react'; +import { IconAccessible, IconBrush, IconChartCandle, IconCode, IconDragDrop, IconLayout } from '@tabler/icons-react'; import { i18n, useTranslation } from 'next-i18next'; import { ReactNode } from 'react'; import { GridstackConfiguration } from './Layout/GridstackConfiguration'; @@ -13,6 +13,7 @@ import { ColorSelector } from './Theme/ColorSelector'; import { CustomCssChanger } from './Theme/CustomCssChanger'; import { DashboardTilesOpacitySelector } from './Theme/OpacitySelector'; import { ShadeSelector } from './Theme/ShadeSelector'; +import { AccessibilitySettings } from './Accessibility/AccessibilitySettings'; export const CustomizationSettingsAccordeon = () => { const items = getItems().map((item) => ( @@ -70,6 +71,13 @@ const getItems = () => { description: t('accordeon.gridstack.description'), content: , }, + { + id: 'accessibility', + image: , + label: t('accordeon.accessibility.name'), + description: t('accordeon.accessibility.description'), + content: , + }, { id: 'page_metadata', image: , diff --git a/src/components/Settings/Customization/Layout/LayoutSelector.tsx b/src/components/Settings/Customization/Layout/LayoutSelector.tsx index 3b05524eb..d03642dfe 100644 --- a/src/components/Settings/Customization/Layout/LayoutSelector.tsx +++ b/src/components/Settings/Customization/Layout/LayoutSelector.tsx @@ -159,7 +159,7 @@ export const LayoutSelector = () => { /> handleChange('enabledPing', ev, setPing)} /> diff --git a/src/tools/config/migrateConfig.ts b/src/tools/config/migrateConfig.ts index b85e83e38..3411be969 100644 --- a/src/tools/config/migrateConfig.ts +++ b/src/tools/config/migrateConfig.ts @@ -44,6 +44,10 @@ export function migrateConfig(config: Config): BackendConfigType { enabledRightSidebar: false, enabledSearchbar: config.modules.search?.enabled ?? true, }, + accessibility: { + disablePingPulse: false, + replacePingDotsWithIcons: false, + }, }, }, wrappers: [ diff --git a/src/tools/server/translation-namespaces.ts b/src/tools/server/translation-namespaces.ts index b7f50623f..96f25690e 100644 --- a/src/tools/server/translation-namespaces.ts +++ b/src/tools/server/translation-namespaces.ts @@ -13,6 +13,7 @@ export const dashboardNamespaces = [ 'settings/general/internationalization', 'settings/general/search-engine', 'settings/general/widget-positions', + 'settings/customization/accessibility', 'settings/customization/general', 'settings/customization/color-selector', 'settings/customization/page-appearance', diff --git a/src/types/settings.ts b/src/types/settings.ts index 4afa31f8b..ddf2e3a0d 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -45,6 +45,12 @@ export interface CustomizationSettingsType { colors: ColorsCustomizationSettingsType; appOpacity?: number; gridstack?: GridstackSettingsType; + accessibility: AccessibilitySettings; +} + +export interface AccessibilitySettings { + disablePingPulse: boolean; + replacePingDotsWithIcons: boolean; } export interface GridstackSettingsType {