[Canvas][Storybook] Convert Shapes to TS, Storybook (#38850) (#38918)

* Shapes stories + TS

* Merging storybook into shapes-ts-stories

* A few tweaks
This commit is contained in:
Clint Andrew Hall 2019-06-13 14:11:20 -05:00 committed by GitHub
parent d7a228a37c
commit 15ddcb9992
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 332 additions and 26 deletions

View file

@ -9,8 +9,22 @@ import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-story
import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer';
import { addSerializer } from 'jest-specific-snapshot';
// Mock EUI generated ids to be consistently predictable for snapshots.
jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`);
// Jest automatically mocks SVGs to be a plain-text string that isn't an SVG. Canvas uses
// them in examples, so let's mock a few for tests.
jest.mock('../canvas_plugin_src/renderers/shape/shapes', () => ({
shapes: {
arrow: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<polygon points="0,40 60,40 60,20 95,50 60,80 60,60 0,60" />
</svg>`,
square: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="0" y="0" width="100" height="100" />
</svg>`,
},
}));
addSerializer(styleSheetSerializer);
// Initialize Storyshots and build the Jest Snapshots

View file

@ -8,12 +8,12 @@ import React from 'react';
import PropTypes from 'prop-types';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { templateFromReactComponent } from '../../../public/lib/template_from_react_component';
import { ShapePickerMini } from '../../../public/components/shape_picker_mini/';
import { ShapePickerPopover } from '../../../public/components/shape_picker_popover/';
const ShapeArgInput = ({ onValueChange, argValue, typeInstance }) => (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<ShapePickerMini
<ShapePickerPopover
value={argValue}
onChange={onValueChange}
shapes={typeInstance.options.shapes}

View file

@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/Shapes/ShapePicker default 1`] = `
<div
className="euiFlexGrid euiFlexGrid--gutterSmall euiFlexGrid--fourths euiFlexGrid--responsive canvasShapePicker"
>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<polygon points=\\"0,40 60,40 60,20 95,50 60,80 60,60 0,60\\"></polygon>
</svg>",
}
}
/>
</button>
</div>
<div
className="euiFlexItem"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
type="button"
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"></rect>
</svg>",
}
}
/>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePicker } from '../shape_picker';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
storiesOf('components/Shapes/ShapePicker', module).add('default', () => (
<ShapePicker shapes={shapes} onChange={action('onChange')} />
));

View file

@ -0,0 +1,3 @@
.canvasShapePicker {
width: ($euiSizeXXL * 4) + ($euiSizeS * 4);
}

View file

@ -9,9 +9,16 @@ import PropTypes from 'prop-types';
import { EuiFlexGrid, EuiFlexItem, EuiLink } from '@elastic/eui';
import { ShapePreview } from '../shape_preview';
export const ShapePicker = ({ shapes, onChange }) => {
interface Props {
shapes: {
[key: string]: string;
};
onChange?: (key: string) => void;
}
export const ShapePicker = ({ shapes, onChange = () => {} }: Props) => {
return (
<EuiFlexGrid gutterSize="s" columns={4}>
<EuiFlexGrid gutterSize="s" columns={4} className="canvasShapePicker">
{Object.keys(shapes)
.sort()
.map(shapeKey => (

View file

@ -1,3 +0,0 @@
.canvasShapePickerMini--popover {
width: 220px;
}

View file

@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/Shapes/ShapePickerPopover default 1`] = `
<div
className="euiPopover euiPopover--anchorDownCenter"
container={null}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
style={
Object {
"fontSize": 0,
}
}
type="button"
>
<div
className="canvasShapePreview"
/>
</button>
</div>
</div>
`;
exports[`Storyshots components/Shapes/ShapePickerPopover interactive 1`] = `
<div
className="euiPopover euiPopover--anchorDownCenter"
container={null}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
style={
Object {
"fontSize": 0,
}
}
type="button"
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"></rect>
</svg>",
}
}
/>
</button>
</div>
</div>
`;
exports[`Storyshots components/Shapes/ShapePickerPopover shape selected 1`] = `
<div
className="euiPopover euiPopover--anchorDownCenter"
container={null}
onKeyDown={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
onTouchEnd={[Function]}
onTouchStart={[Function]}
>
<div
className="euiPopover__anchor"
>
<button
className="euiLink euiLink--primary"
onClick={[Function]}
style={
Object {
"fontSize": 0,
}
}
type="button"
>
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"></rect>
</svg>",
}
}
/>
</button>
</div>
</div>
`;

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePickerPopover } from '../shape_picker_popover';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
class Interactive extends React.Component<{}, { value: string }> {
public state = {
value: 'square',
};
public render() {
return (
<ShapePickerPopover
shapes={shapes}
onChange={value => this.setState({ value })}
value={this.state.value}
/>
);
}
}
storiesOf('components/Shapes/ShapePickerPopover', module)
.add('default', () => <ShapePickerPopover shapes={shapes} onChange={action('onChange')} />)
.add('shape selected', () => (
<ShapePickerPopover shapes={shapes} onChange={action('onChange')} value="square" />
))
.add('interactive', () => <Interactive />, {
info: {
source: false,
propTablesExclude: [Interactive],
},
});

