1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx
Henry Heino a616dc3cd2
Desktop: Fix errors found by automated accessibility testing (#11246)
Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
2024-11-09 12:50:06 +00:00

172 lines
5.2 KiB
TypeScript

import { AppType, MetadataBySection, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting';
import * as React from 'react';
import Setting from '@joplin/lib/models/Setting';
import { _ } from '@joplin/lib/locale';
import { useCallback, useRef } from 'react';
import { focus } from '@joplin/lib/utils/focusHandler';
const styled = require('styled-components').default;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
type StyleProps = any;
interface SectionChangeEvent {
section: SettingMetadataSection;
}
interface Props {
selection: string;
onSelectionChange: (event: SectionChangeEvent)=> void;
sections: MetadataBySection;
}
export const StyledRoot = styled.div`
display: flex;
background-color: ${(props: StyleProps) => props.theme.backgroundColor2};
flex-direction: column;
overflow-x: hidden;
overflow-y: auto;
`;
export const StyledListItem = styled.a`
box-sizing: border-box;
display: flex;
flex-direction: row;
padding: ${(props: StyleProps) => props.theme.mainPadding}px;
background: ${(props: StyleProps) => props.selected ? props.theme.selectedColor2 : 'none'};
transition: 0.1s;
text-decoration: none;
cursor: default;
opacity: ${(props: StyleProps) => props.selected ? 1 : 0.8};
padding-left: ${(props: StyleProps) => props.isSubSection ? '35' : props.theme.mainPadding}px;
&:hover {
background-color: ${(props: StyleProps) => props.theme.backgroundColorHover2};
}
`;
export const StyledDivider = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: row;
color: ${(props: StyleProps) => props.theme.color2};
padding: ${(props: StyleProps) => props.theme.mainPadding}px;
padding-top: ${(props: StyleProps) => props.theme.mainPadding * .8}px;
padding-bottom: ${(props: StyleProps) => props.theme.mainPadding * .8}px;
border-top: 1px solid ${(props: StyleProps) => props.theme.dividerColor};
border-bottom: 1px solid ${(props: StyleProps) => props.theme.dividerColor};
background-color: ${(props: StyleProps) => props.theme.selectedColor2};
font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize)}px;
opacity: 0.58;
`;
export const StyledListItemLabel = styled.span`
font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize * 1.2)}px;
font-weight: 500;
color: ${(props: StyleProps) => props.theme.color2};
white-space: nowrap;
display: flex;
flex: 1;
align-items: center;
user-select: none;
`;
export const StyledListItemIcon = styled.i`
font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize * 1.4)}px;
color: ${(props: StyleProps) => props.theme.color2};
margin-right: ${(props: StyleProps) => props.theme.mainPadding / 1.5}px;
`;
export default function Sidebar(props: Props) {
const buttonRefs = useRef<HTMLElement[]>([]);
// Making a tabbed region accessible involves supporting keyboard interaction.
// See https://www.w3.org/WAI/ARIA/apg/patterns/tabs/ for details
const onKeyDown: React.KeyboardEventHandler<HTMLElement> = useCallback((event) => {
const selectedIndex = props.sections.findIndex(section => section.name === props.selection);
let newIndex = selectedIndex;
if (event.code === 'ArrowUp') {
newIndex --;
} else if (event.code === 'ArrowDown') {
newIndex ++;
} else if (event.code === 'Home') {
newIndex = 0;
} else if (event.code === 'End') {
newIndex = props.sections.length - 1;
}
if (newIndex < 0) newIndex += props.sections.length;
newIndex %= props.sections.length;
if (newIndex !== selectedIndex) {
event.preventDefault();
props.onSelectionChange({ section: props.sections[newIndex] });
const targetButton = buttonRefs.current[newIndex];
if (targetButton) {
focus('Sidebar', targetButton);
}
}
}, [props.sections, props.selection, props.onSelectionChange]);
const buttons: React.ReactNode[] = [];
function renderButton(section: SettingMetadataSection, index: number) {
const selected = props.selection === section.name;
return (
<StyledListItem
key={section.name}
href='#'
role='tab'
ref={(item: HTMLElement) => { buttonRefs.current[index] = item; }}
id={`setting-tab-${section.name}`}
aria-controls={`setting-section-${section.name}`}
aria-selected={selected}
tabIndex={selected ? 0 : -1}
isSubSection={Setting.isSubSection(section.name)}
selected={selected}
onClick={() => { props.onSelectionChange({ section: section }); }}
onKeyDown={onKeyDown}
>
<StyledListItemIcon
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
role='img'
aria-hidden='true'
/>
<StyledListItemLabel>
{Setting.sectionNameToLabel(section.name)}
</StyledListItemLabel>
</StyledListItem>
);
}
function renderDivider(key: string) {
return (
<StyledDivider key={key}>
{_('Plugins')}
</StyledDivider>
);
}
let pluginDividerAdded = false;
let index = 0;
for (const section of props.sections) {
if (section.source === SettingSectionSource.Plugin && !pluginDividerAdded) {
buttons.push(renderDivider('divider-plugins'));
pluginDividerAdded = true;
}
buttons.push(renderButton(section, index));
index ++;
}
return (
<StyledRoot role='tablist'>
{buttons}
</StyledRoot>
);
}