mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-13 18:14:09 +00:00
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2022 The Pybricks Authors
|
|
|
|
import { Classes, Icon, IconName, IconSize, Spinner } from '@blueprintjs/core';
|
|
import { mergeRefs, useId } from '@react-aria/utils';
|
|
import classNames from 'classnames';
|
|
import React, { useRef } from 'react';
|
|
import { FocusRing, useButton } from 'react-aria';
|
|
|
|
type ButtonProps = {
|
|
/** Optional DOM ID for the button. */
|
|
id?: string;
|
|
/** The label for the button. */
|
|
label: string;
|
|
/** If true, the label will not be visible (will use aria-label instead). */
|
|
hideLabel?: boolean;
|
|
/** A description of what the button does (not displayed - read by screen reader). */
|
|
description?: string;
|
|
/** When true, the contents of the button will be replaced with a {@link Spinner}. */
|
|
loading?: boolean;
|
|
/** When true, the button will use the {@link Classes.MINIMAL} style. */
|
|
minimal?: boolean;
|
|
/** Icon that will be displayed to the left of the button content. */
|
|
icon: IconName;
|
|
/** A reference to the underlying <button> HTML element. */
|
|
elementRef?: React.ForwardedRef<HTMLButtonElement>;
|
|
/** Called when the button is pressed. */
|
|
onPress?: () => void;
|
|
};
|
|
|
|
/** Similar to Blueprint.js button with better accessibility. */
|
|
export const Button: React.VoidFunctionComponent<ButtonProps> = ({
|
|
id,
|
|
label,
|
|
hideLabel,
|
|
description,
|
|
loading,
|
|
minimal,
|
|
icon,
|
|
elementRef,
|
|
onPress,
|
|
}) => {
|
|
const isLabelVisible = !hideLabel && !loading;
|
|
|
|
const labelId = useId();
|
|
const descriptionId = useId();
|
|
const ref = useRef<HTMLButtonElement>(null);
|
|
|
|
const { buttonProps, isPressed } = useButton(
|
|
{
|
|
onPress,
|
|
'aria-label': isLabelVisible ? undefined : label,
|
|
'aria-labelledby': isLabelVisible ? labelId : undefined,
|
|
// REVISIT: aria-description is not widley supported yet
|
|
'aria-describedby': description ? descriptionId : undefined,
|
|
},
|
|
ref,
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{/* useButton() breaks the native browser :focus-visible :-/ */}
|
|
<FocusRing focusRingClass="pb-focus-ring">
|
|
<button
|
|
id={id}
|
|
className={classNames(Classes.BUTTON, 'pb-focus-managed', {
|
|
[Classes.ACTIVE]: isPressed,
|
|
[Classes.LOADING]: loading,
|
|
[Classes.MINIMAL]: minimal,
|
|
})}
|
|
ref={mergeRefs(ref, elementRef ?? null)}
|
|
{...buttonProps}
|
|
>
|
|
{loading ? (
|
|
<Spinner
|
|
className={Classes.BUTTON_SPINNER}
|
|
size={IconSize.LARGE}
|
|
/>
|
|
) : (
|
|
<>
|
|
<Icon icon={icon} aria-hidden />
|
|
{isLabelVisible && (
|
|
<span id={labelId} className={Classes.BUTTON_TEXT}>
|
|
{label}
|
|
</span>
|
|
)}
|
|
</>
|
|
)}
|
|
</button>
|
|
</FocusRing>
|
|
{/* This can't be inside the button element, otherwise it messes up the styling */}
|
|
{description && (
|
|
<div id={descriptionId} hidden>
|
|
{description}
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|