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,96 +66,94 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
trigger="mouseenter focus" trigger="mouseenter focus"
zIndex={9999} zIndex={9999}
> >
<ToolbarDeleteButton__Button <div>
aria-label="Delete" <ToolbarDeleteButton__DeleteButton
className="awx-ToolBarBtn"
isDisabled={true}
onClick={[Function]}
variant="plain"
>
<StyledComponent
aria-label="Delete" aria-label="Delete"
className="awx-ToolBarBtn"
forwardedComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "ToolbarDeleteButton__Button-sc-1e3r0eg-0",
"isStatic": true,
"lastClassName": "iyjqWq",
"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;}}",
],
},
"displayName": "ToolbarDeleteButton__Button",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "ToolbarDeleteButton__Button-sc-1e3r0eg-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isDisabled={true} isDisabled={true}
onClick={[Function]} onClick={[Function]}
variant="plain" variant="plain"
> >
<Button <StyledComponent
aria-label="Delete" aria-label="Delete"
className="awx-ToolBarBtn ToolbarDeleteButton__Button-sc-1e3r0eg-0 iyjqWq" forwardedComponent={
component="button" Object {
isActive={false} "$$typeof": Symbol(react.forward_ref),
isBlock={false} "attrs": Array [],
"componentStyle": ComponentStyle {
"componentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"isStatic": true,
"lastClassName": "bQjfFG",
"rules": Array [
"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__DeleteButton",
"foldedComponentIds": Array [],
"render": [Function],
"styledComponentId": "ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0",
"target": [Function],
"toString": [Function],
"warnTooManyClasses": [Function],
"withComponent": [Function],
}
}
forwardedRef={null}
isDisabled={true} isDisabled={true}
isFocus={false}
isHover={false}
onClick={[Function]} onClick={[Function]}
type="button"
variant="plain" variant="plain"
> >
<button <Button
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="ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
disabled={true} component="button"
isActive={false}
isBlock={false}
isDisabled={true}
isFocus={false}
isHover={false}
onClick={[Function]} onClick={[Function]}
tabIndex={null}
type="button" type="button"
variant="plain"
> >
<TrashAltIcon <button
className="awx-ToolBarTrashCanIcon" aria-disabled={null}
color="currentColor" aria-label="Delete"
size="sm" className="pf-c-button pf-m-plain pf-m-disabled ToolbarDeleteButton__DeleteButton-sc-1e3r0eg-0 bQjfFG"
title={null} disabled={true}
onClick={[Function]}
tabIndex={null}
type="button"
> >
<svg <TrashAltIcon
aria-hidden={true} color="currentColor"
aria-labelledby={null} size="sm"
className="awx-ToolBarTrashCanIcon" title={null}
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 448 512"
width="1em"
> >
<path <svg
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z" aria-hidden={true}
transform="" aria-labelledby={null}
/> fill="currentColor"
</svg> height="1em"
</TrashAltIcon> role="img"
</button> style={
</Button> Object {
</StyledComponent> "verticalAlign": "-0.125em",
</ToolbarDeleteButton__Button> }
}
viewBox="0 0 448 512"
width="1em"
>
<path
d="M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"
transform=""
/>
</svg>
</TrashAltIcon>
</button>
</Button>
</StyledComponent>
</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,72 +133,78 @@ 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
<EmptyState> ? (
<EmptyStateIcon icon={CubesIcon} /> <Fragment>
<Title size="lg"> <EmptyStateControlsWrapper>
{i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found`)} {emptyStateControls}
</Title> </EmptyStateControlsWrapper>
<EmptyStateBody> <div css="border-bottom: 1px solid #d2d2d2" />
{i18n._(t`Please add ${getArticle(itemName)} ${itemName} to populate this list`)} <EmptyState>
</EmptyStateBody> <EmptyStateIcon icon={CubesIcon} />
</EmptyState> <Title size="lg">
) : ( {i18n._(t`No ${ucFirst(itemNamePlural || pluralize(itemName))} Found `)}
<Fragment> </Title>
<DataListToolbar <EmptyStateBody>
sortedColumnKey={orderBy} {i18n._(t`Please add ${ucFirst(itemNamePlural || pluralize(itemName))} to populate this list `)}
sortOrder={sortOrder} </EmptyStateBody>
columns={columns} </EmptyState>
onSearch={() => { }} </Fragment>
onSort={this.handleSort} )
showSelectAll={showSelectAll} : (
isAllSelected={isAllSelected} <Fragment>
onSelectAll={onSelectAll} <DataListToolbar
additionalControls={additionalControls} sortedColumnKey={orderBy}
noLeftMargin={alignToolbarLeft} sortOrder={sortOrder}
/> columns={columns}
<DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}> onSearch={() => { }}
{items.map(item => (renderItem ? renderItem(item) : ( onSort={this.handleSort}
<DataListItem showSelectAll={showSelectAll}
aria-labelledby={`items-list-item-${item.id}`} isAllSelected={isAllSelected}
key={item.id} onSelectAll={onSelectAll}
> additionalControls={additionalControls}
<DataListItemRow> noLeftMargin={alignToolbarLeft}
<DataListItemCells dataListCells={[
<DataListCell key="team-name"> />
<TextContent style={detailWrapperStyle}> <DataList aria-label={i18n._(t`${ucFirst(pluralize(itemName))} List`)}>
<Link to={{ pathname: item.url }}> {items.map(item => (renderItem ? renderItem(item) : (
<Text <DataListItem
id={`items-list-item-${item.id}`} aria-labelledby={`items-list-item-${item.id}`}
style={detailLabelStyle} key={item.id}
> >
{item.name} <DataListItemRow>
</Text> <DataListItemCells dataListCells={[
</Link> <DataListCell key="team-name">
</TextContent> <ListItemGrid>
</DataListCell> <Link to={{ pathname: item.url }}>
]} <b id={`items-list-item-${item.id}`}>
/> {item.name}
</DataListItemRow> </b>
</DataListItem> </Link>
)))} </ListItemGrid>
</DataList> </DataListCell>
<Pagination ]}
variant="bottom" />
itemCount={itemCount} </DataListItemRow>
page={queryParams.page || 1} </DataListItem>
perPage={queryParams.page_size} )))}
perPageOptions={showPageSizeOptions ? [ </DataList>
{ title: '5', value: 5 }, <Pagination
{ title: '10', value: 10 }, variant="bottom"
{ title: '20', value: 20 }, itemCount={itemCount}
{ title: '50', value: 50 } page={queryParams.page || 1}
] : []} perPage={queryParams.page_size}
onSetPage={this.handleSetPage} perPageOptions={showPageSizeOptions ? [
onPerPageSelect={this.handleSetPageSize} { title: '5', value: 5 },
/> { title: '10', value: 10 },
</Fragment> { title: '20', value: 20 },
)} { title: '50', value: 50 }
] : []}
onSetPage={this.handleSetPage}
onPerPageSelect={this.handleSetPageSize}
/>
</Fragment>
)}
</Fragment> </Fragment>
); );
} }

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>
</CardHeader> </React.Fragment>
)); </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> : '' }