Merge pull request #203 from AlexSCorey/178-AddOrgBtnV2

178 add org btn v2
This commit is contained in:
Alex Corey
2019-05-17 19:48:08 -04:00
committed by GitHub
10 changed files with 319 additions and 301 deletions

View File

@@ -15,7 +15,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
entryDelay={500} entryDelay={500}
exitDelay={500} exitDelay={500}
maxWidth="18.75rem" maxWidth="18.75rem"
position="left" position="top"
trigger="mouseenter focus" trigger="mouseenter focus"
zIndex={9999} zIndex={9999}
> >
@@ -49,7 +49,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
maxWidth="18.75rem" maxWidth="18.75rem"
onCreate={[Function]} onCreate={[Function]}
performance={true} performance={true}
placement="left" placement="top"
popperOptions={ popperOptions={
Object { Object {
"modifiers": Object { "modifiers": Object {
@@ -66,32 +66,31 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
trigger="mouseenter focus" trigger="mouseenter focus"
zIndex={9999} zIndex={9999}
> >
<ToolbarDeleteButton__Button <div>
<ToolbarDeleteButton__DeleteButton
aria-label="Delete" aria-label="Delete"
className="awx-ToolBarBtn"
isDisabled={true} isDisabled={true}
onClick={[Function]} onClick={[Function]}
variant="plain" variant="plain"
> >
<StyledComponent <StyledComponent
aria-label="Delete" aria-label="Delete"
className="awx-ToolBarBtn"
forwardedComponent={ forwardedComponent={
Object { Object {
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "ToolbarDeleteButton__Button-sc-1e3r0eg-0", "componentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"isStatic": true, "isStatic": true,
"lastClassName": "iyjqWq", "lastClassName": "bQjfFG",
"rules": Array [ "rules": Array [
"width:30px;height:30px;display:flex;justify-content:center;margin-right:20px;border-radius:3px;padding:0;&:disabled{cursor:not-allowed;&:hover{background-color:white;> svg{color:#d2d2d2;}}}&:hover{background-color:#d9534f;> svg{color:white;}}", "padding:5px 8px;&:hover{background-color:#d9534f;color:white;}&[disabled]{color:var(--pf-c-button--m-plain--Color);pointer-events:initial;cursor:not-allowed;}",
], ],
}, },
"displayName": "ToolbarDeleteButton__Button", "displayName": "ToolbarDeleteButton__DeleteButton",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "ToolbarDeleteButton__Button-sc-1e3r0eg-0", "styledComponentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -105,7 +104,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
> >
<Button <Button
aria-label="Delete" aria-label="Delete"
className="awx-ToolBarBtn ToolbarDeleteButton__Button-sc-1e3r0eg-0 iyjqWq" className="ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
component="button" component="button"
isActive={false} isActive={false}
isBlock={false} isBlock={false}
@@ -119,14 +118,13 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
<button <button
aria-disabled={null} aria-disabled={null}
aria-label="Delete" aria-label="Delete"
className="pf-c-button pf-m-plain pf-m-disabled awx-ToolBarBtn ToolbarDeleteButton__Button-sc-1e3r0eg-0 iyjqWq" className="pf-c-button pf-m-plain pf-m-disabled ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
disabled={true} disabled={true}
onClick={[Function]} onClick={[Function]}
tabIndex={null} tabIndex={null}
type="button" type="button"
> >
<TrashAltIcon <TrashAltIcon
className="awx-ToolBarTrashCanIcon"
color="currentColor" color="currentColor"
size="sm" size="sm"
title={null} title={null}
@@ -134,7 +132,6 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
<svg <svg
aria-hidden={true} aria-hidden={true}
aria-labelledby={null} aria-labelledby={null}
className="awx-ToolBarTrashCanIcon"
fill="currentColor" fill="currentColor"
height="1em" height="1em"
role="img" role="img"
@@ -155,7 +152,8 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
</button> </button>
</Button> </Button>
</StyledComponent> </StyledComponent>
</ToolbarDeleteButton__Button> </ToolbarDeleteButton__DeleteButton>
</div>
<Portal <Portal
containerInfo={ containerInfo={
<div> <div>

View File

@@ -72,21 +72,20 @@ describe('<OrganizationDetail />', () => {
const custom_virtualenvDetail = detailWrapper.findWhere(node => node.props().label === 'Ansible Environment'); const custom_virtualenvDetail = detailWrapper.findWhere(node => node.props().label === 'Ansible Environment');
const createdDetail = detailWrapper.findWhere(node => node.props().label === 'Created'); const createdDetail = detailWrapper.findWhere(node => node.props().label === 'Created');
const modifiedDetail = detailWrapper.findWhere(node => node.props().label === 'Last Modified'); const modifiedDetail = detailWrapper.findWhere(node => node.props().label === 'Last Modified');
expect(nameDetail.find('dt').text()).toBe('Name');
expect(nameDetail.find('dd').text()).toBe('Foo');
expect(nameDetail.find('h6').text()).toBe('Name'); expect(descriptionDetail.find('dt').text()).toBe('Description');
expect(nameDetail.find('p').text()).toBe('Foo'); expect(descriptionDetail.find('dd').text()).toBe('Bar');
expect(descriptionDetail.find('h6').text()).toBe('Description'); expect(custom_virtualenvDetail.find('dt').text()).toBe('Ansible Environment');
expect(descriptionDetail.find('p').text()).toBe('Bar'); expect(custom_virtualenvDetail.find('dd').text()).toBe('Fizz');
expect(custom_virtualenvDetail.find('h6').text()).toBe('Ansible Environment'); expect(createdDetail.find('dt').text()).toBe('Created');
expect(custom_virtualenvDetail.find('p').text()).toBe('Fizz'); expect(createdDetail.find('dd').text()).toBe('Bat');
expect(createdDetail.find('h6').text()).toBe('Created'); expect(modifiedDetail.find('dt').text()).toBe('Last Modified');
expect(createdDetail.find('p').text()).toBe('Bat'); expect(modifiedDetail.find('dd').text()).toBe('Boo');
expect(modifiedDetail.find('h6').text()).toBe('Last Modified');
expect(modifiedDetail.find('p').text()).toBe('Boo');
}); });
test('should show edit button for users with edit permission', () => { test('should show edit button for users with edit permission', () => {
@@ -95,9 +94,8 @@ describe('<OrganizationDetail />', () => {
organization={mockDetails} organization={mockDetails}
/> />
).find('OrganizationDetail'); ).find('OrganizationDetail');
const editButton = wrapper.find('Button');
const editLink = wrapper.findWhere(node => node.props().to === '/organizations/undefined/edit'); expect((editButton).prop('to')).toBe('/organizations/undefined/edit');
expect(editLink.length).toBe(1);
}); });
test('should hide edit button for users without edit permission', () => { test('should hide edit button for users without edit permission', () => {
@@ -109,7 +107,8 @@ describe('<OrganizationDetail />', () => {
/> />
).find('OrganizationDetail'); ).find('OrganizationDetail');
const editLink = wrapper.findWhere(node => node.props().to === '/organizations/undefined/edit'); const editLink = wrapper
.findWhere(node => node.props().to === '/organizations/undefined/edit');
expect(editLink.length).toBe(0); expect(editLink.length).toBe(0);
}); });
}); });

View File

@@ -368,11 +368,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "DataListToolbar__Toolbar-ajzso8-1", "componentId": "DataListToolbar__Toolbar-ajzso8-1",
"isStatic": false, "isStatic": false,
"lastClassName": "dwCtVz", "lastClassName": "exECbH",
"rules": Array [ "rules": Array [
"flex-grow:1;margin-left:", "flex-grow:1;margin-left:",
[Function], [Function],
";", ";margin-right:20px;",
], ],
}, },
"displayName": "DataListToolbar__Toolbar", "displayName": "DataListToolbar__Toolbar",
@@ -389,11 +389,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
marginleft={0} marginleft={0}
> >
<Toolbar <Toolbar
className="DataListToolbar__Toolbar-ajzso8-1 dwCtVz" className="DataListToolbar__Toolbar-ajzso8-1 exECbH"
marginleft={0} marginleft={0}
> >
<div <div
className="pf-l-toolbar DataListToolbar__Toolbar-ajzso8-1 dwCtVz" className="pf-l-toolbar DataListToolbar__Toolbar-ajzso8-1 exECbH"
marginleft={0} marginleft={0}
> >
<DataListToolbar__ColumnLeft> <DataListToolbar__ColumnLeft>
@@ -1563,9 +1563,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "DataListToolbar__AdditionalControlsWrapper-ajzso8-5", "componentId": "DataListToolbar__AdditionalControlsWrapper-ajzso8-5",
"isStatic": true, "isStatic": true,
"lastClassName": "cAcBQW", "lastClassName": "bWuACV",
"rules": Array [ "rules": Array [
"display:flex;flex-grow:1;justify-content:flex-end;", "display:flex;flex-grow:1;justify-content:flex-end;align-items:center;& >:not(:first-child){margin-left:20px;}",
], ],
}, },
"displayName": "DataListToolbar__AdditionalControlsWrapper", "displayName": "DataListToolbar__AdditionalControlsWrapper",
@@ -1581,7 +1581,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
forwardedRef={null} forwardedRef={null}
> >
<div <div
className="DataListToolbar__AdditionalControlsWrapper-ajzso8-5 cAcBQW" className="DataListToolbar__AdditionalControlsWrapper-ajzso8-5 bWuACV"
/> />
</StyledComponent> </StyledComponent>
</DataListToolbar__AdditionalControlsWrapper> </DataListToolbar__AdditionalControlsWrapper>
@@ -1770,9 +1770,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "sc-htpNat", "componentId": "sc-bxivhb",
"isStatic": true, "isStatic": true,
"lastClassName": "dlcYLn", "lastClassName": "lohjuH",
"rules": Array [ "rules": Array [
"margin-right: 1.5em;", "margin-right: 1.5em;",
], ],
@@ -1780,7 +1780,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(Link)", "displayName": "Styled(Link)",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "sc-htpNat", "styledComponentId": "sc-bxivhb",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -1795,7 +1795,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
> >
<Link <Link
className="sc-htpNat dlcYLn" className="sc-bxivhb lohjuH"
replace={false} replace={false}
to={ to={
Object { Object {
@@ -1804,7 +1804,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
> >
<a <a
className="sc-htpNat dlcYLn" className="sc-bxivhb lohjuH"
onClick={[Function]} onClick={[Function]}
> >
<b <b
@@ -1825,9 +1825,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "sc-bxivhb", "componentId": "sc-ifAKCX",
"isStatic": true, "isStatic": true,
"lastClassName": "iZoxgU", "lastClassName": "jGZNNo",
"rules": Array [ "rules": Array [
"text-transform: capitalize;", "text-transform: capitalize;",
], ],
@@ -1835,7 +1835,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(Badge)", "displayName": "Styled(Badge)",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "sc-bxivhb", "styledComponentId": "sc-ifAKCX",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -1846,11 +1846,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
isRead={true} isRead={true}
> >
<Badge <Badge
className="sc-bxivhb iZoxgU" className="sc-ifAKCX jGZNNo"
isRead={true} isRead={true}
> >
<span <span
className="pf-c-badge pf-m-read sc-bxivhb iZoxgU" className="pf-c-badge pf-m-read sc-ifAKCX jGZNNo"
> >
email email
</span> </span>
@@ -2245,9 +2245,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "sc-htpNat", "componentId": "sc-bxivhb",
"isStatic": true, "isStatic": true,
"lastClassName": "dlcYLn", "lastClassName": "lohjuH",
"rules": Array [ "rules": Array [
"margin-right: 1.5em;", "margin-right: 1.5em;",
], ],
@@ -2255,7 +2255,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(Link)", "displayName": "Styled(Link)",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "sc-htpNat", "styledComponentId": "sc-bxivhb",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -2270,7 +2270,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
> >
<Link <Link
className="sc-htpNat dlcYLn" className="sc-bxivhb lohjuH"
replace={false} replace={false}
to={ to={
Object { Object {
@@ -2279,7 +2279,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
} }
> >
<a <a
className="sc-htpNat dlcYLn" className="sc-bxivhb lohjuH"
onClick={[Function]} onClick={[Function]}
> >
<b <b
@@ -2300,9 +2300,9 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"$$typeof": Symbol(react.forward_ref), "$$typeof": Symbol(react.forward_ref),
"attrs": Array [], "attrs": Array [],
"componentStyle": ComponentStyle { "componentStyle": ComponentStyle {
"componentId": "sc-bxivhb", "componentId": "sc-ifAKCX",
"isStatic": true, "isStatic": true,
"lastClassName": "iZoxgU", "lastClassName": "jGZNNo",
"rules": Array [ "rules": Array [
"text-transform: capitalize;", "text-transform: capitalize;",
], ],
@@ -2310,7 +2310,7 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
"displayName": "Styled(Badge)", "displayName": "Styled(Badge)",
"foldedComponentIds": Array [], "foldedComponentIds": Array [],
"render": [Function], "render": [Function],
"styledComponentId": "sc-bxivhb", "styledComponentId": "sc-ifAKCX",
"target": [Function], "target": [Function],
"toString": [Function], "toString": [Function],
"warnTooManyClasses": [Function], "warnTooManyClasses": [Function],
@@ -2321,11 +2321,11 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
isRead={true} isRead={true}
> >
<Badge <Badge
className="sc-bxivhb iZoxgU" className="sc-ifAKCX jGZNNo"
isRead={true} isRead={true}
> >
<span <span
className="pf-c-badge pf-m-read sc-bxivhb iZoxgU" className="pf-c-badge pf-m-read sc-ifAKCX jGZNNo"
> >
email email
</span> </span>

View File

@@ -34,6 +34,7 @@ const AWXToolbar = styled.div`
const Toolbar = styled(PFToolbar)` const Toolbar = styled(PFToolbar)`
flex-grow: 1; flex-grow: 1;
margin-left: ${props => (props.marginleft ? '0' : '20px')}; margin-left: ${props => (props.marginleft ? '0' : '20px')};
margin-right: 20px;
`; `;
const ToolbarGroup = styled(PFToolbarGroup)` const ToolbarGroup = styled(PFToolbarGroup)`
@@ -67,6 +68,11 @@ const AdditionalControlsWrapper = styled.div`
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
justify-content: flex-end; justify-content: flex-end;
align-items: center;
& > :not(:first-child) {
margin-left: 20px;
}
`; `;
class DataListToolbar extends React.Component { class DataListToolbar extends React.Component {

View File

@@ -6,17 +6,17 @@ import {
DataListItemRow, DataListItemRow,
DataListItemCells, DataListItemCells,
DataListCell, DataListCell,
Text,
TextContent, TextContent,
Title, Title,
EmptyState, EmptyState,
EmptyStateIcon, EmptyStateIcon,
EmptyStateBody, EmptyStateBody
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import { CubesIcon } from '@patternfly/react-icons'; import { CubesIcon } from '@patternfly/react-icons';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withRouter, Link } from 'react-router-dom'; import { withRouter, Link } from 'react-router-dom';
import styled from 'styled-components';
import Pagination from '../Pagination'; import Pagination from '../Pagination';
import DataListToolbar from '../DataListToolbar'; import DataListToolbar from '../DataListToolbar';
@@ -24,19 +24,26 @@ import {
parseNamespacedQueryString, parseNamespacedQueryString,
updateNamespacedQueryString, updateNamespacedQueryString,
} from '../../util/qs'; } from '../../util/qs';
import { pluralize, getArticle, ucFirst } from '../../util/strings'; import { pluralize, ucFirst } from '../../util/strings';
import { QSConfig } from '../../types'; import { QSConfig } from '../../types';
const detailWrapperStyle = { const EmptyStateControlsWrapper = styled.div`
display: 'grid', display: flex;
gridTemplateColumns: 'minmax(70px, max-content) minmax(60px, max-content)', margin-top: 20px;
}; margin-right: 20px;
margin-bottom: 20px;
justify-content: flex-end;
const detailLabelStyle = { & > :not(:first-child) {
fontWeight: '700', margin-left: 20px;
lineHeight: '24px', }
marginRight: '20px', `;
};
const ListItemGrid = styled(TextContent)`
display: grid;
grid-template-columns: minmax(70px,max-content) repeat(auto-fit, minmax(60px,max-content));
grid-gap: 10px;
`;
class PaginatedDataList extends React.Component { class PaginatedDataList extends React.Component {
constructor (props) { constructor (props) {
@@ -95,6 +102,7 @@ class PaginatedDataList extends React.Component {
render () { render () {
const { const {
emptyStateControls,
items, items,
itemCount, itemCount,
qsConfig, qsConfig,
@@ -125,17 +133,25 @@ class PaginatedDataList extends React.Component {
)} )}
</Fragment> // TODO: replace with proper error handling </Fragment> // TODO: replace with proper error handling
)} )}
{items.length === 0 ? ( {items.length === 0
? (
<Fragment>
<EmptyStateControlsWrapper>
{emptyStateControls}
</EmptyStateControlsWrapper>
<div css="border-bottom: 1px solid #d2d2d2" />
<EmptyState> <EmptyState>
<EmptyStateIcon icon={CubesIcon} /> <EmptyStateIcon icon={CubesIcon} />
<Title size="lg"> <Title size="lg">
{i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found `)} {i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found `)}
</Title> </Title>
<EmptyStateBody> <EmptyStateBody>
{i18n._(t`Please add ${getArticle(itemName)} ${itemName} to populate this list`)} {i18n._(t`Please add ${ucFirst(itemNamePlural || pluralize(itemName))} to populate this list `)}
</EmptyStateBody> </EmptyStateBody>
</EmptyState> </EmptyState>
) : ( </Fragment>
)
: (
<Fragment> <Fragment>
<DataListToolbar <DataListToolbar
sortedColumnKey={orderBy} sortedColumnKey={orderBy}
@@ -148,6 +164,7 @@ class PaginatedDataList extends React.Component {
onSelectAll={onSelectAll} onSelectAll={onSelectAll}
additionalControls={additionalControls} additionalControls={additionalControls}
noLeftMargin={alignToolbarLeft} noLeftMargin={alignToolbarLeft}
/> />
<DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}> <DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}>
{items.map(item => (renderItem ? renderItem(item) : ( {items.map(item => (renderItem ? renderItem(item) : (
@@ -158,16 +175,13 @@ class PaginatedDataList extends React.Component {
<DataListItemRow> <DataListItemRow>
<DataListItemCells dataListCells={[ <DataListItemCells dataListCells={[
<DataListCell key="team-name"> <DataListCell key="team-name">
<TextContent style={detailWrapperStyle}> <ListItemGrid>
<Link to={{ pathname: item.url }}> <Link to={{ pathname: item.url }}>
<Text <b id={`items-list-item-${item.id}`}>
id={`items-list-item-${item.id}`}
style={detailLabelStyle}
>
{item.name} {item.name}
</Text> </b>
</Link> </Link>
</TextContent> </ListItemGrid>
</DataListCell> </DataListCell>
]} ]}
/> />

View File

@@ -1,22 +1,17 @@
import React from 'react'; import React from 'react';
import { string, func } from 'prop-types'; import { string, func } from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Button as PFButton } from '@patternfly/react-core'; import { Button as PFButton, Tooltip } from '@patternfly/react-core';
import { PlusIcon } from '@patternfly/react-icons'; import { PlusIcon } from '@patternfly/react-icons';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import styled from 'styled-components'; import styled from 'styled-components';
const Button = styled(PFButton)` const Button = styled(PFButton)`
&&& { /* higher specificity order */ && {
background-color: #5cb85c; background-color: #5cb85c;
min-width: 0; padding: 5px 8px;
width: 30px; --pf-global--FontSize--md: 14px;
height: 30px;
text-align: center;
padding: 0;
margin: 0;
margin-right: 20px;
} }
`; `;
@@ -25,19 +20,22 @@ function ToolbarAddButton ({ linkTo, onClick, i18n }) {
throw new Error('ToolbarAddButton requires either `linkTo` or `onClick` prop'); throw new Error('ToolbarAddButton requires either `linkTo` or `onClick` prop');
} }
if (linkTo) { if (linkTo) {
// TODO: This should only be a <Link> (no <Button>) but CSS is off
return ( return (
<Link to={linkTo}> <Tooltip
content={i18n._(t`Add`)}
position="top"
>
<Button <Button
component={Link}
to={linkTo}
variant="primary" variant="primary"
aria-label={i18n._(t`Add`)} aria-label={i18n._(t`Add`)}
> >
<PlusIcon /> <PlusIcon />
</Button> </Button>
</Link> </Tooltip>
); );
} }
return ( return (
<Button <Button
variant="primary" variant="primary"

View File

@@ -1,6 +1,6 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { func, bool, number, string, arrayOf, shape } from 'prop-types'; import { func, bool, number, string, arrayOf, shape } from 'prop-types';
import { Button as PFButton, Tooltip } from '@patternfly/react-core'; import { Button, Tooltip } from '@patternfly/react-core';
import { TrashAltIcon } from '@patternfly/react-icons'; import { TrashAltIcon } from '@patternfly/react-icons';
import styled from 'styled-components'; import styled from 'styled-components';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
@@ -8,32 +8,18 @@ import { t } from '@lingui/macro';
import AlertModal from '../AlertModal'; import AlertModal from '../AlertModal';
import { pluralize } from '../../util/strings'; import { pluralize } from '../../util/strings';
const Button = styled(PFButton)` const DeleteButton = styled(Button)`
width: 30px; padding: 5px 8px;
height: 30px;
display: flex;
justify-content: center;
margin-right: 20px;
border-radius: 3px;
padding: 0;
&:disabled {
cursor: not-allowed;
&:hover {
background-color: white;
> svg {
color: #d2d2d2;
}
}
}
&:hover { &:hover {
background-color:#d9534f; background-color:#d9534f;
> svg {
color: white; color: white;
} }
&[disabled] {
color: var(--pf-c-button--m-plain--Color);
pointer-events: initial;
cursor: not-allowed;
} }
`; `;
@@ -118,21 +104,25 @@ class ToolbarDeleteButton extends React.Component {
const isDisabled = itemsToDelete.length === 0 const isDisabled = itemsToDelete.length === 0
|| itemsToDelete.some(cannotDelete); || itemsToDelete.some(cannotDelete);
// NOTE: Once PF supports tooltips on disabled elements,
// we can delete the extra <div> around the <DeleteButton> below.
// See: https://github.com/patternfly/patternfly-react/issues/1894
return ( return (
<Fragment> <Fragment>
<Tooltip <Tooltip
content={this.renderTooltip()} content={this.renderTooltip()}
position="left" position="top"
> >
<Button <div>
className="awx-ToolBarBtn" <DeleteButton
variant="plain" variant="plain"
aria-label={i18n._(t`Delete`)} aria-label={i18n._(t`Delete`)}
onClick={this.handleConfirmDelete} onClick={this.handleConfirmDelete}
isDisabled={isDisabled} isDisabled={isDisabled}
> >
<TrashAltIcon className="awx-ToolBarTrashCanIcon" /> <TrashAltIcon />
</Button> </DeleteButton>
</div>
</Tooltip> </Tooltip>
{ isModalOpen && ( { isModalOpen && (
<AlertModal <AlertModal

View File

@@ -113,12 +113,6 @@ class Organization extends Component {
isAdminOfThisOrg isAdminOfThisOrg
} = this.state; } = this.state;
const tabsStyle = {
paddingTop: '0px',
paddingLeft: '0px',
paddingRight: '0px',
};
const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin || isAuditorOfThisOrg; const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin || isAuditorOfThisOrg;
const canToggleNotifications = isNotifAdmin && ( const canToggleNotifications = isNotifAdmin && (
me.is_system_auditor me.is_system_auditor
@@ -141,11 +135,9 @@ class Organization extends Component {
} }
let cardHeader = ( let cardHeader = (
loading ? '' loading ? '' : (
: ( <CardHeader style={{ padding: 0 }}>
<CardHeader <React.Fragment>
style={tabsStyle}
>
<div className="awx-orgTabs-container"> <div className="awx-orgTabs-container">
<RoutedTabs <RoutedTabs
match={match} match={match}
@@ -158,8 +150,10 @@ class Organization extends Component {
className="awx-orgTabs__bottom-border" className="awx-orgTabs__bottom-border"
/> />
</div> </div>
</React.Fragment>
</CardHeader> </CardHeader>
)); )
);
if (!match) { if (!match) {
cardHeader = null; cardHeader = null;
} }

View File

@@ -4,45 +4,60 @@ import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { import {
CardBody, CardBody as PFCardBody,
Button, Button,
Text, TextList,
TextContent, TextListItem,
TextVariants, TextListVariants,
TextListItemVariants,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import styled from 'styled-components';
import { withNetwork } from '../../../../contexts/Network'; import { withNetwork } from '../../../../contexts/Network';
import BasicChip from '../../../../components/BasicChip/BasicChip'; import BasicChip from '../../../../components/BasicChip/BasicChip';
const detailWrapperStyle = { const CardBody = styled(PFCardBody)`
display: 'flex' padding-top: 20px;
}; `;
const detailLabelStyle = { const DetailList = styled(TextList)`
fontWeight: '700', display: grid;
lineHeight: '24px', grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
marginRight: '20px', grid-gap: 20px;
minWidth: '150px',
textAlign: 'right'
};
const detailValueStyle = { & > div {
lineHeight: '24px', display: grid;
wordBreak: 'break-all' grid-template-columns: 10em 1fr;
}; grid-gap: 20px;
}
`;
const DetailName = styled(TextListItem)`
&& {
grid-column: 1;
font-weight: var(--pf-global--FontWeight--bold);
text-align: right;
}
`;
const DetailValue = styled(TextListItem)`
&& {
grid-column: 2;
word-break: break-all;
}
`;
const InstanceGroupsDetail = styled.div`
grid-column: 1 / -1;
`;
const Detail = ({ label, value }) => { const Detail = ({ label, value }) => {
let detail = null; if (!value) return null;
if (value) { return (
detail = ( <div>
<TextContent style={detailWrapperStyle}> <DetailName component={TextListItemVariants.dt}>{label}</DetailName>
<Text component={TextVariants.h6} style={detailLabelStyle}>{ label }</Text> <DetailValue component={TextListItemVariants.dd}>{value}</DetailValue>
<Text component={TextVariants.p} style={detailValueStyle}>{ value }</Text> </div>
</TextContent>
); );
}
return detail;
}; };
class OrganizationDetail extends Component { class OrganizationDetail extends Component {
@@ -129,7 +144,7 @@ class OrganizationDetail extends Component {
return ( return (
<CardBody> <CardBody>
<div className="pf-l-grid pf-m-gutter pf-m-all-12-col-on-md pf-m-all-6-col-on-lg pf-m-all-4-col-on-xl"> <DetailList component={TextListVariants.dl}>
<Detail <Detail
label={i18n._(t`Name`)} label={i18n._(t`Name`)}
value={name} value={name}
@@ -151,25 +166,25 @@ class OrganizationDetail extends Component {
value={modified} value={modified}
/> />
{(instanceGroups && instanceGroups.length > 0) && ( {(instanceGroups && instanceGroups.length > 0) && (
<TextContent style={{ display: 'flex', gridColumn: '1 / -1' }}> <InstanceGroupsDetail>
<Text <DetailName component={TextListItemVariants.dt}>
component={TextVariants.h6}
style={detailLabelStyle}
>
{i18n._(t`Instance Groups`)} {i18n._(t`Instance Groups`)}
</Text> </DetailName>
<div style={detailValueStyle}> <DetailValue component={TextListItemVariants.dd}>
{instanceGroupChips} {instanceGroupChips}
{overflowChip} {overflowChip}
</div> </DetailValue>
</TextContent> </InstanceGroupsDetail>
)} )}
</div> </DetailList>
{summary_fields.user_capabilities.edit && ( {summary_fields.user_capabilities.edit && (
<div style={{ display: 'flex', flexDirection: 'row-reverse', marginTop: '20px' }}> <div css="margin-top: 10px; text-align: right;">
<Link to={`/organizations/${match.params.id}/edit`}> <Button
<Button>{i18n._(t`Edit`)}</Button> component={Link}
</Link> to={`/organizations/${match.params.id}/edit`}
>
{i18n._(t`Edit`)}
</Button>
</div> </div>
)} )}
{error ? 'error!' : ''} {error ? 'error!' : ''}

View File

@@ -186,6 +186,10 @@ class OrganizationsList extends Component {
onSelect={() => this.handleSelect(o)} onSelect={() => this.handleSelect(o)}
/> />
)} )}
emptyStateControls={
canAdd ? <ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
: null
}
/> />
)} )}
{ isLoading ? <div>loading...</div> : '' } { isLoading ? <div>loading...</div> : '' }