From be936c2eb89fa09ed6147fb9f2dc4c63ed1390bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CA=88=E1=B5=83=E1=B5=A2?= Date: Tue, 23 Jun 2020 14:17:28 -0700 Subject: [PATCH] style: listviews closer to SIP-34 (#10094) --- .../components/ListView/ListView_spec.jsx | 3 +- .../views/chartList/ChartList_spec.jsx | 4 + .../dashboardList/DashboardList_spec.jsx | 4 + .../welcome/DashboardTable_spec.jsx | 8 +- .../src/components/AvatarIcon.tsx | 29 ++- .../src/components/ListView/ListView.tsx | 211 ++++++++---------- .../components/ListView/ListViewStyles.less | 179 +++++++++------ .../src/components/ListView/Pagination.tsx | 11 +- .../components/ListView/TableCollection.tsx | 82 +++++-- .../src/components/Menu/SubMenu.tsx | 16 +- .../src/components/Pagination.tsx | 132 +++++++++++ .../src/types/react-table-config.d.ts | 10 +- superset-frontend/src/utils/common.js | 4 + .../src/views/chartList/ChartList.tsx | 103 +++++---- .../src/views/dashboardList/DashboardList.tsx | 125 +++++------ .../src/views/datasetList/DatasetList.tsx | 179 +++++++++------ .../stylesheets/less/variables.less | 4 - 17 files changed, 687 insertions(+), 417 deletions(-) create mode 100644 superset-frontend/src/components/Pagination.tsx diff --git a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx index e5e04d5e6..52e15ffd6 100644 --- a/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx +++ b/superset-frontend/spec/javascripts/components/ListView/ListView_spec.jsx @@ -19,13 +19,14 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { MenuItem, Pagination } from 'react-bootstrap'; +import { MenuItem } from 'react-bootstrap'; import Select from 'src/components/Select'; import { QueryParamProvider } from 'use-query-params'; import ListView from 'src/components/ListView/ListView'; import ListViewFilters from 'src/components/ListView/Filters'; import ListViewPagination from 'src/components/ListView/Pagination'; +import Pagination from 'src/components/Pagination'; import { areArraysShallowEqual } from 'src/reduxUtils'; import { ThemeProvider } from 'emotion-theming'; import { supersetTheme } from '@superset-ui/style'; diff --git a/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx b/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx index d5785bf9c..1ec72754f 100644 --- a/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx +++ b/superset-frontend/spec/javascripts/views/chartList/ChartList_spec.jsx @@ -21,6 +21,8 @@ import { mount } from 'enzyme'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; +import { ThemeProvider } from 'emotion-theming'; +import { supersetTheme } from '@superset-ui/style'; import ChartList from 'src/views/chartList/ChartList'; import ListView from 'src/components/ListView/ListView'; @@ -77,6 +79,8 @@ describe('ChartList', () => { const mockedProps = {}; const wrapper = mount(, { context: { store }, + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, }); it('renders', () => { diff --git a/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx b/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx index 086d9d1db..456035ec2 100644 --- a/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx +++ b/superset-frontend/spec/javascripts/views/dashboardList/DashboardList_spec.jsx @@ -21,6 +21,8 @@ import { mount } from 'enzyme'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; +import { ThemeProvider } from 'emotion-theming'; +import { supersetTheme } from '@superset-ui/style'; import DashboardList from 'src/views/dashboardList/DashboardList'; import ListView from 'src/components/ListView/ListView'; @@ -67,6 +69,8 @@ describe('DashboardList', () => { const mockedProps = {}; const wrapper = mount(, { context: { store }, + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, }); it('renders', () => { diff --git a/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.jsx b/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.jsx index a31761a41..2fe659b25 100644 --- a/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.jsx +++ b/superset-frontend/spec/javascripts/welcome/DashboardTable_spec.jsx @@ -21,6 +21,8 @@ import { mount } from 'enzyme'; import thunk from 'redux-thunk'; import configureStore from 'redux-mock-store'; import fetchMock from 'fetch-mock'; +import { ThemeProvider } from 'emotion-theming'; +import { supersetTheme } from '@superset-ui/style'; import ListView from 'src/components/ListView/ListView'; import DashboardTable from 'src/welcome/DashboardTable'; @@ -36,7 +38,11 @@ fetchMock.get(dashboardsEndpoint, { result: mockDashboards }); function setup() { // use mount because data fetching is triggered on mount - return mount(, { context: { store } }); + return mount(, { + context: { store }, + wrappingComponent: ThemeProvider, + wrappingComponentProps: { theme: supersetTheme }, + }); } describe('DashboardTable', () => { diff --git a/superset-frontend/src/components/AvatarIcon.tsx b/superset-frontend/src/components/AvatarIcon.tsx index 4d86983cf..7dcf8f7ef 100644 --- a/superset-frontend/src/components/AvatarIcon.tsx +++ b/superset-frontend/src/components/AvatarIcon.tsx @@ -19,15 +19,16 @@ import React from 'react'; import styled from '@superset-ui/style'; import { getCategoricalSchemeRegistry } from '@superset-ui/color'; -import { Tooltip, OverlayTrigger } from 'react-bootstrap'; import Avatar, { ConfigProvider } from 'react-avatar'; +import TooltipWrapper from 'src/components/TooltipWrapper'; interface Props { firstName: string; - iconSize: string; lastName: string; tableName: string; userName: string; + iconSize: number; + textSize: number; } const colorList = getCategoricalSchemeRegistry().get(); @@ -42,18 +43,26 @@ export default function AvatarIcon({ lastName, userName, iconSize, + textSize, }: Props) { const uniqueKey = `${tableName}-${userName}`; const fullName = `${firstName} ${lastName}`; return ( - - {fullName}} - > - - - + + + + + ); } diff --git a/superset-frontend/src/components/ListView/ListView.tsx b/superset-frontend/src/components/ListView/ListView.tsx index 27f68cd14..1017fbe13 100644 --- a/superset-frontend/src/components/ListView/ListView.tsx +++ b/superset-frontend/src/components/ListView/ListView.tsx @@ -19,7 +19,8 @@ import { t } from '@superset-ui/translation'; import React, { FunctionComponent } from 'react'; import { Col, DropdownButton, MenuItem, Row } from 'react-bootstrap'; -import IndeterminateCheckbox from '../IndeterminateCheckbox'; +import Loading from 'src/components/Loading'; +import IndeterminateCheckbox from 'src/components/IndeterminateCheckbox'; import TableCollection from './TableCollection'; import Pagination from './Pagination'; import { FilterMenu, FilterInputs } from './LegacyFilters'; @@ -37,7 +38,6 @@ interface Props { fetchData: (conf: FetchDataConfig) => any; loading: boolean; className?: string; - title?: string; initialSort?: SortColumn[]; filters?: Filters; bulkActions?: Array<{ @@ -59,6 +59,7 @@ const bulkSelectColumnConfig = { /> ), id: 'selection', + size: 'sm', }; const ListView: FunctionComponent = ({ @@ -70,7 +71,6 @@ const ListView: FunctionComponent = ({ loading, initialSort = [], className = '', - title = '', filters = [], bulkActions = [], useNewUIFilters = false, @@ -116,124 +116,111 @@ const ListView: FunctionComponent = ({ } }); } - + if (loading && !data.length) { + return ; + } return ( -
-
- {!useNewUIFilters && ( - <> - {title && filterable && ( - <> - - -

{t(title)}

- - {filterable && ( - - - - )} -
-
- - - )} - - )} - {useNewUIFilters && ( - <> - - -

{t(title)}

- -
-
+
+
+
+ {!useNewUIFilters && filterable && ( + <> + + + + + + +
+ + + )} + {useNewUIFilters && filterable && ( - - )} -
-
- -
-
- - -
-
- {bulkActions.length > 0 && ( - - {t('Actions')} - - } - > - {bulkActions.map(action => ( - // @ts-ignore - +
+ +
+
+ + +
+
+ {bulkActions.length > 0 && ( + + {t('Actions')} + + } + > + {bulkActions.map(action => ( // @ts-ignore - onSelect={(selectedRows: typeof selectedFlatRows) => { - action.onSelect( - selectedRows.map((r: any) => r.original), - ); - }} - > - {action.name} - - ))} - - )} + { + action.onSelect( + selectedRows.map((r: any) => r.original), + ); + }} + > + {action.name} + + ))} + + )} +
-
- - - gotoPage(p - 1)} - hideFirstAndLastPageLinks - /> - - - - {t('showing')}{' '} - - {pageSize * pageIndex + (rows.length && 1)}- - {pageSize * pageIndex + rows.length} - {' '} - {t('of')} {count} - - - + + + + + showing{' '} + + {pageSize * pageIndex + (rows.length && 1)}- + {pageSize * pageIndex + rows.length} + {' '} + of {count} + + + +
+ gotoPage(p - 1)} + hideFirstAndLastPageLinks + />
); }; diff --git a/superset-frontend/src/components/ListView/ListViewStyles.less b/superset-frontend/src/components/ListView/ListViewStyles.less index a5f3d87fd..f922f1b7a 100644 --- a/superset-frontend/src/components/ListView/ListViewStyles.less +++ b/superset-frontend/src/components/ListView/ListViewStyles.less @@ -19,92 +19,133 @@ @import '~stylesheets/less/variables.less'; -.superset-list-view { - .filter-dropdown { - margin-top: 20px; - } +.superset-list-view-container { + text-align: center; - .filter-column { - height: 30px; - padding: 5px; - font-size: 16px; - } + .superset-list-view { + text-align: left; + background-color: white; + border-radius: 4px 0; + margin: 0 16px; + padding-bottom: 48px; - .filter-close { - height: 30px; - padding: 5px; - - i { - font-size: 20px; + .body { + overflow: scroll; } - } - .table-row-loader { - animation: shimmer 2s infinite; - background: linear-gradient( - to right, - #f6f7f8 0%, - #edeef1 20%, - #f6f7f8 40%, - #f6f7f8 100% - ); - background-size: 1000px 100%; - - span { - visibility: hidden; + .filter-dropdown { + margin-top: 20px; } - } - .actions { - font-size: 20px; - white-space: nowrap; + .filter-column { + height: 30px; + padding: 5px; + font-size: 16px; + } - width: 100px; + .filter-close { + height: 30px; + padding: 5px; - svg { - &:hover { - path { - fill: @primary-color; + i { + font-size: 20px; + } + } + + .table-cell-loader { + position: relative; + + .loading-bar { + background-color: @brand-secondary-light4; + border-radius: 7px; + + span { + visibility: hidden; + } + } + + &:after { + position: absolute; + transform: translateY(-50%); + top: 50%; + left: 0; + content: ''; + display: block; + width: 100%; + height: 48px; + background-image: linear-gradient( + 100deg, + rgba(255, 255, 255, 0), + rgba(255, 255, 255, 0.5) 60%, + rgba(255, 255, 255, 0) 80% + ); + background-size: 200px 48px; + background-position: -100px 0; + background-repeat: no-repeat; + animation: loading-shimmer 1s infinite; + } + } + + .actions { + white-space: nowrap; + font-size: 24px; + min-width: 100px; + + svg, + i { + margin-right: 8px; + + &:hover { + path { + fill: @primary-color; + } } } } - } - .action-button { - margin: 0 8px; - } + .table-row { + &:hover { + background-color: @brand-secondary-light5; + } + } - .table-row { - &:hover { - background-color: @table-hover; + .table-row-selected { + background-color: @brand-secondary-light4; + + &:hover { + background-color: @brand-secondary-light4; + } + } + + .table-cell { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 300px; + } + + .sort-icon { + position: absolute; + } + + .form-actions-container { + position: absolute; + left: 28px; + } + + .row-count-container { + position: absolute; + right: 28px; } } - .table-row-selected { - background-color: @table-selected; + @keyframes loading-shimmer { + 40% { + background-position: 100% 0; + } - &:hover { - background-color: @table-selected; + 100% { + background-position: 100% 0; } } - - .table-cell { - max-width: 200px; - text-overflow: ellipsis; - overflow: hidden; - } - - .sort-icon { - position: absolute; - } -} - -@keyframes shimmer { - 0% { - background-position: -1000px 0; - } - - 100% { - background-position: 1000px 0; - } } diff --git a/superset-frontend/src/components/ListView/Pagination.tsx b/superset-frontend/src/components/ListView/Pagination.tsx index 03b8663db..6d1d27245 100644 --- a/superset-frontend/src/components/ListView/Pagination.tsx +++ b/superset-frontend/src/components/ListView/Pagination.tsx @@ -17,8 +17,7 @@ * under the License. */ import React from 'react'; -// @ts-ignore -import { Pagination } from 'react-bootstrap'; +import Pagination from 'src/components/Pagination'; import { createUltimatePagination, ITEM_TYPES, @@ -35,18 +34,14 @@ const ListViewPagination = createUltimatePagination({ [ITEM_TYPES.ELLIPSIS]: ({ isActive, onClick }) => ( ), - [ITEM_TYPES.FIRST_PAGE_LINK]: ({ isActive, onClick }) => ( - - ), [ITEM_TYPES.PREVIOUS_PAGE_LINK]: ({ isActive, onClick }) => ( ), [ITEM_TYPES.NEXT_PAGE_LINK]: ({ isActive, onClick }) => ( ), - [ITEM_TYPES.LAST_PAGE_LINK]: ({ isActive, onClick }) => ( - - ), + [ITEM_TYPES.FIRST_PAGE_LINK]: () => null, + [ITEM_TYPES.LAST_PAGE_LINK]: () => null, }, }); diff --git a/superset-frontend/src/components/ListView/TableCollection.tsx b/superset-frontend/src/components/ListView/TableCollection.tsx index cc8b8bcbe..b08114bd2 100644 --- a/superset-frontend/src/components/ListView/TableCollection.tsx +++ b/superset-frontend/src/components/ListView/TableCollection.tsx @@ -19,6 +19,7 @@ import React from 'react'; import cx from 'classnames'; import { TableInstance } from 'react-table'; +import styled from '@superset-ui/style'; import Icon from 'src/components/Icon'; interface Props { @@ -29,6 +30,56 @@ interface Props { rows: TableInstance['rows']; loading: boolean; } + +const Table = styled.table` + th { + &.xs { + min-width: 25px; + } + &.sm { + min-width: 50px; + } + &.md { + min-width: 75px; + } + &.lg { + min-width: 100px; + } + &.xl { + min-width: 150px; + } + &.xxl { + min-width: 200px; + } + + svg { + display: inline-block; + top: 6px; + position: relative; + } + } + td { + &.xs { + width: 25px; + } + &.sm { + width: 50px; + } + &.md { + width: 75px; + } + &.lg { + width: 100px; + } + &.xl { + width: 150px; + } + &.xxl { + width: 200px; + } + } +`; + export default function TableCollection({ getTableProps, getTableBodyProps, @@ -38,29 +89,31 @@ export default function TableCollection({ loading, }: Props) { return ( - +
{headerGroups.map(headerGroup => ( {headerGroup.headers.map(column => { let sortIcon = ; - if (column.isSortedDesc) { + if (column.isSorted && column.isSortedDesc) { sortIcon = ; - } else if (!column.isSortedDesc) { + } else if (column.isSorted && !column.isSortedDesc) { sortIcon = ; } - return column.hidden ? null : ( ); })} @@ -74,7 +127,6 @@ export default function TableCollection({ row.setState && row.setState({ hover: true })} @@ -86,14 +138,18 @@ export default function TableCollection({ if (cell.column.hidden) return null; const columnCellProps = cell.column.cellProps || {}; - return ( ); })} @@ -101,6 +157,6 @@ export default function TableCollection({ ); })} -
- {column.render('Header')} - {column.sortable && ( - {sortIcon} - )} + + {column.render('Header')} + {column.sortable && sortIcon} +
- {cell.render('Cell')} + + {cell.render('Cell')} +
+ ); } diff --git a/superset-frontend/src/components/Menu/SubMenu.tsx b/superset-frontend/src/components/Menu/SubMenu.tsx index f0fa2c1a4..f565af0fe 100644 --- a/superset-frontend/src/components/Menu/SubMenu.tsx +++ b/superset-frontend/src/components/Menu/SubMenu.tsx @@ -64,11 +64,10 @@ const StyledHeader = styled.header` `; interface SubMenuProps { - createButton: { name: string; url: string | null }; - canCreate: boolean; - label: string; + createButton?: { name: string; url: string | null }; + canCreate?: boolean; name: string; - childs: Array<{ label: string; name: string; url: string }>; + childs?: Array<{ label: string; name: string; url: string }>; } interface SubMenuState { @@ -78,7 +77,10 @@ interface SubMenuState { class SubMenu extends React.PureComponent { state: SubMenuState = { - selectedMenu: this.props.childs[0] && this.props.childs[0].label, + selectedMenu: + this.props.childs && this.props.childs[0] + ? this.props.childs[0].label + : '', isModalOpen: false, }; @@ -99,7 +101,7 @@ class SubMenu extends React.PureComponent { - {this.props.label} + {this.props.name} - {this.props.canCreate && ( + {this.props.canCreate && this.props.createButton && (