feat(my-joy-beta): tags and metadata in instance creation

fixes #983
fixes #982
This commit is contained in:
Sérgio Ramos 2018-01-10 14:39:46 +00:00 committed by Sérgio Ramos
parent ca7e34c8f5
commit ed801eba0a
23 changed files with 11472 additions and 282 deletions

View File

@ -24,9 +24,10 @@
"apr-intercept": "^1.0.4", "apr-intercept": "^1.0.4",
"clipboard-copy": "^1.2.0", "clipboard-copy": "^1.2.0",
"date-fns": "^1.29.0", "date-fns": "^1.29.0",
"declarative-redux-form": "^1.0.4", "declarative-redux-form": "^2.0.8",
"joyent-manifest-editor": "^1.4.0",
"joyent-ui-toolkit": "^4.0.0", "joyent-ui-toolkit": "^4.0.0",
"joyent-manifest-editor": "^1.4.0",
"joyent-ui-toolkit": "^4.0.1",
"lodash.find": "^4.6.0", "lodash.find": "^4.6.0",
"lodash.get": "^4.4.2", "lodash.get": "^4.4.2",
"lodash.isstring": "^4.0.1", "lodash.isstring": "^4.0.1",

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <Toolbar /> without throwing 1`] = ` exports[`renders <Title /> without throwing 1`] = `
Array [ Array [
.c2 { .c2 {
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
@ -97,7 +97,7 @@ Array [
] ]
`; `;
exports[`renders <Toolbar icon="NameIcon"/> without throwing 1`] = ` exports[`renders <Title icon="NameIcon"/> without throwing 1`] = `
Array [ Array [
.c2 { .c2 {
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
@ -214,7 +214,7 @@ Array [
] ]
`; `;
exports[`renders <Toolbar icon="Test" label="Instance name"/> without throwing 1`] = ` exports[`renders <Title icon="Test" label="Instance name"/> without throwing 1`] = `
Array [ Array [
.c2 { .c2 {
color: rgba(73,73,73,1); color: rgba(73,73,73,1);
@ -331,7 +331,7 @@ Array [
] ]
`; `;
exports[`renders <Toolbar label="Test"/> without throwing 1`] = ` exports[`renders <Title label="Test"/> without throwing 1`] = `
Array [ Array [
.c2 { .c2 {
color: rgba(73,73,73,1); color: rgba(73,73,73,1);

View File

@ -7,7 +7,7 @@ import { NameIcon } from 'joyent-ui-toolkit';
import Title from '../title'; import Title from '../title';
import Theme from '@mocks/theme'; import Theme from '@mocks/theme';
it('renders <Toolbar /> without throwing', () => { it('renders <Title /> without throwing', () => {
expect( expect(
renderer renderer
.create( .create(
@ -19,7 +19,7 @@ it('renders <Toolbar /> without throwing', () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('renders <Toolbar label="Test"/> without throwing', () => { it('renders <Title label="Test"/> without throwing', () => {
expect( expect(
renderer renderer
.create( .create(
@ -31,7 +31,7 @@ it('renders <Toolbar label="Test"/> without throwing', () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('renders <Toolbar icon="NameIcon"/> without throwing', () => { it('renders <Title icon="NameIcon"/> without throwing', () => {
expect( expect(
renderer renderer
.create( .create(
@ -43,7 +43,7 @@ it('renders <Toolbar icon="NameIcon"/> without throwing', () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('renders <Toolbar icon="Test" label="Instance name"/> without throwing', () => { it('renders <Title icon="Test" label="Instance name"/> without throwing', () => {
expect( expect(
renderer renderer
.create( .create(

View File

@ -1972,11 +1972,6 @@ exports[`renders <Tag /> without throwing 1`] = `
} }
.c1 { .c1 {
border: 0.0625rem solid rgb(216,216,216);
box-sizing: border-box;
border-radius: 0.1875rem;
font-size: 0.8125rem;
padding: 0.375rem 0.75rem;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
@ -1992,6 +1987,12 @@ exports[`renders <Tag /> without throwing 1`] = `
} }
.c2 { .c2 {
border: 0.0625rem solid rgb(216,216,216);
box-sizing: border-box;
border-radius: 0.1875rem;
font-size: 0.8125rem;
line-height: 1.125rem;
padding: 0.375rem 0.75rem;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
@ -2009,17 +2010,17 @@ exports[`renders <Tag /> without throwing 1`] = `
<div <div
className="c0" className="c0"
> >
<li <div
className="c1" className="c1"
>
<li
className="c2"
onClick={undefined} onClick={undefined}
>
<div
className="c2"
> >
: :
</div>
</li> </li>
</div> </div>
</div>
`; `;
exports[`renders <Tag name value/> without throwing 1`] = ` exports[`renders <Tag name value/> without throwing 1`] = `
@ -2029,11 +2030,6 @@ exports[`renders <Tag name value/> without throwing 1`] = `
} }
.c1 { .c1 {
border: 0.0625rem solid rgb(216,216,216);
box-sizing: border-box;
border-radius: 0.1875rem;
font-size: 0.8125rem;
padding: 0.375rem 0.75rem;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
@ -2049,6 +2045,12 @@ exports[`renders <Tag name value/> without throwing 1`] = `
} }
.c2 { .c2 {
border: 0.0625rem solid rgb(216,216,216);
box-sizing: border-box;
border-radius: 0.1875rem;
font-size: 0.8125rem;
line-height: 1.125rem;
padding: 0.375rem 0.75rem;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
display: -ms-flexbox; display: -ms-flexbox;
@ -2065,18 +2067,18 @@ exports[`renders <Tag name value/> without throwing 1`] = `
<div <div
className="c0" className="c0"
>
<li
className="c1"
onClick={undefined}
> >
<div <div
className="c1"
>
<li
className="c2" className="c2"
onClick={undefined}
> >
name name
: :
value value
</div>
</li> </li>
</div> </div>
</div>
`; `;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Margin } from 'styled-components-spacing'; import { Margin } from 'styled-components-spacing';
import { KeyValue } from '@components/instances'; import { KeyValue } from '@components/instances';
import { TagItem, TagItemContainer } from 'joyent-ui-toolkit'; import { TagItem } from 'joyent-ui-toolkit';
export const AddForm = props => ( export const AddForm = props => (
<KeyValue {...props} method="add" input="input" type="tag" expanded /> <KeyValue {...props} method="add" input="input" type="tag" expanded />
@ -12,12 +12,10 @@ export const EditForm = props => (
<KeyValue {...props} method="edit" input="input" type="tag" expanded /> <KeyValue {...props} method="edit" input="input" type="tag" expanded />
); );
export default ({ name, value, onClick }) => ( export default ({ name, value, onClick, onRemoveClick }) => (
<Margin right={1} bottom={1} key={`${name}-${value}`}> <Margin right={1} bottom={1} key={`${name}-${value}`}>
<TagItem onClick={onClick}> <TagItem onClick={onClick} onRemoveClick={onRemoveClick}>
<TagItemContainer>
{name}: {value} {name}: {value}
</TagItemContainer>
</TagItem> </TagItem>
</Margin> </Margin>
); );

View File

@ -0,0 +1,76 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Theme from '@mocks/theme';
import { Metadata } from '../metadata';
it('renders <Metadata /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata expanded /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata metadata=[] /> without throwing', () => {
const metadata = [
{ name: 'hello', value: 'world' },
{ name: 'hello2', value: 'world2' }
];
expect(
renderer
.create(
<Theme>
<Metadata metadata={metadata} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata metadata=[] expanded /> without throwing', () => {
const metadata = [
{ name: 'hello', value: 'world' },
{ name: 'hello2', value: 'world2' }
];
expect(
renderer
.create(
<Theme>
<Metadata metadata={metadata} expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Metadata addOpen expanded /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Metadata addOpen expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -0,0 +1,76 @@
import React from 'react';
import renderer from 'react-test-renderer';
import 'jest-styled-components';
import Theme from '@mocks/theme';
import { Tags } from '../tags';
it('renders <Tags /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags expanded /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags metadata=[] /> without throwing', () => {
const metadata = [
{ name: 'hello', value: 'world' },
{ name: 'hello2', value: 'world2' }
];
expect(
renderer
.create(
<Theme>
<Tags metadata={metadata} />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags metadata=[] expanded /> without throwing', () => {
const metadata = [
{ name: 'hello', value: 'world' },
{ name: 'hello2', value: 'world2' }
];
expect(
renderer
.create(
<Theme>
<Tags metadata={metadata} expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Tags addOpen expanded /> without throwing', () => {
expect(
renderer
.create(
<Theme>
<Tags addOpen expanded />
</Theme>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { withRouter } from 'react-router';
import { compose, graphql } from 'react-apollo'; import { compose, graphql } from 'react-apollo';
import ReduxForm from 'declarative-redux-form'; import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -45,7 +44,6 @@ const ImageContainer = ({
); );
export default compose( export default compose(
withRouter,
connect( connect(
(state, ownProps) => { (state, ownProps) => {
return { return {

View File

@ -5,17 +5,25 @@ import { ViewContainer, H2 } from 'joyent-ui-toolkit';
import Name from '@containers/create-instance/name'; import Name from '@containers/create-instance/name';
import Image from '@containers/create-instance/image'; import Image from '@containers/create-instance/image';
import Metadata from '@containers/create-instance/metadata';
import Tags from '@containers/create-instance/tags';
export default ({ step }) => ( export default ({ step, ...props }) => (
<ViewContainer> <ViewContainer>
<Margin top={4} bottom={4}> <Margin top={4} bottom={4}>
<H2>Create Instances</H2> <H2>Create Instances</H2>
</Margin> </Margin>
<Margin bottom={4}> <Margin bottom={4}>
<Name expanded={step === 'name'} /> <Name {...props} expanded={step === 'name'} />
</Margin> </Margin>
<Margin bottom={4}> <Margin bottom={4}>
<Image expanded={step === 'image'} /> <Image {...props} expanded={step === 'image'} />
</Margin>
<Margin bottom={4}>
<Tags {...props} expanded={step === 'tags'} />
</Margin>
<Margin bottom={4}>
<Metadata {...props} expanded={step === 'metadata'} />
</Margin> </Margin>
</ViewContainer> </ViewContainer>
); );

View File

@ -0,0 +1,198 @@
import React, { Fragment } from 'react';
import { set } from 'react-redux-values';
import { Margin } from 'styled-components-spacing';
import { compose } from 'react-apollo';
import { destroy, reset } from 'redux-form';
import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux';
import get from 'lodash.get';
import { MetadataIcon, P, Button, H3 } from 'joyent-ui-toolkit';
import Title from '@components/create-instance/title';
import KeyValue from '@components/instances/key-value';
const FORM_NAME_CREATE = 'CREATE-INSTANCE-METADATA-ADD';
const FORM_NAME_EDIT = i => `CREATE-INSTANCE-METADATA-EDIT-${i}`;
export const Metadata = ({
metadata = [],
expanded,
addOpen,
handleAddMetadata,
handleRemoveMetadata,
handleUpdateMetadata,
handleToggleExpanded,
handleCancelEdit,
handleChangeAddOpen,
handleNext,
handleEdit
}) => (
<Fragment>
<Title icon={<MetadataIcon />}>Metadata</Title>
{expanded ? (
<Margin bottom={3}>
<P>
Metadata can be used to pass data to the instance. It can also be used
to inject a custom boot script. Unlike tags, metadata is only viewable
inside the instance.{' '}
<a
target="__blank"
href="https://docs.joyent.com/public-cloud/tags-metadata/metadata"
>
Read the docs
</a>
</P>
</Margin>
) : null}
<Margin bottom={4}>
<H3>
{metadata.length} key:value pair{metadata.length === 1 ? '' : 's'}
</H3>
</Margin>
{metadata.map(({ name, value, expanded }, index) => (
<ReduxForm
form={FORM_NAME_EDIT(index)}
key={index}
initialValues={{ name, value }}
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
onSubmit={newValue => handleUpdateMetadata(index, newValue)}
>
{props => (
<KeyValue
{...props}
initialValues={{ name, value }}
expanded={expanded}
method="edit"
input="textarea"
type="metadata"
onToggleExpanded={() => handleToggleExpanded(index)}
onCancel={() => handleCancelEdit(index)}
onRemove={() => handleRemoveMetadata(index)}
/>
)}
</ReduxForm>
))}
{expanded && addOpen ? (
<ReduxForm
form={FORM_NAME_CREATE}
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
onSubmit={handleAddMetadata}
>
{props => (
<KeyValue
{...props}
method="add"
input="textarea"
type="metadata"
expanded
onCancel={() => handleChangeAddOpen(false)}
/>
)}
</ReduxForm>
) : null}
<div>
{expanded ? (
<Fragment>
<Button
type="button"
onClick={() => handleChangeAddOpen(true)}
secondary
>
Add Metadata
</Button>
<Button type="button" onClick={handleNext}>
Next
</Button>
</Fragment>
) : (
<Button type="button" onClick={handleEdit} secondary>
Edit
</Button>
)}
</div>
</Fragment>
);
export default compose(
connect(({ values }, ownProps) => ({
addOpen: get(values, 'create-instance-metadata-add-open', false),
metadata: get(values, 'create-instance-metadata', [])
})),
connect(null, (dispatch, { metadata = [], history }) => ({
handleNext: () => {
return history.push(`/instances/~create/networks`);
},
handleEdit: () => {
return history.push(`/instances/~create/metadata`);
},
handleAddMetadata: value => {
const toggleToClosed = set({
name: `create-instance-metadata-add-open`,
value: false
});
const appendMetadata = set({
name: `create-instance-metadata`,
value: metadata.concat([{ ...value, expanded: false }])
});
return dispatch([
destroy(FORM_NAME_CREATE),
toggleToClosed,
appendMetadata
]);
},
handleUpdateMetadata: (index, newMetadata) => {
metadata[index] = {
...newMetadata,
expanded: false
};
return dispatch([
destroy(FORM_NAME_EDIT(index)),
set({ name: `create-instance-metadata`, value: metadata.slice() })
]);
},
handleChangeAddOpen: value => {
return dispatch([
reset(FORM_NAME_CREATE),
set({ name: `create-instance-metadata-add-open`, value })
]);
},
handleToggleExpanded: index => {
metadata[index] = {
...metadata[index],
expanded: !metadata[index].expanded
};
return dispatch(
set({
name: `create-instance-metadata`,
value: metadata.slice()
})
);
},
handleCancelEdit: index => {
metadata[index] = {
...metadata[index],
expanded: false
};
return dispatch([
reset(FORM_NAME_EDIT(index)),
set({ name: `create-instance-metadata`, value: metadata.slice() })
]);
},
handleRemoveMetadata: index => {
metadata.splice(index, 1);
return dispatch([
destroy(FORM_NAME_EDIT(index)),
set({ name: `create-instance-metadata`, value: metadata.slice() })
]);
}
}))
)(Metadata);

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { withRouter } from 'react-router';
import { compose } from 'react-apollo'; import { compose } from 'react-apollo';
import ReduxForm from 'declarative-redux-form'; import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -32,7 +31,6 @@ const NameContainer = ({ expanded, name, handleSubmit, handleCancel }) => (
); );
export default compose( export default compose(
withRouter,
connect( connect(
(state, ownProps) => ({ (state, ownProps) => ({
...ownProps, ...ownProps,

View File

@ -0,0 +1,180 @@
import React, { Fragment } from 'react';
import { set } from 'react-redux-values';
import { Margin } from 'styled-components-spacing';
import { compose } from 'react-apollo';
import { destroy, reset } from 'redux-form';
import ReduxForm from 'declarative-redux-form';
import { connect } from 'react-redux';
import get from 'lodash.get';
import { TagsIcon, P, Button, H3, TagList } from 'joyent-ui-toolkit';
import Title from '@components/create-instance/title';
import Tag from '@components/instances/tags';
import KeyValue from '@components/instances/key-value';
const FORM_NAME_CREATE = 'CREATE-INSTANCE-TAGS-ADD';
const FORM_NAME_EDIT = i => `CREATE-INSTANCE-TAGS-EDIT-${i}`;
export const Tags = ({
tags = [],
expanded,
addOpen,
handleAddTag,
handleRemoveTag,
handleUpdateTag,
handleToggleExpanded,
handleCancelEdit,
handleChangeAddOpen,
handleNext,
handleEdit
}) => (
<Fragment>
<Title icon={<TagsIcon />}>Tags</Title>
{expanded ? (
<Margin bottom={3}>
<P>
Tags can be used to identify your instances, group multiple instances
together, define firewall and affinity rules, and more.{' '}
<a
target="__blank"
href="https://docs.joyent.com/public-cloud/tags-metadata/tags"
>
Read the docs
</a>
</P>
</Margin>
) : null}
<Margin bottom={4}>
<H3>
{tags.length} Tag{tags.length === 1 ? '' : 's'}
</H3>
</Margin>
<TagList>
{tags.map(({ name, value }, index) => (
<Tag
key={index}
name={name}
value={value}
onRemoveClick={() => handleRemoveTag(index)}
/>
))}
</TagList>
{expanded && addOpen ? (
<ReduxForm
form={FORM_NAME_CREATE}
destroyOnUnmount={false}
forceUnregisterOnUnmount={true}
onSubmit={handleAddTag}
>
{props => (
<KeyValue
{...props}
method="add"
input="input"
type="tag"
expanded
onCancel={() => handleChangeAddOpen(false)}
/>
)}
</ReduxForm>
) : null}
<div>
{expanded ? (
<Fragment>
<Button
type="button"
onClick={() => handleChangeAddOpen(true)}
secondary
>
Add Tag
</Button>
<Button type="button" onClick={handleNext}>
Next
</Button>
</Fragment>
) : (
<Button type="button" onClick={handleEdit} secondary>
Edit
</Button>
)}
</div>
</Fragment>
);
export default compose(
connect(({ values }, ownProps) => ({
addOpen: get(values, 'create-instance-tags-add-open', false),
tags: get(values, 'create-instance-tags', [])
})),
connect(null, (dispatch, { tags = [], history }) => ({
handleNext: () => {
return history.push(`/instances/~create/metadata`);
},
handleEdit: () => {
return history.push(`/instances/~create/tags`);
},
handleAddTag: value => {
const toggleToClosed = set({
name: `create-instance-tags-add-open`,
value: false
});
const appendTag = set({
name: `create-instance-tags`,
value: tags.concat([{ ...value, expanded: false }])
});
return dispatch([destroy(FORM_NAME_CREATE), toggleToClosed, appendTag]);
},
handleUpdateTag: (index, newTag) => {
tags[index] = {
...newTag,
expanded: false
};
return dispatch([
destroy(FORM_NAME_EDIT(index)),
set({ name: `create-instance-tags`, value: tags.slice() })
]);
},
handleChangeAddOpen: value => {
return dispatch([
reset(FORM_NAME_CREATE),
set({ name: `create-instance-tags-add-open`, value })
]);
},
handleToggleExpanded: index => {
tags[index] = {
...tags[index],
expanded: !tags[index].expanded
};
return dispatch(
set({
name: `create-instance-tags`,
value: tags.slice()
})
);
},
handleCancelEdit: index => {
tags[index] = {
...tags[index],
expanded: false
};
return dispatch([
reset(FORM_NAME_EDIT(index)),
set({ name: `create-instance-tags`, value: tags.slice() })
]);
},
handleRemoveTag: index => {
tags.splice(index, 1);
return dispatch([
destroy(FORM_NAME_EDIT(index)),
set({ name: `create-instance-tags`, value: tags.slice() })
]);
}
}))
)(Tags);

View File

@ -6786,10 +6786,26 @@ exports[`renders <Tags tags /> without throwing 1`] = `
} }
.c19 { .c19 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
}
.c20 {
border: 0.0625rem solid rgb(216,216,216); border: 0.0625rem solid rgb(216,216,216);
box-sizing: border-box; box-sizing: border-box;
border-radius: 0.1875rem; border-radius: 0.1875rem;
font-size: 0.8125rem; font-size: 0.8125rem;
line-height: 1.125rem;
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -6818,21 +6834,6 @@ exports[`renders <Tags tags /> without throwing 1`] = `
flex-wrap: wrap; flex-wrap: wrap;
} }
.c20 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
-ms-flex-positive: 1;
flex-grow: 1;
}
@media only screen and (min-width:0em) { @media only screen and (min-width:0em) {
.c2 { .c2 {
-webkit-flex-basis: 58.333333333333336%; -webkit-flex-basis: 58.333333333333336%;
@ -6986,52 +6987,52 @@ exports[`renders <Tags tags /> without throwing 1`] = `
> >
<div <div
className="c18" className="c18"
>
<li
className="c19"
onClick={undefined}
> >
<div <div
className="c19"
>
<li
className="c20" className="c20"
onClick={undefined}
> >
name1 name1
: :
value1 value1
</div>
</li> </li>
</div> </div>
</div>
<div <div
className="c18" className="c18"
>
<li
className="c19"
onClick={undefined}
> >
<div <div
className="c19"
>
<li
className="c20" className="c20"
onClick={undefined}
> >
name2 name2
: :
value2 value2
</div>
</li> </li>
</div> </div>
</div>
<div <div
className="c18" className="c18"
>
<li
className="c19"
onClick={undefined}
> >
<div <div
className="c19"
>
<li
className="c20" className="c20"
onClick={undefined}
> >
name3 name3
: :
value3 value3
</div>
</li> </li>
</div> </div>
</div>
</ul> </ul>
</div> </div>
`; `;

View File

@ -338,7 +338,9 @@ export default compose(
}); });
// reverts the individual item mutation flags // reverts the individual item mutation flags
const setMutatingFalse = name !== 'remove' && selected.map(({ id }) => const setMutatingFalse =
name !== 'remove' &&
selected.map(({ id }) =>
set({ name: `${id}-mutating`, value: false }) set({ name: `${id}-mutating`, value: false })
); );

