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([]); // 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 = 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 ( { 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} > ); } function renderDivider(key: string) { return ( {_('Plugins')} ); } 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 ( {buttons} ); }