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 {