mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-01 11:12:42 +02:00
fix: keyboard accessibility for example site (#5946)
* fix: keyboard accessibility for example site * chore: remove redundant assignment of DOM attribute Co-authored-by: Joe Anderson <joe@anderbell.studio> * refactor: replace MouseEvent with PointerEvent for improved button interactions --------- Co-authored-by: Joe Anderson <joe@anderbell.studio>
This commit is contained in:
@@ -10,7 +10,7 @@ import 'prismjs/components/prism-python'
|
|||||||
import 'prismjs/components/prism-sql'
|
import 'prismjs/components/prism-sql'
|
||||||
import 'prismjs/components/prism-tsx'
|
import 'prismjs/components/prism-tsx'
|
||||||
import 'prismjs/components/prism-typescript'
|
import 'prismjs/components/prism-typescript'
|
||||||
import React, { ChangeEvent, MouseEvent, useCallback, useState } from 'react'
|
import React, { ChangeEvent, PointerEvent, useCallback, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Editor,
|
Editor,
|
||||||
Element,
|
Element,
|
||||||
@@ -143,10 +143,10 @@ const CodeBlockButton = () => {
|
|||||||
<Button
|
<Button
|
||||||
data-test-id="code-block-button"
|
data-test-id="code-block-button"
|
||||||
active
|
active
|
||||||
onMouseDown={(event: MouseEvent<HTMLButtonElement>) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
handleClick()
|
|
||||||
}}
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Icon>code</Icon>
|
<Icon>code</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -20,14 +20,16 @@ export const Button = React.forwardRef(
|
|||||||
reversed: boolean
|
reversed: boolean
|
||||||
} & BaseProps
|
} & BaseProps
|
||||||
>,
|
>,
|
||||||
ref: Ref<HTMLSpanElement>
|
ref: Ref<HTMLButtonElement>
|
||||||
) => (
|
) => (
|
||||||
<span
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cx(
|
className={cx(
|
||||||
className,
|
|
||||||
css`
|
css`
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${reversed
|
color: ${reversed
|
||||||
? active
|
? active
|
||||||
@@ -36,7 +38,8 @@ export const Button = React.forwardRef(
|
|||||||
: active
|
: active
|
||||||
? 'black'
|
? 'black'
|
||||||
: '#ccc'};
|
: '#ccc'};
|
||||||
`
|
`,
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { css } from '@emotion/css'
|
import { css } from '@emotion/css'
|
||||||
import React, { MouseEvent, useMemo, useState } from 'react'
|
import React, { PointerEvent, useMemo, useState } from 'react'
|
||||||
import { createEditor, Descendant, Transforms } from 'slate'
|
import { createEditor, Descendant, Transforms } from 'slate'
|
||||||
import { withHistory } from 'slate-history'
|
import { withHistory } from 'slate-history'
|
||||||
import {
|
import {
|
||||||
@@ -130,10 +130,10 @@ const InsertEditableVoidButton = () => {
|
|||||||
const editor = useSlateStatic()
|
const editor = useSlateStatic()
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
insertEditableVoid(editor)
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() => insertEditableVoid(editor)}
|
||||||
>
|
>
|
||||||
<Icon>add</Icon>
|
<Icon>add</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import isHotkey from 'is-hotkey'
|
import isHotkey from 'is-hotkey'
|
||||||
import React, { MouseEvent, useCallback, useMemo, useState } from 'react'
|
import React, { PointerEvent, useCallback, useMemo, useState } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Editor, createEditor, Descendant } from 'slate'
|
import { Editor, createEditor, Descendant } from 'slate'
|
||||||
import { withHistory } from 'slate-history'
|
import { withHistory } from 'slate-history'
|
||||||
@@ -115,10 +115,10 @@ const MarkButton = ({ format, icon }: MarkButtonProps) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
active={isMarkActive(editor, format)}
|
active={isMarkActive(editor, format)}
|
||||||
onMouseDown={(event: MouseEvent) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
toggleMark(editor, format)
|
|
||||||
}}
|
}}
|
||||||
|
onClick={() => toggleMark(editor, format)}
|
||||||
>
|
>
|
||||||
<Icon>{icon}</Icon>
|
<Icon>{icon}</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -2,7 +2,7 @@ import { css } from '@emotion/css'
|
|||||||
import imageExtensions from 'image-extensions'
|
import imageExtensions from 'image-extensions'
|
||||||
import isHotkey from 'is-hotkey'
|
import isHotkey from 'is-hotkey'
|
||||||
import isUrl from 'is-url'
|
import isUrl from 'is-url'
|
||||||
import React, { MouseEvent, useMemo } from 'react'
|
import React, { PointerEvent, useMemo } from 'react'
|
||||||
import { Descendant, Transforms, createEditor } from 'slate'
|
import { Descendant, Transforms, createEditor } from 'slate'
|
||||||
import { withHistory } from 'slate-history'
|
import { withHistory } from 'slate-history'
|
||||||
import {
|
import {
|
||||||
@@ -135,6 +135,9 @@ const Image = ({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
active
|
active
|
||||||
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
|
||||||
|
event.preventDefault()
|
||||||
|
}}
|
||||||
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
onClick={() => Transforms.removeNodes(editor, { at: path })}
|
||||||
className={css`
|
className={css`
|
||||||
display: ${selected && focused ? 'inline' : 'none'};
|
display: ${selected && focused ? 'inline' : 'none'};
|
||||||
@@ -155,8 +158,10 @@ const InsertImageButton = () => {
|
|||||||
const editor = useSlateStatic()
|
const editor = useSlateStatic()
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onMouseDown={(event: MouseEvent) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
const url = window.prompt('Enter the URL of the image:')
|
const url = window.prompt('Enter the URL of the image:')
|
||||||
if (url && !isImageUrl(url)) {
|
if (url && !isImageUrl(url)) {
|
||||||
alert('URL is not an image')
|
alert('URL is not an image')
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { css } from '@emotion/css'
|
import { css } from '@emotion/css'
|
||||||
import { isKeyHotkey } from 'is-hotkey'
|
import { isKeyHotkey } from 'is-hotkey'
|
||||||
import isUrl from 'is-url'
|
import isUrl from 'is-url'
|
||||||
import React, { MouseEvent, useMemo } from 'react'
|
import React, { PointerEvent, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
createEditor,
|
createEditor,
|
||||||
Descendant,
|
Descendant,
|
||||||
@@ -400,8 +400,10 @@ const AddLinkButton = () => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
active={isLinkActive(editor)}
|
active={isLinkActive(editor)}
|
||||||
onMouseDown={(event: MouseEvent) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
const url = window.prompt('Enter the URL of the link:')
|
const url = window.prompt('Enter the URL of the link:')
|
||||||
if (!url) return
|
if (!url) return
|
||||||
insertLink(editor, url)
|
insertLink(editor, url)
|
||||||
@@ -418,7 +420,10 @@ const RemoveLinkButton = () => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
active={isLinkActive(editor)}
|
active={isLinkActive(editor)}
|
||||||
onMouseDown={(event: MouseEvent) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
if (isLinkActive(editor)) {
|
if (isLinkActive(editor)) {
|
||||||
unwrapLink(editor)
|
unwrapLink(editor)
|
||||||
}
|
}
|
||||||
@@ -434,8 +439,10 @@ const ToggleEditableButtonButton = () => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
active
|
active
|
||||||
onMouseDown={(event: MouseEvent) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
if (isButtonActive(editor)) {
|
if (isButtonActive(editor)) {
|
||||||
unwrapButton(editor)
|
unwrapButton(editor)
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import isHotkey from 'is-hotkey'
|
import isHotkey from 'is-hotkey'
|
||||||
import React, { KeyboardEvent, MouseEvent, useCallback, useMemo } from 'react'
|
import React, { KeyboardEvent, PointerEvent, useCallback, useMemo } from 'react'
|
||||||
import {
|
import {
|
||||||
Descendant,
|
Descendant,
|
||||||
Editor,
|
Editor,
|
||||||
@@ -247,10 +247,10 @@ const BlockButton = ({ format, icon }: BlockButtonProps) => {
|
|||||||
format,
|
format,
|
||||||
isAlignType(format) ? 'align' : 'type'
|
isAlignType(format) ? 'align' : 'type'
|
||||||
)}
|
)}
|
||||||
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
toggleBlock(editor, format)
|
}
|
||||||
}}
|
onClick={() => toggleBlock(editor, format)}
|
||||||
>
|
>
|
||||||
<Icon>{icon}</Icon>
|
<Icon>{icon}</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -267,10 +267,10 @@ const MarkButton = ({ format, icon }: MarkButtonProps) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
active={isMarkActive(editor, format)}
|
active={isMarkActive(editor, format)}
|
||||||
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
|
onPointerDown={(event: PointerEvent<HTMLButtonElement>) =>
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
toggleMark(editor, format)
|
}
|
||||||
}}
|
onClick={() => toggleMark(editor, format)}
|
||||||
>
|
>
|
||||||
<Icon>{icon}</Icon>
|
<Icon>{icon}</Icon>
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -135,6 +135,9 @@ const TabList = ({
|
|||||||
...props
|
...props
|
||||||
}: React.HTMLAttributes<HTMLDivElement> & { isVisible?: boolean }) => (
|
}: React.HTMLAttributes<HTMLDivElement> & { isVisible?: boolean }) => (
|
||||||
<div
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-label="Examples navigation"
|
||||||
|
aria-hidden={!isVisible}
|
||||||
{...props}
|
{...props}
|
||||||
className={css`
|
className={css`
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
@@ -143,11 +146,14 @@ const TabList = ({
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding-top: 0.2em;
|
padding-top: 0.2em;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transition: width 0.2s;
|
transition:
|
||||||
|
width 0.2s,
|
||||||
|
visibility 0.2s;
|
||||||
width: ${isVisible ? '200px' : '0'};
|
width: ${isVisible ? '200px' : '0'};
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
z-index: 3; /* To appear above the underlay */
|
z-index: 3; /* To appear above the underlay */
|
||||||
|
visibility: ${isVisible ? 'visible' : 'hidden'};
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -171,10 +177,16 @@ const TabListUnderlay = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const TabButton = (props: React.HTMLAttributes<HTMLSpanElement>) => (
|
const TabButton = (props: React.HTMLAttributes<HTMLSpanElement>) => (
|
||||||
<span
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
|
aria-label="Toggle examples menu"
|
||||||
|
aria-haspopup="menu"
|
||||||
className={css`
|
className={css`
|
||||||
margin-left: 0.8em;
|
margin-left: 0.8em;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -204,6 +216,8 @@ const Tab = React.forwardRef(
|
|||||||
<a
|
<a
|
||||||
ref={ref}
|
ref={ref}
|
||||||
href={href}
|
href={href}
|
||||||
|
role="menuitem"
|
||||||
|
aria-current={active ? 'page' : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
className={css`
|
className={css`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -342,6 +356,12 @@ const ExamplePage = ({ example }: { example: string }) => {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
setShowTabs(!showTabs)
|
setShowTabs(!showTabs)
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setShowTabs(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-expanded={showTabs}
|
||||||
>
|
>
|
||||||
<Icon>menu</Icon>
|
<Icon>menu</Icon>
|
||||||
</TabButton>
|
</TabButton>
|
||||||
@@ -368,7 +388,17 @@ const ExamplePage = ({ example }: { example: string }) => {
|
|||||||
legacyBehavior
|
legacyBehavior
|
||||||
passHref
|
passHref
|
||||||
>
|
>
|
||||||
<Tab onClick={() => setShowTabs(false)}>{n}</Tab>
|
<Tab
|
||||||
|
onClick={() => setShowTabs(false)}
|
||||||
|
active={p === path}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setShowTabs(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{n}
|
||||||
|
</Tab>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
@@ -393,6 +423,11 @@ const ExamplePage = ({ example }: { example: string }) => {
|
|||||||
<TabListUnderlay
|
<TabListUnderlay
|
||||||
isVisible={showTabs}
|
isVisible={showTabs}
|
||||||
onClick={() => setShowTabs(false)}
|
onClick={() => setShowTabs(false)}
|
||||||
|
onKeyDown={(e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setShowTabs(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
Reference in New Issue
Block a user