2024-08-02 15:49:15 +02:00
|
|
|
import { AppType, MetadataBySection, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting';
|
2020-09-15 15:01:07 +02:00
|
|
|
import * as React from 'react';
|
2021-01-22 00:40:14 +02:00
|
|
|
import Setting from '@joplin/lib/models/Setting';
|
|
|
|
import { _ } from '@joplin/lib/locale';
|
2024-08-02 15:49:15 +02:00
|
|
|
import { useCallback, useRef } from 'react';
|
|
|
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
2020-09-15 15:01:07 +02:00
|
|
|
const styled = require('styled-components').default;
|
|
|
|
|
2024-04-05 13:16:49 +02:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied;
|
|
|
|
type StyleProps = any;
|
|
|
|
|
2024-08-02 15:49:15 +02:00
|
|
|
interface SectionChangeEvent {
|
|
|
|
section: SettingMetadataSection;
|
|
|
|
}
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
interface Props {
|
2020-11-12 21:29:22 +02:00
|
|
|
selection: string;
|
2024-08-02 15:49:15 +02:00
|
|
|
onSelectionChange: (event: SectionChangeEvent)=> void;
|
|
|
|
sections: MetadataBySection;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export const StyledRoot = styled.div`
|
|
|
|
display: flex;
|
2024-04-05 13:16:49 +02:00
|
|
|
background-color: ${(props: StyleProps) => props.theme.backgroundColor2};
|
2020-09-15 15:01:07 +02:00
|
|
|
flex-direction: column;
|
2021-02-07 14:55:28 +02:00
|
|
|
overflow-x: hidden;
|
|
|
|
overflow-y: auto;
|
2020-09-15 15:01:07 +02:00
|
|
|
`;
|
|
|
|
|
|
|
|
export const StyledListItem = styled.a`
|
|
|
|
box-sizing: border-box;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
2024-04-05 13:16:49 +02:00
|
|
|
padding: ${(props: StyleProps) => props.theme.mainPadding}px;
|
|
|
|
background: ${(props: StyleProps) => props.selected ? props.theme.selectedColor2 : 'none'};
|
2020-09-15 15:01:07 +02:00
|
|
|
transition: 0.1s;
|
|
|
|
text-decoration: none;
|
|
|
|
cursor: default;
|
2024-04-05 13:16:49 +02:00
|
|
|
opacity: ${(props: StyleProps) => props.selected ? 1 : 0.8};
|
|
|
|
padding-left: ${(props: StyleProps) => props.isSubSection ? '35' : props.theme.mainPadding}px;
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
&:hover {
|
2024-04-05 13:16:49 +02:00
|
|
|
background-color: ${(props: StyleProps) => props.theme.backgroundColorHover2};
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2021-01-22 00:40:14 +02:00
|
|
|
export const StyledDivider = styled.div`
|
|
|
|
box-sizing: border-box;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: row;
|
2024-04-05 13:16:49 +02:00
|
|
|
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;
|
2024-11-09 14:50:06 +02:00
|
|
|
opacity: 0.58;
|
2021-01-22 00:40:14 +02:00
|
|
|
`;
|
|
|
|
|
2020-09-15 15:01:07 +02:00
|
|
|
export const StyledListItemLabel = styled.span`
|
2024-04-05 13:16:49 +02:00
|
|
|
font-size: ${(props: StyleProps) => Math.round(props.theme.fontSize * 1.2)}px;
|
2020-09-15 15:01:07 +02:00
|
|
|
font-weight: 500;
|
2024-04-05 13:16:49 +02:00
|
|
|
color: ${(props: StyleProps) => props.theme.color2};
|
2020-09-15 15:01:07 +02:00
|
|
|
white-space: nowrap;
|
|
|
|
display: flex;
|
|
|
|
flex: 1;
|
|
|
|
align-items: center;
|
|
|
|
user-select: none;
|
|
|
|
`;
|
|
|
|
|
|
|
|
export const StyledListItemIcon = styled.i`
|
2024-04-05 13:16:49 +02:00
|
|
|
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;
|
2020-09-15 15:01:07 +02:00
|
|
|
`;
|
|
|
|
|
2021-01-12 14:28:55 +02:00
|
|
|
export default function Sidebar(props: Props) {
|
2024-08-02 15:49:15 +02:00
|
|
|
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;
|
2020-09-15 15:01:07 +02:00
|
|
|
|
2024-08-02 15:49:15 +02:00
|
|
|
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) {
|
2020-09-15 15:01:07 +02:00
|
|
|
const selected = props.selection === section.name;
|
|
|
|
return (
|
2023-11-10 16:00:59 +02:00
|
|
|
<StyledListItem
|
|
|
|
key={section.name}
|
|
|
|
href='#'
|
2023-11-12 17:01:14 +02:00
|
|
|
role='tab'
|
2024-08-02 15:49:15 +02:00
|
|
|
ref={(item: HTMLElement) => { buttonRefs.current[index] = item; }}
|
|
|
|
|
|
|
|
id={`setting-tab-${section.name}`}
|
|
|
|
aria-controls={`setting-section-${section.name}`}
|
2023-11-10 16:00:59 +02:00
|
|
|
aria-selected={selected}
|
2024-08-02 15:49:15 +02:00
|
|
|
tabIndex={selected ? 0 : -1}
|
|
|
|
|
2023-11-10 16:00:59 +02:00
|
|
|
isSubSection={Setting.isSubSection(section.name)}
|
|
|
|
selected={selected}
|
|
|
|
onClick={() => { props.onSelectionChange({ section: section }); }}
|
2024-08-02 15:49:15 +02:00
|
|
|
onKeyDown={onKeyDown}
|
2023-11-10 16:00:59 +02:00
|
|
|
>
|
2023-11-09 21:19:08 +02:00
|
|
|
<StyledListItemIcon
|
|
|
|
className={Setting.sectionNameToIcon(section.name, AppType.Desktop)}
|
2024-08-02 15:49:15 +02:00
|
|
|
role='img'
|
2024-11-09 14:50:06 +02:00
|
|
|
aria-hidden='true'
|
2023-11-09 21:19:08 +02:00
|
|
|
/>
|
2020-09-15 15:01:07 +02:00
|
|
|
<StyledListItemLabel>
|
|
|
|
{Setting.sectionNameToLabel(section.name)}
|
|
|
|
</StyledListItemLabel>
|
|
|
|
</StyledListItem>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-01-22 00:40:14 +02:00
|
|
|
function renderDivider(key: string) {
|
|
|
|
return (
|
|
|
|
<StyledDivider key={key}>
|
|
|
|
{_('Plugins')}
|
|
|
|
</StyledDivider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let pluginDividerAdded = false;
|
|
|
|
|
2024-08-02 15:49:15 +02:00
|
|
|
let index = 0;
|
2024-03-09 13:03:57 +02:00
|
|
|
for (const section of props.sections) {
|
2021-01-22 00:40:14 +02:00
|
|
|
if (section.source === SettingSectionSource.Plugin && !pluginDividerAdded) {
|
|
|
|
buttons.push(renderDivider('divider-plugins'));
|
|
|
|
pluginDividerAdded = true;
|
|
|
|
}
|
|
|
|
|
2024-08-02 15:49:15 +02:00
|
|
|
buttons.push(renderButton(section, index));
|
|
|
|
index ++;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-11-12 17:01:14 +02:00
|
|
|
<StyledRoot role='tablist'>
|
2020-09-15 15:01:07 +02:00
|
|
|
{buttons}
|
|
|
|
</StyledRoot>
|
|
|
|
);
|
|
|
|
}
|