mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Shared-UX] Migrate Toolbar component from presentation to shared-ux (#137224)
This commit is contained in:
parent
927fd674ce
commit
1ebfd79daf
9 changed files with 542 additions and 9 deletions
|
@ -1,9 +1,23 @@
|
|||
---
|
||||
id: sharedUX/ButtonToolbar
|
||||
slug: /shared-ux/button-toolbar
|
||||
title: Button Toolbar
|
||||
description:
|
||||
tags: ['shared-ux', 'component']
|
||||
date: 2022-06-14
|
||||
id: sharedUX/Components/Toolbar
|
||||
slug: /shared-ux/components/button_toolbar
|
||||
title: Toolbar
|
||||
summary: An implementation of the popover, primary button, icon button group and add from library button
|
||||
tags: ['shared-ux', 'component', 'toolbar']
|
||||
date: 2022-07-28
|
||||
---
|
||||
|
||||
This `toolbar` component accepts a `children` prop. Children can include a `popover` or a generic `button`. It can also include the `IconButtonGroup` and `AddFromLibrary` component for soltuions.
|
||||
Styling of the popover and button follow the primary styles.
|
||||
|
||||
|
||||
```jsx
|
||||
<Toolbar>
|
||||
{{
|
||||
primaryButton,
|
||||
iconButtonGroup,
|
||||
extraButtons,
|
||||
addFromLibraryButton,
|
||||
}}
|
||||
</Toolbar>
|
||||
```
|
|
@ -21,7 +21,7 @@ export default {
|
|||
},
|
||||
};
|
||||
|
||||
const quickButtons = [
|
||||
const iconButtons = [
|
||||
{
|
||||
label: 'Text',
|
||||
onClick: action('onTextClick'),
|
||||
|
@ -65,7 +65,7 @@ const argTypes = {
|
|||
type Params = Record<keyof typeof argTypes, any>;
|
||||
|
||||
export const IconButtonGroup = ({ buttonCount }: Params) => {
|
||||
return <Component legend="Example icon group" buttons={quickButtons.slice(0, buttonCount)} />;
|
||||
return <Component legend="Example icon group" buttons={iconButtons.slice(0, buttonCount)} />;
|
||||
};
|
||||
|
||||
IconButtonGroup.argTypes = argTypes;
|
||||
|
|
|
@ -16,3 +16,7 @@ export type {
|
|||
|
||||
export { ToolbarPopover } from './popover';
|
||||
export type { ToolbarPopoverProps } from './popover';
|
||||
|
||||
export { Toolbar } from './toolbar';
|
||||
export type { ToolbarProps } from './toolbar';
|
||||
export type { ToolbarButton } from './toolbar';
|
||||
|
|
|
@ -7,5 +7,5 @@ tags: ['shared-ux', 'component']
|
|||
date: 2022-03-28
|
||||
---
|
||||
|
||||
This component is a thing wrapper around `EuiPopover` that handles open and close state. Its open and close state is controlled by a `ToolbarButton` button component.
|
||||
This component is a thin wrapper around `EuiPopover` that handles open and close state. Its open and close state is controlled by a `ToolbarButton` button component.
|
||||
This popover requires a label and children.
|
200
packages/shared-ux/button_toolbar/src/toolbar/__snapshots__/toolbar.test.tsx.snap
generated
Normal file
200
packages/shared-ux/button_toolbar/src/toolbar/__snapshots__/toolbar.test.tsx.snap
generated
Normal file
|
@ -0,0 +1,200 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Toolbar /> is rendered 1`] = `
|
||||
<Toolbar
|
||||
intl={
|
||||
Object {
|
||||
"defaultFormats": Object {},
|
||||
"defaultLocale": "en",
|
||||
"formatDate": [Function],
|
||||
"formatHTMLMessage": [Function],
|
||||
"formatMessage": [Function],
|
||||
"formatNumber": [Function],
|
||||
"formatPlural": [Function],
|
||||
"formatRelative": [Function],
|
||||
"formatTime": [Function],
|
||||
"formats": Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
},
|
||||
"formatters": Object {
|
||||
"getDateTimeFormat": [Function],
|
||||
"getMessageFormat": [Function],
|
||||
"getNumberFormat": [Function],
|
||||
"getPluralFormat": [Function],
|
||||
"getRelativeFormat": [Function],
|
||||
},
|
||||
"locale": "en",
|
||||
"messages": Object {},
|
||||
"now": [Function],
|
||||
"onError": [Function],
|
||||
"textComponent": Symbol(react.fragment),
|
||||
"timeZone": null,
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<PrimaryButton
|
||||
label="Create chart"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
fill={true}
|
||||
iconSide="left"
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
>
|
||||
<EuiButtonDisplay
|
||||
baseClassName="euiButton"
|
||||
color="primary"
|
||||
disabled={false}
|
||||
element="button"
|
||||
fill={true}
|
||||
iconSide="left"
|
||||
isDisabled={false}
|
||||
onClick={[Function]}
|
||||
size="m"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="euiButton euiButton--primary euiButton--fill"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"minWidth": undefined,
|
||||
}
|
||||
}
|
||||
type="button"
|
||||
>
|
||||
<EuiButtonContentDeprecated
|
||||
className="euiButton__content"
|
||||
iconSide="left"
|
||||
textProps={
|
||||
Object {
|
||||
"className": "euiButton__text",
|
||||
}
|
||||
}
|
||||
>
|
||||
<span
|
||||
className="euiButtonContent euiButton__content"
|
||||
>
|
||||
<span
|
||||
className="euiButton__text"
|
||||
>
|
||||
Create chart
|
||||
</span>
|
||||
</span>
|
||||
</EuiButtonContentDeprecated>
|
||||
</button>
|
||||
</EuiButtonDisplay>
|
||||
</EuiButton>
|
||||
</PrimaryButton>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
gutterSize="xs"
|
||||
responsive={false}
|
||||
wrap={true}
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--gutterExtraSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--wrap"
|
||||
/>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</Toolbar>
|
||||
`;
|
13
packages/shared-ux/button_toolbar/src/toolbar/index.ts
Normal file
13
packages/shared-ux/button_toolbar/src/toolbar/index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export { Toolbar } from './toolbar';
|
||||
|
||||
export type { Props as ToolbarProps } from './toolbar';
|
||||
|
||||
export type { ToolbarButton } from './toolbar';
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Story } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { EuiContextMenu } from '@elastic/eui';
|
||||
|
||||
import { Toolbar } from './toolbar';
|
||||
import { AddFromLibraryButton, IconButtonGroup, PrimaryButton } from '../buttons';
|
||||
import { ToolbarPopover } from '../popover';
|
||||
|
||||
const iconButtons = [
|
||||
{
|
||||
label: 'Text',
|
||||
onClick: action('onTextClick'),
|
||||
iconType: 'visText',
|
||||
title: 'Text as markdown',
|
||||
},
|
||||
{
|
||||
label: 'Control',
|
||||
onClick: action('onControlClick'),
|
||||
iconType: 'controlsHorizontal',
|
||||
},
|
||||
{
|
||||
label: 'Link',
|
||||
onClick: action('onLinkClick'),
|
||||
iconType: 'link',
|
||||
},
|
||||
{
|
||||
label: 'Image',
|
||||
onClick: action('onImageClick'),
|
||||
iconType: 'image',
|
||||
},
|
||||
{
|
||||
label: 'Markup',
|
||||
onClick: action('onMarkupClick'),
|
||||
iconType: 'visVega',
|
||||
},
|
||||
];
|
||||
|
||||
const primaryButtonConfigs = {
|
||||
Generic: <PrimaryButton label="Primary Action" iconType="apps" onClick={action('generic')} />,
|
||||
Canvas: (
|
||||
<ToolbarPopover label="Add element" iconType="plusInCircle" panelPaddingSize="none">
|
||||
{() => (
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: 'Open editor',
|
||||
items: [
|
||||
{
|
||||
name: 'Lens',
|
||||
icon: 'lensApp',
|
||||
},
|
||||
{
|
||||
name: 'Maps',
|
||||
icon: 'logoMaps',
|
||||
},
|
||||
{
|
||||
name: 'TSVB',
|
||||
icon: 'visVisualBuilder',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</ToolbarPopover>
|
||||
),
|
||||
Dashboard: (
|
||||
<PrimaryButton label="Create chart" iconType="plusInCircle" onClick={action('dashboard')} />
|
||||
),
|
||||
};
|
||||
|
||||
const extraButtonConfigs = {
|
||||
Generic: undefined,
|
||||
Canvas: undefined,
|
||||
Dashboard: [
|
||||
<ToolbarPopover iconType="visualizeApp" label="All editors" panelPaddingSize="none">
|
||||
{() => (
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: 'Open editor',
|
||||
items: [
|
||||
{
|
||||
name: 'Lens',
|
||||
icon: 'lensApp',
|
||||
},
|
||||
{
|
||||
name: 'Maps',
|
||||
icon: 'logoMaps',
|
||||
},
|
||||
{
|
||||
name: 'TSVB',
|
||||
icon: 'visVisualBuilder',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</ToolbarPopover>,
|
||||
],
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Toolbar',
|
||||
description: 'A universal toolbar for solutions.',
|
||||
component: Toolbar,
|
||||
argTypes: {
|
||||
iconButtonCount: {
|
||||
defaultValue: 2,
|
||||
control: {
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 5,
|
||||
step: 1,
|
||||
},
|
||||
},
|
||||
showAddFromLibraryButton: {
|
||||
defaultValue: true,
|
||||
control: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
solution: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// https://github.com/storybookjs/storybook/issues/11543#issuecomment-684130442
|
||||
parameters: {
|
||||
docs: {
|
||||
source: {
|
||||
type: 'code',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template: Story<{
|
||||
solution: 'Generic' | 'Canvas' | 'Dashboard';
|
||||
iconButtonCount: number;
|
||||
showAddFromLibraryButton: boolean;
|
||||
}> = ({ iconButtonCount, solution, showAddFromLibraryButton }) => {
|
||||
const primaryButton = primaryButtonConfigs[solution];
|
||||
const extraButtons = extraButtonConfigs[solution];
|
||||
let iconButtonGroup;
|
||||
let addFromLibraryButton;
|
||||
|
||||
if (iconButtonCount > 0) {
|
||||
iconButtonGroup = (
|
||||
<IconButtonGroup buttons={iconButtons.slice(0, iconButtonCount)} legend="example" />
|
||||
);
|
||||
}
|
||||
|
||||
if (showAddFromLibraryButton) {
|
||||
addFromLibraryButton = <AddFromLibraryButton onClick={action('addFromLibrary')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
{{
|
||||
primaryButton,
|
||||
iconButtonGroup,
|
||||
extraButtons,
|
||||
addFromLibraryButton,
|
||||
}}
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
|
||||
export const Generic = Template.bind({});
|
||||
Generic.args = {
|
||||
...Template.args,
|
||||
solution: 'Generic',
|
||||
};
|
||||
|
||||
export const Canvas = Template.bind({});
|
||||
Canvas.args = {
|
||||
...Template.args,
|
||||
solution: 'Canvas',
|
||||
};
|
||||
|
||||
export const Dashboard = Template.bind({});
|
||||
Dashboard.args = {
|
||||
...Template.args,
|
||||
solution: 'Dashboard',
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
|
||||
import { Toolbar } from './toolbar';
|
||||
import { PrimaryButton } from '../buttons';
|
||||
|
||||
describe('<Toolbar />', () => {
|
||||
test('is rendered', () => {
|
||||
const primaryButton = <PrimaryButton label="Create chart" onClick={() => 'click'} />;
|
||||
const children = { primaryButton };
|
||||
const component = mountWithIntl(<Toolbar children={children} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('onClick works as expected when the primary button is clicked', () => {
|
||||
const mockClickHandler = jest.fn();
|
||||
const primaryButton = <PrimaryButton label="Create chart" onClick={mockClickHandler} />;
|
||||
const children = { primaryButton };
|
||||
const component = mountWithIntl(<Toolbar children={children} />);
|
||||
component.find('button').simulate('click');
|
||||
expect(mockClickHandler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
69
packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx
Normal file
69
packages/shared-ux/button_toolbar/src/toolbar/toolbar.tsx
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IconButtonGroup, PrimaryButton } from '../buttons';
|
||||
import { ToolbarPopover } from '../popover';
|
||||
|
||||
/** type for cases with both button or a popover could be used */
|
||||
export type ToolbarButton = typeof PrimaryButton | typeof ToolbarPopover;
|
||||
|
||||
/** Specific type for the toolbar children in its props */
|
||||
interface NamedSlots {
|
||||
primaryButton: ReactElement<ToolbarButton>;
|
||||
iconButtonGroup?: ReactElement<typeof IconButtonGroup>;
|
||||
extraButtons?: Array<ReactElement<ToolbarButton>> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Props for a generic toolbar component
|
||||
*/
|
||||
export interface Props {
|
||||
children: NamedSlots;
|
||||
}
|
||||
|
||||
const errorText = i18n.translate('sharedUXPackages.buttonToolbar.toolbar.errorToolbarText', {
|
||||
defaultMessage:
|
||||
'There are over 120 extra buttons. Please consider limiting the number of buttons.',
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param children of the toolbar such as a popover or button
|
||||
* @returns Toolbar component
|
||||
*/
|
||||
export const Toolbar = ({ children }: Props) => {
|
||||
const { primaryButton, iconButtonGroup, extraButtons = [] } = children;
|
||||
|
||||
if (extraButtons.length > 120) {
|
||||
throw new Error(errorText);
|
||||
}
|
||||
|
||||
const extra = extraButtons.map((button, index) =>
|
||||
button ? (
|
||||
<EuiFlexItem grow={false} key={`button-${index}`}>
|
||||
{button}
|
||||
</EuiFlexItem>
|
||||
) : null
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem grow={false}>{primaryButton}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup wrap={true} responsive={false} alignItems="center" gutterSize="xs">
|
||||
{iconButtonGroup ? <EuiFlexItem grow={false}>{iconButtonGroup}</EuiFlexItem> : null}
|
||||
{extra}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue