From 29e17ac49e48e6bb4dd1c91632b3d10a37fb1b0d Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 28 May 2019 08:49:03 -0400 Subject: [PATCH] Standardize chip height (#213) * make all chips the same size * create DetailList, Detail components; clean up Chips, ChipGroup * delete BasicChip in favor of * create our own ChipGroup to handle overflow --- __tests__/components/Chip/ChipGroup.test.jsx | 69 ++ __tests__/components/Lookup.test.jsx | 2 +- .../DeleteToolbarButton.test.jsx.snap | 1 + __tests__/components/SelectedList.test.jsx | 74 +- .../DeleteRoleConfirmationModal.test.jsx.snap | 558 +++++---- .../OrganizationAccessItem.test.jsx.snap | 1047 ++++++++++++----- .../OrganizationNotifications.test.jsx.snap | 65 +- package-lock.json | 54 +- package.json | 4 +- src/app.scss | 7 - src/components/BasicChip/BasicChip.jsx | 21 - src/components/BasicChip/basicChip.scss | 18 - src/components/Chip/Chip.jsx | 18 + src/components/Chip/ChipGroup.jsx | 41 + src/components/Chip/index.js | 2 + src/components/DetailList/Detail.jsx | 56 + src/components/DetailList/DetailList.jsx | 28 + src/components/DetailList/index.js | 2 + src/components/Lookup/Lookup.jsx | 6 +- src/components/SelectedList/SelectedList.jsx | 116 +- src/components/SelectedList/styles.scss | 22 - .../VerticalSeparator/VerticalSeparator.jsx | 23 +- src/index.jsx | 1 - .../components/OrganizationAccessItem.jsx | 164 ++- .../Organization/OrganizationDetail.jsx | 106 +- 25 files changed, 1455 insertions(+), 1050 deletions(-) create mode 100644 __tests__/components/Chip/ChipGroup.test.jsx delete mode 100644 src/components/BasicChip/BasicChip.jsx delete mode 100644 src/components/BasicChip/basicChip.scss create mode 100644 src/components/Chip/Chip.jsx create mode 100644 src/components/Chip/ChipGroup.jsx create mode 100644 src/components/Chip/index.js create mode 100644 src/components/DetailList/Detail.jsx create mode 100644 src/components/DetailList/DetailList.jsx create mode 100644 src/components/DetailList/index.js delete mode 100644 src/components/SelectedList/styles.scss diff --git a/__tests__/components/Chip/ChipGroup.test.jsx b/__tests__/components/Chip/ChipGroup.test.jsx new file mode 100644 index 0000000000..982f52592c --- /dev/null +++ b/__tests__/components/Chip/ChipGroup.test.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithContexts } from '../../enzymeHelpers'; +import { ChipGroup, Chip } from '../../../src/components/Chip'; + +describe('', () => { + test('should render all chips', () => { + const wrapper = mountWithContexts( + + One + Two + Three + Four + Five + Six + + ); + expect(wrapper.find(Chip)).toHaveLength(6); + expect(wrapper.find('li')).toHaveLength(6); + }); + + test('should render show more toggle', () => { + const wrapper = mountWithContexts( + + One + Two + Three + Four + Five + Six + Seven + + ); + expect(wrapper.find(Chip)).toHaveLength(6); + const toggle = wrapper.find(Chip).at(5); + expect(toggle.prop('isOverflowChip')).toBe(true); + expect(toggle.text()).toEqual('2 more'); + }); + + test('should render show less toggle', () => { + const wrapper = mountWithContexts( + + One + Two + Three + Four + Five + Six + Seven + + ); + expect(wrapper.find(Chip)).toHaveLength(6); + const toggle = wrapper.find(Chip).at(5); + expect(toggle.prop('isOverflowChip')).toBe(true); + act(() => { + toggle.prop('onClick')(); + }); + wrapper.update(); + expect(wrapper.find(Chip)).toHaveLength(8); + expect(wrapper.find(Chip).at(7).text()).toEqual('Show Less'); + act(() => { + const toggle2 = wrapper.find(Chip).at(7); + expect(toggle2.prop('isOverflowChip')).toBe(true); + toggle2.prop('onClick')(); + }); + wrapper.update(); + expect(wrapper.find(Chip)).toHaveLength(6); + }); +}); diff --git a/__tests__/components/Lookup.test.jsx b/__tests__/components/Lookup.test.jsx index 5fb3630995..9fee58c795 100644 --- a/__tests__/components/Lookup.test.jsx +++ b/__tests__/components/Lookup.test.jsx @@ -141,7 +141,7 @@ describe('', () => { sortedColumnKey="name" /> ).find('Lookup'); - const chip = wrapper.find('li.pf-c-chip'); + const chip = wrapper.find('.pf-c-chip'); expect(chip).toHaveLength(2); }); diff --git a/__tests__/components/PaginatedDataList/__snapshots__/DeleteToolbarButton.test.jsx.snap b/__tests__/components/PaginatedDataList/__snapshots__/DeleteToolbarButton.test.jsx.snap index 037450f8d3..323a45080b 100644 --- a/__tests__/components/PaginatedDataList/__snapshots__/DeleteToolbarButton.test.jsx.snap +++ b/__tests__/components/PaginatedDataList/__snapshots__/DeleteToolbarButton.test.jsx.snap @@ -111,6 +111,7 @@ exports[` should render button 1`] = ` isDisabled={true} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" diff --git a/__tests__/components/SelectedList.test.jsx b/__tests__/components/SelectedList.test.jsx index 4eaa52bf61..861a0e052f 100644 --- a/__tests__/components/SelectedList.test.jsx +++ b/__tests__/components/SelectedList.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { mount } from 'enzyme'; import SelectedList from '../../src/components/SelectedList'; +import { ChipGroup } from '../../src/components/Chip'; describe('', () => { test('initially renders succesfully', () => { @@ -22,7 +23,8 @@ describe('', () => { /> ); }); - test('showOverflow should set showOverflow state to true', () => { + + test('showOverflow should set showOverflow on ChipGroup', () => { const wrapper = mount( ', () => { onRemove={() => {}} /> ); - expect(wrapper.state('showOverflow')).toBe(false); - wrapper.instance().showOverflow(); - expect(wrapper.state('showOverflow')).toBe(true); - }); - test('Overflow chip should be shown when more selected.length exceeds showOverflowAfter', () => { - const mockSelected = [ - { - id: 1, - name: 'foo' - }, { - id: 2, - name: 'bar' - }, { - id: 3, - name: 'foobar' - }, { - id: 4, - name: 'baz' - }, { - id: 5, - name: 'foobaz' - } - ]; - const wrapper = mount( - {}} - /> - ); - expect(wrapper.find('Chip').length).toBe(4); - expect(wrapper.find('[isOverflowChip=true]').length).toBe(1); - }); - test('Clicking overflow chip should show all chips', () => { - const mockSelected = [ - { - id: 1, - name: 'foo' - }, { - id: 2, - name: 'bar' - }, { - id: 3, - name: 'foobar' - }, { - id: 4, - name: 'baz' - }, { - id: 5, - name: 'foobaz' - } - ]; - const wrapper = mount( - {}} - /> - ); - expect(wrapper.find('Chip').length).toBe(4); - expect(wrapper.find('[isOverflowChip=true]').length).toBe(1); - wrapper.find('[isOverflowChip=true] button').simulate('click'); - expect(wrapper.find('Chip').length).toBe(5); - expect(wrapper.find('[isOverflowChip=true]').length).toBe(0); + const chipGroup = wrapper.find(ChipGroup); + expect(chipGroup).toHaveLength(1); + expect(chipGroup.prop('showOverflowAfter')).toEqual(5); }); + test('Clicking remove on chip calls onRemove callback prop with correct params', () => { const onRemove = jest.fn(); const mockSelected = [ diff --git a/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap b/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap index 015774b1c2..5c952a0815 100644 --- a/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap +++ b/__tests__/pages/Organizations/components/__snapshots__/DeleteRoleConfirmationModal.test.jsx.snap @@ -29,6 +29,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="danger" @@ -44,6 +45,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="secondary" @@ -69,6 +71,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="danger" @@ -84,6 +87,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="secondary" @@ -112,85 +116,81 @@ exports[` should render initially 1`] = ` class="pf-l-bullseye" > @@ -210,6 +210,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="danger" @@ -225,6 +226,7 @@ exports[` should render initially 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="secondary" @@ -250,233 +252,227 @@ exports[` should render initially 1`] = `
-
-
- + + + + + + + <h3 + className="pf-c-title pf-m-2xl" + > + + Remove {0} Access + + </h3> + + + - + + +
+ + + + + + +
+
-
+
-
+
diff --git a/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap b/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap index d8a646bade..9f7d483a8c 100644 --- a/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap +++ b/__tests__/pages/Organizations/components/__snapshots__/OrganizationAccessItem.test.jsx.snap @@ -50,8 +50,7 @@ exports[` initially renders succesfully 1`] = `
- initially renders succesfully 1`] = ` > - - jane - - + + - + + + , initially renders succesfully 1`] = ` isIcon={false} width={1} > -
    - + + Member + + } - > - Team Roles - - - Member - -
+ /> +
, ] } key=".0" rowid="access-list-item" > -
- -
- - - - -
+ + jane + + + + + + + , + + + + + Member + + } + /> + + , + ] + } + forwardedComponent={ + Object { + "$$typeof": Symbol(react.forward_ref), + "attrs": Array [], + "componentStyle": ComponentStyle { + "componentId": "OrganizationAccessItem__DataListItemCells-sc-1yema4k-0", + "isStatic": true, + "lastClassName": "OJmEc", + "rules": Array [ + "align-items:start;", + ], + }, + "displayName": "OrganizationAccessItem__DataListItemCells", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "OrganizationAccessItem__DataListItemCells-sc-1yema4k-0", + "target": [Function], + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + } + } + forwardedRef={null} + rowid="access-list-item" + > + + -
- Name -
+ jane +
- + + + + , + + + + + Member + + } - > -

- jane brown -

-
-
-
-
-
-
- + + , + ] + } + rowid="access-list-item" >
-
    - -
    - Team Roles -
    -
    - - -
  • - - Member - - - - - -
  • -
    -
    -
+ + jane + + + + + + +
+ + + + + +
+ + + + + +
+ Name +
+
+
+
+
+ + + + +
+ jane brown +
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ + + + +
+ + + Member + + + } + > + + + + +
+ Team Roles +
+
+
+
+
+ + + + +
+ + + +
    + + .pf-c-button{padding:3px 8px;}", + [Function], + ], + }, + "displayName": "Chip", + "foldedComponentIds": Array [], + "render": [Function], + "styledComponentId": "Chip-sc-1rzr8oo-0", + "target": [Function], + "toString": [Function], + "warnTooManyClasses": [Function], + "withComponent": [Function], + } + } + forwardedRef={null} + isReadOnly={false} + onClick={[Function]} + > + + +
  • + + Member + + + + + +
  • +
    +
    +
    +
    +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- - - + + + diff --git a/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap index 1a61793f72..6328d9bff3 100644 --- a/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap +++ b/__tests__/pages/Organizations/screens/Organization/__snapshots__/OrganizationNotifications.test.jsx.snap @@ -714,7 +714,7 @@ exports[` initially renders succesfully 1`] = ` } > - initially renders succesfully 1`] = ` - + @@ -932,6 +932,7 @@ exports[` initially renders succesfully 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="tertiary" @@ -984,20 +985,37 @@ exports[` initially renders succesfully 1`] = `
- + + forwardedRef={null} + > + + +
@@ -1327,7 +1345,7 @@ exports[` initially renders succesfully 1`] = ` } > - initially renders succesfully 1`] = ` - + @@ -1466,6 +1484,7 @@ exports[` initially renders succesfully 1`] = ` isDisabled={false} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" @@ -2748,7 +2767,7 @@ exports[` initially renders succesfully 1`] = `
- initially renders succesfully 1`] = ` - initially renders succesfully 1`] = ` items - +
- + initially renders succesfully 1`] = ` isDisabled={true} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" @@ -3107,6 +3127,7 @@ exports[` initially renders succesfully 1`] = ` isDisabled={true} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" @@ -3179,6 +3200,7 @@ exports[` initially renders succesfully 1`] = ` isDisabled={true} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" @@ -3230,6 +3252,7 @@ exports[` initially renders succesfully 1`] = ` isDisabled={true} isFocus={false} isHover={false} + isInline={false} onClick={[Function]} type="button" variant="plain" diff --git a/package-lock.json b/package-lock.json index 5c40be73c6..7830da2ec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1179,6 +1179,19 @@ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.8.2.tgz", "integrity": "sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw==" }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.18.tgz", + "integrity": "sha512-834DrzO2Ne3upCW+mJJPC/E6BsFcj+2Z1HmPIhbpbj8UaKmXWum4NClqLpUiMetugRlHuG4jbIHNdv2/lc3c1Q==" + }, + "@fortawesome/free-brands-svg-icons": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.8.2.tgz", + "integrity": "sha512-nhEWctDOP6f+Ka10LXAFoF+6mtWidC2iQgTBGRGgydmhBtcIEwyxWVx5wQHa86A1zAMi5TnipDAYQs2qn7DD6A==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.18" + } + }, "@jest/console": { "version": "24.7.1", "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", @@ -1762,18 +1775,18 @@ } }, "@patternfly/patternfly": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.6.5.tgz", - "integrity": "sha512-L0k+ea2N679ytQwTqYJ7RewVDx6FDpm9burtHgD8fhcIN0UJJo7v2IkhLya2WzH2/O7L7TiF45lb/ZVoFVckDA==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-2.7.0.tgz", + "integrity": "sha512-mewkgiqaQQTLabpOUCi9BrFJ6cQAvgNtOT7Z+LHS3PKIGFKkTfjAd+hm0TktbohNQJS8KvH5qcUV/WuLZ2/UbA==" }, "@patternfly/react-core": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.2.6.tgz", - "integrity": "sha512-58GBM8SfhoggQldGArUR3LwvcMe+GaW/uTjB1/a3RTG6HXTfFfs33UXnQnN2IgWZmHOyxpjnWq2yEh5xwJs5HQ==", + "version": "3.16.14", + "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-3.16.14.tgz", + "integrity": "sha512-JKBwXs6+YlmOSQGsKOraiioLIYysdHqMYWsTgLqomXouOyAk3q30/TKQudZbRs/Iw7Gg3W2Ub9ed3pDI7D+R6Q==", "requires": { - "@patternfly/react-icons": "^3.7.4", - "@patternfly/react-styles": "^3.0.2", - "@patternfly/react-tokens": "^2.3.3", + "@patternfly/react-icons": "^3.9.1", + "@patternfly/react-styles": "^3.2.0", + "@patternfly/react-tokens": "^2.5.1", "@tippy.js/react": "^1.1.1", "emotion": "^9.2.9", "exenv": "^1.2.2", @@ -1781,14 +1794,17 @@ }, "dependencies": { "@patternfly/react-icons": { - "version": "3.7.5", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.7.5.tgz", - "integrity": "sha512-Bejd6GAWfcDgA7YxvIcrohcBPVZUG34E3LWaJSHLUf8XADf33q7UvQ4YQ1eWk477o/GjZA3AX/71Y6op71WdDA==" + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-3.9.2.tgz", + "integrity": "sha512-EwPB+Nodd7zwiFh7R3Qq6Dif+xUR3WOwaJ+SRbP5NsxEAJf3CyYyrd7rbN8yFrFLTMKzknT2ez9XrP/5Lgr5LQ==", + "requires": { + "@fortawesome/free-brands-svg-icons": "^5.8.1" + } }, "@patternfly/react-tokens": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.3.3.tgz", - "integrity": "sha512-+2SSGvOV1rZr1l6+p2QzORVJhpOKjrHqCBfkp10La7O93+mVLYU0vugTp1elhxeh32IuLCJAPDLrCJPBAfmYKw==" + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-2.5.1.tgz", + "integrity": "sha512-ZykUfWT4yCELXhGGwQxcNqnXwe3sqfSuoL6IlQRV6wtUKk+/et7NkVvrRwvci926+Usemr4IQVc8V9gbHqRN/A==" } } }, @@ -1798,9 +1814,9 @@ "integrity": "sha512-Bejd6GAWfcDgA7YxvIcrohcBPVZUG34E3LWaJSHLUf8XADf33q7UvQ4YQ1eWk477o/GjZA3AX/71Y6op71WdDA==" }, "@patternfly/react-styles": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.0.2.tgz", - "integrity": "sha512-JiGxDkC4JArQJ13RQOjSUE4jPmwrAq8f5E+qg0tVws1A9BQ2l3uGCRFMVbfa57qqjqk0jXNmIVdFSeCG18qwJQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-3.2.0.tgz", + "integrity": "sha512-8M7bo4kPvypHlbzuynV53xjk5176EktfEgZ283sfHmvUlU3Yvq2+m8hS9ERHE9gd91Ip4HiyucAVVACd2gtHNQ==", "requires": { "@babel/helper-plugin-utils": "^7.0.0-beta.48", "camel-case": "^3.0.0", @@ -3279,7 +3295,7 @@ }, "babel-plugin-syntax-object-rest-spread": { "version": "6.13.0", - "resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=" }, "babel-plugin-syntax-trailing-function-commas": { diff --git a/package.json b/package.json index 7678e5dfa3..0560116610 100644 --- a/package.json +++ b/package.json @@ -51,8 +51,8 @@ }, "dependencies": { "@lingui/react": "^2.7.2", - "@patternfly/patternfly": "^2.6.5", - "@patternfly/react-core": "^3.2.6", + "@patternfly/patternfly": "^2.7.0", + "@patternfly/react-core": "^3.16.14", "@patternfly/react-icons": "^3.7.5", "@patternfly/react-tokens": "^2.3.3", "axios": "^0.18.0", diff --git a/src/app.scss b/src/app.scss index b0eeef4e9d..8f621a094a 100644 --- a/src/app.scss +++ b/src/app.scss @@ -303,13 +303,6 @@ text-align: right; } -.awx-c-chip { - padding: 3px 0; - height: 24px; - margin-right: 10px; - margin-bottom: 10px; -} - .awx-orgTabs-container{ display: flex } diff --git a/src/components/BasicChip/BasicChip.jsx b/src/components/BasicChip/BasicChip.jsx deleted file mode 100644 index 625cdb0ff4..0000000000 --- a/src/components/BasicChip/BasicChip.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { Chip } from '@patternfly/react-core'; -import './basicChip.scss'; - -const BasicChip = ({ children, onToggle, isOverflowChip }) => ( - - {children} - -); - -BasicChip.propTypes = { - children: PropTypes.node.isRequired, -}; - -export default BasicChip; diff --git a/src/components/BasicChip/basicChip.scss b/src/components/BasicChip/basicChip.scss deleted file mode 100644 index 63c4d1d9ea..0000000000 --- a/src/components/BasicChip/basicChip.scss +++ /dev/null @@ -1,18 +0,0 @@ -.awx-c-chip--basic { - padding: 3px 8px; - height: 24px; - margin-right: 10px; - margin-bottom: 10px; - - &.pf-c-chip { - margin-top: 0; - } - - &.pf-m-overflow { - padding: 0; - } - - &:not(.pf-m-overflow) .pf-c-button { - display: none; - } -} diff --git a/src/components/Chip/Chip.jsx b/src/components/Chip/Chip.jsx new file mode 100644 index 0000000000..0186f0a081 --- /dev/null +++ b/src/components/Chip/Chip.jsx @@ -0,0 +1,18 @@ + +import { Chip } from '@patternfly/react-core'; +import styled from 'styled-components'; + +export default styled(Chip)` + --pf-c-chip--m-read-only--PaddingTop: 3px; + --pf-c-chip--m-read-only--PaddingRight: 8px; + --pf-c-chip--m-read-only--PaddingBottom: 3px; + --pf-c-chip--m-read-only--PaddingLeft: 8px; + + & > .pf-c-button { + padding: 3px 8px; + } + + ${props => (props.isOverflowChip && ` + padding: 0; + `)} +`; diff --git a/src/components/Chip/ChipGroup.jsx b/src/components/Chip/ChipGroup.jsx new file mode 100644 index 0000000000..6e9ff98156 --- /dev/null +++ b/src/components/Chip/ChipGroup.jsx @@ -0,0 +1,41 @@ +import React, { useState } from 'react'; +import { number } from 'prop-types'; +import styled from 'styled-components'; +import Chip from './Chip'; + +const ChipGroup = ({ children, className, showOverflowAfter, ...props }) => { + const [isExpanded, setIsExpanded] = useState(!showOverflowAfter); + const toggleIsOpen = () => setIsExpanded(!isExpanded); + + const mappedChildren = React.Children.map(children, c => ( + React.cloneElement(c, { component: 'li' }) + )); + const showOverflowToggle = showOverflowAfter && children.length > showOverflowAfter; + const numToShow = isExpanded + ? children.length + : Math.min(showOverflowAfter, children.length); + const expandedText = 'Show Less'; + const collapsedText = `${children.length - showOverflowAfter} more`; + + return ( +
    + {mappedChildren.slice(0, numToShow)} + {showOverflowToggle && ( + + {isExpanded ? expandedText : collapsedText} + + )} +
+ ); +}; +ChipGroup.propTypes = { + showOverflowAfter: number, +}; +ChipGroup.defaultProps = { + showOverflowAfter: null, +}; + +export default styled(ChipGroup)` + --pf-c-chip-group--c-chip--MarginRight: 10px; + --pf-c-chip-group--c-chip--MarginBottom: 10px; +`; diff --git a/src/components/Chip/index.js b/src/components/Chip/index.js new file mode 100644 index 0000000000..96e20db252 --- /dev/null +++ b/src/components/Chip/index.js @@ -0,0 +1,2 @@ +export { default as ChipGroup } from './ChipGroup'; +export { default as Chip } from './Chip'; diff --git a/src/components/DetailList/Detail.jsx b/src/components/DetailList/Detail.jsx new file mode 100644 index 0000000000..1c6e4b4579 --- /dev/null +++ b/src/components/DetailList/Detail.jsx @@ -0,0 +1,56 @@ +import React, { Fragment } from 'react'; +import { node, bool } from 'prop-types'; +import { TextListItem, TextListItemVariants } from '@patternfly/react-core'; +import styled from 'styled-components'; + +const DetailName = styled(({ fullWidth, ...props }) => ( + +))` + font-weight: var(--pf-global--FontWeight--bold); + text-align: right; + ${props => props.fullWidth && ` + grid-column: 1; + `} +`; + +const DetailValue = styled(({ fullWidth, ...props }) => ( + +))` + word-break: break-all; + ${props => props.fullWidth && ` + grid-column: 2 / -1; + `} +`; + +const Detail = ({ label, value, fullWidth }) => { + if (!value) return null; + return ( + + + {label} + + + {value} + + + ); +}; +Detail.propTypes = { + label: node.isRequired, + value: node, + fullWidth: bool, +}; +Detail.defaultProps = { + value: null, + fullWidth: false, +}; + +export default Detail; +export { DetailName }; +export { DetailValue }; diff --git a/src/components/DetailList/DetailList.jsx b/src/components/DetailList/DetailList.jsx new file mode 100644 index 0000000000..8925d76043 --- /dev/null +++ b/src/components/DetailList/DetailList.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { TextList, TextListVariants } from '@patternfly/react-core'; +import styled from 'styled-components'; + +const DetailList = ({ children, stacked, ...props }) => ( + + {children} + +); + +export default styled(DetailList)` + display: grid; + grid-gap: 20px; + ${props => (props.stacked ? (` + grid-template-columns: auto 1fr; + `) : (` + --column-count: 1; + grid-template-columns: repeat(var(--column-count), auto minmax(10em, 1fr)); + + @media (min-width: 920px) { + --column-count: 2; + } + + @media (min-width: 1210px) { + --column-count: 3; + } + `))} +`; diff --git a/src/components/DetailList/index.js b/src/components/DetailList/index.js new file mode 100644 index 0000000000..665470d178 --- /dev/null +++ b/src/components/DetailList/index.js @@ -0,0 +1,2 @@ +export { default as DetailList } from './DetailList'; +export { default as Detail, DetailName, DetailValue } from './Detail'; diff --git a/src/components/Lookup/Lookup.jsx b/src/components/Lookup/Lookup.jsx index 4df08cef8f..999bcb9e04 100644 --- a/src/components/Lookup/Lookup.jsx +++ b/src/components/Lookup/Lookup.jsx @@ -5,7 +5,6 @@ import { SearchIcon } from '@patternfly/react-icons'; import { Button, ButtonVariant, - Chip, InputGroup, Modal, } from '@patternfly/react-core'; @@ -17,6 +16,7 @@ import PaginatedDataList from '../PaginatedDataList'; import DataListToolbar from '../DataListToolbar'; import CheckboxListItem from '../ListItem'; import SelectedList from '../SelectedList'; +import { ChipGroup, Chip } from '../Chip'; import { getQSConfig, parseNamespacedQueryString } from '../../util/qs'; class Lookup extends React.Component { @@ -128,13 +128,13 @@ class Lookup extends React.Component { const header = lookupHeader || i18n._(t`items`); const chips = value ? ( -
+ {value.map(chip => ( this.toggleSelected(chip)}> {chip.name} ))} -
+
) : null; return ( diff --git a/src/components/SelectedList/SelectedList.jsx b/src/components/SelectedList/SelectedList.jsx index 485eaa615c..24d1b5ab53 100644 --- a/src/components/SelectedList/SelectedList.jsx +++ b/src/components/SelectedList/SelectedList.jsx @@ -1,40 +1,24 @@ -import React, { Component, Fragment } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { - Chip -} from '@patternfly/react-core'; - -import BasicChip from '../BasicChip/BasicChip'; +import { Split as PFSplit, SplitItem } from '@patternfly/react-core'; +import styled from 'styled-components'; +import { ChipGroup, Chip } from '../Chip'; import VerticalSeparator from '../VerticalSeparator'; -const selectedRowStyling = { - paddingTop: '15px', - paddingBottom: '5px', - borderLeft: '0', - borderRight: '0' -}; +const Split = styled(PFSplit)` + padding-top: 15px; + padding-bottom: 5px; + border-bottom: #ebebeb var(--pf-global--BorderWidth--sm) solid; + align-items: baseline; +`; -const selectedLabelStyling = { - alignSelf: 'center', - fontSize: '14px', - fontWeight: 'bold' -}; +const SplitLabelItem = styled(SplitItem)` + font-size: 14px; + font-weight: bold; + word-break: initial; +`; class SelectedList extends Component { - constructor (props) { - super(props); - - this.state = { - showOverflow: false - }; - - this.showOverflow = this.showOverflow.bind(this); - } - - showOverflow = () => { - this.setState({ showOverflow: true }); - }; - render () { const { label, @@ -44,58 +28,26 @@ class SelectedList extends Component { displayKey, isReadOnly } = this.props; - const { showOverflow } = this.state; - const visibleItems = selected.slice(0, showOverflow ? selected.length : showOverflowAfter); return ( -
-
-
- {label} -
- -
-
- {isReadOnly ? ( - - {visibleItems - .map(selectedItem => ( - - {selectedItem[displayKey]} - - )) - } - - ) : ( - - {visibleItems - .map(selectedItem => ( - onRemove(selectedItem)} - > - {selectedItem[displayKey]} - - )) - } - - )} - {( - !showOverflow - && selected.length > showOverflowAfter - ) && ( - this.showOverflow()} - > - {`${(selected.length - showOverflowAfter).toString()} more`} - - )} -
-
-
-
+ + + {label} + + + + + {selected.map(item => ( + onRemove(item)} + > + {item[displayKey]} + + ))} + + + ); } } diff --git a/src/components/SelectedList/styles.scss b/src/components/SelectedList/styles.scss deleted file mode 100644 index 7db18e2c3f..0000000000 --- a/src/components/SelectedList/styles.scss +++ /dev/null @@ -1,22 +0,0 @@ -.awx-selectedList { - --awx-selectedList--BackgroundColor: var(--pf-global--BackgroundColor--light-100); - --awx-selectedList--BorderColor: #ebebeb; - --awx-selectedList--BorderWidth: var(--pf-global--BorderWidth--sm); - --awx-selectedList--FontSize: var(--pf-c-chip__text--FontSize); - - - .pf-l-split { - padding-top: 20px; - padding-bottom: 10px; - border-bottom: var(--awx-selectedList--BorderWidth) solid var(--awx-selectedList--BorderColor); - } - .pf-l-split__item:first-child { - display: flex; - white-space: nowrap; - height: 30px; - } - .pf-c-chip { - margin-right: 10px; - margin-bottom: 10px; - } -} \ No newline at end of file diff --git a/src/components/VerticalSeparator/VerticalSeparator.jsx b/src/components/VerticalSeparator/VerticalSeparator.jsx index 90f832ec3d..ebe53e5ada 100644 --- a/src/components/VerticalSeparator/VerticalSeparator.jsx +++ b/src/components/VerticalSeparator/VerticalSeparator.jsx @@ -1,18 +1,19 @@ import React from 'react'; +import styled from 'styled-components'; + +const Separator = styled.span` + display: inline-block; + width: 1px; + height: 30px; + margin-right: 20px; + margin-left: 20px; + background-color: #d7d7d7; + vertical-align: middle; +`; const VerticalSeparator = () => (
- +
); diff --git a/src/index.jsx b/src/index.jsx index 95fa2ae4fb..3af4df738b 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -12,7 +12,6 @@ import { t } from '@lingui/macro'; import '@patternfly/react-core/dist/styles/base.css'; import './app.scss'; -import './components/SelectedList/styles.scss'; import './components/AddRole/styles.scss'; import { Config } from './contexts/Config'; diff --git a/src/pages/Organizations/components/OrganizationAccessItem.jsx b/src/pages/Organizations/components/OrganizationAccessItem.jsx index f6caf4a499..773553c323 100644 --- a/src/pages/Organizations/components/OrganizationAccessItem.jsx +++ b/src/pages/Organizations/components/OrganizationAccessItem.jsx @@ -5,55 +5,21 @@ import { t } from '@lingui/macro'; import { DataListItem, DataListItemRow, - DataListItemCells, + DataListItemCells as PFDataListItemCells, DataListCell, Text, TextContent, TextVariants, - Chip, } from '@patternfly/react-core'; import { Link } from 'react-router-dom'; +import styled from 'styled-components'; import { AccessRecord } from '../../../types'; -import BasicChip from '../../../components/BasicChip/BasicChip'; +import { DetailList, Detail } from '../../../components/DetailList'; +import { ChipGroup, Chip } from '../../../components/Chip'; -const userRolesWrapperStyle = { - display: 'flex', - flexWrap: 'wrap', -}; - -const detailWrapperStyle = { - display: 'grid', - gridTemplateColumns: 'minmax(70px, max-content) minmax(60px, max-content)', -}; - -const detailLabelStyle = { - fontWeight: '700', - lineHeight: '24px', - marginRight: '20px', -}; - -const detailValueStyle = { - lineHeight: '28px', - overflow: 'visible', -}; - -/* TODO: does PF offer any sort of
treatment for this? */ -const Detail = ({ label, value, url, customStyles }) => { - let detail = null; - if (value) { - detail = ( - - {url ? ( - - {label} - ) : ({label} - )} - {value} - - ); - } - return detail; -}; +const DataListItemCells = styled(PFDataListItemCells)` + align-items: start; +`; class OrganizationAccessItem extends React.Component { static propTypes = { @@ -61,6 +27,11 @@ class OrganizationAccessItem extends React.Component { onRoleDelete: func.isRequired, }; + constructor (props) { + super(props); + this.renderChip = this.renderChip.bind(this); + } + getRoleLists () { const { accessRecord } = this.props; const teamRoles = []; @@ -80,8 +51,21 @@ class OrganizationAccessItem extends React.Component { return [teamRoles, userRoles]; } + renderChip (role) { + const { accessRecord, onRoleDelete } = this.props; + return ( + { onRoleDelete(role, accessRecord); }} + > + {role.name} + + ); + } + render () { - const { accessRecord, onRoleDelete, i18n } = this.props; + const { accessRecord, i18n } = this.props; const [teamRoles, userRoles] = this.getRoleLists(); return ( @@ -90,76 +74,60 @@ class OrganizationAccessItem extends React.Component { {accessRecord.username && ( - + {accessRecord.url ? ( - - + + {accessRecord.username} - - + + ) : ( - + {accessRecord.username} )} )} {accessRecord.first_name || accessRecord.last_name ? ( - + + + ) : ( null )} , - {userRoles.length > 0 && ( -
    - - {i18n._(t`User Roles`)} - - {userRoles.map(role => ( - role.user_capabilities.unattach ? ( - { onRoleDelete(role, accessRecord); }} - > - {role.name} - - ) : ( - - {role.name} - - ) - ))} -
- )} - {teamRoles.length > 0 && ( -
    - - {i18n._(t`Team Roles`)} - - {teamRoles.map(role => ( - role.user_capabilities.unattach ? ( - { onRoleDelete(role, accessRecord); }} - > - {role.name} - - ) : ( - - {role.name} - - ) - ))} -
- )} + + {userRoles.length > 0 && ( + + {userRoles.map(this.renderChip)} + + )} + /> + )} + {teamRoles.length > 0 && ( + + {teamRoles.map(this.renderChip)} + + )} + /> + )} +
]} /> diff --git a/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx b/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx index 14af5d1954..811317df80 100644 --- a/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx +++ b/src/pages/Organizations/screens/Organization/OrganizationDetail.jsx @@ -2,75 +2,24 @@ import React, { Component } from 'react'; import { Link, withRouter } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; - -import { - CardBody as PFCardBody, - Button, - TextList, - TextListItem, - TextListVariants, - TextListItemVariants, -} from '@patternfly/react-core'; +import { CardBody as PFCardBody, Button } from '@patternfly/react-core'; import styled from 'styled-components'; +import { DetailList, Detail } from '../../../../components/DetailList'; import { withNetwork } from '../../../../contexts/Network'; -import BasicChip from '../../../../components/BasicChip/BasicChip'; +import { ChipGroup, Chip } from '../../../../components/Chip'; const CardBody = styled(PFCardBody)` padding-top: 20px; `; -const DetailList = styled(TextList)` - display: grid; - grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); - grid-gap: 20px; - - & > div { - display: grid; - 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 }) => { - if (!value) return null; - return ( -
- {label} - {value} -
- ); -}; - class OrganizationDetail extends Component { constructor (props) { super(props); this.state = { instanceGroups: [], - isToggleOpen: false, error: false }; - - this.handleChipToggle = this.handleChipToggle.bind(this); this.loadInstanceGroups = this.loadInstanceGroups.bind(this); } @@ -78,12 +27,6 @@ class OrganizationDetail extends Component { this.loadInstanceGroups(); } - handleChipToggle = () => { - this.setState((prevState) => ({ - isToggleOpen: !prevState.isToggleOpen - })); - } - async loadInstanceGroups () { const { api, @@ -106,7 +49,6 @@ class OrganizationDetail extends Component { const { error, instanceGroups, - isToggleOpen, } = this.state; const { @@ -121,30 +63,10 @@ class OrganizationDetail extends Component { match, i18n } = this.props; - const showOverflowChipAfter = 5; - - const instanceGroupChips = instanceGroups.slice(0, isToggleOpen - ? instanceGroups.length : showOverflowChipAfter) - .map(instanceGroup => ( - - {instanceGroup.name} - - )); - - const overflowChip = (instanceGroups.length > showOverflowChipAfter) ? ( - - {isToggleOpen ? 'Show less' : `${(instanceGroups.length - showOverflowChipAfter).toString()} more`} - - ) : null; return ( - + {(instanceGroups && instanceGroups.length > 0) && ( - - - {i18n._(t`Instance Groups`)} - - - {instanceGroupChips} - {overflowChip} - - + + {instanceGroups.map(ig => ( + {ig.name} + ))} + + )} + /> )} {summary_fields.user_capabilities.edit && (