View File

@ -29,6 +29,11 @@ export default () => (
{/* Breadcrumb */} {/* Breadcrumb */}
<Switch> <Switch>
<Route
path="/instances/~create/:section?"
exact
component={Breadcrumb}
/>
<Route <Route
path="/instances/~:action/:instance?" path="/instances/~:action/:instance?"
exact exact
@ -99,7 +104,7 @@ export default () => (
<Route <Route
path="/instances/~create/" path="/instances/~create/"
exact exact
component={({ match }) => <Redirect to="/instances/~create/name" />} component={() => <Redirect to="/instances/~create/name" />}
/> />
<Route <Route
path="/instances/~create/name" path="/instances/~create/name"

View File

@ -2,7 +2,6 @@ import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { Subscriber } from 'joy-react-broadcast'; import { Subscriber } from 'joy-react-broadcast';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
import rndId from 'rnd-id';
import isUndefined from 'lodash.isundefined'; import isUndefined from 'lodash.isundefined';
import Label from './label'; import Label from './label';
import is from 'styled-is'; import is from 'styled-is';

View File

@ -65,7 +65,7 @@ export {
Anchor as SectionListAnchor Anchor as SectionListAnchor
} from './section-list'; } from './section-list';
export { TagItem, TagList, TagItemContainer } from './tags'; export { TagItem, TagList } from './tags';
export { export {
Actions as ActionsIcon, Actions as ActionsIcon,

View File

@ -1,7 +0,0 @@
import styled from 'styled-components';
export default styled.div`
display: flex;
align-items: center;
flex-grow: 1;
`;

View File

@ -1,3 +1,2 @@
export { default as TagItem } from './item'; export { default as TagItem } from './item';
export { default as TagList } from './list'; export { default as TagList } from './list';
export { default as TagItemContainer } from './container';

View File

@ -1,12 +1,30 @@
import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import remcalc from 'remcalc'; import remcalc from 'remcalc';
import is from 'styled-is'; import is from 'styled-is';
export default styled.li` import { Close } from '../icons';
const CloseIcon = Close.extend`
margin-left: ${remcalc(12)};
${is('onClick')`
cursor: pointer;
`};
`;
const Container = styled.div`
display: flex;
align-items: center;
flex-grow: 1;
`;
const Tag = styled.li`
border: ${remcalc(1)} solid ${props => props.theme.grey}; border: ${remcalc(1)} solid ${props => props.theme.grey};
box-sizing: border-box; box-sizing: border-box;
border-radius: ${remcalc(3)}; border-radius: ${remcalc(3)};
font-size: ${remcalc(13)}; font-size: ${remcalc(13)};
line-height: ${remcalc(18)};
padding: ${remcalc(6)} ${remcalc(12)}; padding: ${remcalc(6)} ${remcalc(12)};
display: flex; display: flex;
align-items: center; align-items: center;
@ -16,3 +34,12 @@ export default styled.li`
cursor: pointer; cursor: pointer;
`}; `};
`; `;
export default ({ children, onRemoveClick, ...rest }) => (
<Container>
<Tag {...rest}>
{children}
{onRemoveClick ? <CloseIcon disabled onClick={onRemoveClick} /> : null}
</Tag>
</Container>
);

938
yarn.lock

File diff suppressed because it is too large Load Diff