Commit f6042890 authored by Robert Knight's avatar Robert Knight

Typecheck UI components in src/annotator/components

This follows the same pattern that was introduced for `SvgIcon` in
`propTypes` declarations are kept around for use at runtime by other
code which is not yet typechecked.

An issue that I encountered is that TS doesn't know about the custom
HTML element types we use. For the moment I have ignored these with
`@ts-ignore`, although I believe it should be possible to teach it.
parent d5b8d349
......@@ -5,6 +5,14 @@ import propTypes from 'prop-types';
import { useShortcut } from '../../shared/shortcut';
import SvgIcon from '../../shared/components/svg-icon';
/**
* @param {Object} props
* @param {number} [props.badgeCount]
* @param {string} [props.icon]
* @param {string} props.label
* @param {() => any} props.onClick
* @param {string|null} props.shortcut
*/
function ToolbarButton({ badgeCount, icon, label, onClick, shortcut }) {
useShortcut(shortcut, onClick);
......@@ -36,9 +44,31 @@ ToolbarButton.propTypes = {
shortcut: propTypes.string,
};
/**
* Union of possible toolbar commands.
*
* @typedef {'annotate'|'highlight'|'show'} Command
*/
/**
* @typedef AdderToolbarProps
* @prop {'up'|'down'} arrowDirection -
* Whether the arrow pointing out of the toolbar towards the selected text
* should appear above the toolbar pointing Up or below the toolbar pointing
* Down.
* @prop {boolean} isVisible - Whether to show the toolbar or not.
* @prop {(c: Command) => any} onCommand - Called when a toolbar button is clicked.
* @prop {number} [annotationCount] -
* Number of annotations associated with the selected text.
* If non-zero, a "Show" button is displayed to allow the user to see the
* annotations that correspond to the selection.
*/
/**
* The toolbar that is displayed above selected text in the document providing
* options to create annotations or highlights.
*
* @param {AdderToolbarProps} props
*/
export default function AdderToolbar({
arrowDirection,
......@@ -63,6 +93,7 @@ export default function AdderToolbar({
// nb. The adder is hidden using the `visibility` property rather than `display`
// so that we can compute its size in order to position it before display.
return (
// @ts-ignore - TS doesn't know about our custom element types.
<hypothesis-adder-toolbar
class={classnames('annotator-adder', {
'annotator-adder--arrow-down': arrowDirection === 'down',
......@@ -71,6 +102,7 @@ export default function AdderToolbar({
})}
style={{ visibility: isVisible ? 'visible' : 'hidden' }}
>
{/* @ts-ignore */}
<hypothesis-adder-actions className="annotator-adder-actions">
<ToolbarButton
icon="annotate"
......@@ -95,35 +127,16 @@ export default function AdderToolbar({
shortcut={showShortcut}
/>
)}
{/* @ts-ignore */}
</hypothesis-adder-actions>
{/* @ts-ignore */}
</hypothesis-adder-toolbar>
);
}
AdderToolbar.propTypes = {
/**
* Whether the arrow pointing out of the toolbar towards the selected text
* should appear above the toolbar pointing Up or below the toolbar pointing
* Down.
*/
arrowDirection: propTypes.oneOf(['up', 'down']).isRequired,
/**
* Whether to show the toolbar or not.
*/
isVisible: propTypes.bool.isRequired,
/**
* Callback invoked with the name ("annotate", "highlight", "show") of the
* selected command when a toolbar command is clicked.
*/
onCommand: propTypes.func.isRequired,
/**
* Number of annotations associated with the selected text.
*
* If non-zero, a "Show" button is displayed to allow the user to see the
* annotations that correspond to the selection.
*/
annotationCount: propTypes.number,
};
......@@ -4,6 +4,16 @@ import { createElement } from 'preact';
import SvgIcon from '../../shared/components/svg-icon';
/**
* @param {Object} props
* @param {import("preact/hooks").Ref<HTMLButtonElement>} [props.buttonRef]
* @param {boolean} [props.expanded]
* @param {string} [props.extraClasses]
* @param {string} props.label
* @param {string} props.icon
* @param {() => any} props.onClick
* @param {boolean} [props.selected]
*/
function ToolbarButton({
buttonRef,
expanded,
......@@ -11,7 +21,7 @@ function ToolbarButton({
label,
icon,
onClick,
selected,
selected = false,
}) {
const handleClick = event => {
// Stop event from propagating up to the document and being treated as a
......@@ -45,9 +55,39 @@ ToolbarButton.propTypes = {
selected: propTypes.bool,
};
/**
* @typedef ToolbarProps
*
* @prop {() => any} closeSidebar -
* Callback for the "Close sidebar" button. This button is only shown when
* `useMinimalControls` is true and the sidebar is open.
* @prop {() => any} createAnnotation -
* Callback for the "Create annotation" / "Create page note" button. The type
* of annotation depends on whether there is a text selection and is decided
* by the caller.
* @prop {boolean} isSidebarOpen - Is the sidebar currently visible?
* @prop {'annotation'|'note'} newAnnotationType -
* Icon to show on the "Create annotation" button indicating what kind of annotation
* will be created.
* @prop {boolean} showHighlights - Are highlights currently visible in the document?
* @prop {() => any} toggleHighlights -
* Callback to toggle visibility of highlights in the document.
* @prop {() => any} toggleSidebar -
* Callback to toggle the visibility of the sidebar.
* @prop {import("preact/hooks").Ref<HTMLButtonElement>} [toggleSidebarRef] -
* Ref that gets set to the toolbar button for toggling the sidebar.
* This is exposed to enable the drag-to-resize functionality of this
* button.
* @prop {boolean} [useMinimalControls] -
* If true, all controls are hidden except for the "Close sidebar" button
* when the sidebar is open.
*/
/**
* Controls on the edge of the sidebar for opening/closing the sidebar,
* controlling highlight visibility and creating new page notes.
*
* @param {ToolbarProps} props
*/
export default function Toolbar({
closeSidebar,
......@@ -102,43 +142,13 @@ export default function Toolbar({
}
Toolbar.propTypes = {
/**
* Callback for the "Close sidebar" button. This button is only shown when
* `useMinimalControls` is true and the sidebar is open.
*/
closeSidebar: propTypes.func.isRequired,
/**
* Callback for the "Create annotation" / "Create page note" button. The type
* of annotation depends on whether there is a text selection and is decided
* by the caller.
*/
createAnnotation: propTypes.func.isRequired,
/** Is the sidebar currently visible? */
isSidebarOpen: propTypes.bool.isRequired,
newAnnotationType: propTypes.oneOf(['annotation', 'note']).isRequired,
/** Are highlights currently visible in the document? */
showHighlights: propTypes.bool.isRequired,
/** Callback to toggle visibility of highlights in the document. */
toggleHighlights: propTypes.func.isRequired,
/** Callback to toggle the visibility of the sidebar. */
toggleSidebar: propTypes.func.isRequired,
/**
* Ref that gets set to the toolbar button for toggling the sidebar.
* This is exposed to enable the drag-to-resize functionality of this
* button.
*/
toggleSidebarRef: propTypes.any,
/**
* If true, all controls are hidden except for the "Close sidebar" button
* when the sidebar is open.
*/
useMinimalControls: propTypes.bool,
};
......@@ -12,6 +12,7 @@
},
"include": [
"annotator/config/*.js",
"annotator/components/*.js",
"annotator/util/*.js",
"boot/*.js",
"shared/*.js",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment