Commit 93234f34 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add basic `Panel` component

Add a basic Panel component to be used by `SidebarPanel` and for
other basic panel-y needs.
parent a98ce5fe
import { SvgIcon } from '@hypothesis/frontend-shared';
import Button from './Button';
/**
* @typedef PanelProps
* @prop {import("preact").ComponentChildren} children
* @prop {string} [icon] - Name of optional icon to render in header
* @prop {() => any} [onClose] - handler for closing the panel; if provided,
* will render a close button that invokes this onClick
* @prop {string} title
*/
/**
* Render a "panel"-like interface
*
* @param {PanelProps} props
*/
export default function Panel({ children, icon, onClose, title }) {
const withCloseButton = typeof onClose === 'function';
return (
<div className="Panel">
<div className="Panel__header">
{icon && (
<div className="Panel__header-icon">
<SvgIcon name={icon} title={title} />
</div>
)}
<h2 className="Panel__title u-stretch">{title}</h2>
{withCloseButton && (
<div>
<Button icon="cancel" buttonText="Close" onClick={onClose} />
</div>
)}
</div>
<div className="Panel__content">{children}</div>
</div>
);
}
import { mount } from 'enzyme';
import Panel from '../Panel';
import { $imports } from '../Panel';
import { checkAccessibility } from '../../../test-util/accessibility';
import mockImportedComponents from '../../../test-util/mock-imported-components';
describe('Panel', () => {
const createPanel = props =>
mount(
<Panel title="Test Panel" {...props}>
Test panel
</Panel>
);
beforeEach(() => {
$imports.$mock(mockImportedComponents());
});
afterEach(() => {
$imports.$restore();
});
it('renders the provided title', () => {
const wrapper = createPanel({ title: 'My Panel' });
const titleEl = wrapper.find('.Panel__title');
assert.equal(titleEl.text(), 'My Panel');
});
it('renders an icon if provided', () => {
const wrapper = createPanel({ icon: 'restricted' });
const icon = wrapper.find('SvgIcon').filter({ name: 'restricted' });
assert.isTrue(icon.exists());
});
context('when `onClose` is provided', () => {
it('renders a close button', () => {
const wrapper = createPanel({
onClose: sinon.stub(),
});
const closeButton = wrapper.find('Button');
assert.isTrue(closeButton.exists());
assert.equal(closeButton.props().buttonText, 'Close');
});
it('invokes `onClose` handler when close button is clicked', () => {
const onClose = sinon.stub();
const wrapper = createPanel({
onClose,
});
wrapper.find('Button').props().onClick();
assert.calledOnce(onClose);
});
});
it(
'should pass a11y checks',
checkAccessibility({
content: () => createPanel(),
})
);
});
@use "../../mixins/molecules";
.Panel {
@include molecules.panel;
position: relative;
margin-bottom: 0.75em;
}
......@@ -42,6 +42,7 @@
@use './components/NotebookView';
@use './components/NotebookResultCount';
@use './components/PaginationNavigation';
@use './components/Panel';
@use './components/SelectionTabs';
@use './components/ShareAnnotationsPanel';
@use './components/SearchInput';
......
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