View file

@ -6,6 +6,6 @@
import { pure } from 'recompose';
import { ShapePickerMini as Component } from './shape_picker_mini';
import { ShapePickerPopover as Component } from './shape_picker_popover';
export const ShapePickerMini = pure(Component);
export const ShapePickerPopover = pure(Component);

View file

@ -7,29 +7,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { EuiLink } from '@elastic/eui';
// @ts-ignore untyped local
import { Popover } from '../popover';
import { ShapePicker } from '../shape_picker/';
import { ShapePicker } from '../shape_picker';
import { ShapePreview } from '../shape_preview';
export const ShapePickerMini = ({ shapes, onChange, value, anchorPosition }) => {
const button = handleClick => (
export enum AnchorPosition {
TOP = 'top',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right',
}
interface Props {
shapes: {
[key: string]: string;
};
onChange?: (key: string) => void;
value?: string;
anchorPosition?: AnchorPosition;
}
export const ShapePickerPopover = ({ shapes, onChange, value, anchorPosition }: Props) => {
const button = (handleClick: () => unknown) => (
<EuiLink style={{ fontSize: 0 }} onClick={handleClick}>
<ShapePreview shape={shapes[value]} />
<ShapePreview shape={value ? shapes[value] : undefined} />
</EuiLink>
);
return (
<Popover
panelClassName="canvas canvasShapePickerMini--popover"
button={button}
anchorPosition={anchorPosition}
>
<Popover panelClassName="canvas" button={button} anchorPosition={anchorPosition}>
{() => <ShapePicker onChange={onChange} shapes={shapes} />}
</Popover>
);
};
ShapePickerMini.propTypes = {
ShapePickerPopover.propTypes = {
shapes: PropTypes.object.isRequired,
value: PropTypes.string,
onChange: PropTypes.func,

View file

@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Storyshots components/Shapes/ShapePreview arrow 1`] = `
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<polygon points=\\"0,40 60,40 60,20 95,50 60,80 60,60 0,60\\"></polygon>
</svg>",
}
}
/>
`;
exports[`Storyshots components/Shapes/ShapePreview square 1`] = `
<div
className="canvasShapePreview"
dangerouslySetInnerHTML={
Object {
"__html": "<svg xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2.5 -2.5 105 105\\" fill=\\"none\\" stroke=\\"black\\">
<rect x=\\"0\\" y=\\"0\\" width=\\"100\\" height=\\"100\\"></rect>
</svg>",
}
}
/>
`;

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { storiesOf } from '@storybook/react';
import React from 'react';
import { ShapePreview } from '../shape_preview';
import { shapes } from '../../../../canvas_plugin_src/renderers/shape/shapes';
storiesOf('components/Shapes/ShapePreview', module)
.add('arrow', () => <ShapePreview shape={shapes.arrow} />)
.add('square', () => <ShapePreview shape={shapes.square} />);

View file

@ -7,17 +7,31 @@
import React from 'react';
import PropTypes from 'prop-types';
export const ShapePreview = ({ shape }) => {
interface Props {
shape?: string;
}
export const ShapePreview = ({ shape }: Props) => {
if (!shape) {
return <div className="canvasShapePreview" />;
}
const weight = 5;
const parser = new DOMParser();
const [shapeSvg] = parser.parseFromString(shape, 'image/svg+xml').getElementsByTagName('svg');
const shapeSvg = parser
.parseFromString(shape, 'image/svg+xml')
.getElementsByTagName('svg')
.item(0);
if (!shapeSvg) {
throw new Error('An unexpected error occurred: the SVG was not parseable');
}
shapeSvg.setAttribute('fill', 'none');
shapeSvg.setAttribute('stroke', 'black');
const initialViewBox = shapeSvg
.getAttribute('viewBox')
.split(' ')
.map(v => parseInt(v, 10));
const viewBox = shapeSvg.getAttribute('viewBox') || '0 0 0 0';
const initialViewBox = viewBox.split(' ').map((v: string) => parseInt(v, 10));
let [minX, minY, width, height] = initialViewBox;
minX -= weight / 2;

View file

@ -44,7 +44,7 @@
@import '../components/remove_icon/remove_icon';
@import '../components/rotation_handle/rotation_handle';
@import '../components/shape_preview/shape_preview';
@import '../components/shape_picker_mini/shape_picker_mini';
@import '../components/shape_picker/shape_picker';
@import '../components/sidebar/sidebar';
@import '../components/sidebar_header/sidebar_header';
@import '../components/toolbar/toolbar';

View file

@ -5,3 +5,8 @@
*/
declare module '*.png';
declare module '*.svg' {
const content: string;
export = content;
}