michael-s-molina commented on code in PR #21520:
URL: https://github.com/apache/superset/pull/21520#discussion_r1015980277
##########
superset-frontend/src/components/MetadataBar/MetadataBar.stories.mdx:
##########
@@ -1,17 +1,25 @@
-import { Meta, Source } from '@storybook/addon-docs';
+import { Meta, Source, Story } from '@storybook/addon-docs';
-<Meta title="MetadataBar/Overview" />
+<Meta title="Design System/Components/MetadataBar/Overview" />
-# Usage
+# Metadata bar
-The metadata bar component is used to display additional information about an
entity. Some of the common applications in Superset are:
+The metadata bar component is used to display additional information about an
entity.
+
+## Usage
+
+Some of the common applications in Superset are:
- Display the chart's metadata in Explore to help the user understand what
dashboards this chart is added to and get
to know the details of the chart
- Display the database's metadata in a drill to detail modal to help the user
understand what data they are looking
at while accessing the feature in the dashboard
-# Variations
+## Basic example
+
+<Story id="design-system-components-metadatabar-examples--basic" />
Review Comment:
You need to update the link to the component page on line 36 to
`[MetadataBar](/story/design-system-components-metadatabar-examples--basic)`
##########
superset-frontend/src/components/Table/Table.overview.mdx:
##########
@@ -0,0 +1,260 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Overview" />
+
+# Table
+
+A table is UI that allows the user to explore data in a tabular format.
+
+## Usage
+
+Common table applications in Superset:
+
+- Display lists of user-generated entities (e.g. dashboard, charts, queries)
for further exploration and use
+- Display data that can help the user make a decision (e.g. query results)
+
+This component provides a general use Table.
+
+---
+
+### [Basic
example](./?path=/docs/design-system-components-table-examples--basic)
+
+<Story id="design-system-components-table-examples--basic" />
+
+### Data and Columns
+
+To set the visible columns and data for the table you use the `columns` and
`data` props.
+
+<details>
+
+The basic table example for the `columns` prop is:
+
+```
+const basicColumns: = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ width: 150,
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('name', a, b),
+ },
+ {
+ title: 'Category',
+ dataIndex: 'category',
+ key: 'category',
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('category', a, b),
+ },
+ {
+ title: 'Price',
+ dataIndex: 'price',
+ key: 'price',
+ sorter: (a: BasicData, b: BasicData) =>
+ numericalSort('price', a, b),
+ },
+ {
+ title: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ },
+];
+```
+
+The data prop is:
+
+```
+const basicData: = [
+ {
+ key: 1,
+ name: 'Floppy Disk 10 pack',
+ category: 'Disk Storage',
+ price: '9.99'
+ description: 'A real blast from the past',
+ },
+ {
+ key: 2,
+ name: 'DVD 100 pack',
+ category: 'Optical Storage',
+ price: '27.99'
+ description: 'Still pretty ancient',
+ },
+ {
+ key: 3,
+ name: '128 GB SSD',
+ category: 'Hardrive',
+ price: '49.99'
+ description: 'Reliable and fast data storage',
+ },
+];
+```
+
+</details>
+
+### Column Sort Functions
+
+To ensure consistency for column sorting and to avoid redundant definitions
for common column sorters, reusable sort functions are provided.
+When defining the object for the `columns` prop you can provide an optional
attribute `sorter`.
+The function provided in the `sorter` prop is given the entire record
representing a row as props `a` and `b`.
+When using a provided sorter function the pattern is to wrap the call to the
sorter with an inline function, then specify the specific attribute value from
`dataIndex`, representing a column
+of the data object for that row, as the first argument of the sorter function.
+
+#### alphabeticalSort
+
+The alphabeticalSort is for columns that display a string of text.
+
+<details>
+
+```
+import { alphabeticalSort } from 'src/components/Table/sorters';
+
+const basicColumns = [
+ {
+ title: 'Column Name',
+ dataIndex: 'columnName',
+ key: 'columnName',
+ sorter: (a, b) =>
+ alphabeticalSort('columnName', a, b),
+ }
+]
+```
+
+</details>
+
+#### numericSort
+
+The numericalSort is for columns that display a numeric value.
+
+<details>
+
+```
+import { numericalSort } from './sorters';
+
+const basicColumns = [
+ {
+ title: 'Height',
+ dataIndex: 'height',
+ key: 'height',
+ sorter: (a, b) =>
+ numericalSort('height', a, b),
+ }
+]
+```
+
+</details>
+
+If a different sort option is needed, consider adding it as a re-usable sort
function following the pattern provided above.
Review Comment:
```suggestion
If a different sort option is needed, consider adding it as a reusable sort
function following the pattern provided above.
```
##########
superset-frontend/src/components/Table/Table.overview.mdx:
##########
@@ -0,0 +1,260 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Overview" />
+
+# Table
+
+A table is UI that allows the user to explore data in a tabular format.
+
+## Usage
+
+Common table applications in Superset:
+
+- Display lists of user-generated entities (e.g. dashboard, charts, queries)
for further exploration and use
+- Display data that can help the user make a decision (e.g. query results)
+
+This component provides a general use Table.
+
+---
+
+### [Basic
example](./?path=/docs/design-system-components-table-examples--basic)
+
+<Story id="design-system-components-table-examples--basic" />
+
+### Data and Columns
+
+To set the visible columns and data for the table you use the `columns` and
`data` props.
+
+<details>
+
+The basic table example for the `columns` prop is:
+
+```
+const basicColumns: = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ width: 150,
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('name', a, b),
+ },
+ {
+ title: 'Category',
+ dataIndex: 'category',
+ key: 'category',
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('category', a, b),
+ },
+ {
+ title: 'Price',
+ dataIndex: 'price',
+ key: 'price',
+ sorter: (a: BasicData, b: BasicData) =>
+ numericalSort('price', a, b),
+ },
+ {
+ title: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ },
+];
+```
+
+The data prop is:
+
+```
+const basicData: = [
+ {
+ key: 1,
+ name: 'Floppy Disk 10 pack',
+ category: 'Disk Storage',
+ price: '9.99'
+ description: 'A real blast from the past',
+ },
+ {
+ key: 2,
+ name: 'DVD 100 pack',
+ category: 'Optical Storage',
+ price: '27.99'
+ description: 'Still pretty ancient',
+ },
+ {
+ key: 3,
+ name: '128 GB SSD',
+ category: 'Hardrive',
+ price: '49.99'
+ description: 'Reliable and fast data storage',
+ },
+];
+```
+
+</details>
+
+### Column Sort Functions
+
+To ensure consistency for column sorting and to avoid redundant definitions
for common column sorters, reusable sort functions are provided.
+When defining the object for the `columns` prop you can provide an optional
attribute `sorter`.
+The function provided in the `sorter` prop is given the entire record
representing a row as props `a` and `b`.
+When using a provided sorter function the pattern is to wrap the call to the
sorter with an inline function, then specify the specific attribute value from
`dataIndex`, representing a column
+of the data object for that row, as the first argument of the sorter function.
+
+#### alphabeticalSort
+
+The alphabeticalSort is for columns that display a string of text.
+
+<details>
+
+```
+import { alphabeticalSort } from 'src/components/Table/sorters';
+
+const basicColumns = [
+ {
+ title: 'Column Name',
+ dataIndex: 'columnName',
+ key: 'columnName',
+ sorter: (a, b) =>
+ alphabeticalSort('columnName', a, b),
+ }
+]
+```
+
+</details>
+
+#### numericSort
+
+The numericalSort is for columns that display a numeric value.
+
+<details>
+
+```
+import { numericalSort } from './sorters';
+
+const basicColumns = [
+ {
+ title: 'Height',
+ dataIndex: 'height',
+ key: 'height',
+ sorter: (a, b) =>
+ numericalSort('height', a, b),
+ }
+]
+```
+
+</details>
+
+If a different sort option is needed, consider adding it as a re-usable sort
function following the pattern provided above.
+
+---
+
+### Cell Content Renderers
+
+By default each column will render the value as simple text. Often you will
want to show formatted values, such as a numeric column showing as currency, or
a more complex component such as a button or action menu as a cell value.
+Cell Renderers are React components provided to the optional `render`
attribute on a column definition that enables injecting a specific React
component to enable this.
+
+<Story id="design-system-components-table-examples--cell-renderers" />
+
+For convenience and consistency, the Table component provides pre-built Cell
Renderers for:
+The following data types can be displayed in table cells.
+
+- Text (default)
+- [Button
Cell](./?path=/docs/design-system-components-table-cell-renderers-buttoncell--basic)
+- [Numeric
Cell](./docs/design-system-components-table-cell-renderers-numericcell--basic)
+ - Support Locale and currency formatting
+ - w/ icons - Coming Soon
+- [Action Menu
Cell](./?path=/docs/design-system-components-table-cell-renderers-actioncell-overview--page)
+- Provide a list of menu options with callback functions that retain a
reference to the row the menu is defined for
+- Custom
+ - You can provide your own React component as a cell renderer in cases not
supported
+
+---
+
+### Loading
+
+The table can be set to a loading state simply by setting the loading prop to
true | false
+
+<Story id="design-system-components-table-examples--loading" />
+
+---
+
+### Pagination
+
+Table displays set number of rows at a time, user navigates table via
pagination. Use in scenarios where the user is searching for a specific piece
of content.
+The default page size and page size options for menu are configurable via the
`pageSizeOptions` and `defaultPageSize` props.
Review Comment:
```suggestion
The default page size and page size options for the menu are configurable
via the `pageSizeOptions` and `defaultPageSize` props.
```
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/ActionCell.test.tsx:
##########
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { configure } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import ActionCell, { appendDataToMenu } from './index';
+import { exampleMenuOptions, exampleRow } from './fixtures';
+
+test('renders with default props', async () => {
+ configure({ testIdAttribute: 'data-test' });
Review Comment:
This is already set in `spec/helpers/setup.ts`
```suggestion
```
##########
superset-frontend/src/components/Table/cell-renderers/ButtonCell/ButtonCell.test.tsx:
##########
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { configure } from '@testing-library/react';
Review Comment:
```suggestion
```
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
Review Comment:
We generally don't leave a blank line after the license. It's not a blocker
for me, but if you choose to remove it to keep the codebase consistent then you
can update the other files.
##########
superset-frontend/src/components/Table/Table.overview.mdx:
##########
@@ -0,0 +1,260 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Overview" />
+
+# Table
+
+A table is UI that allows the user to explore data in a tabular format.
+
+## Usage
+
+Common table applications in Superset:
+
+- Display lists of user-generated entities (e.g. dashboard, charts, queries)
for further exploration and use
+- Display data that can help the user make a decision (e.g. query results)
+
+This component provides a general use Table.
+
+---
+
+### [Basic
example](./?path=/docs/design-system-components-table-examples--basic)
+
+<Story id="design-system-components-table-examples--basic" />
+
+### Data and Columns
+
+To set the visible columns and data for the table you use the `columns` and
`data` props.
+
+<details>
+
+The basic table example for the `columns` prop is:
+
+```
+const basicColumns: = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ width: 150,
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('name', a, b),
+ },
+ {
+ title: 'Category',
+ dataIndex: 'category',
+ key: 'category',
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('category', a, b),
+ },
+ {
+ title: 'Price',
+ dataIndex: 'price',
+ key: 'price',
+ sorter: (a: BasicData, b: BasicData) =>
+ numericalSort('price', a, b),
+ },
+ {
+ title: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ },
+];
+```
+
+The data prop is:
+
+```
+const basicData: = [
+ {
+ key: 1,
+ name: 'Floppy Disk 10 pack',
+ category: 'Disk Storage',
+ price: '9.99'
+ description: 'A real blast from the past',
+ },
+ {
+ key: 2,
+ name: 'DVD 100 pack',
+ category: 'Optical Storage',
+ price: '27.99'
+ description: 'Still pretty ancient',
+ },
+ {
+ key: 3,
+ name: '128 GB SSD',
+ category: 'Hardrive',
+ price: '49.99'
+ description: 'Reliable and fast data storage',
+ },
+];
+```
+
+</details>
+
+### Column Sort Functions
+
+To ensure consistency for column sorting and to avoid redundant definitions
for common column sorters, reusable sort functions are provided.
+When defining the object for the `columns` prop you can provide an optional
attribute `sorter`.
+The function provided in the `sorter` prop is given the entire record
representing a row as props `a` and `b`.
+When using a provided sorter function the pattern is to wrap the call to the
sorter with an inline function, then specify the specific attribute value from
`dataIndex`, representing a column
+of the data object for that row, as the first argument of the sorter function.
+
+#### alphabeticalSort
+
+The alphabeticalSort is for columns that display a string of text.
+
+<details>
+
+```
+import { alphabeticalSort } from 'src/components/Table/sorters';
+
+const basicColumns = [
+ {
+ title: 'Column Name',
+ dataIndex: 'columnName',
+ key: 'columnName',
+ sorter: (a, b) =>
+ alphabeticalSort('columnName', a, b),
+ }
+]
+```
+
+</details>
+
+#### numericSort
+
+The numericalSort is for columns that display a numeric value.
+
+<details>
+
+```
+import { numericalSort } from './sorters';
+
+const basicColumns = [
+ {
+ title: 'Height',
+ dataIndex: 'height',
+ key: 'height',
+ sorter: (a, b) =>
+ numericalSort('height', a, b),
+ }
+]
+```
+
+</details>
+
+If a different sort option is needed, consider adding it as a re-usable sort
function following the pattern provided above.
+
+---
+
+### Cell Content Renderers
+
+By default each column will render the value as simple text. Often you will
want to show formatted values, such as a numeric column showing as currency, or
a more complex component such as a button or action menu as a cell value.
+Cell Renderers are React components provided to the optional `render`
attribute on a column definition that enables injecting a specific React
component to enable this.
+
+<Story id="design-system-components-table-examples--cell-renderers" />
+
+For convenience and consistency, the Table component provides pre-built Cell
Renderers for:
+The following data types can be displayed in table cells.
+
+- Text (default)
+- [Button
Cell](./?path=/docs/design-system-components-table-cell-renderers-buttoncell--basic)
+- [Numeric
Cell](./docs/design-system-components-table-cell-renderers-numericcell--basic)
+ - Support Locale and currency formatting
+ - w/ icons - Coming Soon
+- [Action Menu
Cell](./?path=/docs/design-system-components-table-cell-renderers-actioncell-overview--page)
+- Provide a list of menu options with callback functions that retain a
reference to the row the menu is defined for
+- Custom
+ - You can provide your own React component as a cell renderer in cases not
supported
+
+---
+
+### Loading
+
+The table can be set to a loading state simply by setting the loading prop to
true | false
+
+<Story id="design-system-components-table-examples--loading" />
+
+---
+
+### Pagination
+
+Table displays set number of rows at a time, user navigates table via
pagination. Use in scenarios where the user is searching for a specific piece
of content.
Review Comment:
```suggestion
The table displays a set number of rows at a time, the user navigates the
table via pagination. Use in scenarios where the user is searching for a
specific piece of content.
```
##########
superset-frontend/src/components/Table/Table.overview.mdx:
##########
@@ -0,0 +1,260 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Overview" />
+
+# Table
+
+A table is UI that allows the user to explore data in a tabular format.
+
+## Usage
+
+Common table applications in Superset:
+
+- Display lists of user-generated entities (e.g. dashboard, charts, queries)
for further exploration and use
+- Display data that can help the user make a decision (e.g. query results)
+
+This component provides a general use Table.
+
+---
+
+### [Basic
example](./?path=/docs/design-system-components-table-examples--basic)
+
+<Story id="design-system-components-table-examples--basic" />
+
+### Data and Columns
+
+To set the visible columns and data for the table you use the `columns` and
`data` props.
+
+<details>
+
+The basic table example for the `columns` prop is:
+
+```
+const basicColumns: = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ key: 'name',
+ width: 150,
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('name', a, b),
+ },
+ {
+ title: 'Category',
+ dataIndex: 'category',
+ key: 'category',
+ sorter: (a: BasicData, b: BasicData) =>
+ alphabeticalSort('category', a, b),
+ },
+ {
+ title: 'Price',
+ dataIndex: 'price',
+ key: 'price',
+ sorter: (a: BasicData, b: BasicData) =>
+ numericalSort('price', a, b),
+ },
+ {
+ title: 'Description',
+ dataIndex: 'description',
+ key: 'description',
+ },
+];
+```
+
+The data prop is:
+
+```
+const basicData: = [
+ {
+ key: 1,
+ name: 'Floppy Disk 10 pack',
+ category: 'Disk Storage',
+ price: '9.99'
+ description: 'A real blast from the past',
+ },
+ {
+ key: 2,
+ name: 'DVD 100 pack',
+ category: 'Optical Storage',
+ price: '27.99'
+ description: 'Still pretty ancient',
+ },
+ {
+ key: 3,
+ name: '128 GB SSD',
+ category: 'Hardrive',
+ price: '49.99'
+ description: 'Reliable and fast data storage',
+ },
+];
+```
+
+</details>
+
+### Column Sort Functions
+
+To ensure consistency for column sorting and to avoid redundant definitions
for common column sorters, reusable sort functions are provided.
+When defining the object for the `columns` prop you can provide an optional
attribute `sorter`.
+The function provided in the `sorter` prop is given the entire record
representing a row as props `a` and `b`.
+When using a provided sorter function the pattern is to wrap the call to the
sorter with an inline function, then specify the specific attribute value from
`dataIndex`, representing a column
+of the data object for that row, as the first argument of the sorter function.
+
+#### alphabeticalSort
+
+The alphabeticalSort is for columns that display a string of text.
+
+<details>
+
+```
+import { alphabeticalSort } from 'src/components/Table/sorters';
+
+const basicColumns = [
+ {
+ title: 'Column Name',
+ dataIndex: 'columnName',
+ key: 'columnName',
+ sorter: (a, b) =>
+ alphabeticalSort('columnName', a, b),
+ }
+]
+```
+
+</details>
+
+#### numericSort
+
+The numericalSort is for columns that display a numeric value.
+
+<details>
+
+```
+import { numericalSort } from './sorters';
+
+const basicColumns = [
+ {
+ title: 'Height',
+ dataIndex: 'height',
+ key: 'height',
+ sorter: (a, b) =>
+ numericalSort('height', a, b),
+ }
+]
+```
+
+</details>
+
+If a different sort option is needed, consider adding it as a re-usable sort
function following the pattern provided above.
+
+---
+
+### Cell Content Renderers
+
+By default each column will render the value as simple text. Often you will
want to show formatted values, such as a numeric column showing as currency, or
a more complex component such as a button or action menu as a cell value.
Review Comment:
```suggestion
By default, each column will render the value as simple text. Often you will
want to show formatted values, such as a numeric column showing as currency, or
a more complex component such as a button or action menu as a cell value.
```
##########
superset-frontend/src/components/DesignSystem.stories.mdx:
##########
@@ -0,0 +1,25 @@
+import { Meta, Source } from '@storybook/addon-docs';
+import AtomicDesign from './atomic-design.png';
+
+<Meta title="Design System/Introduction" />
+
+# Superset Design System
+
+A design system is a complete set of standards intended to manage design at
scale using reusable components and patterns.
+
+You can get an overview of Atmomic Design concepts and link to full book on
the topic here:
Review Comment:
```suggestion
You can get an overview of Atomic Design concepts and a link to the full
book on the topic here:
```
##########
superset-frontend/src/components/Dropdown/index.tsx:
##########
@@ -66,14 +67,35 @@ const MenuDotsWrapper = styled.div`
padding-left: ${({ theme }) => theme.gridUnit}px;
`;
+export enum IconOrientation {
+ VERTICAL = 'vertical',
+ HORIZONTAL = 'horizontal',
+}
export interface DropdownProps extends DropDownProps {
overlay: React.ReactElement;
+ orientation?: IconOrientation;
Review Comment:
The first time I read the property I thought it referred to the orientation
of the dropdown. Maybe rename it to `iconOrientation`? It would be helpful when
reading the property from a parent component and the type is not displayed.
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/ActionCell.overview.mdx:
##########
@@ -0,0 +1,69 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Cell
Renderers/ActionCell/Overview" />
+
+# ActionCell
+
+An ActionCell is used to display overflow icon that opens a menu allowing the
user to take actions
+specific to the data in the table row that the cell is a member of.
Review Comment:
```suggestion
An ActionCell is used to display an overflow icon that opens a menu allowing
the user to take actions
specific to the data in the table row that the cell is a member of.
```
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, useRef, ReactElement } from 'react';
+import { Table as AntTable, ConfigProvider } from 'antd';
+import type { ColumnsType, TableProps as AntTableProps } from 'antd/es/table';
+import { t, useTheme } from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import styled, { StyledComponent } from '@emotion/styled';
+import InteractiveTableUtils from './utils/InteractiveTableUtils';
+
+export const SUPERSET_TABLE_COLUMN = 'superset/table-column';
+export interface TableDataType {
+ key: React.Key;
+}
+
+export enum SelectionType {
+ 'DISABLED' = 'disabled',
+ 'SINGLE' = 'single',
+ 'MULTI' = 'multi',
+}
+
+export interface Locale {
+ /**
+ * Text contained within the Table UI.
+ */
+ filterTitle: string;
+ filterConfirm: string;
+ filterReset: string;
+ filterEmptyText: string;
+ filterCheckall: string;
+ filterSearchPlaceholder: string;
+ emptyText: string;
+ selectAll: string;
+ selectInvert: string;
+ selectNone: string;
+ selectionAll: string;
+ sortTitle: string;
+ expand: string;
+ collapse: string;
+ triggerDesc: string;
+ triggerAsc: string;
+ cancelSort: string;
+}
+
+export interface TableProps extends AntTableProps<TableProps> {
+ /**
+ * Data that will populate the each row and map to the column key.
+ */
+ data: object[];
+ /**
+ * Table column definitions.
+ */
+ columns: ColumnsType<any>;
+ /**
+ * Array of row keys to represent list of selected rows.
+ */
+ selectedRows?: React.Key[];
+ /**
+ * Callback function invoked when a row is selected by user.
+ */
+ handleRowSelection?: Function;
+ /**
+ * Controls the size of the table.
+ */
+ size: TableSize;
+ /**
+ * Adjusts the padding around elements for different amounts of spacing
between elements.
+ */
+ selectionType?: SelectionType;
+ /*
+ * Places table in visual loading state. Use while waiting to retrieve data
or perform an async operation that will update the table.
+ */
+ loading?: boolean;
+ /**
+ * Uses a sticky header which always displays when vertically scrolling the
table. Default: true
+ */
+ sticky?: boolean;
+ /**
+ * Controls if columns are resizable by user.
+ */
+ resizable?: boolean;
+ /**
+ * EXPERIMENTAL: Controls if columns are re-orderable by user drag drop.
+ */
+ reorderable?: boolean;
+ /**
+ * Default number of rows table will display per page of data.
+ */
+ defaultPageSize?: number;
+ /**
+ * Array of numeric options for the number of rows table will display per
page of data.
+ * The user can select from these options in the page size drop down menu.
+ */
+ pageSizeOptions?: string[];
+ /**
+ * Set table to display no data even if data has been provided
+ */
+ hideData?: boolean;
+ /**
+ * emptyComponent
+ */
+ emptyComponent?: ReactElement;
+ /**
+ * Enables setting the text displayed in various components and tooltips
within the Table UI.
+ */
+ locale?: Locale;
+ /**
+ * Restricts the visible height of the table and allows for internal
scrolling within the table
+ * when the number of rows exceeds the visible space.
+ */
+ height?: number;
+}
+
+export enum TableSize {
+ SMALL = 'small',
+ MIDDLE = 'middle',
+}
+
+const defaultRowSelection: React.Key[] = [];
+// This accounts for the tables header and pagination if user gives table
instance a height. this is a temp solution
+const HEIGHT_OFFSET = 108;
+
+const StyledTable: StyledComponent<any> = styled(AntTable)<any>`
+ ${({ theme, height }) => `
+ .ant-table-body {
+ overflow: scroll;
+ height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined};
+ }
+
+ th.ant-table-cell {
+ font-weight: ${theme.typography.weights.bold};
+ color: ${theme.colors.grayscale.dark1};
+ user-select: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .ant-pagination-item-active {
+ border-color: ${theme.colors.primary.base};
+ }
+ `}
+`;
+
+const defaultLocale = {
+ filterTitle: t('Filter menu'),
+ filterConfirm: t('OK'),
+ filterReset: t('Reset'),
+ filterEmptyText: t('No filters'),
+ filterCheckall: t('Select all items'),
+ filterSearchPlaceholder: t('Search in filters'),
+ emptyText: t('No data'),
+ selectAll: t('Select current page'),
+ selectInvert: t('Invert current page'),
+ selectNone: t('Clear all data'),
+ selectionAll: t('Select all data'),
+ sortTitle: t('Sort'),
+ expand: t('Expand row'),
+ collapse: t('Collapse row'),
+ triggerDesc: t('Click to sort descending'),
+ triggerAsc: t('Click to sort ascending'),
+ cancelSort: t('Click to cancel sorting'),
+};
+
+const selectionMap = {};
+selectionMap[SelectionType.MULTI] = 'checkbox';
+selectionMap[SelectionType.SINGLE] = 'radio';
+selectionMap[SelectionType.DISABLED] = null;
+
+export function Table(props: TableProps) {
+ const {
+ data,
+ columns,
+ selectedRows = defaultRowSelection,
+ handleRowSelection,
+ size,
+ selectionType = SelectionType.DISABLED,
+ sticky = true,
+ loading = false,
+ resizable = false,
+ reorderable = false,
+ defaultPageSize = 15,
+ pageSizeOptions = ['5', '15', '25', '50', '100'],
+ hideData = false,
+ emptyComponent,
+ locale,
+ ...rest
+ } = props;
+
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
+ const [derivedColumns, setDerivedColumns] = useState(columns);
+ const [pageSize, setPageSize] = useState(defaultPageSize);
+ const [mergedLocale, setMergedLocale] = useState({ ...defaultLocale });
+ const [selectedRowKeys, setSelectedRowKeys] =
+ useState<React.Key[]>(selectedRows);
+ const interactiveTableUtils = useRef<InteractiveTableUtils | null>(null);
+
+ const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(newSelectedRowKeys);
+ handleRowSelection?.(newSelectedRowKeys);
+ };
+
+ const selectionTypeValue = selectionMap[selectionType];
+ const rowSelection = {
+ type: selectionTypeValue,
+ selectedRowKeys,
+ onChange: onSelectChange,
+ };
+
+ const renderEmpty = () =>
+ emptyComponent ?? <div>{mergedLocale.emptyText}</div>;
+
+ const initializeInteractiveTable = () => {
+ if (interactiveTableUtils.current) {
+ interactiveTableUtils.current?.clearListeners();
+ }
+ const table = wrapperRef.current?.getElementsByTagName('table')[0];
+ if (table) {
+ interactiveTableUtils.current = new InteractiveTableUtils(
+ table,
+ derivedColumns,
+ setDerivedColumns,
+ );
+ if (reorderable) {
+ interactiveTableUtils?.current?.initializeDragDropColumns(
+ reorderable,
+ table,
+ );
+ }
+ if (resizable) {
+ interactiveTableUtils?.current?.initializeResizableColumns(
+ resizable,
+ table,
+ );
+ }
+ }
+ };
+
+ // Log use of experimental features
+ useEffect(() => {
+ if (reorderable === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'EXPERIMENTAL FEATURE ENABLED: The "reorderable" prop of Table is
experimental and NOT recommended for use in production deployments.',
+ );
+ }
+ if (resizable === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'EXPERIMENTAL FEATURE ENABLED: The "resizable" prop of Table is
experimental and NOT recommended for use in production deployments.',
+ );
+ }
+ }, [reorderable, resizable]);
+
+ useEffect(() => {
+ let updatedLocale;
+ if (locale) {
+ // This spread allows for locale to only contain a subset of locale
overrides on props
+ updatedLocale = { ...defaultLocale, ...locale };
+ } else {
+ updatedLocale = { ...defaultLocale };
+ }
+ setMergedLocale(updatedLocale);
+ }, [locale]);
+
+ useEffect(() => {
+ initializeInteractiveTable();
+ return () => {
+ interactiveTableUtils?.current?.clearListeners?.();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [wrapperRef, reorderable, resizable]);
Review Comment:
Can you add the `initializeInteractiveTable` dependency and wrap the
function with `useCallback`?
##########
superset-frontend/src/components/DesignSystem.stories.mdx:
##########
@@ -0,0 +1,25 @@
+import { Meta, Source } from '@storybook/addon-docs';
+import AtomicDesign from './atomic-design.png';
+
+<Meta title="Design System/Introduction" />
+
+# Superset Design System
+
+A design system is a complete set of standards intended to manage design at
scale using reusable components and patterns.
+
+You can get an overview of Atmomic Design concepts and link to full book on
the topic here:
+
+<a href="https://bradfrost.com/blog/post/atomic-web-design/" target="_blank">
+ Intro to Atomic Design
+</a>
+
+While the Superset Design System will use Atomic Design priciples, we are
choosing different language to describe the elements.
Review Comment:
```suggestion
While the Superset Design System will use Atomic Design principles, we
choose a different language to describe the elements.
```
##########
superset-frontend/src/components/Table/Table.test.tsx:
##########
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import type { ColumnsType } from 'antd/es/table';
+import { Table, TableSize } from './index';
+
+interface BasicData {
+ columnName: string;
+ columnType: string;
+ dataType: string;
+}
+
+const testData: BasicData[] = [
+ {
+ columnName: 'Number',
+ columnType: 'Numerical',
+ dataType: 'number',
+ },
+ {
+ columnName: 'String',
+ columnType: 'Physical',
+ dataType: 'string',
+ },
+ {
+ columnName: 'Date',
+ columnType: 'Virtual',
+ dataType: 'date',
+ },
+];
+
+const testColumns: ColumnsType<BasicData> = [
+ {
+ title: 'Column Name',
+ dataIndex: 'columnName',
+ key: 'columnName',
+ },
+ {
+ title: 'Column Type',
+ dataIndex: 'columnType',
+ key: 'columnType',
+ },
+ {
+ title: 'Data Type',
+ dataIndex: 'dataType',
+ key: 'dataType',
+ },
+];
+
+test('renders with default props', async () => {
+ render(
+ <Table size={TableSize.MIDDLE} columns={testColumns} data={testData} />,
+ );
+ expect(
+ await screen.findByText(testColumns[0].title as string),
+ ).toBeInTheDocument();
+ expect(
+ await screen.findByText(testColumns[1].title as string),
+ ).toBeInTheDocument();
+ expect(
+ await screen.findByText(testColumns[2].title as string),
+ ).toBeInTheDocument();
Review Comment:
```suggestion
await waitFor(() =>
testColumns.forEach(column =>
expect(screen.getByText(column.title as string)).toBeInTheDocument(),
),
);
```
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/ActionCell.overview.mdx:
##########
@@ -0,0 +1,69 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Cell
Renderers/ActionCell/Overview" />
+
+# ActionCell
+
+An ActionCell is used to display overflow icon that opens a menu allowing the
user to take actions
+specific to the data in the table row that the cell is a member of.
+
+### [Basic
example](./?path=/docs/design-system-components-table-cell-renderers-actioncell--basic)
+
+<Story id="design-system-components-table-cell-renderers-actioncell--basic" />
+
+---
+
+## Usage
+
+The action cell accepts an array of objects that define the label, tooltip, an
onClick callback functions
+and an optional data payload to be provided back to the onClick handler
function.
Review Comment:
```suggestion
The action cell accepts an array of objects that define the label, tooltip,
onClick callback functions,
and an optional data payload to be provided back to the onClick handler
function.
```
##########
superset-frontend/src/components/Table/Table.stories.tsx:
##########
@@ -0,0 +1,471 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import { ColumnsType } from 'antd/es/table';
+import { Table, TableSize, SUPERSET_TABLE_COLUMN } from './index';
+import { numericalSort, alphabeticalSort } from './sorters';
+import ButtonCell from './cell-renderers/ButtonCell';
+import ActionCell from './cell-renderers/ActionCell';
+import { exampleMenuOptions } from './cell-renderers/ActionCell/fixtures';
+import NumericCell, {
+ CurrencyCode,
+ LocaleCode,
+ Style,
+} from './cell-renderers/NumericCell';
+
+export default {
+ title: 'Design System/Components/Table/Examples',
+ component: Table,
+} as ComponentMeta<typeof Table>;
+
+// eslint-disable-next-line no-alert
+const handleClick = (data: object, index: number) =>
+ alert(`I was Clicked: ${JSON.stringify(data)}, index: ${index}`);
Review Comment:
Can we use Storybook actions for this purpose instead of alerts? You can
check the MetadataBar component for an example.
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx:
##########
@@ -0,0 +1,146 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { styled } from '@superset-ui/core';
+import { Dropdown, IconOrientation } from 'src/components/Dropdown';
+import { Menu } from 'src/components/Menu';
+import { MenuProps } from 'antd/lib/menu';
+
+/**
+ * Props interface for Action Cell Renderer
+ */
+export interface ActionCellProps {
+ /**
+ * The Menu option presented to user when menu displays
+ */
+ menuOptions: ActionMenuItem[];
+ /**
+ * Object representing the data rendering the Table row with attribute for
each column
+ */
+ row: object;
+}
+
+export interface ActionMenuItem {
+ /**
+ * Click handler specific to the menu item
+ * @param menuItem The definition of the menu item that was clicked
+ * @returns ActionMenuItem
+ */
+ onClick: (menuItem: ActionMenuItem) => void;
+ /**
+ * Label user will see displayed in the list of menu options
+ */
+ label: string;
+ /**
+ * Optional tooltip user will see if they hover over the menu option to get
more context
+ */
+ tooltip?: string;
+ /**
+ * Optional variable that can contain data relevant to the menu item that you
+ * want easy access to in the callback function for the menu
+ */
+ payload?: any;
+ /**
+ * Object representing the data rendering the Table row with attribute for
each column
+ */
+ row?: object;
+}
+
+/**
+ * Props interface for ActionMenu
+ */
+export interface ActionMenuProps {
+ menuOptions: ActionMenuItem[];
+ setVisible: (visible: boolean) => void;
+}
+
+const SHADOW =
+ 'box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12), 0px 9px 28px 8px rgba(0,
0, 0, 0.05)';
+const FILTER = 'drop-shadow(0px 6px 16px rgba(0, 0, 0, 0.08))';
+
+const StyledMenu = styled(Menu)`
+ box-shadow: ${SHADOW} !important;
Review Comment:
Can we use theme colors for the shadow? You can pick the closest one.
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/ActionCell.overview.mdx:
##########
@@ -0,0 +1,69 @@
+import { Meta, Source, Story, ArgsTable } from '@storybook/addon-docs';
+
+<Meta title="Design System/Components/Table/Cell
Renderers/ActionCell/Overview" />
+
+# ActionCell
+
+An ActionCell is used to display overflow icon that opens a menu allowing the
user to take actions
+specific to the data in the table row that the cell is a member of.
+
+### [Basic
example](./?path=/docs/design-system-components-table-cell-renderers-actioncell--basic)
+
+<Story id="design-system-components-table-cell-renderers-actioncell--basic" />
+
+---
+
+## Usage
+
+The action cell accepts an array of objects that define the label, tooltip, an
onClick callback functions
+and an optional data payload to be provided back to the onClick handler
function.
+
+### [Basic
example](./?path=/docs/design-system-components-table-cell-renderers-actioncell--basic)
+
+<Story id="design-system-components-table-cell-renderers-actioncell--basic" />
+
+```
+import { ActionMenuItem } from 'src/components/Table/cell-renderers/index';
+
+export const exampleMenuOptions: ActionMenuItem[] = [
+ {
+ label: 'Action 1',
+ tooltip: "This is a tip, don't spend it all in one place",
+ onClick: (item: ActionMenuItem) => {
+ // eslint-disable-next-line no-alert
+ alert(JSON.stringify(item));
+ },
+ payload: {
+ taco: 'spicy chicken',
+ },
+ },
+ {
+ label: 'Action 2',
+ tooltip: 'This is another tip',
+ onClick: (item: ActionMenuItem) => {
+ // eslint-disable-next-line no-alert
+ alert(JSON.stringify(item));
+ },
+ payload: {
+ taco: 'saucy tofu',
+ },
+ },
+];
+
+```
+
+Within context of adding an action cell to cell definitions provided to the
table using the ActionCell component
Review Comment:
```suggestion
Within the context of adding an action cell to cell definitions provided to
the table using the ActionCell component
```
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/ActionCell.test.tsx:
##########
@@ -0,0 +1,49 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { configure } from '@testing-library/react';
Review Comment:
```suggestion
```
##########
superset-frontend/src/components/Table/cell-renderers/ButtonCell/index.tsx:
##########
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import Button, { ButtonStyle, ButtonSize } from 'src/components/Button';
+
+type onClickFunction = (row: object, index: number) => void;
Review Comment:
```suggestion
type onClickFunction = (row: object, index: number) => void;
```
##########
superset-frontend/src/components/Table/cell-renderers/ActionCell/index.tsx:
##########
@@ -0,0 +1,146 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect } from 'react';
+import { styled } from '@superset-ui/core';
+import { Dropdown, IconOrientation } from 'src/components/Dropdown';
+import { Menu } from 'src/components/Menu';
+import { MenuProps } from 'antd/lib/menu';
+
+/**
+ * Props interface for Action Cell Renderer
+ */
+export interface ActionCellProps {
+ /**
+ * The Menu option presented to user when menu displays
+ */
+ menuOptions: ActionMenuItem[];
+ /**
+ * Object representing the data rendering the Table row with attribute for
each column
+ */
+ row: object;
+}
+
+export interface ActionMenuItem {
+ /**
+ * Click handler specific to the menu item
+ * @param menuItem The definition of the menu item that was clicked
+ * @returns ActionMenuItem
+ */
+ onClick: (menuItem: ActionMenuItem) => void;
+ /**
+ * Label user will see displayed in the list of menu options
+ */
+ label: string;
+ /**
+ * Optional tooltip user will see if they hover over the menu option to get
more context
+ */
+ tooltip?: string;
+ /**
+ * Optional variable that can contain data relevant to the menu item that you
+ * want easy access to in the callback function for the menu
+ */
+ payload?: any;
+ /**
+ * Object representing the data rendering the Table row with attribute for
each column
+ */
+ row?: object;
+}
+
+/**
+ * Props interface for ActionMenu
+ */
+export interface ActionMenuProps {
+ menuOptions: ActionMenuItem[];
+ setVisible: (visible: boolean) => void;
+}
+
+const SHADOW =
+ 'box-shadow: 0px 3px 6px -4px rgba(0, 0, 0, 0.12), 0px 9px 28px 8px rgba(0,
0, 0, 0.05)';
+const FILTER = 'drop-shadow(0px 6px 16px rgba(0, 0, 0, 0.08))';
+
+const StyledMenu = styled(Menu)`
+ box-shadow: ${SHADOW} !important;
Review Comment:
We always try to avoid `!important`. Is it possible to use a more specific
selector?
##########
superset-frontend/src/components/Table/cell-renderers/ButtonCell/ButtonCell.stories.tsx:
##########
@@ -0,0 +1,63 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { ComponentStory, ComponentMeta } from '@storybook/react';
+import { ButtonCell } from './index';
+
+export default {
+ title: 'Design System/Components/Table/Cell Renderers/ButtonCell',
+ component: ButtonCell,
+} as ComponentMeta<typeof ButtonCell>;
+
+// eslint-disable-next-line no-alert
+const clickHandler = () => alert(`I was Clicked`);
Review Comment:
Same as before. Use Storybook actions instead of alerts.
##########
superset-frontend/src/components/Table/cell-renderers/NumericCell/index.tsx:
##########
@@ -0,0 +1,419 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
Review Comment:
```suggestion
import React from 'react';
import { logging } from '@superset-ui/core';
```
##########
superset-frontend/src/components/Table/cell-renderers/ButtonCell/ButtonCell.test.tsx:
##########
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { configure } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import ButtonCell from './index';
+import { exampleRow } from '../fixtures';
+
+test('renders with default props', async () => {
+ configure({ testIdAttribute: 'data-test' });
Review Comment:
```suggestion
```
##########
superset-frontend/src/components/Table/cell-renderers/NumericCell/index.tsx:
##########
@@ -0,0 +1,419 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React from 'react';
+
+export interface NumericCellProps {
+ /**
+ * The number to display (before optional formatting applied)
+ */
+ value: number;
+ /**
+ * ISO 639-1 language code with optional region or script modifier (e.g.
en_US).
+ */
+ locale?: LocaleCode;
+ /**
+ * Options for number formatting
+ */
+ options?: NumberOptions;
+}
+
+interface NumberOptions {
+ /**
+ * Style of number to display
+ */
+ style?: Style;
+
+ /**
+ * ISO 4217 currency code
+ */
+ currency?: CurrencyCode;
+
+ /**
+ * Languages in the form of a ISO 639-1 language code with optional region
or script modifier (e.g. de_AT).
+ */
+ maximumFractionDigits?: number;
+
+ /**
+ * A number from 1 to 21 (default is 21)
+ */
+ maximumSignificantDigits?: number;
+
+ /**
+ * A number from 0 to 20 (default is 3)
+ */
+ minimumFractionDigits?: number;
+
+ /**
+ * A number from 1 to 21 (default is 1)
+ */
+ minimumIntegerDigits?: number;
+
+ /**
+ * A number from 1 to 21 (default is 21)
+ */
+ minimumSignificantDigits?: number;
+}
+
+export enum Style {
+ CURRENCY = 'currency',
+ DECIMAL = 'decimal',
+ PERCENT = 'percent',
+}
+
+export enum CurrencyDisplay {
+ SYMBOL = 'symbol',
+ CODE = 'code',
+ NAME = 'name',
+}
+
+export enum LocaleCode {
+ af = 'af',
+ ak = 'ak',
+ sq = 'sq',
+ am = 'am',
+ ar = 'ar',
+ hy = 'hy',
+ as = 'as',
+ az = 'az',
+ bm = 'bm',
+ bn = 'bn',
+ eu = 'eu',
+ be = 'be',
+ bs = 'bs',
+ br = 'br',
+ bg = 'bg',
+ my = 'my',
+ ca = 'ca',
+ ce = 'ce',
+ zh = 'zh',
+ zh_Hans = 'zh-Hans',
+ zh_Hant = 'zh-Hant',
+ cu = 'cu',
+ kw = 'kw',
+ co = 'co',
+ hr = 'hr',
+ cs = 'cs',
+ da = 'da',
+ nl = 'nl',
+ nl_BE = 'nl-BE',
+ dz = 'dz',
+ en = 'en',
+ en_AU = 'en-AU',
+ en_CA = 'en-CA',
+ en_GB = 'en-GB',
+ en_US = 'en-US',
+ eo = 'eo',
+ et = 'et',
+ ee = 'ee',
+ fo = 'fo',
+ fi = 'fi',
+ fr = 'fr',
+ fr_CA = 'fr-CA',
+ fr_CH = 'fr-CH',
+ ff = 'ff',
+ gl = 'gl',
+ lg = 'lg',
+ ka = 'ka',
+ de = 'de',
+ de_AT = 'de-AT',
+ de_CH = 'de-CH',
+ el = 'el',
+ gu = 'gu',
+ ht = 'ht',
+ ha = 'ha',
+ he = 'he',
+ hi = 'hi',
+ hu = 'hu',
+ is = 'is',
+ ig = 'ig',
+ id = 'id',
+ ia = 'ia',
+ ga = 'ga',
+ it = 'it',
+ ja = 'ja',
+ jv = 'jv',
+ kl = 'kl',
+ kn = 'kn',
+ ks = 'ks',
+ kk = 'kk',
+ km = 'km',
+ ki = 'ki',
+ rw = 'rw',
+ ko = 'ko',
+ ku = 'ku',
+ ky = 'ky',
+ lo = 'lo',
+ la = 'la',
+ lv = 'lv',
+ ln = 'ln',
+ lt = 'lt',
+ lu = 'lu',
+ lb = 'lb',
+ mk = 'mk',
+ mg = 'mg',
+ ms = 'ms',
+ ml = 'ml',
+ mt = 'mt',
+ gv = 'gv',
+ mi = 'mi',
+ mr = 'mr',
+ mn = 'mn',
+ ne = 'ne',
+ nd = 'nd',
+ se = 'se',
+ nb = 'nb',
+ nn = 'nn',
+ ny = 'ny',
+ or = 'or',
+ om = 'om',
+ os = 'os',
+ ps = 'ps',
+ fa = 'fa',
+ fa_AF = 'fa-AF',
+ pl = 'pl',
+ pt = 'pt',
+ pt_BR = 'pt-BR',
+ pt_PT = 'pt-PT',
+ pa = 'pa',
+ qu = 'qu',
+ ro = 'ro',
+ ro_MD = 'ro-MD',
+ rm = 'rm',
+ rn = 'rn',
+ ru = 'ru',
+ sm = 'sm',
+ sg = 'sg',
+ sa = 'sa',
+ gd = 'gd',
+ sr = 'sr',
+ sn = 'sn',
+ ii = 'ii',
+ sd = 'sd',
+ si = 'si',
+ sk = 'sk',
+ sl = 'sl',
+ so = 'so',
+ st = 'st',
+ es = 'es',
+ es_ES = 'es-ES',
+ es_MX = 'es-MX',
+ su = 'su',
+ sw = 'sw',
+ sw_CD = 'sw-CD',
+ sv = 'sv',
+ tg = 'tg',
+ ta = 'ta',
+ tt = 'tt',
+ te = 'te',
+ th = 'th',
+ bo = 'bo',
+ ti = 'ti',
+ to = 'to',
+ tr = 'tr',
+ tk = 'tk',
+ uk = 'uk',
+ ur = 'ur',
+ ug = 'ug',
+ uz = 'uz',
+ vi = 'vi',
+ vo = 'vo',
+ cy = 'cy',
+ fy = 'fy',
+ wo = 'wo',
+ xh = 'xh',
+ yi = 'yi',
+ yo = 'yo',
+ zu = 'zu',
+}
+
+export enum CurrencyCode {
+ AED = 'AED',
+ AFN = 'AFN',
+ ALL = 'ALL',
+ AMD = 'AMD',
+ ANG = 'ANG',
+ AOA = 'AOA',
+ ARS = 'ARS',
+ AUD = 'AUD',
+ AWG = 'AWG',
+ AZN = 'AZN',
+ BAM = 'BAM',
+ BBD = 'BBD',
+ BDT = 'BDT',
+ BGN = 'BGN',
+ BHD = 'BHD',
+ BIF = 'BIF',
+ BMD = 'BMD',
+ BND = 'BND',
+ BOB = 'BOB',
+ BRL = 'BRL',
+ BSD = 'BSD',
+ BTN = 'BTN',
+ BWP = 'BWP',
+ BYN = 'BYN',
+ BZD = 'BZD',
+ CAD = 'CAD',
+ CDF = 'CDF',
+ CHF = 'CHF',
+ CLP = 'CLP',
+ CNY = 'CNY',
+ COP = 'COP',
+ CRC = 'CRC',
+ CUC = 'CUC',
+ CUP = 'CUP',
+ CVE = 'CVE',
+ CZK = 'CZK',
+ DJF = 'DJF',
+ DKK = 'DKK',
+ DOP = 'DOP',
+ DZD = 'DZD',
+ EGP = 'EGP',
+ ERN = 'ERN',
+ ETB = 'ETB',
+ EUR = 'EUR',
+ FJD = 'FJD',
+ FKP = 'FKP',
+ GBP = 'GBP',
+ GEL = 'GEL',
+ GHS = 'GHS',
+ GIP = 'GIP',
+ GMD = 'GMD',
+ GNF = 'GNF',
+ GTQ = 'GTQ',
+ GYD = 'GYD',
+ HKD = 'HKD',
+ HNL = 'HNL',
+ HRK = 'HRK',
+ HTG = 'HTG',
+ HUF = 'HUF',
+ IDR = 'IDR',
+ ILS = 'ILS',
+ INR = 'INR',
+ IQD = 'IQD',
+ IRR = 'IRR',
+ ISK = 'ISK',
+ JMD = 'JMD',
+ JOD = 'JOD',
+ JPY = 'JPY',
+ KES = 'KES',
+ KGS = 'KGS',
+ KHR = 'KHR',
+ KMF = 'KMF',
+ KPW = 'KPW',
+ KRW = 'KRW',
+ KWD = 'KWD',
+ KYD = 'KYD',
+ KZT = 'KZT',
+ LAK = 'LAK',
+ LBP = 'LBP',
+ LKR = 'LKR',
+ LRD = 'LRD',
+ LSL = 'LSL',
+ LYD = 'LYD',
+ MAD = 'MAD',
+ MDL = 'MDL',
+ MGA = 'MGA',
+ MKD = 'MKD',
+ MMK = 'MMK',
+ MNT = 'MNT',
+ MOP = 'MOP',
+ MRU = 'MRU',
+ MUR = 'MUR',
+ MVR = 'MVR',
+ MWK = 'MWK',
+ MXN = 'MXN',
+ MYR = 'MYR',
+ MZN = 'MZN',
+ NAD = 'NAD',
+ NGN = 'NGN',
+ NIO = 'NIO',
+ NOK = 'NOK',
+ NPR = 'NPR',
+ NZD = 'NZD',
+ OMR = 'OMR',
+ PAB = 'PAB',
+ PEN = 'PEN',
+ PGK = 'PGK',
+ PHP = 'PHP',
+ PKR = 'PKR',
+ PLN = 'PLN',
+ PYG = 'PYG',
+ QAR = 'QAR',
+ RON = 'RON',
+ RSD = 'RSD',
+ RUB = 'RUB',
+ RWF = 'RWF',
+ SAR = 'SAR',
+ SBD = 'SBD',
+ SCR = 'SCR',
+ SDG = 'SDG',
+ SEK = 'SEK',
+ SGD = 'SGD',
+ SHP = 'SHP',
+ SLL = 'SLL',
+ SOS = 'SOS',
+ SRD = 'SRD',
+ SSP = 'SSP',
+ STN = 'STN',
+ SVC = 'SVC',
+ SYP = 'SYP',
+ SZL = 'SZL',
+ THB = 'THB',
+ TJS = 'TJS',
+ TMT = 'TMT',
+ TND = 'TND',
+ TOP = 'TOP',
+ TRY = 'TRY',
+ TTD = 'TTD',
+ TWD = 'TWD',
+ TZS = 'TZS',
+ UAH = 'UAH',
+ UGX = 'UGX',
+ USD = 'USD',
+ UYU = 'UYU',
+ UZS = 'UZS',
+ VES = 'VES',
+ VND = 'VND',
+ VUV = 'VUV',
+ WST = 'WST',
+ XAF = 'XAF',
+ XCD = 'XCD',
+ XOF = 'XOF',
+ XPF = 'XPF',
+ YER = 'YER',
+ ZAR = 'ZAR',
+ ZMW = 'ZMW',
+ ZWL = 'ZWL',
+}
+
+export function NumericCell(props: NumericCellProps) {
+ const { value, locale = LocaleCode.en_US, options } = props;
+ let displayValue = value?.toString() ?? value;
+ try {
+ displayValue = value?.toLocaleString?.(locale, options);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
Review Comment:
```suggestion
logging.error(e);
```
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, useRef, ReactElement } from 'react';
+import { Table as AntTable, ConfigProvider } from 'antd';
+import type { ColumnsType, TableProps as AntTableProps } from 'antd/es/table';
+import { t, useTheme } from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import styled, { StyledComponent } from '@emotion/styled';
+import InteractiveTableUtils from './utils/InteractiveTableUtils';
+
+export const SUPERSET_TABLE_COLUMN = 'superset/table-column';
+export interface TableDataType {
+ key: React.Key;
+}
+
+export enum SelectionType {
+ 'DISABLED' = 'disabled',
+ 'SINGLE' = 'single',
+ 'MULTI' = 'multi',
+}
+
+export interface Locale {
+ /**
+ * Text contained within the Table UI.
+ */
+ filterTitle: string;
+ filterConfirm: string;
+ filterReset: string;
+ filterEmptyText: string;
+ filterCheckall: string;
+ filterSearchPlaceholder: string;
+ emptyText: string;
+ selectAll: string;
+ selectInvert: string;
+ selectNone: string;
+ selectionAll: string;
+ sortTitle: string;
+ expand: string;
+ collapse: string;
+ triggerDesc: string;
+ triggerAsc: string;
+ cancelSort: string;
+}
+
+export interface TableProps extends AntTableProps<TableProps> {
+ /**
+ * Data that will populate the each row and map to the column key.
+ */
+ data: object[];
+ /**
+ * Table column definitions.
+ */
+ columns: ColumnsType<any>;
+ /**
+ * Array of row keys to represent list of selected rows.
+ */
+ selectedRows?: React.Key[];
+ /**
+ * Callback function invoked when a row is selected by user.
+ */
+ handleRowSelection?: Function;
+ /**
+ * Controls the size of the table.
+ */
+ size: TableSize;
+ /**
+ * Adjusts the padding around elements for different amounts of spacing
between elements.
+ */
+ selectionType?: SelectionType;
+ /*
+ * Places table in visual loading state. Use while waiting to retrieve data
or perform an async operation that will update the table.
+ */
+ loading?: boolean;
+ /**
+ * Uses a sticky header which always displays when vertically scrolling the
table. Default: true
+ */
+ sticky?: boolean;
+ /**
+ * Controls if columns are resizable by user.
+ */
+ resizable?: boolean;
+ /**
+ * EXPERIMENTAL: Controls if columns are re-orderable by user drag drop.
+ */
+ reorderable?: boolean;
+ /**
+ * Default number of rows table will display per page of data.
+ */
+ defaultPageSize?: number;
+ /**
+ * Array of numeric options for the number of rows table will display per
page of data.
+ * The user can select from these options in the page size drop down menu.
+ */
+ pageSizeOptions?: string[];
+ /**
+ * Set table to display no data even if data has been provided
+ */
+ hideData?: boolean;
+ /**
+ * emptyComponent
+ */
+ emptyComponent?: ReactElement;
+ /**
+ * Enables setting the text displayed in various components and tooltips
within the Table UI.
+ */
+ locale?: Locale;
+ /**
+ * Restricts the visible height of the table and allows for internal
scrolling within the table
+ * when the number of rows exceeds the visible space.
+ */
+ height?: number;
+}
+
+export enum TableSize {
+ SMALL = 'small',
+ MIDDLE = 'middle',
+}
+
+const defaultRowSelection: React.Key[] = [];
+// This accounts for the tables header and pagination if user gives table
instance a height. this is a temp solution
+const HEIGHT_OFFSET = 108;
+
+const StyledTable: StyledComponent<any> = styled(AntTable)<any>`
+ ${({ theme, height }) => `
+ .ant-table-body {
+ overflow: scroll;
+ height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined};
+ }
+
+ th.ant-table-cell {
+ font-weight: ${theme.typography.weights.bold};
+ color: ${theme.colors.grayscale.dark1};
+ user-select: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .ant-pagination-item-active {
+ border-color: ${theme.colors.primary.base};
+ }
+ `}
+`;
+
+const defaultLocale = {
+ filterTitle: t('Filter menu'),
+ filterConfirm: t('OK'),
+ filterReset: t('Reset'),
+ filterEmptyText: t('No filters'),
+ filterCheckall: t('Select all items'),
+ filterSearchPlaceholder: t('Search in filters'),
+ emptyText: t('No data'),
+ selectAll: t('Select current page'),
+ selectInvert: t('Invert current page'),
+ selectNone: t('Clear all data'),
+ selectionAll: t('Select all data'),
+ sortTitle: t('Sort'),
+ expand: t('Expand row'),
+ collapse: t('Collapse row'),
+ triggerDesc: t('Click to sort descending'),
+ triggerAsc: t('Click to sort ascending'),
+ cancelSort: t('Click to cancel sorting'),
+};
+
+const selectionMap = {};
+selectionMap[SelectionType.MULTI] = 'checkbox';
+selectionMap[SelectionType.SINGLE] = 'radio';
+selectionMap[SelectionType.DISABLED] = null;
+
+export function Table(props: TableProps) {
+ const {
+ data,
+ columns,
+ selectedRows = defaultRowSelection,
+ handleRowSelection,
+ size,
+ selectionType = SelectionType.DISABLED,
+ sticky = true,
+ loading = false,
+ resizable = false,
+ reorderable = false,
+ defaultPageSize = 15,
+ pageSizeOptions = ['5', '15', '25', '50', '100'],
+ hideData = false,
+ emptyComponent,
+ locale,
+ ...rest
+ } = props;
+
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
+ const [derivedColumns, setDerivedColumns] = useState(columns);
+ const [pageSize, setPageSize] = useState(defaultPageSize);
+ const [mergedLocale, setMergedLocale] = useState({ ...defaultLocale });
+ const [selectedRowKeys, setSelectedRowKeys] =
+ useState<React.Key[]>(selectedRows);
+ const interactiveTableUtils = useRef<InteractiveTableUtils | null>(null);
+
+ const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(newSelectedRowKeys);
+ handleRowSelection?.(newSelectedRowKeys);
+ };
+
+ const selectionTypeValue = selectionMap[selectionType];
+ const rowSelection = {
+ type: selectionTypeValue,
+ selectedRowKeys,
+ onChange: onSelectChange,
+ };
+
+ const renderEmpty = () =>
+ emptyComponent ?? <div>{mergedLocale.emptyText}</div>;
+
+ const initializeInteractiveTable = () => {
+ if (interactiveTableUtils.current) {
+ interactiveTableUtils.current?.clearListeners();
+ }
+ const table = wrapperRef.current?.getElementsByTagName('table')[0];
+ if (table) {
+ interactiveTableUtils.current = new InteractiveTableUtils(
+ table,
+ derivedColumns,
+ setDerivedColumns,
+ );
+ if (reorderable) {
+ interactiveTableUtils?.current?.initializeDragDropColumns(
+ reorderable,
+ table,
+ );
+ }
+ if (resizable) {
+ interactiveTableUtils?.current?.initializeResizableColumns(
+ resizable,
+ table,
+ );
+ }
+ }
+ };
+
+ // Log use of experimental features
+ useEffect(() => {
+ if (reorderable === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'EXPERIMENTAL FEATURE ENABLED: The "reorderable" prop of Table is
experimental and NOT recommended for use in production deployments.',
+ );
+ }
+ if (resizable === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
Review Comment:
```suggestion
logging.warn(
```
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, useRef, ReactElement } from 'react';
+import { Table as AntTable, ConfigProvider } from 'antd';
+import type { ColumnsType, TableProps as AntTableProps } from 'antd/es/table';
+import { t, useTheme } from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import styled, { StyledComponent } from '@emotion/styled';
+import InteractiveTableUtils from './utils/InteractiveTableUtils';
+
+export const SUPERSET_TABLE_COLUMN = 'superset/table-column';
+export interface TableDataType {
+ key: React.Key;
+}
+
+export enum SelectionType {
+ 'DISABLED' = 'disabled',
+ 'SINGLE' = 'single',
+ 'MULTI' = 'multi',
+}
+
+export interface Locale {
+ /**
+ * Text contained within the Table UI.
+ */
+ filterTitle: string;
+ filterConfirm: string;
+ filterReset: string;
+ filterEmptyText: string;
+ filterCheckall: string;
+ filterSearchPlaceholder: string;
+ emptyText: string;
+ selectAll: string;
+ selectInvert: string;
+ selectNone: string;
+ selectionAll: string;
+ sortTitle: string;
+ expand: string;
+ collapse: string;
+ triggerDesc: string;
+ triggerAsc: string;
+ cancelSort: string;
+}
+
+export interface TableProps extends AntTableProps<TableProps> {
+ /**
+ * Data that will populate the each row and map to the column key.
+ */
+ data: object[];
+ /**
+ * Table column definitions.
+ */
+ columns: ColumnsType<any>;
+ /**
+ * Array of row keys to represent list of selected rows.
+ */
+ selectedRows?: React.Key[];
+ /**
+ * Callback function invoked when a row is selected by user.
+ */
+ handleRowSelection?: Function;
+ /**
+ * Controls the size of the table.
+ */
+ size: TableSize;
+ /**
+ * Adjusts the padding around elements for different amounts of spacing
between elements.
+ */
+ selectionType?: SelectionType;
+ /*
+ * Places table in visual loading state. Use while waiting to retrieve data
or perform an async operation that will update the table.
+ */
+ loading?: boolean;
+ /**
+ * Uses a sticky header which always displays when vertically scrolling the
table. Default: true
+ */
+ sticky?: boolean;
+ /**
+ * Controls if columns are resizable by user.
+ */
+ resizable?: boolean;
+ /**
+ * EXPERIMENTAL: Controls if columns are re-orderable by user drag drop.
+ */
+ reorderable?: boolean;
+ /**
+ * Default number of rows table will display per page of data.
+ */
+ defaultPageSize?: number;
+ /**
+ * Array of numeric options for the number of rows table will display per
page of data.
+ * The user can select from these options in the page size drop down menu.
+ */
+ pageSizeOptions?: string[];
+ /**
+ * Set table to display no data even if data has been provided
+ */
+ hideData?: boolean;
+ /**
+ * emptyComponent
+ */
+ emptyComponent?: ReactElement;
+ /**
+ * Enables setting the text displayed in various components and tooltips
within the Table UI.
+ */
+ locale?: Locale;
+ /**
+ * Restricts the visible height of the table and allows for internal
scrolling within the table
+ * when the number of rows exceeds the visible space.
+ */
+ height?: number;
+}
+
+export enum TableSize {
+ SMALL = 'small',
+ MIDDLE = 'middle',
+}
+
+const defaultRowSelection: React.Key[] = [];
+// This accounts for the tables header and pagination if user gives table
instance a height. this is a temp solution
+const HEIGHT_OFFSET = 108;
+
+const StyledTable: StyledComponent<any> = styled(AntTable)<any>`
+ ${({ theme, height }) => `
+ .ant-table-body {
+ overflow: scroll;
+ height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined};
+ }
+
+ th.ant-table-cell {
+ font-weight: ${theme.typography.weights.bold};
+ color: ${theme.colors.grayscale.dark1};
+ user-select: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .ant-pagination-item-active {
+ border-color: ${theme.colors.primary.base};
+ }
+ `}
+`;
+
+const defaultLocale = {
+ filterTitle: t('Filter menu'),
+ filterConfirm: t('OK'),
+ filterReset: t('Reset'),
+ filterEmptyText: t('No filters'),
+ filterCheckall: t('Select all items'),
+ filterSearchPlaceholder: t('Search in filters'),
+ emptyText: t('No data'),
+ selectAll: t('Select current page'),
+ selectInvert: t('Invert current page'),
+ selectNone: t('Clear all data'),
+ selectionAll: t('Select all data'),
+ sortTitle: t('Sort'),
+ expand: t('Expand row'),
+ collapse: t('Collapse row'),
+ triggerDesc: t('Click to sort descending'),
+ triggerAsc: t('Click to sort ascending'),
+ cancelSort: t('Click to cancel sorting'),
+};
+
+const selectionMap = {};
+selectionMap[SelectionType.MULTI] = 'checkbox';
+selectionMap[SelectionType.SINGLE] = 'radio';
+selectionMap[SelectionType.DISABLED] = null;
+
+export function Table(props: TableProps) {
+ const {
+ data,
+ columns,
+ selectedRows = defaultRowSelection,
+ handleRowSelection,
+ size,
+ selectionType = SelectionType.DISABLED,
+ sticky = true,
+ loading = false,
+ resizable = false,
+ reorderable = false,
+ defaultPageSize = 15,
+ pageSizeOptions = ['5', '15', '25', '50', '100'],
+ hideData = false,
+ emptyComponent,
+ locale,
+ ...rest
+ } = props;
+
+ const wrapperRef = useRef<HTMLDivElement | null>(null);
+ const [derivedColumns, setDerivedColumns] = useState(columns);
+ const [pageSize, setPageSize] = useState(defaultPageSize);
+ const [mergedLocale, setMergedLocale] = useState({ ...defaultLocale });
+ const [selectedRowKeys, setSelectedRowKeys] =
+ useState<React.Key[]>(selectedRows);
+ const interactiveTableUtils = useRef<InteractiveTableUtils | null>(null);
+
+ const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
+ setSelectedRowKeys(newSelectedRowKeys);
+ handleRowSelection?.(newSelectedRowKeys);
+ };
+
+ const selectionTypeValue = selectionMap[selectionType];
+ const rowSelection = {
+ type: selectionTypeValue,
+ selectedRowKeys,
+ onChange: onSelectChange,
+ };
+
+ const renderEmpty = () =>
+ emptyComponent ?? <div>{mergedLocale.emptyText}</div>;
+
+ const initializeInteractiveTable = () => {
+ if (interactiveTableUtils.current) {
+ interactiveTableUtils.current?.clearListeners();
+ }
+ const table = wrapperRef.current?.getElementsByTagName('table')[0];
+ if (table) {
+ interactiveTableUtils.current = new InteractiveTableUtils(
+ table,
+ derivedColumns,
+ setDerivedColumns,
+ );
+ if (reorderable) {
+ interactiveTableUtils?.current?.initializeDragDropColumns(
+ reorderable,
+ table,
+ );
+ }
+ if (resizable) {
+ interactiveTableUtils?.current?.initializeResizableColumns(
+ resizable,
+ table,
+ );
+ }
+ }
+ };
+
+ // Log use of experimental features
+ useEffect(() => {
+ if (reorderable === true) {
+ // eslint-disable-next-line no-console
+ console.warn(
Review Comment:
```suggestion
logging.warn(
```
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, useRef, ReactElement } from 'react';
+import { Table as AntTable, ConfigProvider } from 'antd';
+import type { ColumnsType, TableProps as AntTableProps } from 'antd/es/table';
+import { t, useTheme } from '@superset-ui/core';
Review Comment:
```suggestion
import { t, useTheme, logging } from '@superset-ui/core';
```
##########
superset-frontend/src/components/Loading/index.tsx:
##########
@@ -20,6 +20,8 @@
import React from 'react';
import { styled } from '@superset-ui/core';
import cls from 'classnames';
+// @ts-ignore
Review Comment:
You can follow the same pattern as
https://github.com/apache/superset/blob/cd1b379bdf323f78c2e7d574525a55898c920942/superset-frontend/plugins/plugin-chart-table/types/external.d.ts#L20
If you search for `declare module` in the codebase you'll see many examples.
##########
superset-frontend/src/components/Table/index.tsx:
##########
@@ -0,0 +1,320 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * License); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useState, useEffect, useRef, ReactElement } from 'react';
+import { Table as AntTable, ConfigProvider } from 'antd';
+import type { ColumnsType, TableProps as AntTableProps } from 'antd/es/table';
+import { t, useTheme } from '@superset-ui/core';
+import Loading from 'src/components/Loading';
+import styled, { StyledComponent } from '@emotion/styled';
+import InteractiveTableUtils from './utils/InteractiveTableUtils';
+
+export const SUPERSET_TABLE_COLUMN = 'superset/table-column';
+export interface TableDataType {
+ key: React.Key;
+}
+
+export enum SelectionType {
+ 'DISABLED' = 'disabled',
+ 'SINGLE' = 'single',
+ 'MULTI' = 'multi',
+}
+
+export interface Locale {
+ /**
+ * Text contained within the Table UI.
+ */
+ filterTitle: string;
+ filterConfirm: string;
+ filterReset: string;
+ filterEmptyText: string;
+ filterCheckall: string;
+ filterSearchPlaceholder: string;
+ emptyText: string;
+ selectAll: string;
+ selectInvert: string;
+ selectNone: string;
+ selectionAll: string;
+ sortTitle: string;
+ expand: string;
+ collapse: string;
+ triggerDesc: string;
+ triggerAsc: string;
+ cancelSort: string;
+}
+
+export interface TableProps extends AntTableProps<TableProps> {
+ /**
+ * Data that will populate the each row and map to the column key.
+ */
+ data: object[];
+ /**
+ * Table column definitions.
+ */
+ columns: ColumnsType<any>;
+ /**
+ * Array of row keys to represent list of selected rows.
+ */
+ selectedRows?: React.Key[];
+ /**
+ * Callback function invoked when a row is selected by user.
+ */
+ handleRowSelection?: Function;
+ /**
+ * Controls the size of the table.
+ */
+ size: TableSize;
+ /**
+ * Adjusts the padding around elements for different amounts of spacing
between elements.
+ */
+ selectionType?: SelectionType;
+ /*
+ * Places table in visual loading state. Use while waiting to retrieve data
or perform an async operation that will update the table.
+ */
+ loading?: boolean;
+ /**
+ * Uses a sticky header which always displays when vertically scrolling the
table. Default: true
+ */
+ sticky?: boolean;
+ /**
+ * Controls if columns are resizable by user.
+ */
+ resizable?: boolean;
+ /**
+ * EXPERIMENTAL: Controls if columns are re-orderable by user drag drop.
+ */
+ reorderable?: boolean;
+ /**
+ * Default number of rows table will display per page of data.
+ */
+ defaultPageSize?: number;
+ /**
+ * Array of numeric options for the number of rows table will display per
page of data.
+ * The user can select from these options in the page size drop down menu.
+ */
+ pageSizeOptions?: string[];
+ /**
+ * Set table to display no data even if data has been provided
+ */
+ hideData?: boolean;
+ /**
+ * emptyComponent
+ */
+ emptyComponent?: ReactElement;
+ /**
+ * Enables setting the text displayed in various components and tooltips
within the Table UI.
+ */
+ locale?: Locale;
+ /**
+ * Restricts the visible height of the table and allows for internal
scrolling within the table
+ * when the number of rows exceeds the visible space.
+ */
+ height?: number;
+}
+
+export enum TableSize {
+ SMALL = 'small',
+ MIDDLE = 'middle',
+}
+
+const defaultRowSelection: React.Key[] = [];
+// This accounts for the tables header and pagination if user gives table
instance a height. this is a temp solution
+const HEIGHT_OFFSET = 108;
+
+const StyledTable: StyledComponent<any> = styled(AntTable)<any>`
+ ${({ theme, height }) => `
+ .ant-table-body {
+ overflow: scroll;
+ height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined};
+ }
+
+ th.ant-table-cell {
+ font-weight: ${theme.typography.weights.bold};
+ color: ${theme.colors.grayscale.dark1};
+ user-select: none;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .ant-pagination-item-active {
+ border-color: ${theme.colors.primary.base};
+ }
+ `}
Review Comment:
```suggestion
${({ theme, height }) => `
.ant-table-body {
overflow: scroll;
height: ${height ? `${height - HEIGHT_OFFSET}px` : undefined};
}
th.ant-table-cell {
font-weight: ${theme.typography.weights.bold};
color: ${theme.colors.grayscale.dark1};
user-select: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ant-pagination-item-active {
border-color: ${theme.colors.primary.base};
}
`}
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]