editor: improve a18y and visual appearance

This commit is contained in:
David Lechner
2022-05-12 16:14:48 -05:00
parent edd8ebfd64
commit fa0a9a109c
3 changed files with 105 additions and 21 deletions
+28 -2
View File
@@ -13,7 +13,7 @@ import { editorActivateFile, editorCloseFile } from './actions';
describe('Editor', () => {
describe('tabs', () => {
it('should dispatch action when tab is clicked', async () => {
it('should dispatch activate action when tab is clicked', async () => {
const [editor, dispatch] = testRender(<Editor />, {
editor: { openFiles: ['test.file'] },
});
@@ -23,7 +23,23 @@ describe('Editor', () => {
expect(dispatch).toHaveBeenCalledWith(editorActivateFile('test.file'));
});
it('should dispatch action when close button is clicked', async () => {
it.each(['enter', 'space'])(
'should dispatch activate action when % button is pressed',
async (button) => {
const [editor, dispatch] = testRender(<Editor />, {
editor: { openFiles: ['test.file'] },
});
userEvent.type(
editor.getByRole('tab', { name: 'test.file' }),
`{${button}}`,
);
expect(dispatch).toHaveBeenCalledWith(editorActivateFile('test.file'));
},
);
it('should dispatch close action when close button is clicked', async () => {
const [editor, dispatch] = testRender(<Editor />, {
editor: { openFiles: ['test.file'] },
});
@@ -32,6 +48,16 @@ describe('Editor', () => {
expect(dispatch).toHaveBeenCalledWith(editorCloseFile('test.file'));
});
it('should dispatch close action when delete button is pressed', async () => {
const [editor, dispatch] = testRender(<Editor />, {
editor: { openFiles: ['test.file'] },
});
userEvent.type(editor.getByRole('tab', { name: 'test.file' }), '{delete}');
expect(dispatch).toHaveBeenCalledWith(editorCloseFile('test.file'));
});
});
describe('context menu', () => {
+25 -4
View File
@@ -13,6 +13,7 @@ import {
Tab,
TabId,
Tabs,
Text,
} from '@blueprintjs/core';
import { ContextMenu2, ResizeSensor2 } from '@blueprintjs/popover2';
import { I18n, useI18n } from '@shopify/react-i18n';
@@ -202,25 +203,44 @@ const EditorTabs: React.VoidFunctionComponent<EditorTabsProps> = ({
const labelId = useUniqueId('pb-editor');
// close tab when delete key is pressed
const handleKeyDown = useCallback(
(e: React.KeyboardEvent, fileName: string) => {
if (e.key === 'Delete') {
dispatch(editorCloseFile(fileName));
e.preventDefault();
e.stopPropagation();
}
},
[dispatch],
);
return (
<Tabs
className="pb-editor-tabs"
className="pb-editor-tablist"
selectedTabId={activeFile}
onChange={handleChange}
>
{openFiles.map((fileName, i) => (
<Tab
className="pb-editor-tab"
className="pb-editor-tablist-tab"
aria-labelledby={`${labelId}.${i}`}
key={i}
id={fileName}
onKeyDown={(e) => handleKeyDown(e, fileName)}
>
<span id={`${labelId}.${i}`}>{fileName}</span>
<Text tagName="span" id={`${labelId}.${i}`} ellipsize={true}>
{fileName}
</Text>
<Button
title={i18n.translate(I18nId.CloseFileTooltip, { fileName })}
minimal={true}
small={true}
icon={'cross'}
// tabs are closed with delete button by keyboard, so
// don't focus the close button
tabIndex={-1}
onFocus={(e) => e.preventDefault()}
onClick={(e) => {
dispatch(editorCloseFile(fileName));
// prevent triggering Tabs onChange
@@ -414,7 +434,8 @@ const Editor: React.VFC = () => {
<EditorTabs onChange={() => editor?.focus()} i18n={i18n} />
<ResizeSensor2 onResize={() => editor?.layout()}>
<ContextMenu2
className="h-100"
className="pb-editor-tabpanel"
role="tabpanel"
// NB: we have to create a new context menu each time it is
// shown in order to get some state, like canUndo and canRedo
// that don't have events to monitor changes.
+52 -15
View File
@@ -4,6 +4,7 @@
// Custom styling for the Editor control.
@use '@blueprintjs/core/lib/scss/variables' as bp;
@use '../variables.scss' as pb;
// add "BETA" watermark
@@ -19,22 +20,58 @@
pointer-events: none;
}
.#{bp.$ns}-dark .pb-editor-placeholder {
color: bp.$pt-dark-text-color-muted;
}
.pb-editor {
display: flex;
flex-direction: column;
justify-content: flex-start;
.pb-editor-tab {
padding: 3px;
}
&-tablist {
padding: bp.$pt-grid-size * 0.3;
overflow-x: auto;
@include pb.background-contrast(6%);
.pb-editor-tabs {
padding: 3px 6px;
}
.bp4-tab-list > * {
padding-left: bp.$pt-grid-size * 1.5;
.pb-editor-placeholder {
pointer-events: none;
width: max-content;
color: bp.$pt-text-color-muted;
font-style: italic;
padding-left: 4px;
&:not(:last-child) {
margin: unset;
}
}
&-tab {
padding-right: bp.$pt-grid-size * 0.5;
border-right: 1px solid bp.$pt-divider-black;
max-width: bp.$pt-grid-size * 15;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
.#{bp.$ns}-dark & {
border-right-color: bp.$pt-dark-divider-white;
}
& .#{bp.$ns}-text-overflow-ellipsis {
min-width: 0;
flex: 1 1 auto;
}
}
}
&-tabpanel {
min-height: 0;
flex: 1 1 auto;
}
&-placeholder {
pointer-events: none;
width: max-content;
color: bp.$pt-text-color-muted;
font-style: italic;
padding-left: 4px;
.#{bp.$ns}-dark & {
color: bp.$pt-dark-text-color-muted;
}
}
}