forked from external-repos/squoosh
85 lines
2.1 KiB
TypeScript
85 lines
2.1 KiB
TypeScript
import { h, cloneElement, Component, VNode, createRef, ComponentChildren, ComponentProps } from "preact";
|
|
import { ClickOutsideDetector } from "../ClickOutsideDetector";
|
|
import * as style from './style.css';
|
|
import 'add-css:./style.css';
|
|
|
|
type Anchor = 'left' | 'right' | 'top' | 'bottom';
|
|
|
|
interface Props extends ComponentProps<'aside'> {
|
|
showing?: boolean;
|
|
direction?: 'up' | 'down';
|
|
anchor?: Anchor | Anchor[];
|
|
toggle?: VNode;
|
|
children?: ComponentChildren;
|
|
}
|
|
|
|
interface State {
|
|
showing: boolean;
|
|
}
|
|
|
|
export default class Flyout extends Component<Props, State> {
|
|
state = {
|
|
showing: this.props.showing === true
|
|
};
|
|
|
|
private menu = createRef<HTMLElement>();
|
|
|
|
private hide = () => {
|
|
this.setState({ showing: false });
|
|
};
|
|
|
|
private toggle = () => {
|
|
this.setState({ showing: !this.state.showing });
|
|
};
|
|
|
|
componentWillReceiveProps({ showing }: Props) {
|
|
if (showing !== this.props.showing) {
|
|
this.setState({ showing });
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps: Props, prevState: State) {
|
|
if (this.state.showing && !prevState.showing) {
|
|
const menu = this.menu.current;
|
|
if (menu) {
|
|
let toFocus = menu.firstElementChild;
|
|
for (let child of menu.children) {
|
|
if (child.hasAttribute('autofocus')) {
|
|
toFocus = child;
|
|
break;
|
|
}
|
|
}
|
|
// @ts-ignore-next
|
|
if (toFocus) toFocus.focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
render({ direction, anchor, toggle, children, ...props }: Props, { showing }: State) {
|
|
const toggleProps = {
|
|
flyoutOpen: showing,
|
|
onClick: this.toggle
|
|
};
|
|
|
|
const anchorText = Array.isArray(anchor) ? anchor.join(' ') : anchor;
|
|
|
|
return (
|
|
<span class={style.wrap} data-flyout-open={showing ? '' : undefined}>
|
|
<ClickOutsideDetector onClick={this.hide}>
|
|
{toggle && cloneElement(toggle, toggleProps)}
|
|
|
|
<aside
|
|
{...props}
|
|
ref={this.menu}
|
|
hidden={!showing}
|
|
data-anchor={anchorText}
|
|
data-direction={direction}
|
|
>
|
|
{children}
|
|
</aside>
|
|
</ClickOutsideDetector>
|
|
</span>
|
|
);
|
|
}
|
|
}
|