From b66aa3cd699e90cc5bd5caa2c498499e3f54a026 Mon Sep 17 00:00:00 2001 From: Taylor Jones Date: Wed, 28 Oct 2020 10:17:39 -0500 Subject: [PATCH] docs(storybook): run storiesof-to-csf codemod https://github.com/storybookjs/storybook/blob/next/lib/codemod/README.md#storiesof-to-csf --- .../__snapshots__/Welcome.story.storyshot | 4 +- .../AccordionItemDefer.story.storyshot | 8 +- src/components/AddCard/AddCard.story.jsx | 17 +- .../BarChartCard/BarChartCard.story.jsx | 1029 +-- .../Breadcrumb/Breadcrumb.story.jsx | 209 +- src/components/Button/Button.story.jsx | 306 +- src/components/Card/Card.story.jsx | 755 ++- .../Card/__snapshots__/Card.story.storyshot | 3 +- .../CardCodeEditor/CardCodeEditor.story.jsx | 56 +- .../CardEditor/CardEditor.story.jsx | 219 +- src/components/ComboBox/ComboBox.story.jsx | 241 +- .../ComposedModal/ComposedModal.story.jsx | 341 +- .../ComposedStructuredList.story.jsx | 202 +- .../ComposedStructuredList.story.storyshot | 93 - src/components/Dashboard/Dashboard.story.jsx | 2081 +++--- .../Dashboard/DashboardGrid.story.jsx | 1535 ++--- .../Dashboard/DashboardHeader.story.jsx | 262 +- .../__snapshots__/Dashboard.story.storyshot | 91 - .../DashboardEditor/DashboardEditor.story.jsx | 571 +- .../DashboardEditorHeader.story.jsx | 89 +- .../DateTimePicker/DateTimePicker.story.jsx | 268 +- src/components/FileDrop/FileDrop.story.jsx | 128 +- .../FilterTags/FilterTags.story.jsx | 119 +- .../FlyoutMenu/FlyoutMenu.story.jsx | 168 +- src/components/FormItem/FormItem.story.jsx | 39 +- src/components/GaugeCard/GaugeCard.story.jsx | 185 +- src/components/Header/Header.story.jsx | 88 +- src/components/Hero/Hero.story.jsx | 150 +- .../Hero/__snapshots__/Hero.story.storyshot | 97 - .../IconSwitch/IconSwitch.story.jsx | 286 +- .../ImageCard/HotspotContent.story.jsx | 352 +- src/components/ImageCard/ImageCard.story.jsx | 358 +- .../ImageCard/ImageHotspots.story.jsx | 193 +- .../HierarchyList/HierarchyList.story.jsx | 668 +- src/components/List/List.story.jsx | 776 ++- .../List/ListItem/ListItem.story.jsx | 607 +- .../List/SimpleList/SimpleList.story.jsx | 666 +- src/components/ListCard/ListCard.story.jsx | 112 +- .../MultiSelect/MultiSelect.story.jsx | 148 +- .../NavigationBar/NavigationBar.story.jsx | 76 +- .../OverflowMenu/OverflowMenu.story.jsx | 141 +- src/components/Page/EditPage.story.jsx | 56 +- src/components/Page/PageHero.story.jsx | 96 +- .../__snapshots__/EditPage.story.storyshot | 97 - .../__snapshots__/PageHero.story.storyshot | 97 - .../PageTitleBar/PageTitleBar.story.jsx | 293 +- .../PageWizard/PageWizard.story.jsx | 429 +- .../PieChartCard/PieChartCard.story.jsx | 553 +- .../ProgressIndicator.story.jsx | 150 +- .../ResourceList/ResourceList.story.jsx | 69 +- src/components/SideNav/SideNav.story.jsx | 129 +- .../SimplePagination.story.jsx | 33 +- .../SuiteHeader/SuiteHeader.story.jsx | 239 +- .../SuiteHeaderAppSwitcher.story.jsx | 80 +- .../SuiteHeaderLogoutModal.story.jsx | 33 +- .../SuiteHeaderProfile.story.jsx | 39 +- src/components/Table/Table.story.jsx | 5903 +++++++++-------- .../TableBodyRow/TableBodyRow.story.jsx | 263 +- .../TableDetailWizard.story.jsx | 111 +- .../TableManageViewsModal.story.jsx | 606 +- .../TableSaveViewModal.story.jsx | 494 +- .../TableSaveViewModal.story.storyshot | 8 +- .../TableViewDropdown.story.jsx | 197 +- src/components/TableCard/TableCard.story.jsx | 2237 ++++--- src/components/TextInput/TextInput.story.jsx | 182 +- .../TileCatalog/TileCatalog.story.jsx | 169 +- .../TileCatalogNew/TileCatalogNew.story.jsx | 318 +- .../TileGallery/TileGallery.story.jsx | 280 +- .../__snapshots__/TileGallery.story.storyshot | 28 +- .../TimePickerSpinner.story.jsx | 76 +- .../TimeSeriesCard/TimeSeriesCard.story.jsx | 2493 +++---- src/components/ValueCard/ValueCard.story.jsx | 2728 ++++---- .../WizardInline/WizardInline.story.jsx | 220 +- .../WizardInline.story.storyshot | 93 - .../WizardModal/WizardModal.story.jsx | 204 +- 75 files changed, 16988 insertions(+), 15752 deletions(-) diff --git a/.storybook/__snapshots__/Welcome.story.storyshot b/.storybook/__snapshots__/Welcome.story.storyshot index 972404381c..39d895db04 100644 --- a/.storybook/__snapshots__/Welcome.story.storyshot +++ b/.storybook/__snapshots__/Welcome.story.storyshot @@ -59,7 +59,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte onAnimationEnd={[Function]} > - )) - .add('loading with secondary', () => ( - - )) - .add('not loading', () => ) - .add( - 'Default', - () => { - const regularProps = props.regular(); - return ( -
- -   - -   - -   - -
- ); - }, - { - info: { - text: ` - Buttons are used to initialize an action, either in the background or - foreground of an experience. - - There are several kinds of buttons. - - Primary buttons should be used for the principle call to action - on the page. - - Secondary buttons should be used for secondary actions on each page. - - Danger buttons should be used for a negative action (such as Delete) on the page. - - Modify the behavior of the button by changing its event properties. - - Field buttons may be use directly next to an input element, to visually align their heights. - - Small buttons may be used when there is not enough space for a - regular sized button. This issue is most found in tables. Small button should have three words - or less. - - When words are not enough, icons can be used in buttons to better communicate what the button does. Icons are - always paired with text. - `, - }, - } - ) - .add('Icon-only buttons', () => - - - ); + }, +}; + +export const Loading = () => ( + +); + +Loading.story = { + name: 'loading', +}; + +export const LoadingWithSecondary = () => ( + +); + +LoadingWithSecondary.story = { + name: 'loading with secondary', +}; + +export const NotLoading = () => ; + +NotLoading.story = { + name: 'not loading', +}; + +export const _Default = () => { + const regularProps = props.regular(); + return ( +
+ +   + +   + +   + +
+ ); +}; + +_Default.story = { + parameters: { + info: { + text: ` + Buttons are used to initialize an action, either in the background or + foreground of an experience. + + There are several kinds of buttons. + + Primary buttons should be used for the principle call to action + on the page. + + Secondary buttons should be used for secondary actions on each page. + + Danger buttons should be used for a negative action (such as Delete) on the page. + + Modify the behavior of the button by changing its event properties. + + Field buttons may be use directly next to an input element, to visually align their heights. + + Small buttons may be used when there is not enough space for a + regular sized button. This issue is most found in tables. Small button should have three words + or less. + + When words are not enough, icons can be used in buttons to better communicate what the button does. Icons are + always paired with text. + `, }, - { - info: { - text: ` - When an action required by the user has more than one option, always use a a negative action button (secondary) paired with a positive action button (primary) in that order. Negative action buttons will be on the left. Positive action buttons should be on the right. When these two types buttons are paired in the correct order, they will automatically space themselves apart. - `, - }, - } - ) - .add( - 'skeleton', - () => ( -
- -   - -   - -
- ), - { - info: { - text: ` - Placeholder skeleton state to use when content is loading. - `, - }, - } + }, +}; + +export const IconOnlyButtons = () => ( + + + ); +}; + +SetsOfButtons.story = { + name: 'Sets of Buttons', + + parameters: { + info: { + text: ` + When an action required by the user has more than one option, always use a a negative action button (secondary) paired with a positive action button (primary) in that order. Negative action buttons will be on the left. Positive action buttons should be on the right. When these two types buttons are paired in the correct order, they will automatically space themselves apart. + `, + }, + }, +}; + +export const Skeleton = () => ( +
+ +   + +   + +
+); + +Skeleton.story = { + name: 'skeleton', + + parameters: { + info: { + text: ` + Placeholder skeleton state to use when content is loading. + `, + }, + }, +}; diff --git a/src/components/Card/Card.story.jsx b/src/components/Card/Card.story.jsx index fe7615fd17..8821d4b73c 100644 --- a/src/components/Card/Card.story.jsx +++ b/src/components/Card/Card.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text, select, boolean, object } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { Tree16 } from '@carbon/icons-react'; @@ -30,356 +29,428 @@ export const getDataStateProp = () => ({ ), }); -storiesOf('Watson IoT/Card', module) - .addParameters({ +export default { + title: 'Watson IoT/Card', + + parameters: { component: Card, - }) - .add('basic', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('with ellipsed title tooltip & external tooltip', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- this is the external tooltip content

} - /> -
- ); - }) - .add('basic with render prop', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- ( -

- Content width is {childSize.width} and height is{' '} - {childSize.height} -

- )} - /> -
- ); - }) - .add('with loading', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('with range selector', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('with custom range selector', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('is editable', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('is expandable', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('is expandable - custom expand icon', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); - }) - .add('with empty state', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
+ }, + + excludeStories: ['getDataStateProp'], +}; + +export const Basic = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +Basic.story = { + name: 'basic', +}; + +export const WithEllipsedTitleTooltipExternalTooltip = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ this is the external tooltip content

} + /> +
+ ); +}; + +WithEllipsedTitleTooltipExternalTooltip.story = { + name: 'with ellipsed title tooltip & external tooltip', +}; + +export const BasicWithRenderProp = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ ( +

+ Content width is {childSize.width} and height is {childSize.height} +

+ )} + /> +
+ ); +}; + +BasicWithRenderProp.story = { + name: 'basic with render prop', +}; + +export const WithLoading = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +WithLoading.story = { + name: 'with loading', +}; + +export const WithRangeSelector = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +WithRangeSelector.story = { + name: 'with range selector', +}; + +export const WithCustomRangeSelector = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +WithCustomRangeSelector.story = { + name: 'with custom range selector', +}; + +export const IsEditable = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +IsEditable.story = { + name: 'is editable', +}; + +export const IsExpandable = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +IsExpandable.story = { + name: 'is expandable', +}; + +export const IsExpandableCustomExpandIcon = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +IsExpandableCustomExpandIcon.story = { + name: 'is expandable - custom expand icon', +}; + +export const WithEmptyState = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +WithEmptyState.story = { + name: 'with empty state', +}; + +export const SizeGallery = () => { + return Object.keys(CARD_SIZES).map((i) => ( + +

{i}

+
-
- ); - }) - .add('size gallery', () => { - return Object.keys(CARD_SIZES).map((i) => ( - -

{i}

-
- -
-
- )); - }) - .add('error', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
-
- ); - }) - .add('error/small', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.SMALL); - return ( -
- -
- ); - }) - .add( - 'implementing a custom card', - () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - const SampleCustomCard = ({ title, isEditable, ...others }) => ( - - {!isEditable - ? (_$, { cardToolbar, values }) => ( - ({ - id: `rowid-${index}`, - values: value, - }))} - view={{ toolbar: { customToolbarContent: cardToolbar } }} - /> - ) - : 'Fake Sample Data'} - - ); - - return ( - - ); - }, - { - inline: true, - source: true, - info: { - text: ` - To develop a custom card component. - - Create a new card component that uses the base Card component - - See the simple SampleCustomCard in the source code of this story for an example - - If you want to hide the title/toolbar, do not pass a title prop - - (Optionally, if you want to use the card in a Dashboard) Extend the Card Renderer so the Dashboard knows how to render your card type - - (Optionally, if you want to use the card in a Dashboard) Create a validator for this card type within "utils/schemas/validators" and add it to the validateDashboardJSON function used to validate dashboards on import. - - ## Data flow for a card in the dashboard - All data loading for a card goes through the dashboard's onFetchData function. There are two ways to trigger a refetch of data for a card. The first is to directly interact - with the Card's range controls. The second is for the Dashboard to trigger that all of the cards need a reload by updating it's isLoading bit. The CardRenderer component will call the onSetupCard function of the dashboard first - for each card (if it exists), then will call the onFetchData function for the dashboard. - `, - }, - } + + )); +}; + +SizeGallery.story = { + name: 'size gallery', +}; + +export const Error = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + return ( +
+ +
+ ); +}; + +Error.story = { + name: 'error', +}; + +export const ErrorSmall = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.SMALL); + return ( +
+ +
); +}; + +ErrorSmall.story = { + name: 'error/small', +}; + +export const ImplementingACustomCard = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + const SampleCustomCard = ({ title, isEditable, ...others }) => ( + + {!isEditable + ? (_$, { cardToolbar, values }) => ( +
({ + id: `rowid-${index}`, + values: value, + }))} + view={{ toolbar: { customToolbarContent: cardToolbar } }} + /> + ) + : 'Fake Sample Data'} + + ); + + return ( + + ); +}; + +ImplementingACustomCard.story = { + name: 'implementing a custom card', + + parameters: { + inline: true, + source: true, + info: { + text: ` + To develop a custom card component. + - Create a new card component that uses the base Card component + - See the simple SampleCustomCard in the source code of this story for an example + - If you want to hide the title/toolbar, do not pass a title prop + - (Optionally, if you want to use the card in a Dashboard) Extend the Card Renderer so the Dashboard knows how to render your card type + - (Optionally, if you want to use the card in a Dashboard) Create a validator for this card type within "utils/schemas/validators" and add it to the validateDashboardJSON function used to validate dashboards on import. + + ## Data flow for a card in the dashboard + All data loading for a card goes through the dashboard's onFetchData function. There are two ways to trigger a refetch of data for a card. The first is to directly interact + with the Card's range controls. The second is for the Dashboard to trigger that all of the cards need a reload by updating it's isLoading bit. The CardRenderer component will call the onSetupCard function of the dashboard first + for each card (if it exists), then will call the onFetchData function for the dashboard. + `, + }, + }, +}; diff --git a/src/components/Card/__snapshots__/Card.story.storyshot b/src/components/Card/__snapshots__/Card.story.storyshot index 1951fdc142..4f701131ff 100644 --- a/src/components/Card/__snapshots__/Card.story.storyshot +++ b/src/components/Card/__snapshots__/Card.story.storyshot @@ -296,8 +296,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Card >

Content width is - and height is - + and height is NaN

diff --git a/src/components/CardCodeEditor/CardCodeEditor.story.jsx b/src/components/CardCodeEditor/CardCodeEditor.story.jsx index 1e6c54229b..f2fb4bde49 100644 --- a/src/components/CardCodeEditor/CardCodeEditor.story.jsx +++ b/src/components/CardCodeEditor/CardCodeEditor.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { select } from '@storybook/addon-knobs'; import CardCodeEditor from './CardCodeEditor'; @@ -28,24 +27,39 @@ export const isValidCallback = (val, setError) => { return false; }; -storiesOf('Watson IoT/CardCodeEditor', module) - .addParameters({ +export default { + title: 'Watson IoT/CardCodeEditor', + + parameters: { component: CardCodeEditor, - }) - .add('default', () => ( - {}} - language={select('Editor language', ['json', 'javascript', 'css'])} - onCopy={(value) => console.log(value)} - initialValue="/* write your code here */" - /> - )) - .add('with preloaded content', () => ( - {}} - onCopy={(value) => console.log(value)} - initialValue={JSON.stringify(pkgjson, null, 2)} - /> - )); + }, + + excludeStories: ['isValidCallback'], +}; + +export const Default = () => ( + {}} + language={select('Editor language', ['json', 'javascript', 'css'])} + onCopy={(value) => console.log(value)} + initialValue="/* write your code here */" + /> +); + +Default.story = { + name: 'default', +}; + +export const WithPreloadedContent = () => ( + {}} + onCopy={(value) => console.log(value)} + initialValue={JSON.stringify(pkgjson, null, 2)} + /> +); + +WithPreloadedContent.story = { + name: 'with preloaded content', +}; diff --git a/src/components/CardEditor/CardEditor.story.jsx b/src/components/CardEditor/CardEditor.story.jsx index 157ed156ce..30e9b05188 100644 --- a/src/components/CardEditor/CardEditor.story.jsx +++ b/src/components/CardEditor/CardEditor.story.jsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, object } from '@storybook/addon-knobs'; @@ -42,103 +41,123 @@ const CardEditorInteractive = () => { ); }; -storiesOf('Watson IoT Experimental/CardEditor', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/CardEditor', + decorators: [withKnobs], + + parameters: { component: CardEditor, - }) - .addDecorator(withKnobs) - .add('default', () => ( -
- -
- )) - .add('for TimeSeries', () => ( -
- -
- )) - .add('with no card defined (gallery view)', () => ( -
- -
- )) - .add('interactive', () => ); + }, +}; + +export const Default = () => ( +
+ +
+); + +Default.story = { + name: 'default', +}; + +export const ForTimeSeries = () => ( +
+ +
+); + +ForTimeSeries.story = { + name: 'for TimeSeries', +}; + +export const WithNoCardDefinedGalleryView = () => ( +
+ +
+); + +WithNoCardDefinedGalleryView.story = { + name: 'with no card defined (gallery view)', +}; + +export const Interactive = () => ; + +Interactive.story = { + name: 'interactive', +}; diff --git a/src/components/ComboBox/ComboBox.story.jsx b/src/components/ComboBox/ComboBox.story.jsx index b73c57cc1a..e2fbba0312 100644 --- a/src/components/ComboBox/ComboBox.story.jsx +++ b/src/components/ComboBox/ComboBox.story.jsx @@ -4,7 +4,6 @@ // TODO: https://github.com/carbon-design-system/carbon/issues/5067 import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import isEmpty from 'lodash/isEmpty'; import isEqual from 'lodash/isEqual'; import { action } from '@storybook/addon-actions'; @@ -139,116 +138,136 @@ const Wrapper = ({ children }) => (
{children}
); -storiesOf('Watson IoT Experimental/ComboBox', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/ComboBox', + decorators: [withKnobs], + + parameters: { component: ComboBox, - }) - .addDecorator(withKnobs) - .add( - 'Default', - () => ( - - (item ? item.text : '')} - {...props()} - /> - - ), - { - info: { - text: 'ComboBox', - propTablesExclude: [Wrapper], - }, - } - ) - .add( - 'Items as components', - () => ( - - (item ? item.text : '')} - itemToElement={itemToElement} - /> - - ), - { - info: { - text: 'ComboBox', - propTablesExclude: [Wrapper], - }, - } - ) - .add( - 'application-level control for selection', - () => , - { - info: { - text: `Controlled ComboBox example application`, - propTables: [ComboBox], - propTablesExclude: [ControlledComboBoxApp], - }, - } - ) - .add( - 'Experimental multi-value tags', - () => ( - - (item ? item.text : '')} - /> - - ), - { - info: { - text: - 'This variation of the ComboBox is experimental. By setting `hasMultiValue` to true, when an item is selected it will create a persistent tag above the ComboBox. If the entered text does not match an item in the list, it will be added to the list.', - propTablesExclude: [Wrapper], - }, - } - ) - .add( - 'Experimental add new items to list', - () => ( - - (item ? item.text : '')} - addToList - /> - - ), - { - info: { - text: - 'This variation of the ComboBox is experimental. By setting `addToList` to true, if an entered item is not part of the list options, it will be added to the list upon hitting enter.', - propTablesExclude: [Wrapper], - }, - } - ) - .add( - 'Custom onBlur function automatically adds item to the list', - () => { - return ( - - - - ); + }, + + excludeStories: ['items'], +}; + +export const Default = () => ( + + (item ? item.text : '')} + {...props()} + /> + +); + +Default.story = { + parameters: { + info: { + text: 'ComboBox', + propTablesExclude: [Wrapper], }, - { - info: { - text: - 'This variation of the ComboBox is experimental. By setting `addToList` to true, if an entered item is not part of the list options, it will be added to the list upon hitting enter.', - propTablesExclude: [Wrapper], - }, - } + }, +}; + +export const ItemsAsComponents = () => ( + + (item ? item.text : '')} + itemToElement={itemToElement} + /> + +); + +ItemsAsComponents.story = { + name: 'Items as components', + + parameters: { + info: { + text: 'ComboBox', + propTablesExclude: [Wrapper], + }, + }, +}; + +export const ApplicationLevelControlForSelection = () => ( + +); + +ApplicationLevelControlForSelection.story = { + name: 'application-level control for selection', + + parameters: { + info: { + text: `Controlled ComboBox example application`, + propTables: [ComboBox], + propTablesExclude: [ControlledComboBoxApp], + }, + }, +}; + +export const ExperimentalMultiValueTags = () => ( + + (item ? item.text : '')} + /> + +); + +ExperimentalMultiValueTags.story = { + name: 'Experimental multi-value tags', + + parameters: { + info: { + text: + 'This variation of the ComboBox is experimental. By setting `hasMultiValue` to true, when an item is selected it will create a persistent tag above the ComboBox. If the entered text does not match an item in the list, it will be added to the list.', + propTablesExclude: [Wrapper], + }, + }, +}; + +export const ExperimentalAddNewItemsToList = () => ( + + (item ? item.text : '')} + addToList + /> + +); + +ExperimentalAddNewItemsToList.story = { + name: 'Experimental add new items to list', + + parameters: { + info: { + text: + 'This variation of the ComboBox is experimental. By setting `addToList` to true, if an entered item is not part of the list options, it will be added to the list upon hitting enter.', + propTablesExclude: [Wrapper], + }, + }, +}; + +export const CustomOnBlurFunctionAutomaticallyAddsItemToTheList = () => { + return ( + + + ); +}; + +CustomOnBlurFunctionAutomaticallyAddsItemToTheList.story = { + name: 'Custom onBlur function automatically adds item to the list', + + parameters: { + info: { + text: + 'This variation of the ComboBox is experimental. By setting `addToList` to true, if an entered item is not part of the list options, it will be added to the list upon hitting enter.', + propTablesExclude: [Wrapper], + }, + }, +}; diff --git a/src/components/ComposedModal/ComposedModal.story.jsx b/src/components/ComposedModal/ComposedModal.story.jsx index 5c10012036..375bc2958f 100644 --- a/src/components/ComposedModal/ComposedModal.story.jsx +++ b/src/components/ComposedModal/ComposedModal.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean, object, text } from '@storybook/addon-knobs'; import { spacing05 } from '@carbon/layout'; @@ -18,12 +17,12 @@ const CustomFooter = styled.div` } `; -// Ugh I shouldn't have to add these info here, but it's not being picked up by react-docgen! -storiesOf('Watson IoT/ComposedModal', module) - .addParameters({ +export default { + title: 'Watson IoT/ComposedModal', + + parameters: { component: ComposedModal, - }) - .addParameters({ + info: `Renders a carbon modal dialog. This dialog adds these additional features on top of the base carbon dialog: - adds header.helpText prop to explain dialog - adds type prop for warning and error type dialogs @@ -36,140 +35,196 @@ storiesOf('Watson IoT/ComposedModal', module) We also prevent the dialog from closing if you click outside it. This dialog can be decorated by reduxDialog HoC and/or reduxForm HoC to automatically populate the fields below marked as REDUXFORM or REDUXDIALOG`, - }) - .add('warning dialog', () => ( - - )) - .add('big modal', () => ( - - Lots of really wide content here... - - )) - .add('fetching data', () => ( - - )) - .add('error states', () => ( - - {text('body content', '')} - - )) - .add('sending data', () => ( - - )) - .add('no footer', () => ( - - )) - .add('custom footer', () => ( - custom footer element} - onClose={action('close')} - /> - )) - .add('primary button is hidden', () => ( - - )) - .add('header custom nodes', () => ( - Custom node label, - title: Custom node title, - }} - onClose={action('close')} - onSubmit={action('submit')} - /> - )) - .add('i18n', () => ( - - )) - .add('composed modal with overflow and tooltip', () => ( - - - - - - Hi there - - - )); + }, +}; + +export const WarningDialog = () => ( + +); + +WarningDialog.story = { + name: 'warning dialog', +}; + +export const BigModal = () => ( + + Lots of really wide content here... + +); + +BigModal.story = { + name: 'big modal', +}; + +export const FetchingData = () => ( + +); + +FetchingData.story = { + name: 'fetching data', +}; + +export const ErrorStates = () => ( + + {text('body content', '')} + +); + +ErrorStates.story = { + name: 'error states', +}; + +export const SendingData = () => ( + +); + +SendingData.story = { + name: 'sending data', +}; + +export const NoFooter = () => ( + +); + +NoFooter.story = { + name: 'no footer', +}; + +export const _CustomFooter = () => ( + custom footer element} + onClose={action('close')} + /> +); + +_CustomFooter.story = { + name: 'custom footer', +}; + +export const PrimaryButtonIsHidden = () => ( + +); + +PrimaryButtonIsHidden.story = { + name: 'primary button is hidden', +}; + +export const HeaderCustomNodes = () => ( + Custom node label, + title: Custom node title, + }} + onClose={action('close')} + onSubmit={action('submit')} + /> +); + +HeaderCustomNodes.story = { + name: 'header custom nodes', +}; + +export const I18N = () => ( + +); + +I18N.story = { + name: 'i18n', +}; + +export const ComposedModalWithOverflowAndTooltip = () => ( + + + + + + Hi there + + +); + +ComposedModalWithOverflowAndTooltip.story = { + name: 'composed modal with overflow and tooltip', +}; diff --git a/src/components/ComposedStructuredList/ComposedStructuredList.story.jsx b/src/components/ComposedStructuredList/ComposedStructuredList.story.jsx index a08e6f9741..be9e690730 100644 --- a/src/components/ComposedStructuredList/ComposedStructuredList.story.jsx +++ b/src/components/ComposedStructuredList/ComposedStructuredList.story.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { select } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; -import { storiesOf } from '@storybook/react'; import DeprecationNotice, { deprecatedStoryTitle, @@ -55,94 +54,113 @@ const StructuredListInputProps = { className: 'checks', }; -storiesOf('Watson IoT/ComposedStructuredList (Deprecated)', module) - .add(deprecatedStoryTitle, () => ( - - )) - .add('default ', () => ( - - )) - .add('with empty state', () => ( - - )) - .add('with fixed column widths', () => ( - ({ ...i, width: `${10 + idx * 2}rem` }))} - data={data} - isFixedWidth - loadingDataLabel="No data is available yet." - onRowClick={action('onRowClick')} - /> - )) - .add('with empty state with fixed column width', () => ( - ({ ...i, width: `${10 + idx * 2}rem` }))} - data={[]} - isFixedWidth - loadingDataLabel="No data is available yet." - onRowClick={action('onRowClick')} - /> - )) - .add( - 'custom cell renderer', - () => ( - ({ - ...column, - renderDataFunction: ({ value }) => ( - {value} - ), - }))} - data={data} - onRowClick={action('onRowClick')} - /> - ), - { - info: { - text: ` - - To render a your own widget in a list cell, pass a renderDataFunction prop along with your column metadata. - -
- - ~~~js - columns=[ { id: 'columnA', title: 'A', renderDataFunction: myCustomRenderer }, ...] - ~~~ - -
- - The renderDataFunction is called with this payload - -
- - ~~~js - { - value: PropTypes.any (current cell value), - columnId: PropTypes.string, - rowId: PropTypes.string, - row: the full data for this rowPropTypes.object like this {col: value, col2: value} - } - - const myCustomRenderer = ({ value }) => {value} - ~~~ - -
- - `, - }, - } - ); +export default { + title: 'Watson IoT/ComposedStructuredList (Deprecated)', +}; + +export const Default = () => ( + +); + +Default.story = { + name: 'default ', +}; + +export const WithEmptyState = () => ( + +); + +WithEmptyState.story = { + name: 'with empty state', +}; + +export const WithFixedColumnWidths = () => ( + ({ ...i, width: `${10 + idx * 2}rem` }))} + data={data} + isFixedWidth + loadingDataLabel="No data is available yet." + onRowClick={action('onRowClick')} + /> +); + +WithFixedColumnWidths.story = { + name: 'with fixed column widths', +}; + +export const WithEmptyStateWithFixedColumnWidth = () => ( + ({ ...i, width: `${10 + idx * 2}rem` }))} + data={[]} + isFixedWidth + loadingDataLabel="No data is available yet." + onRowClick={action('onRowClick')} + /> +); + +WithEmptyStateWithFixedColumnWidth.story = { + name: 'with empty state with fixed column width', +}; + +export const CustomCellRenderer = () => ( + ({ + ...column, + renderDataFunction: ({ value }) => ( + {value} + ), + }))} + data={data} + onRowClick={action('onRowClick')} + /> +); + +CustomCellRenderer.story = { + name: 'custom cell renderer', + + parameters: { + info: { + text: ` + + To render a your own widget in a list cell, pass a renderDataFunction prop along with your column metadata. + +
+ + ~~~js + columns=[ { id: 'columnA', title: 'A', renderDataFunction: myCustomRenderer }, ...] + ~~~ + +
+ + The renderDataFunction is called with this payload + +
+ + ~~~js + { + value: PropTypes.any (current cell value), + columnId: PropTypes.string, + rowId: PropTypes.string, + row: the full data for this rowPropTypes.object like this {col: value, col2: value} + } + + const myCustomRenderer = ({ value }) => {value} + ~~~ + +
+ + `, + }, + }, +}; diff --git a/src/components/ComposedStructuredList/__snapshots__/ComposedStructuredList.story.storyshot b/src/components/ComposedStructuredList/__snapshots__/ComposedStructuredList.story.storyshot index 1e7186e386..72996b6654 100644 --- a/src/components/ComposedStructuredList/__snapshots__/ComposedStructuredList.story.storyshot +++ b/src/components/ComposedStructuredList/__snapshots__/ComposedStructuredList.story.storyshot @@ -739,96 +739,3 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Compo `; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/ComposedStructuredList (Deprecated) ️⛔ Deprecation Notice 1`] = ` -
-
-
- - - - - warning icon - - -
-

- Deprecation Notice -

-
- ComposedStructuredList has been deprecated and will be removed in the next major version of carbon-addons-iot-react. -
-
- Refactor usages of ComposedStructuredList to use StructuredList instead. -
-
-
-
- -
-`; diff --git a/src/components/Dashboard/Dashboard.story.jsx b/src/components/Dashboard/Dashboard.story.jsx index cb1e293823..dba17dfb08 100644 --- a/src/components/Dashboard/Dashboard.story.jsx +++ b/src/components/Dashboard/Dashboard.story.jsx @@ -1,6 +1,5 @@ import React from 'react'; import { text, boolean } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { Application32, Group32 } from '@carbon/icons-react'; import { spacing05, spacing04, spacing09 } from '@carbon/layout'; @@ -476,959 +475,944 @@ const commonDashboardProps = { }, }; -storiesOf('Watson IoT/Dashboard (Deprecated)', module) - .add(deprecatedStoryTitle, () => ( - - )) - .add( - 'basic dashboard', - () => { - return ( - - - - ); +export default { + title: 'Watson IoT/Dashboard (Deprecated)', + excludeStories: ['originalCards'], +}; + +export const BasicDashboard = () => { + return ( + + + + ); +}; + +BasicDashboard.story = { + name: 'basic dashboard', + + parameters: { + info: { + text: ` + ## Data Fetching + To wire this dashboard to your own backend, implement the onFetchData callback to retrieve data for each card. + You will be passed an object containing all of the card props (including the currently selected range of the card) and can use these to determine which data to fetch. + + Return a promise that will resolve into an updated card object with data values + For instance you could return {...card, values: [{timestamp: 1234123123,temperature: 35.5}]} + + If you want to trigger all the cards of the dashboard to load from an outside event (like a change in the data range that the dashboard is displaying), set the isLoading bit to true. + Once all the cards have finished loading the setIsLoading(false) will be called from the Dashboard. + + # Component Overview + `, }, - { - info: { - text: ` - ## Data Fetching - To wire this dashboard to your own backend, implement the onFetchData callback to retrieve data for each card. - You will be passed an object containing all of the card props (including the currently selected range of the card) and can use these to determine which data to fetch. + }, +}; - Return a promise that will resolve into an updated card object with data values - For instance you could return {...card, values: [{timestamp: 1234123123,temperature: 35.5}]} +export const BasicWithoutLastUpdatedHeader = () => { + return ( + + + + ); +}; - If you want to trigger all the cards of the dashboard to load from an outside event (like a change in the data range that the dashboard is displaying), set the isLoading bit to true. - Once all the cards have finished loading the setIsLoading(false) will be called from the Dashboard. +BasicWithoutLastUpdatedHeader.story = { + name: 'basic - without last updated header', +}; - # Component Overview - `, - }, - } - ) - .add('basic - without last updated header', () => { - return ( - - - - ); - }) - .add('custom actions', () => { - return ( - - - - ); - }) - .add('sidebar', () => { - return ( - - -

Sidebar content

-

goes

-

here

- - } - /> -
- ); - }) - .add('i18n labels', () => { - return ( - - { + return ( + + + + ); +}; - // table i18n - searchPlaceholder: text('searchPlaceholder', 'Search'), - filterButtonAria: text('filterButtonAria', 'Filters'), - defaultFilterStringPlaceholdText: text( - 'defaultFilterStringPlaceholdText', - 'Type and hit enter to apply' - ), - /** pagination */ - pageBackwardAria: text( - 'i18n.pageBackwardAria', - '__Previous page__' - ), - pageForwardAria: text('i18n.pageForwardAria', '__Next page__'), - pageNumberAria: text('i18n.pageNumberAria', '__Page Number__'), - itemsPerPage: text('i18n.itemsPerPage', '__Items per page:__'), - itemsRange: (min, max) => `__${min}–${max} items__`, - currentPage: (page) => `__page ${page}__`, - itemsRangeWithTotal: (min, max, total) => - `__${min}–${max} of ${total} items__`, - pageRange: (current, total) => `__${current} of ${total} pages__`, - /** table body */ - overflowMenuAria: text('i18n.overflowMenuAria', '__More actions__'), - clickToExpandAria: text( - 'i18n.clickToExpandAria', - '__Click to expand content__' - ), - clickToCollapseAria: text( - 'i18n.clickToCollapseAria', - '__Click to collapse content__' - ), - selectAllAria: text('i18n.selectAllAria', '__Select all items__'), - selectRowAria: text('i18n.selectRowAria', '__Select row__'), - /** toolbar */ - clearAllFilters: text( - 'i18n.clearAllFilters', - '__Clear all filters__' - ), - columnSelectionButtonAria: text( - 'i18n.columnSelectionButtonAria', - '__Column Selection__' - ), - clearFilterAria: text('i18n.clearFilterAria', '__Clear filter__'), - filterAria: text('i18n.filterAria', '__Filter__'), - openMenuAria: text('i18n.openMenuAria', '__Open menu__'), - closeMenuAria: text('i18n.closeMenuAria', '__Close menu__'), - clearSelectionAria: text( - 'i18n.clearSelectionAria', - '__Clear selection__' - ), - /** empty state */ - emptyMessage: text('i18n.emptyMessage', '__There is no data__'), - emptyMessageWithFilters: text( - 'i18n.emptyMessageWithFilters', - '__No results match the current filters__' - ), - emptyButtonLabelWithFilters: text( - 'i18n.emptyButtonLabel', - '__Clear all filters__' - ), - inProgressText: text('i18n.inProgressText', '__In Progress__'), - actionFailedText: text( - 'i18n.actionFailedText', - '__Action Failed__' - ), - learnMoreText: text('i18n.learnMoreText', '__Learn More__'), - dismissText: text('i18n.dismissText', '__Dismiss__'), - downloadIconDescription: text( - 'downloadIconDescription', - 'Download table content' - ), - }} - /> - - ); - }) - .add('full screen table card', () => { - const data = [...Array(35)].map((id, index) => ({ - id: `row-${index}`, - values: { - timestamp: 1569819600000, - campus: 'Campus_EGL', - peopleCount_EnterpriseBuilding_mean: 150.5335383714, - headCount_EnterpriseBuilding_mean: 240, - capacity_EnterpriseBuilding_mean: 300, - allocatedSeats_EnterpriseBuilding_mean: 240, - }, - })); - return ( - - - - ); - }) - .add('full screen line card', () => { - const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); - return ( +CustomActions.story = { + name: 'custom actions', +}; + +export const Sidebar = () => { + return ( + +

Sidebar content

+

goes

+

here

+ + } /> - ); - }) - .add('line card with no options', () => { - const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); - return ( +
+ ); +}; + +Sidebar.story = { + name: 'sidebar', +}; + +export const I18NLabels = () => { + return ( + `__${min}–${max} items__`, + currentPage: (page) => `__page ${page}__`, + itemsRangeWithTotal: (min, max, total) => + `__${min}–${max} of ${total} items__`, + pageRange: (current, total) => `__${current} of ${total} pages__`, + /** table body */ + overflowMenuAria: text('i18n.overflowMenuAria', '__More actions__'), + clickToExpandAria: text( + 'i18n.clickToExpandAria', + '__Click to expand content__' + ), + clickToCollapseAria: text( + 'i18n.clickToCollapseAria', + '__Click to collapse content__' + ), + selectAllAria: text('i18n.selectAllAria', '__Select all items__'), + selectRowAria: text('i18n.selectRowAria', '__Select row__'), + /** toolbar */ + clearAllFilters: text( + 'i18n.clearAllFilters', + '__Clear all filters__' + ), + columnSelectionButtonAria: text( + 'i18n.columnSelectionButtonAria', + '__Column Selection__' + ), + clearFilterAria: text('i18n.clearFilterAria', '__Clear filter__'), + filterAria: text('i18n.filterAria', '__Filter__'), + openMenuAria: text('i18n.openMenuAria', '__Open menu__'), + closeMenuAria: text('i18n.closeMenuAria', '__Close menu__'), + clearSelectionAria: text( + 'i18n.clearSelectionAria', + '__Clear selection__' + ), + /** empty state */ + emptyMessage: text('i18n.emptyMessage', '__There is no data__'), + emptyMessageWithFilters: text( + 'i18n.emptyMessageWithFilters', + '__No results match the current filters__' + ), + emptyButtonLabelWithFilters: text( + 'i18n.emptyButtonLabel', + '__Clear all filters__' + ), + inProgressText: text('i18n.inProgressText', '__In Progress__'), + actionFailedText: text('i18n.actionFailedText', '__Action Failed__'), + learnMoreText: text('i18n.learnMoreText', '__Learn More__'), + dismissText: text('i18n.dismissText', '__Dismiss__'), + downloadIconDescription: text( + 'downloadIconDescription', + 'Download table content' + ), + }} /> - ); - }) - .add('full screen bar chart card', () => { - const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); - return ( + + ); +}; + +I18NLabels.story = { + name: 'i18n labels', +}; + +export const FullScreenTableCard = () => { + const data = [...Array(35)].map((id, index) => ({ + id: `row-${index}`, + values: { + timestamp: 1569819600000, + campus: 'Campus_EGL', + peopleCount_EnterpriseBuilding_mean: 150.5335383714, + headCount_EnterpriseBuilding_mean: 240, + capacity_EnterpriseBuilding_mean: 300, + allocatedSeats_EnterpriseBuilding_mean: 240, + }, + })); + return ( + { - acc.push(dataPoint); - acc.push({ - ...dataPoint, - temperature: dataPoint.temperature / 2, - ENTITY_ID: 'Sensor2-2', - }); - return acc; - }, []), + values: data, }, ]} /> - ); - }) - .add('full screen image card', () => { - const content = { - src: imageFile, - alt: 'Sample image', - zoomMax: 10, - }; - return ( - - - - ); - }) - .add('only value cards', () => { - const numberThresholds = [ - { comparison: '<', value: '40', color: 'red', icon: 'close' }, - { comparison: '<', value: '70', color: 'green', icon: 'checkmark' }, - { comparison: '<', value: '80', color: 'orange', icon: 'warning' }, - { comparison: '>=', value: '90', color: 'red', icon: 'close' }, - ]; - const stringThresholds = [ - { comparison: '=', value: 'Low', color: 'green' }, - { comparison: '=', value: 'Guarded', color: 'blue' }, - { comparison: '=', value: 'Elevated', color: 'gold' }, - { comparison: '=', value: 'High', color: 'orange' }, - { comparison: '=', value: 'Severe', color: 'red' }, - ]; - const stringThresholdsWithIcons = [ - { comparison: '=', value: 'Low', color: 'green', icon: 'checkmark' }, - { comparison: '=', value: 'Elevated', color: 'gold', icon: 'warning' }, - { comparison: '=', value: 'High', color: 'orange', icon: 'warning' }, - { comparison: '=', value: 'Severe', color: 'red', icon: 'close' }, - ]; - const extraProps = { - lastUpdated: 'Now', - }; - const dashboards = [ - ({ - title: `${v[0]} ${v[2] || ''}`, - id: `xsmall-number-${idx}`, - size: CARD_SIZES.SMALL, - type: CARD_TYPES.VALUE, + + ); +}; + +FullScreenTableCard.story = { + name: 'full screen table card', +}; + +export const FullScreenLineCard = () => { + const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); + return ( + , - ({ - title: 'Temperature', - id: `xsmall-number-${idx}`, - size: CARD_SIZES.SMALL, - type: CARD_TYPES.VALUE, + values: data, + }, + ]} + /> + ); +}; + +FullScreenLineCard.story = { + name: 'full screen line card', +}; + +export const LineCardWithNoOptions = () => { + const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); + return ( + + ); +}; + +LineCardWithNoOptions.story = { + name: 'line card with no options', +}; + +export const FullScreenBarChartCard = () => { + const data = getIntervalChartData('day', 7, { min: 10, max: 100 }, 100); + return ( + , + values: data.reduce((acc, dataPoint) => { + acc.push(dataPoint); + acc.push({ + ...dataPoint, + temperature: dataPoint.temperature / 2, + ENTITY_ID: 'Sensor2-2', + }); + return acc; + }, []), + }, + ]} + /> + ); +}; + +FullScreenBarChartCard.story = { + name: 'full screen bar chart card', +}; + +export const FullScreenImageCard = () => { + const content = { + src: imageFile, + alt: 'Sample image', + zoomMax: 10, + }; + return ( + ({ - title: 'Humidity', - id: `xsmall-number-threshold-${idx}`, + title="Expandable card, click expand to expand image" + cards={[ + { + title: 'Expanded card', + id: `expandedcard`, + size: CARD_SIZES.LARGE, + type: CARD_TYPES.IMAGE, + content, + }, + ]} + /> + + ); +}; + +FullScreenImageCard.story = { + name: 'full screen image card', +}; + +export const OnlyValueCards = () => { + const numberThresholds = [ + { comparison: '<', value: '40', color: 'red', icon: 'close' }, + { comparison: '<', value: '70', color: 'green', icon: 'checkmark' }, + { comparison: '<', value: '80', color: 'orange', icon: 'warning' }, + { comparison: '>=', value: '90', color: 'red', icon: 'close' }, + ]; + const stringThresholds = [ + { comparison: '=', value: 'Low', color: 'green' }, + { comparison: '=', value: 'Guarded', color: 'blue' }, + { comparison: '=', value: 'Elevated', color: 'gold' }, + { comparison: '=', value: 'High', color: 'orange' }, + { comparison: '=', value: 'Severe', color: 'red' }, + ]; + const stringThresholdsWithIcons = [ + { comparison: '=', value: 'Low', color: 'green', icon: 'checkmark' }, + { comparison: '=', value: 'Elevated', color: 'gold', icon: 'warning' }, + { comparison: '=', value: 'High', color: 'orange', icon: 'warning' }, + { comparison: '=', value: 'Severe', color: 'red', icon: 'close' }, + ]; + const extraProps = { + lastUpdated: 'Now', + }; + const dashboards = [ + ({ + title: `${v[0]} ${v[2] || ''}`, + id: `xsmall-number-${idx}`, + size: CARD_SIZES.SMALL, + type: CARD_TYPES.VALUE, + content: { + attributes: [{ dataSourceId: 'v', unit: v[2] }], + }, + values: { v: v[1] }, + dataState: + idx === 5 + ? { + type: 'NO_DATA', + label: 'No data available for this score at this time', + description: 'The last successful score was 68', + } + : undefined, + }))} + />, + ({ + title: 'Temperature', + id: `xsmall-number-${idx}`, + size: CARD_SIZES.SMALL, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { + dataSourceId: 'v', + secondaryValue: + idx === 2 + ? { dataSourceId: 'v2', trend: 'up', color: 'green' } + : idx === 3 + ? { dataSourceId: 'v2', trend: 'down', color: 'red' } + : undefined, + label: + idx === 1 + ? 'Weekly Avg' + : idx === 3 + ? 'Long label that might not fit' + : undefined, + unit: '˚F', + }, + ], + }, + values: { v, v2: '3.2' }, + }))} + />, + ({ + title: 'Humidity', + id: `xsmall-number-threshold-${idx}`, + size: CARD_SIZES.SMALL, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { dataSourceId: 'v', unit: '%', thresholds: numberThresholds }, + ], + }, + values: { v }, + }))} + />, + i.value) + .map((v, idx) => ({ + title: 'Danger Level', + id: `xsmall-string-threshold-${idx}`, size: CARD_SIZES.SMALL, type: CARD_TYPES.VALUE, content: { - attributes: [ - { dataSourceId: 'v', unit: '%', thresholds: numberThresholds }, - ], + attributes: [{ dataSourceId: 'v', thresholds: stringThresholds }], }, values: { v }, }))} - />, - i.value) - .map((v, idx) => ({ - title: 'Danger Level', - id: `xsmall-string-threshold-${idx}`, - size: CARD_SIZES.SMALL, + />, + ({ + title: `${v[0]} ${v[2] || ''}`, + id: `xsmallwide-number-${idx}`, + size: CARD_SIZES.SMALLWIDE, + type: CARD_TYPES.VALUE, + content: { + attributes: [{ dataSourceId: 'v', unit: v[2] }], + }, + values: { v: v[1] }, + })) + .concat( + [65.3, 48.7, 88.1, 103.2].map((v, idx) => ({ + title: 'Temperature', + id: `xsmallwide-number-trend-${idx}`, + size: CARD_SIZES.SMALLWIDE, type: CARD_TYPES.VALUE, content: { - attributes: [{ dataSourceId: 'v', thresholds: stringThresholds }], + attributes: [ + { + dataSourceId: 'v', + secondaryValue: + idx === 2 + ? { dataSourceId: 'v2', trend: 'up', color: 'green' } + : idx === 3 + ? { dataSourceId: 'v2', trend: 'down', color: 'red' } + : undefined, + label: + idx === 1 + ? 'Weekly Avg' + : idx === 3 + ? 'Long label that might not fit' + : undefined, + unit: '˚F', + }, + ], }, - values: { v }, - }))} - />, - ({ - title: `${v[0]} ${v[2] || ''}`, - id: `xsmallwide-number-${idx}`, + values: { v, v2: 3.2 }, + })) + ) + .concat( + [38.2, 65.3, 77.7, 91].map((v, idx) => ({ + title: 'Humidity', + id: `xsmallwide-number-threshold-${idx}`, size: CARD_SIZES.SMALLWIDE, type: CARD_TYPES.VALUE, content: { - attributes: [{ dataSourceId: 'v', unit: v[2] }], + attributes: [ + { + dataSourceId: 'v', + unit: '%', + thresholds: numberThresholds, + }, + ], }, - values: { v: v[1] }, + values: { v }, })) - .concat( - [65.3, 48.7, 88.1, 103.2].map((v, idx) => ({ - title: 'Temperature', - id: `xsmallwide-number-trend-${idx}`, + ) + .concat( + stringThresholds + .map((i) => i.value) + .map((v, idx) => ({ + title: 'Danger Level', + id: `xsmallwide-string-threshold-${idx}`, size: CARD_SIZES.SMALLWIDE, type: CARD_TYPES.VALUE, content: { attributes: [ - { - dataSourceId: 'v', - secondaryValue: - idx === 2 - ? { dataSourceId: 'v2', trend: 'up', color: 'green' } - : idx === 3 - ? { dataSourceId: 'v2', trend: 'down', color: 'red' } - : undefined, - label: - idx === 1 - ? 'Weekly Avg' - : idx === 3 - ? 'Long label that might not fit' - : undefined, - unit: '˚F', - }, - ], - }, - values: { v, v2: 3.2 }, - })) - ) - .concat( - [38.2, 65.3, 77.7, 91].map((v, idx) => ({ - title: 'Humidity', - id: `xsmallwide-number-threshold-${idx}`, - size: CARD_SIZES.SMALLWIDE, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { - dataSourceId: 'v', - unit: '%', - thresholds: numberThresholds, - }, + { dataSourceId: 'v', thresholds: stringThresholds }, ], }, values: { v }, })) - ) - .concat( - stringThresholds - .map((i) => i.value) - .map((v, idx) => ({ - title: 'Danger Level', - id: `xsmallwide-string-threshold-${idx}`, - size: CARD_SIZES.SMALLWIDE, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { dataSourceId: 'v', thresholds: stringThresholds }, - ], - }, - values: { v }, - })) - )} - />, - ({ - title: v[0], - id: `xsmallwide-multi-${idx}`, - size: CARD_SIZES.SMALLWIDE, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { - dataSourceId: 'v1', - unit: v[2], - label: v[3], - }, - { - dataSourceId: 'v2', - unit: v[5], - label: v[6], - }, - ], - }, - values: { v1: v[1], v2: v[4] }, - }))} - />, - , + ({ + title: v[0], + id: `xsmallwide-multi-${idx}`, + size: CARD_SIZES.SMALLWIDE, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { + dataSourceId: 'v1', + unit: v[2], + label: v[3], + }, + { + dataSourceId: 'v2', + unit: v[5], + label: v[6], + }, ], - ].map((v, idx) => ({ - title: v[0], - id: `xsmallwide-multi-${idx}`, - size: CARD_SIZES.SMALLWIDE, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { - dataSourceId: 'v1', - unit: v[2], - label: v[3], - secondaryValue: - v[5] !== null - ? { - dataSourceId: 'v1trend', - trend: v[5], - color: v[6], - } - : undefined, - }, - { - dataSourceId: 'v2', - unit: v[8], - label: v[9], - secondaryValue: - v[11] !== null - ? { - dataSourceId: 'v2trend', - trend: v[11], - color: v[12], - } - : undefined, - }, - ], - }, - values: { v1: v[1], v1trend: v[4], v2: v[7], v2trend: v[10] }, - }))} - />, - ({ - title: 'Humidity', - id: `xsmallwide-multi-number-threshold-${idx}`, - size: CARD_SIZES.SMALLWIDE, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { - dataSourceId: 'v1', - unit: v[1], - label: v[2], - thresholds: numberThresholds, - }, - { - dataSourceId: 'v2', - unit: v[4], - label: v[5], - thresholds: numberThresholds, - }, - ], - }, - values: { v1: v[0], v2: v[3] }, - }))} - />, - , + ({ + title: v[0], + id: `xsmallwide-multi-${idx}`, + size: CARD_SIZES.SMALLWIDE, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { + dataSourceId: 'v1', + unit: v[2], + label: v[3], + secondaryValue: + v[5] !== null + ? { + dataSourceId: 'v1trend', + trend: v[5], + color: v[6], + } + : undefined, + }, + { + dataSourceId: 'v2', + unit: v[8], + label: v[9], + secondaryValue: + v[11] !== null + ? { + dataSourceId: 'v2trend', + trend: v[11], + color: v[12], + } + : undefined, + }, ], - [ - 'Danger Level', - 'Severe', - null, - 'Current', - null, - 'Low', - null, - 'Last Week', - null, - 'High', - null, - 'Last Month', - null, + }, + values: { v1: v[1], v1trend: v[4], v2: v[7], v2trend: v[10] }, + }))} + />, + ({ + title: 'Humidity', + id: `xsmallwide-multi-number-threshold-${idx}`, + size: CARD_SIZES.SMALLWIDE, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { + dataSourceId: 'v1', + unit: v[1], + label: v[2], + thresholds: numberThresholds, + }, + { + dataSourceId: 'v2', + unit: v[4], + label: v[5], + thresholds: numberThresholds, + }, ], - [ - 'Danger Level', - 'Low', - null, - 'Current', - null, - 'Severe', - null, - 'Last Week', - null, - 'Elevated', - null, - 'Last Month', - null, + }, + values: { v1: v[0], v2: v[3] }, + }))} + />, + ({ + title: v[0], + id: `xsmallwide-multi-number-threshold-${idx}`, + size: CARD_SIZES.MEDIUM, + type: CARD_TYPES.VALUE, + content: { + attributes: [ + { + dataSourceId: 'v1', + unit: v[2], + label: v[3], + thresholds: + idx === 1 + ? stringThresholds + : idx === 2 + ? stringThresholdsWithIcons + : undefined, + secondaryValue: + v[4] !== null + ? { + dataSourceId: 'v1trend', + trend: v[4], + color: v[4] === 'down' ? 'red' : 'green', + } + : undefined, + }, + { + dataSourceId: 'v2', + unit: v[6], + label: v[7], + thresholds: + idx === 1 + ? stringThresholds + : idx === 2 + ? stringThresholdsWithIcons + : undefined, + secondaryValue: + v[8] !== null + ? { + dataSourceId: 'v2trend', + trend: v[8], + color: v[8] === 'down' ? 'red' : 'green', + } + : undefined, + }, + { + dataSourceId: 'v3', + unit: v[10], + label: v[11], + thresholds: + idx === 1 + ? stringThresholds + : idx === 2 + ? stringThresholdsWithIcons + : undefined, + secondaryValue: + v[12] !== null + ? { + dataSourceId: 'v2', + trend: v[12], + color: v[12] === 'down' ? 'red' : 'green', + } + : undefined, + }, ], - ].map((v, idx) => ({ - title: v[0], - id: `xsmallwide-multi-number-threshold-${idx}`, - size: CARD_SIZES.MEDIUM, - type: CARD_TYPES.VALUE, - content: { - attributes: [ - { - dataSourceId: 'v1', - unit: v[2], - label: v[3], - thresholds: - idx === 1 - ? stringThresholds - : idx === 2 - ? stringThresholdsWithIcons - : undefined, - secondaryValue: - v[4] !== null - ? { - dataSourceId: 'v1trend', - trend: v[4], - color: v[4] === 'down' ? 'red' : 'green', - } - : undefined, - }, - { - dataSourceId: 'v2', - unit: v[6], - label: v[7], - thresholds: - idx === 1 - ? stringThresholds - : idx === 2 - ? stringThresholdsWithIcons - : undefined, - secondaryValue: - v[8] !== null - ? { - dataSourceId: 'v2trend', - trend: v[8], - color: v[8] === 'down' ? 'red' : 'green', - } - : undefined, - }, - { - dataSourceId: 'v3', - unit: v[10], - label: v[11], - thresholds: - idx === 1 - ? stringThresholds - : idx === 2 - ? stringThresholdsWithIcons - : undefined, - secondaryValue: - v[12] !== null - ? { - dataSourceId: 'v2', - trend: v[12], - color: v[12] === 'down' ? 'red' : 'green', - } - : undefined, - }, - ], - }, - values: { - v1: v[1], - v1trend: v[1] / 5, - v2: v[5], - v2trend: v[5] / 5, - v3: v[9], - }, - }))} - />, - ]; + }, + values: { + v1: v[1], + v1trend: v[1] / 5, + v2: v[5], + v2trend: v[5] / 5, + v3: v[9], + }, + }))} + />, + ]; - return ( - - {dashboards.map((dashboard, index) => [ -
-

"Largest" Rendering (1056px width)

-
- {dashboard} -
, -
-

"Tightest" Rendering (1057px width)

-
- {dashboard} -
, - ])} -
- ); - }) - .add('with custom cards', () => { - return ( - -
- -
-

View Dashboards

-
-

- View pinned dashboards to keep track of your world in - IoT. -

-
- View Dashboards -
-
- - ), - id: 'viewDashboards', - size: CARD_SIZES.MEDIUM, - type: 'CUSTOM', - }, - { - content: ( - -
-

Connect Devices

-
-

- Connect devices and collect data by using the Watson IoT - Platform Service. -

-
- Connect Devices -
-
-
- ), - id: 'connectDevices', - size: CARD_SIZES.MEDIUM, - type: 'CUSTOM', - }, - { - content: ( + return ( + + {dashboards.map((dashboard, index) => [ +
+

"Largest" Rendering (1056px width)

+
+ {dashboard} +
, +
+

"Tightest" Rendering (1057px width)

+
+ {dashboard} +
, + ])} +
+ ); +}; + +OnlyValueCards.story = { + name: 'only value cards', +}; + +export const WithCustomCards = () => { + return ( + +
+
-

Monitor Entities

+

View Dashboards


- Expore your entities and analyze their associated data. + View pinned dashboards to keep track of your world in IoT.

Monitor Entities
- ), - id: 'monitorEntities', - size: CARD_SIZES.MEDIUM, - type: 'CUSTOM', - }, - { - content: ( + + ), + id: 'viewDashboards', + size: CARD_SIZES.MEDIUM, + type: 'CUSTOM', + }, + { + content: ( +
-

Track Usage

+

Connect Devices


+

+ Connect devices and collect data by using the Watson IoT + Platform Service. +

- + Connect Devices
- ), - id: 'trackUsage', - size: CARD_SIZES.SMALLWIDE, - type: 'CUSTOM', - }, - { - content: ( -
-

Administer Users

-
-
- -
+ + ), + id: 'connectDevices', + size: CARD_SIZES.MEDIUM, + type: 'CUSTOM', + }, + { + content: ( +
+

Monitor Entities

+
+

Expore your entities and analyze their associated data.

+
+ Monitor Entities
- ), - id: 'administerUsers', - size: CARD_SIZES.SMALLWIDE, - type: 'CUSTOM', - }, - { - content: { - data: [ - { - id: 'row-9', - value: 'Explore entity metrics in the data lake', - link: - 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-explore.html', - extraContent: ( - - View your device data in the entity view of the main - Watson IoT Platform dashboard. If your plan includes - Watson IoT Platform Analytics, the data is stored in - the data lake for later retrieval and processing. - - ), - }, - { - id: 'row-10', - value: - 'Perform simple calculations on your entity metrics', - link: - 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-calculate.html', - extraContent: ( - - Process your entity metrics by running simple or - complex calculations to create calculated metrics. - - ), - }, - { - id: 'row-11', - value: 'View entity metrics in a monitoring dashboard', - link: - 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-monitor.html', - extraContent: ( - - Visualize your entity metrics in monitoring dashboards - to get an overview of your data. - - ), - }, - ], - loadData: () => {}, - }, - id: 'tutorials', - size: CARD_SIZES.MEDIUMWIDE, - title: 'Tutorials', - type: 'LIST', +
+ ), + id: 'monitorEntities', + size: CARD_SIZES.MEDIUM, + type: 'CUSTOM', + }, + { + content: ( +
+

Track Usage

+
+
+ +
+
+ ), + id: 'trackUsage', + size: CARD_SIZES.SMALLWIDE, + type: 'CUSTOM', + }, + { + content: ( +
+

Administer Users

+
+
+ +
+
+ ), + id: 'administerUsers', + size: CARD_SIZES.SMALLWIDE, + type: 'CUSTOM', + }, + { + content: { + data: [ + { + id: 'row-9', + value: 'Explore entity metrics in the data lake', + link: + 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-explore.html', + extraContent: ( + + View your device data in the entity view of the main + Watson IoT Platform dashboard. If your plan includes + Watson IoT Platform Analytics, the data is stored in the + data lake for later retrieval and processing. + + ), + }, + { + id: 'row-10', + value: 'Perform simple calculations on your entity metrics', + link: + 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-calculate.html', + extraContent: ( + + Process your entity metrics by running simple or complex + calculations to create calculated metrics. + + ), + }, + { + id: 'row-11', + value: 'View entity metrics in a monitoring dashboard', + link: + 'https://www.ibm.com/support/knowledgecenter/SSQP8H/iot/guides/micro-monitor.html', + extraContent: ( + + Visualize your entity metrics in monitoring dashboards + to get an overview of your data. + + ), + }, + ], + loadData: () => {}, }, - { - content: { - data: [ - { - id: 'row-9', - value: - 'Notice - IBM Watson IoT Platform support for IBM Cloud resource groups', - link: 'https://internetofthings.ibmcloud.com', - }, - { - id: 'row-10', - value: - 'New Connectivity Status API now available on IBM Watson IoT Platform', - link: 'https://internetofthings.ibmcloud.com', - }, - { - id: 'row-11', - value: 'Advanced Notice - Sunset of SPAPI support', - link: 'https://internetofthings.ibmcloud.com', - }, - { - id: 'row-12', - value: - 'Advanced Notice - Withdrawal of RTI features from Watson IoT Platform', - link: 'https://internetofthings.ibmcloud.com', - }, - ], - loadData: () => {}, - }, - id: 'announcements', - size: CARD_SIZES.MEDIUMWIDE, - title: 'Announcements', - type: 'LIST', + id: 'tutorials', + size: CARD_SIZES.MEDIUMWIDE, + title: 'Tutorials', + type: 'LIST', + }, + { + content: { + data: [ + { + id: 'row-9', + value: + 'Notice - IBM Watson IoT Platform support for IBM Cloud resource groups', + link: 'https://internetofthings.ibmcloud.com', + }, + { + id: 'row-10', + value: + 'New Connectivity Status API now available on IBM Watson IoT Platform', + link: 'https://internetofthings.ibmcloud.com', + }, + { + id: 'row-11', + value: 'Advanced Notice - Sunset of SPAPI support', + link: 'https://internetofthings.ibmcloud.com', + }, + { + id: 'row-12', + value: + 'Advanced Notice - Withdrawal of RTI features from Watson IoT Platform', + link: 'https://internetofthings.ibmcloud.com', + }, + ], + loadData: () => {}, }, - ]} - hasLastUpdated={false} - /> -
- - ); - }); + id: 'announcements', + size: CARD_SIZES.MEDIUMWIDE, + title: 'Announcements', + type: 'LIST', + }, + ]} + hasLastUpdated={false} + /> +
+
+ ); +}; + +WithCustomCards.story = { + name: 'with custom cards', +}; diff --git a/src/components/Dashboard/DashboardGrid.story.jsx b/src/components/Dashboard/DashboardGrid.story.jsx index 751d74e808..b168b224c5 100644 --- a/src/components/Dashboard/DashboardGrid.story.jsx +++ b/src/components/Dashboard/DashboardGrid.story.jsx @@ -1,5 +1,4 @@ import React, { Fragment, useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean } from '@storybook/addon-knobs'; @@ -63,796 +62,816 @@ const commonGridProps = { onLayoutChange: action('onLayoutChange'), }; -storiesOf('Watson IoT/Dashboard Grid', module) - .add( - 'dashboard, default layouts', - () => { - return ( - - Resize your window to see the callback handlers get triggered in the - Actions tab. - - {Cards} - - - ); +export default { + title: 'Watson IoT/Dashboard Grid', +}; + +export const DashboardDefaultLayouts = () => { + return ( + + Resize your window to see the callback handlers get triggered in the + Actions tab. + + {Cards} + + + ); +}; + +DashboardDefaultLayouts.story = { + name: 'dashboard, default layouts', + + parameters: { + info: { + text: ` + This is the simplest way to use the dashboard grid, just pass it a set of cards and let it figure out it's own layout to use. + # Component Overview + `, }, - { - info: { - text: ` - This is the simplest way to use the dashboard grid, just pass it a set of cards and let it figure out it's own layout to use. + }, +}; + +export const DashboardIsEditable = () => { + return ( + + You can drag and drop the cards around. Watch the handler get triggered on + the Actions tab. + + + {Cards} + + + + ); +}; + +DashboardIsEditable.story = { + name: 'dashboard, is Editable', + + parameters: { + info: { + text: ` + The onLayoutChange handler is triggered as you drag and drop the cards around # Component Overview `, - }, - } - ) - .add( - 'dashboard, is Editable', - () => { + }, + }, +}; + +export const DashboardCustomLayout = () => { + return ( + + Passes a custom layout to the dashboard grid. Only the lg and md + breakpoint have a custom layout defined. Resize the screen to see the + cards reposition and resize themselves at different layouts. + + + {Cards} + + + + ); +}; + +DashboardCustomLayout.story = { + name: 'dashboard, custom layout', + + parameters: { + info: { + text: ` + The breakpoint property tells the dashboard which + layout to use. You should listen to the onBreakpointChange event to keep + track of which breakpoint is currently being used in your local components state, and pass back in the breakpoint accordingly. + # Component Overview + `, + }, + }, +}; + +export const DashboardAllCardSizes = () => { + const CARDS_ALL_SIZES = [ + , + , + , + , + , + , + , + , + ]; + return ( + + Resize your window to see the callback handlers get triggered in the + Actions tab. + + + {CARDS_ALL_SIZES} + + + + ); +}; + +DashboardAllCardSizes.story = { + name: 'dashboard, all card sizes', + + parameters: { + info: { + text: ` + This is the simplest way to use the dashboard grid, just pass it a set of cards and let it figure out it's own layout to use. + # Component Overview + `, + }, + }, +}; + +export const DashboardResizableCard = () => { + return React.createElement(() => { + const [currentSize, setCurrentSize] = useState(CARD_SIZES.SMALL); + const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); + const isResizable = boolean('isResizable', true); + const layouts = { + max: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + xl: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + lg: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + md: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + sm: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + xs: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + }; + + return ( + + The card is resizable by dragging and the card size prop is + automatically updated to match the new size during the drag process. + + + setCurrentBreakpoint(newBreakpoint) + } + onCardSizeChange={(cardSizeData, gridData) => { + const { size } = cardSizeData; + action('onCardSizeChange')(cardSizeData, gridData); + setCurrentSize(size); + }} + onResizeStop={action('onResizeStop')}> + {[ + , + ]} + + + + ); + }); +}; + +DashboardResizableCard.story = { + name: 'dashboard, resizable card', + + parameters: { + info: { + source: true, + text: ` + This story demonstrates how a card can be resizable by dragging. During reszie the cards' size prop is + automatically updated to match the new size. + See the source code for the full example. + + ~~~js + const [currentSize, setCurrentSize] = useState(CARD_SIZES.SMALL); + const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); + const isResizable = boolean('isResizable', false); + const layouts = { + max: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + xl: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + lg: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + md: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + sm: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], + xs: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], + }; + return ( - You can drag and drop the cards around. Watch the handler get - triggered on the Actions tab. + The card is resizable by dragging and the cards' size prop is + automatically updated to match the new size during the drag process. - {Cards} + layouts={layouts} + breakpoint={currentBreakpoint} + onBreakpointChange={(newBreakpoint) => + setCurrentBreakpoint(newBreakpoint) + } + onCardSizeChange={(cardSizeData, gridData) => { + const { size } = cardSizeData; + setCurrentSize(size); + }} + onResizeStop={() => {}}> + ); + ~~~ + `, }, + }, +}; + +export const DashboardAllCardsAsResizable = () => { + const barChartCardValues = [ { - info: { - text: ` - The onLayoutChange handler is triggered as you drag and drop the cards around - # Component Overview - `, - }, - } - ) - .add( - 'dashboard, custom layout', - () => { - return ( - - Passes a custom layout to the dashboard grid. Only the lg and md - breakpoint have a custom layout defined. Resize the screen to see the - cards reposition and resize themselves at different layouts. - - - {Cards} - - - - ); + city: 'A', + particles: 447, }, { - info: { - text: ` - The breakpoint property tells the dashboard which - layout to use. You should listen to the onBreakpointChange event to keep - track of which breakpoint is currently being used in your local components state, and pass back in the breakpoint accordingly. - # Component Overview - `, - }, - } - ) - .add( - 'dashboard, all card sizes', - () => { - const CARDS_ALL_SIZES = [ - , - , - , - , - , - , - , - , - ]; - return ( - - Resize your window to see the callback handlers get triggered in the - Actions tab. - - - {CARDS_ALL_SIZES} - - - - ); + city: 'B', + particles: 528, }, { - info: { - text: ` - This is the simplest way to use the dashboard grid, just pass it a set of cards and let it figure out it's own layout to use. - # Component Overview - `, - }, - } - ) - .add( - 'dashboard, resizable card', - () => { - return React.createElement(() => { - const [currentSize, setCurrentSize] = useState(CARD_SIZES.SMALL); - const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); - const isResizable = boolean('isResizable', true); - const layouts = { - max: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - xl: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - lg: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - md: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - sm: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - xs: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - }; - - return ( - - The card is resizable by dragging and the card size prop is - automatically updated to match the new size during the drag process. - - - setCurrentBreakpoint(newBreakpoint) - } - onCardSizeChange={(cardSizeData, gridData) => { - const { size } = cardSizeData; - action('onCardSizeChange')(cardSizeData, gridData); - setCurrentSize(size); - }} - onResizeStop={action('onResizeStop')}> - {[ - , - ]} - - - - ); - }); + city: 'C', + particles: 435, }, { - info: { - source: true, - text: ` - This story demonstrates how a card can be resizable by dragging. During reszie the cards' size prop is - automatically updated to match the new size. - See the source code for the full example. - - ~~~js - const [currentSize, setCurrentSize] = useState(CARD_SIZES.SMALL); - const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); - const isResizable = boolean('isResizable', false); - const layouts = { - max: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - xl: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - lg: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - md: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - sm: [{ i: 'card', x: 0, y: 0, w: 2, h: 1 }], - xs: [{ i: 'card', x: 0, y: 0, w: 4, h: 1 }], - }; - - return ( - - The card is resizable by dragging and the cards' size prop is - automatically updated to match the new size during the drag process. - - - setCurrentBreakpoint(newBreakpoint) - } - onCardSizeChange={(cardSizeData, gridData) => { - const { size } = cardSizeData; - setCurrentSize(size); - }} - onResizeStop={() => {}}> - - - - - ); - ~~~ - `, + city: 'D', + particles: 388, + }, + ]; + + const pieChartCardValues = [ + { + category: 'A', + group: '2V2N 9KYPM', + value: 1, + }, + { + category: 'B', + group: 'L22I P66EP L22I P66EP', + value: 10, + }, + { + category: 'C', + group: 'JQAI 2M4L1', + value: 20, + }, + ]; + + const timeSeriesCardContent = { + includeZeroOnXaxis: true, + includeZeroOnYaxis: true, + series: [ + { + dataSourceId: 'temperature', + label: 'Temperature', }, - } - ) - .add( - 'dashboard, all cards as resizable', - () => { - const barChartCardValues = [ - { - city: 'A', - particles: 447, - }, - { - city: 'B', - particles: 528, - }, - { - city: 'C', - particles: 435, - }, - { - city: 'D', - particles: 388, - }, - ]; + ], + timeDataSourceId: 'timestamp', + xLabel: 'Time', + yLabel: 'Temperature (˚F)', + }; - const pieChartCardValues = [ - { - category: 'A', - group: '2V2N 9KYPM', - value: 1, - }, - { - category: 'B', - group: 'L22I P66EP L22I P66EP', - value: 10, - }, - { - category: 'C', - group: 'JQAI 2M4L1', - value: 20, + const imageCardValues = { + hotspots: [ + { + color: 'purple', + content: { + attributes: [ + { + dataSourceId: 'temperature', + label: 'Temp', + precision: 2, + }, + ], + description: 'Description', + title: 'My Device', + values: { + deviceid: '73000', + temperature: 35.05, + }, }, - ]; + icon: 'arrowDown', + x: 35, + y: 65, + }, + ], + }; - const timeSeriesCardContent = { - includeZeroOnXaxis: true, - includeZeroOnYaxis: true, - series: [ + const gaugeCardContent = { + gauges: [ + { + backgroundColor: '#e0e0e0', + color: 'orange', + dataSourceId: 'usage', + maximumValue: 100, + minimumValue: 0, + shape: 'circle', + thresholds: [ { - dataSourceId: 'temperature', - label: 'Temperature', + color: 'red', + comparison: '>', + label: 'Poor', + value: 0, }, - ], - timeDataSourceId: 'timestamp', - xLabel: 'Time', - yLabel: 'Temperature (˚F)', - }; - - const imageCardValues = { - hotspots: [ { - color: 'purple', - content: { - attributes: [ - { - dataSourceId: 'temperature', - label: 'Temp', - precision: 2, - }, - ], - description: 'Description', - title: 'My Device', - values: { - deviceid: '73000', - temperature: 35.05, - }, - }, - icon: 'arrowDown', - x: 35, - y: 65, + color: '#f1c21b', + comparison: '>', + label: 'Fair', + value: 60, + }, + { + color: 'green', + comparison: '>', + label: 'Good', + value: 80, }, ], - }; + trend: { + color: '', + dataSourceId: 'usageTrend', + trend: 'up', + }, + units: '%', + }, + ], + }; - const gaugeCardContent = { - gauges: [ - { - backgroundColor: '#e0e0e0', - color: 'orange', - dataSourceId: 'usage', - maximumValue: 100, - minimumValue: 0, - shape: 'circle', - thresholds: [ - { + const listCardData = [ + { + id: 'row-1', + value: 'Row content 1', + link: 'https://internetofthings.ibmcloud.com/', + }, + { + id: 'row-2', + value: 'Row content 2', + link: 'https://internetofthings.ibmcloud.com/', + }, + { id: 'row-3', value: 'Row content 3' }, + { + id: 'row-4', + value: 'Row content 4', + link: 'https://internetofthings.ibmcloud.com/', + extraContent: ( + + + + ), + }, + { id: 'row-5', value: 'Row content 5' }, + { id: 'row-6', value: 'Row content 6' }, + { id: 'row-7', value: 'Row content 7' }, + { id: 'row-8', value: 'Row content 8' }, + ]; + + return React.createElement(() => { + const [currentSizes, setCurrentSizes] = useState({ + card: CARD_SIZES.SMALL, + valueCard: CARD_SIZES.SMALLWIDE, + gaugeCard: CARD_SIZES.MEDIUMTHIN, + pieChartCard: CARD_SIZES.MEDIUM, + tableCard: CARD_SIZES.LARGE, + imageCard: CARD_SIZES.LARGE, + timeSeriesCard: CARD_SIZES.LARGETHIN, + listCard: CARD_SIZES.LARGETHIN, + barChartCard: CARD_SIZES.LARGEWIDE, + }); + const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); + const isResizable = boolean('isResizable', true); + + const CARDS_ALL_SIZES = [ + +

This is a basic card

+
, + ', - label: 'Poor', - value: 0, - }, - { - color: '#f1c21b', - comparison: '>', - label: 'Fair', - value: 60, }, - { - color: 'green', - comparison: '>', - label: 'Good', - value: 80, - }, - ], - trend: { - color: '', - dataSourceId: 'usageTrend', - trend: 'up', }, - units: '%', - }, - ], - }; - - const listCardData = [ - { - id: 'row-1', - value: 'Row content 1', - link: 'https://internetofthings.ibmcloud.com/', - }, - { - id: 'row-2', - value: 'Row content 2', - link: 'https://internetofthings.ibmcloud.com/', - }, - { id: 'row-3', value: 'Row content 3' }, - { - id: 'row-4', - value: 'Row content 4', - link: 'https://internetofthings.ibmcloud.com/', - extraContent: ( - - - - ), - }, - { id: 'row-5', value: 'Row content 5' }, - { id: 'row-6', value: 'Row content 6' }, - { id: 'row-7', value: 'Row content 7' }, - { id: 'row-8', value: 'Row content 8' }, - ]; - - return React.createElement(() => { - const [currentSizes, setCurrentSizes] = useState({ - card: CARD_SIZES.SMALL, - valueCard: CARD_SIZES.SMALLWIDE, - gaugeCard: CARD_SIZES.MEDIUMTHIN, - pieChartCard: CARD_SIZES.MEDIUM, - tableCard: CARD_SIZES.LARGE, - imageCard: CARD_SIZES.LARGE, - timeSeriesCard: CARD_SIZES.LARGETHIN, - listCard: CARD_SIZES.LARGETHIN, - barChartCard: CARD_SIZES.LARGEWIDE, - }); - const [currentBreakpoint, setCurrentBreakpoint] = useState('lg'); - const isResizable = boolean('isResizable', true); - - const CARDS_ALL_SIZES = [ - -

This is a basic card

-
, - , - Health - of floor 8

} - values={{ - usage: 81, - usageTrend: '12%', - }} - />, - , - {}} - size={currentSizes.tableCard} - isResizable={isResizable} - />, - , - , - {}} - />, - , - ]; - - const layouts = { - max: [ - { i: 'card', x: 0, y: 0, w: 2, h: 1 }, - { i: 'valueCard', x: 2, y: 0, w: 4, h: 1 }, - { i: 'gaugeCard', x: 0, y: 2, w: 2, h: 2 }, - { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 6, y: 2, w: 8, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, - { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, - { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, - ], - xl: [ - { i: 'card', x: 0, y: 0, w: 2, h: 1 }, - { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, - { i: 'gaugeCard', x: 0, y: 2, w: 4, h: 2 }, - { i: 'pieChartCard', x: 4, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, - { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, - { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, - ], - lg: [ - { i: 'card', x: 0, y: 0, w: 4, h: 1 }, - { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, - { i: 'gaugeCard', x: 0, y: 2, w: 4, h: 2 }, - { i: 'pieChartCard', x: 4, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, - { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, - { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, ], - md: [ - { i: 'card', x: 0, y: 0, w: 4, h: 1 }, - { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, - { i: 'gaugeCard', x: 0, y: 2, w: 2, h: 2 }, - { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, - { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, - { i: 'barChartCard', x: 8, y: 8, w: 8, h: 4 }, - ], - sm: [ - { i: 'card', x: 0, y: 0, w: 2, h: 1 }, - { i: 'valueCard', x: 4, y: 0, w: 4, h: 2 }, - { i: 'gaugeCard', x: 0, y: 0, w: 2, h: 2 }, - { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 8, y: 0, w: 4, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 0, w: 4, h: 4 }, - { i: 'listCard', x: 0, y: 0, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 0, w: 4, h: 4 }, - { i: 'barChartCard', x: 8, y: 0, w: 4, h: 4 }, - ], - xs: [ - { i: 'card', x: 0, y: 0, w: 4, h: 1 }, - { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, - { i: 'gaugeCard', x: 0, y: 0, w: 4, h: 2 }, - { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, - { i: 'imageCard', x: 8, y: 0, w: 4, h: 2 }, - { i: 'timeSeriesCard', x: 0, y: 0, w: 4, h: 4 }, - { i: 'listCard', x: 0, y: 0, w: 4, h: 4 }, - { i: 'tableCard', x: 4, y: 0, w: 4, h: 4 }, - { i: 'barChartCard', x: 8, y: 0, w: 4, h: 4 }, + }} + values={{ occupancy: 88 }} + />, + Health - of floor 8

} + values={{ + usage: 81, + usageTrend: '12%', + }} + />, + , + {}} + size={currentSizes.tableCard} + isResizable={isResizable} + />, + , + , + {}} + />, + { - minWidthLayouts[breakpoint] = breakpointLayout.map((cardLayout) => { - const cardLayoutCopy = { ...cardLayout }; - switch (cardLayoutCopy.i) { - case 'pieChartCard': - cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; - cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; - break; - case 'barChartCard': - cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; - cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; - break; - case 'tableCard': - cardLayoutCopy.minW = CARD_DIMENSIONS.LARGE.max.w; - cardLayoutCopy.minH = CARD_DIMENSIONS.LARGE.max.h; - break; - case 'imageCard': - cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; - cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; - break; - default: - break; - } - return cardLayoutCopy; - }); - }); - - return ( - -

- All cards are resizable by dragging and the card size prop is - automatically updated to match the new size during the drag - process. Some cards have a minimal size defined. -

- - { - action('onBreakpointChange')(newBreakpoint); - setCurrentBreakpoint(newBreakpoint); - }} - onCardSizeChange={(...[{ id, size }, rest]) => { - action('onCardSizeChange')({ id, size }, rest); - setCurrentSizes((old) => ({ ...old, [id]: size })); - }} - onResizeStop={action('onResizeStop')}> - {CARDS_ALL_SIZES} - - -
- ); + type: 'SIMPLE', + unit: 'P', + xLabel: 'Cities', + yLabel: 'Particles', + }} + values={barChartCardValues} + />, + ]; + + const layouts = { + max: [ + { i: 'card', x: 0, y: 0, w: 2, h: 1 }, + { i: 'valueCard', x: 2, y: 0, w: 4, h: 1 }, + { i: 'gaugeCard', x: 0, y: 2, w: 2, h: 2 }, + { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 6, y: 2, w: 8, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, + { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, + { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, + ], + xl: [ + { i: 'card', x: 0, y: 0, w: 2, h: 1 }, + { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, + { i: 'gaugeCard', x: 0, y: 2, w: 4, h: 2 }, + { i: 'pieChartCard', x: 4, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, + { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, + { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, + ], + lg: [ + { i: 'card', x: 0, y: 0, w: 4, h: 1 }, + { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, + { i: 'gaugeCard', x: 0, y: 2, w: 4, h: 2 }, + { i: 'pieChartCard', x: 4, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, + { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, + { i: 'barChartCard', x: 8, y: 8, w: 16, h: 4 }, + ], + md: [ + { i: 'card', x: 0, y: 0, w: 4, h: 1 }, + { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, + { i: 'gaugeCard', x: 0, y: 2, w: 2, h: 2 }, + { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 8, y: 2, w: 8, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 4, w: 4, h: 4 }, + { i: 'listCard', x: 4, y: 4, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 4, w: 8, h: 4 }, + { i: 'barChartCard', x: 8, y: 8, w: 8, h: 4 }, + ], + sm: [ + { i: 'card', x: 0, y: 0, w: 2, h: 1 }, + { i: 'valueCard', x: 4, y: 0, w: 4, h: 2 }, + { i: 'gaugeCard', x: 0, y: 0, w: 2, h: 2 }, + { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 8, y: 0, w: 4, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 0, w: 4, h: 4 }, + { i: 'listCard', x: 0, y: 0, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 0, w: 4, h: 4 }, + { i: 'barChartCard', x: 8, y: 0, w: 4, h: 4 }, + ], + xs: [ + { i: 'card', x: 0, y: 0, w: 4, h: 1 }, + { i: 'valueCard', x: 4, y: 0, w: 4, h: 1 }, + { i: 'gaugeCard', x: 0, y: 0, w: 4, h: 2 }, + { i: 'pieChartCard', x: 2, y: 0, w: 4, h: 2 }, + { i: 'imageCard', x: 8, y: 0, w: 4, h: 2 }, + { i: 'timeSeriesCard', x: 0, y: 0, w: 4, h: 4 }, + { i: 'listCard', x: 0, y: 0, w: 4, h: 4 }, + { i: 'tableCard', x: 4, y: 0, w: 4, h: 4 }, + { i: 'barChartCard', x: 8, y: 0, w: 4, h: 4 }, + ], + }; + + // Set minimum sizes for the cards that requires it + const minWidthLayouts = {}; + Object.entries(layouts).forEach(([breakpoint, breakpointLayout]) => { + minWidthLayouts[breakpoint] = breakpointLayout.map((cardLayout) => { + const cardLayoutCopy = { ...cardLayout }; + switch (cardLayoutCopy.i) { + case 'pieChartCard': + cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; + cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; + break; + case 'barChartCard': + cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; + cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; + break; + case 'tableCard': + cardLayoutCopy.minW = CARD_DIMENSIONS.LARGE.max.w; + cardLayoutCopy.minH = CARD_DIMENSIONS.LARGE.max.h; + break; + case 'imageCard': + cardLayoutCopy.minW = CARD_DIMENSIONS.MEDIUMTHIN.max.w; + cardLayoutCopy.minH = CARD_DIMENSIONS.MEDIUMTHIN.max.h; + break; + default: + break; + } + return cardLayoutCopy; }); - }, - { - info: { - source: true, - text: ` - This story demonstrates how all cards can be resizable by dragging. During reszie the cards' size prop is - automatically updated to match the new size. Some cards have a minimal size defined. + }); - See the source code for the full example. - `, - }, - } - ); + return ( + +

+ All cards are resizable by dragging and the card size prop is + automatically updated to match the new size during the drag process. + Some cards have a minimal size defined. +

+ + { + action('onBreakpointChange')(newBreakpoint); + setCurrentBreakpoint(newBreakpoint); + }} + onCardSizeChange={(...[{ id, size }, rest]) => { + action('onCardSizeChange')({ id, size }, rest); + setCurrentSizes((old) => ({ ...old, [id]: size })); + }} + onResizeStop={action('onResizeStop')}> + {CARDS_ALL_SIZES} + + +
+ ); + }); +}; + +DashboardAllCardsAsResizable.story = { + name: 'dashboard, all cards as resizable', + + parameters: { + info: { + source: true, + text: ` + This story demonstrates how all cards can be resizable by dragging. During reszie the cards' size prop is + automatically updated to match the new size. Some cards have a minimal size defined. + + See the source code for the full example. + `, + }, + }, +}; diff --git a/src/components/Dashboard/DashboardHeader.story.jsx b/src/components/Dashboard/DashboardHeader.story.jsx index 34369eb346..ef95e447df 100644 --- a/src/components/Dashboard/DashboardHeader.story.jsx +++ b/src/components/Dashboard/DashboardHeader.story.jsx @@ -3,119 +3,74 @@ import { text } from '@storybook/addon-knobs'; import Pin from '@carbon/icons-react/lib/pin/20'; import Edit from '@carbon/icons-react/lib/edit/20'; import { TrashCan20 } from '@carbon/icons-react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { DatePicker, DatePickerInput } from 'carbon-components-react'; import DashboardHeader from './DashboardHeader'; -storiesOf('Watson IoT/Dashboard Header (Deprecated)', module) - .add('basic', () => { - return ( -
- -
- ); - }) - .add('with filter', () => { - return ( -
- - - - } - /> -
- ); - }) - .add('with custom actions', () => { - return ( -
- }, - { id: 'delete', labelText: 'Delete', icon: TrashCan20 }, - ]} - onDashboardAction={action('onDashboardAction')} - /> -
- ); - }) - .add('with filter and custom actions', () => { - return ( -
- - - - } - actions={[ - { id: 'edit', labelText: 'Edit', icon: }, - { id: 'delete', labelText: 'Delete', icon: }, - { id: 'pin', labelText: 'Pin', icon: }, - ]} - onDashboardAction={action('onDashboardAction')} - /> -
- ); - }) - .add('with custom actions component', () => { - return ( +export default { + title: 'Watson IoT/Dashboard Header (Deprecated)', +}; + +export const Basic = () => { + return ( +
+ +
+ ); +}; + +Basic.story = { + name: 'basic', +}; + +export const WithFilter = () => { + return ( +
+ + + + } + /> +
+ ); +}; + +WithFilter.story = { + name: 'with filter', +}; + +export const WithCustomActions = () => { + return ( +
- we can now send custom components -
- ), - }, + { id: 'edit', labelText: 'Edit', icon: }, + { id: 'delete', labelText: 'Delete', icon: TrashCan20 }, ]} + onDashboardAction={action('onDashboardAction')} /> - ); - }); +
+ ); +}; + +WithCustomActions.story = { + name: 'with custom actions', +}; + +export const WithFilterAndCustomActions = () => { + return ( +
+ + + + } + actions={[ + { id: 'edit', labelText: 'Edit', icon: }, + { id: 'delete', labelText: 'Delete', icon: }, + { id: 'pin', labelText: 'Pin', icon: }, + ]} + onDashboardAction={action('onDashboardAction')} + /> +
+ ); +}; + +WithFilterAndCustomActions.story = { + name: 'with filter and custom actions', +}; + +export const WithCustomActionsComponent = () => { + return ( + + we can now send custom components + + ), + }, + ]} + /> + ); +}; + +WithCustomActionsComponent.story = { + name: 'with custom actions component', +}; diff --git a/src/components/Dashboard/__snapshots__/Dashboard.story.storyshot b/src/components/Dashboard/__snapshots__/Dashboard.story.storyshot index 25e97bc574..96dbbc0eca 100644 --- a/src/components/Dashboard/__snapshots__/Dashboard.story.storyshot +++ b/src/components/Dashboard/__snapshots__/Dashboard.story.storyshot @@ -27718,94 +27718,3 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Dashb `; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Dashboard (Deprecated) ️⛔ Deprecation Notice 1`] = ` -
-
-
- - - - - warning icon - - -
-

- Deprecation Notice -

-
- Dashboard has been deprecated and will be removed in the next major version of carbon-addons-iot-react. -
-
-
-
-
- -
-`; diff --git a/src/components/DashboardEditor/DashboardEditor.story.jsx b/src/components/DashboardEditor/DashboardEditor.story.jsx index bd84313160..0fc21a35c5 100644 --- a/src/components/DashboardEditor/DashboardEditor.story.jsx +++ b/src/components/DashboardEditor/DashboardEditor.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, @@ -16,279 +15,307 @@ import DashboardEditor from './DashboardEditor'; const mockDataItems = ['Torque Max', 'Torque Min', 'Torque Mean']; -storiesOf('Watson IoT Experimental/DashboardEditor', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/DashboardEditor', + decorators: [withKnobs], + + parameters: { component: DashboardEditor, - }) - .addDecorator(withKnobs) - .add('default', () => ( -
- Dashboard library, - Favorites, - ]} - /> -
- )) - .add('with initialValue', () => ( -
- ( +
+ Dashboard library, + Favorites, + ]} + /> +
+); + +Default.story = { + name: 'default', +}; + +export const WithInitialValue = () => ( +
+ Dashboard library, - Favorites, - ]} - /> -
- )) - .add('with notifications', () => ( -
- Dashboard library, - Favorites, - ]} - notification={ - <> - - - - - } - /> -
- )) - .add('custom header renderer', () => ( -
-

Custom Header

} /> -
- )) - .add('custom card preview renderer', () => ( -
- Dashboard library, + Favorites, + ]} + /> +
+); + +WithInitialValue.story = { + name: 'with initialValue', +}; + +export const WithNotifications = () => ( +
+ Dashboard library, + Favorites, + ]} + notification={ + <> + + + + + } + /> +
+); + +WithNotifications.story = { + name: 'with notifications', +}; + +export const CustomHeaderRenderer = () => ( +
+

Custom Header

} /> +
+); + +CustomHeaderRenderer.story = { + name: 'custom header renderer', +}; + +export const CustomCardPreviewRenderer = () => ( +
+ Dashboard library, + Favorites, + ]} + renderCardPreview={( + cardJson, + isSelected, + onSelectCard, + onDuplicateCard, + onRemoveCard + ) => { + const commonProps = isSelected + ? { className: 'selected-card' } + : { + availableActions: { edit: true, clone: true, delete: true }, + onCardAction: (id, actionId) => { + if (actionId === CARD_ACTIONS.EDIT_CARD) { + onSelectCard(id); + } + if (actionId === CARD_ACTIONS.CLONE_CARD) { + onDuplicateCard(id); + } + if (actionId === CARD_ACTIONS.DELETE_CARD) { + onRemoveCard(id); + } }, - }, - ], - layouts: {}, - })} - onEditTitle={action('onEditTitle')} - onImport={action('onImport')} - onExport={action('onExport')} - onDelete={action('onDelete')} - onCancel={action('onCancel')} - onSubmit={action('onSubmit')} - supportedCardTypes={array('supportedCardTypes', [ - 'TIMESERIES', - 'SIMPLE_BAR', - 'GROUPED_BAR', - 'STACKED_BAR', - 'VALUE', - 'IMAGE', - 'TABLE', - 'CUSTOM', - ])} - i18n={{ - cardType_CUSTOM: 'Custom', - }} - headerBreadcrumbs={[ - Dashboard library, - Favorites, - ]} - renderCardPreview={( - cardJson, - isSelected, - onSelectCard, - onDuplicateCard, - onRemoveCard - ) => { - const commonProps = isSelected - ? { className: 'selected-card' } - : { - availableActions: { edit: true, clone: true, delete: true }, - onCardAction: (id, actionId) => { - if (actionId === CARD_ACTIONS.EDIT_CARD) { - onSelectCard(id); - } - if (actionId === CARD_ACTIONS.CLONE_CARD) { - onDuplicateCard(id); - } - if (actionId === CARD_ACTIONS.DELETE_CARD) { - onRemoveCard(id); - } - }, - }; - return cardJson.type === 'CUSTOM' ? ( - -
- This content is rendered by the renderCardPreview function. The - "value" property on the card will be rendered here: -

{cardJson.value}

-
-
- ) : undefined; - }} - /> -
- )); + }; + return cardJson.type === 'CUSTOM' ? ( + +
+ This content is rendered by the renderCardPreview function. The + "value" property on the card will be rendered here: +

{cardJson.value}

+
+
+ ) : undefined; + }} + /> +
+); + +CustomCardPreviewRenderer.story = { + name: 'custom card preview renderer', +}; diff --git a/src/components/DashboardEditor/DashboardEditorHeader/DashboardEditorHeader.story.jsx b/src/components/DashboardEditor/DashboardEditorHeader/DashboardEditorHeader.story.jsx index 3ff8aefafd..2474348eab 100644 --- a/src/components/DashboardEditor/DashboardEditorHeader/DashboardEditorHeader.story.jsx +++ b/src/components/DashboardEditor/DashboardEditorHeader/DashboardEditorHeader.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, text } from '@storybook/addon-knobs'; @@ -7,43 +6,53 @@ import { Link } from '../../../index'; import DashboardEditorHeader from './DashboardEditorHeader'; -storiesOf( - 'Watson IoT Experimental/DashboardEditor/DashboardEditorHeader', - module -) - .addParameters({ +export default { + title: 'Watson IoT Experimental/DashboardEditor/DashboardEditorHeader', + decorators: [withKnobs], + + parameters: { component: DashboardEditorHeader, - }) - .addDecorator(withKnobs) - .add('default', () => ( -
- Dashboard library, - Favorites, - ]} - onImport={action('onImport')} - onExport={action('onExport')} - onDelete={action('onDelete')} - onCancel={action('onCancel')} - onSubmit={action('onSubmit')} - dashboardJson={{}} - /> -
- )) - .add('with editable title and no import/export/delete', () => ( -
- Dashboard library, - Favorites, - ]} - onEditTitle={action('onEditTitle')} - onCancel={action('onCancel')} - onSubmit={action('onSubmit')} - dashboardJson={{}} - /> -
- )); + }, +}; + +export const Default = () => ( +
+ Dashboard library, + Favorites, + ]} + onImport={action('onImport')} + onExport={action('onExport')} + onDelete={action('onDelete')} + onCancel={action('onCancel')} + onSubmit={action('onSubmit')} + dashboardJson={{}} + /> +
+); + +Default.story = { + name: 'default', +}; + +export const WithEditableTitleAndNoImportExportDelete = () => ( +
+ Dashboard library, + Favorites, + ]} + onEditTitle={action('onEditTitle')} + onCancel={action('onCancel')} + onSubmit={action('onSubmit')} + dashboardJson={{}} + /> +
+); + +WithEditableTitleAndNoImportExportDelete.story = { + name: 'with editable title and no import/export/delete', +}; diff --git a/src/components/DateTimePicker/DateTimePicker.story.jsx b/src/components/DateTimePicker/DateTimePicker.story.jsx index 68f7053cfb..91a3cb7841 100644 --- a/src/components/DateTimePicker/DateTimePicker.story.jsx +++ b/src/components/DateTimePicker/DateTimePicker.story.jsx @@ -1,6 +1,5 @@ /* eslint-disable react/jsx-pascal-case */ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { boolean, text, select } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { spacing06 } from '@carbon/layout'; @@ -36,122 +35,151 @@ export const defaultAbsoluteValue = { }, }; -storiesOf('Watson IoT Experimental/DateTime Picker', module) - .add('Default', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }) - .add('Selected preset', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }) - .add('Selected relative', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }) - .add('Selected absolute', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }) - .add('Without a relative option', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }) - .add('Light version', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); - return ( -
- -
- ); - }); +export default { + title: 'Watson IoT Experimental/DateTime Picker', + excludeStories: ['defaultRelativeValue', 'defaultAbsoluteValue'], +}; + +export const Default = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +export const SelectedPreset = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +SelectedPreset.story = { + name: 'Selected preset', +}; + +export const SelectedRelative = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +SelectedRelative.story = { + name: 'Selected relative', +}; + +export const SelectedAbsolute = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +SelectedAbsolute.story = { + name: 'Selected absolute', +}; + +export const WithoutARelativeOption = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +WithoutARelativeOption.story = { + name: 'Without a relative option', +}; + +export const LightVersion = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( +
+ +
+ ); +}; + +LightVersion.story = { + name: 'Light version', +}; diff --git a/src/components/FileDrop/FileDrop.story.jsx b/src/components/FileDrop/FileDrop.story.jsx index 3ca38863a9..0eb249dc84 100644 --- a/src/components/FileDrop/FileDrop.story.jsx +++ b/src/components/FileDrop/FileDrop.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; @@ -14,53 +13,82 @@ const FileDropProps = { onError: action('onError'), }; -storiesOf('Watson IoT/FileDrop', module) - .addParameters({ +export default { + title: 'Watson IoT/FileDrop', + + parameters: { component: FileDrop, - }) - .add('Browse', () => ( - - )) - .add('Browse only one', () => ( - - )) - .add('Drag and drop', () => ( - - )) - .add('Drag only one file', () => ( - - )) - .add('Show files false', () => ( - - )) - .add('Accept JSON', () => ( - - )); + }, +}; + +export const Browse = () => ( + +); + +export const BrowseOnlyOne = () => ( + +); + +BrowseOnlyOne.story = { + name: 'Browse only one', +}; + +export const DragAndDrop = () => ( + +); + +DragAndDrop.story = { + name: 'Drag and drop', +}; + +export const DragOnlyOneFile = () => ( + +); + +DragOnlyOneFile.story = { + name: 'Drag only one file', +}; + +export const ShowFilesFalse = () => ( + +); + +ShowFilesFalse.story = { + name: 'Show files false', +}; + +export const AcceptJson = () => ( + +); + +AcceptJson.story = { + name: 'Accept JSON', +}; diff --git a/src/components/FilterTags/FilterTags.story.jsx b/src/components/FilterTags/FilterTags.story.jsx index ad8f12214d..89bb5879ad 100644 --- a/src/components/FilterTags/FilterTags.story.jsx +++ b/src/components/FilterTags/FilterTags.story.jsx @@ -1,5 +1,4 @@ import React, { useState, useRef } from 'react'; -import { storiesOf } from '@storybook/react'; import { Tag, Button } from '../../index'; @@ -77,59 +76,75 @@ const StatefulFilterTags = ({ tags }) => { ); }; -storiesOf('Watson IoT Experimental/FilterTags', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/FilterTags', + + parameters: { component: FilterTags, - }) - .add('Default Example', () => , { + }, + + excludeStories: ['tagData'], +}; + +export const DefaultExample = () => ; + +DefaultExample.story = { + parameters: { info: { propTables: [FilterTags], propTablesExclude: [StatefulFilterTags], }, - }) - .add( - 'With hasOverflow set to false', - () => ( - - {tagData.map((tag) => ( - - {tag.text} - - ))} - - ), - { - info: { - propTables: [FilterTags], - propTablesExclude: [Tag], - }, - } - ) - .add( - 'With tagContainer prop', - () => ( - - {tagData.map((tag) => ( - - {tag.text} - - ))} - - ), - { - info: { - propTables: [FilterTags], - propTablesExclude: [Tag], - }, - } - ); + }, +}; + +export const WithHasOverflowSetToFalse = () => ( + + {tagData.map((tag) => ( + + {tag.text} + + ))} + +); + +WithHasOverflowSetToFalse.story = { + name: 'With hasOverflow set to false', + + parameters: { + info: { + propTables: [FilterTags], + propTablesExclude: [Tag], + }, + }, +}; + +export const WithTagContainerProp = () => ( + + {tagData.map((tag) => ( + + {tag.text} + + ))} + +); + +WithTagContainerProp.story = { + name: 'With tagContainer prop', + + parameters: { + info: { + propTables: [FilterTags], + propTablesExclude: [Tag], + }, + }, +}; diff --git a/src/components/FlyoutMenu/FlyoutMenu.story.jsx b/src/components/FlyoutMenu/FlyoutMenu.story.jsx index b6883e3889..3b6a07bda6 100644 --- a/src/components/FlyoutMenu/FlyoutMenu.story.jsx +++ b/src/components/FlyoutMenu/FlyoutMenu.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { boolean, number, select } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { @@ -32,88 +31,91 @@ const CustomFooter = ({ setIsOpen, isOpen }) => { ); }; -storiesOf('Watson IoT Experimental/Flyout Menu', module) - .add('Default Example', () => ( -
- - This is some flyout content - -
- )) - .add('Large Flyout Example', () => ( -
- -
-

This is a header

+export default { + title: 'Watson IoT Experimental/Flyout Menu', +}; -

- Cras justo odio, dapibus ac facilisis in, egestas eget quam. - Vestibulum id ligula porta felis euismod semper. -

+export const DefaultExample = () => ( +
+ + This is some flyout content + +
+); -
-
- -
- )) - .add('Custom Footer Example', () => ( -
- -
-

This is a header

+export const LargeFlyoutExample = () => ( +
+ +
+

This is a header

-

- Cras justo odio, dapibus ac facilisis in, egestas eget quam. - Vestibulum id ligula porta felis euismod semper. -

-
-
-
- )); +

+ Cras justo odio, dapibus ac facilisis in, egestas eget quam. + Vestibulum id ligula porta felis euismod semper. +

+ +
+
+ +
+); + +export const CustomFooterExample = () => ( +
+ +
+

This is a header

+ +

+ Cras justo odio, dapibus ac facilisis in, egestas eget quam. + Vestibulum id ligula porta felis euismod semper. +

+
+
+
+); diff --git a/src/components/FormItem/FormItem.story.jsx b/src/components/FormItem/FormItem.story.jsx index 9c2c1b5940..e8a428eb72 100644 --- a/src/components/FormItem/FormItem.story.jsx +++ b/src/components/FormItem/FormItem.story.jsx @@ -1,23 +1,24 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { FormItem, NumberInput } from 'carbon-components-react'; -// Duplicating old carbon story for awareness of the component. -// Can replace with Carbon variant when they release -storiesOf('FormItem', module) - .addParameters({ +export default { + title: 'FormItem', + + parameters: { component: FormItem, - }) - .add( - 'Default', - () => ( - - - - ), - { - info: { - text: 'Form item.', - }, - } - ); + }, +}; + +export const Default = () => ( + + + +); + +Default.story = { + parameters: { + info: { + text: 'Form item.', + }, + }, +}; diff --git a/src/components/GaugeCard/GaugeCard.story.jsx b/src/components/GaugeCard/GaugeCard.story.jsx index e0b8350ace..18b6b9a830 100644 --- a/src/components/GaugeCard/GaugeCard.story.jsx +++ b/src/components/GaugeCard/GaugeCard.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text, number, select, boolean } from '@storybook/addon-knobs'; import { layout05 } from '@carbon/layout'; @@ -51,88 +50,106 @@ const content = { ], }; -storiesOf('Watson IoT Experimental/GaugeCard', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/GaugeCard', + + parameters: { component: GaugeCard, - }) - .add('basic', () => { - return ( -
- Health - of floor 8

} - id="GaugeCard" - title={text('Text', 'Health')} - size={CARD_SIZES.SMALL} - values={{ - usage: number('Gauge value', 81), - usageTrend: '12%', - }} - content={content} - /> -
- ); - }) - .add('basic with expand', () => { - return ( -
- Health - of floor 8

} - id="GaugeCard" - title={text('Text', 'Health')} - size={CARD_SIZES.SMALL} - values={{ - usage: number('Gauge value', 81), - usageTrend: '12%', - }} - isExpanded={false} - availableActions={{ - expand: true, - }} - content={content} - onCardAction={action('Expand button clicked')} - /> -
- ); - }) - .add('with data state no-data', () => { - const myDataState = { - type: select( - 'dataState : Type', - Object.keys(CARD_DATA_STATE), - CARD_DATA_STATE.NO_DATA - ), - ...getDataStateProp(), - }; - const content = { - gauges: [], - }; + }, +}; + +export const Basic = () => { + return ( +
+ Health - of floor 8

} + id="GaugeCard" + title={text('Text', 'Health')} + size={CARD_SIZES.SMALL} + values={{ + usage: number('Gauge value', 81), + usageTrend: '12%', + }} + content={content} + /> +
+ ); +}; + +Basic.story = { + name: 'basic', +}; + +export const BasicWithExpand = () => { + return ( +
+ Health - of floor 8

} + id="GaugeCard" + title={text('Text', 'Health')} + size={CARD_SIZES.SMALL} + values={{ + usage: number('Gauge value', 81), + usageTrend: '12%', + }} + isExpanded={false} + availableActions={{ + expand: true, + }} + content={content} + onCardAction={action('Expand button clicked')} + /> +
+ ); +}; + +BasicWithExpand.story = { + name: 'basic with expand', +}; + +export const WithDataStateNoData = () => { + const myDataState = { + type: select( + 'dataState : Type', + Object.keys(CARD_DATA_STATE), + CARD_DATA_STATE.NO_DATA + ), + ...getDataStateProp(), + }; + const content = { + gauges: [], + }; - return ( -
- Health - of floor 8

} - id="GaugeCard" - title={text('Text', 'Health')} - size={select('size', Object.keys(CARD_SIZES), CARD_SIZES.SMALL)} - values={{ - usage: number('Gauge value', 81), - usageTrend: '12%', - }} - content={content} - dataState={myDataState} - /> -
-
- ); - }); + return ( +
+ Health - of floor 8

} + id="GaugeCard" + title={text('Text', 'Health')} + size={select('size', Object.keys(CARD_SIZES), CARD_SIZES.SMALL)} + values={{ + usage: number('Gauge value', 81), + usageTrend: '12%', + }} + content={content} + dataState={myDataState} + /> +
+
+ ); +}; + +WithDataStateNoData.story = { + name: 'with data state no-data', +}; diff --git a/src/components/Header/Header.story.jsx b/src/components/Header/Header.story.jsx index 5f2b0fbf27..61b608897c 100644 --- a/src/components/Header/Header.story.jsx +++ b/src/components/Header/Header.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import styled from 'styled-components'; import { text } from '@storybook/addon-knobs'; @@ -158,39 +157,62 @@ const headerPanel = { )), }; -storiesOf('Watson IoT/Header', module) - .addParameters({ +export default { + title: 'Watson IoT/Header', + + parameters: { component: Header, - }) - .add('Header action buttons with dropdowns', () => ( -
- - - )) - .add('header submenu', () => ( -
- -
- )) - .add('Header no submenu', () => ( + }, +}; + +export const HeaderActionButtonsWithDropdowns = () => ( +
, - }, - ]} + headerPanel={headerPanel} + appSwitcherLabel={text('AppSwitcher label', 'AppSwitcher')} /> - )) - .add('header subtitle', () => ( -
- -
- )); + +); + +HeaderActionButtonsWithDropdowns.story = { + name: 'Header action buttons with dropdowns', +}; + +export const HeaderSubmenu = () => ( +
+ +
+); + +HeaderSubmenu.story = { + name: 'header submenu', +}; + +export const HeaderNoSubmenu = () => ( + , + }, + ]} + /> +); + +HeaderNoSubmenu.story = { + name: 'Header no submenu', +}; + +export const HeaderSubtitle = () => ( +
+ +
+); + +HeaderSubtitle.story = { + name: 'header subtitle', +}; diff --git a/src/components/Hero/Hero.story.jsx b/src/components/Hero/Hero.story.jsx index dba81f3f66..48cc74af23 100644 --- a/src/components/Hero/Hero.story.jsx +++ b/src/components/Hero/Hero.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { Button } from 'carbon-components-react'; @@ -30,57 +29,98 @@ const tooltip = { linkLabel: 'Learn more', }; -storiesOf('Watson IoT/Hero (Deprecated)', module) - .addDecorator((storyFn) => {storyFn()}) - .add(deprecatedStoryTitle, () => ( - - )) - .add('normal', () => ) - .add('with description', () => ( - - )) - .add('isLoading', () => ) - .add('with right content', () => ( - -
Here is a long message relating some status... 
- - -
- } - /> - )) - .add('with breadcrumb with right content', () => ( - breadcrumb Right Content
- } - /> - )) - .add('with breadcrumb', () => ( - - )) - .add('with tooltip', () => ( - - )) - .add('with tooltip (no link)', () => ( - - )) - .add('with close button', () => ( - - )); +export default { + title: 'Watson IoT/Hero (Deprecated)', + decorators: [(storyFn) => {storyFn()}], +}; + +export const Normal = () => ; + +Normal.story = { + name: 'normal', +}; + +export const WithDescription = () => ( + +); + +WithDescription.story = { + name: 'with description', +}; + +export const IsLoading = () => ; + +IsLoading.story = { + name: 'isLoading', +}; + +export const WithRightContent = () => ( + +
Here is a long message relating some status... 
+ + +
+ } + /> +); + +WithRightContent.story = { + name: 'with right content', +}; + +export const WithBreadcrumbWithRightContent = () => ( + breadcrumb Right Content
+ } + /> +); + +WithBreadcrumbWithRightContent.story = { + name: 'with breadcrumb with right content', +}; + +export const WithBreadcrumb = () => ( + +); + +WithBreadcrumb.story = { + name: 'with breadcrumb', +}; + +export const WithTooltip = () => ( + +); + +WithTooltip.story = { + name: 'with tooltip', +}; + +export const WithTooltipNoLink = () => ( + +); + +WithTooltipNoLink.story = { + name: 'with tooltip (no link)', +}; + +export const WithCloseButton = () => ( + +); + +WithCloseButton.story = { + name: 'with close button', +}; diff --git a/src/components/Hero/__snapshots__/Hero.story.storyshot b/src/components/Hero/__snapshots__/Hero.story.storyshot index 104a06b710..fc481f758b 100644 --- a/src/components/Hero/__snapshots__/Hero.story.storyshot +++ b/src/components/Hero/__snapshots__/Hero.story.storyshot @@ -1000,100 +1000,3 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Hero
`; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Hero (Deprecated) ️⛔ Deprecation Notice 1`] = ` -
-
-
-
- - - - - warning icon - - -
-

- Deprecation Notice -

-
- Hero has been deprecated and will be removed in the next major version of carbon-addons-iot-react. -
-
- Refactor usages of Hero to use PageTitleBar instead. -
-
-
-
-
- -
-`; diff --git a/src/components/IconSwitch/IconSwitch.story.jsx b/src/components/IconSwitch/IconSwitch.story.jsx index 466f201ef1..117f47c67e 100644 --- a/src/components/IconSwitch/IconSwitch.story.jsx +++ b/src/components/IconSwitch/IconSwitch.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { withKnobs, select } from '@storybook/addon-knobs'; import { ContentSwitcher } from 'carbon-components-react'; import { @@ -29,139 +28,158 @@ const listIcons = { [ICON_SWITCH_SIZES.large]: List24, }; -storiesOf('Watson IoT/IconSwitch', module) - .addParameters({ +export default { + title: 'Watson IoT/IconSwitch', + decorators: [withKnobs], + + parameters: { component: IconSwitch, - }) - .addDecorator(withKnobs) - .add( - 'unselected', - () => { - const size = select( - 'Size', - Object.values(ICON_SWITCH_SIZES), - ICON_SWITCH_SIZES.default, - 'size' - ); - return ( - - ); + }, +}; + +export const Unselected = () => { + const size = select( + 'Size', + Object.values(ICON_SWITCH_SIZES), + ICON_SWITCH_SIZES.default, + 'size' + ); + return ( + + ); +}; + +Unselected.story = { + name: 'unselected', + + parameters: { + info: { + text: + 'Designed to be embedded in ContentSwitcher - see Watson IoT/ContentSwitcher', }, - { - info: { - text: - 'Designed to be embedded in ContentSwitcher - see Watson IoT/ContentSwitcher', - }, - } - ) - .add( - 'selected', - () => { - const size = select( - 'Size', - Object.values(ICON_SWITCH_SIZES), - ICON_SWITCH_SIZES.default, - 'size' - ); - return ( - - ); + }, +}; + +export const Selected = () => { + const size = select( + 'Size', + Object.values(ICON_SWITCH_SIZES), + ICON_SWITCH_SIZES.default, + 'size' + ); + return ( + + ); +}; + +Selected.story = { + name: 'selected', + + parameters: { + info: { + text: + 'Designed to be embedded in ContentSwitcher - see Watson IoT/ContentSwitcher', }, - { - info: { - text: - 'Designed to be embedded in ContentSwitcher - see Watson IoT/ContentSwitcher', - }, - } - ) - .add('example - used in ContentSwitcher', () => { - const size = select( - 'Size', - Object.values(ICON_SWITCH_SIZES), - ICON_SWITCH_SIZES.default, - 'size' - ); - - return ( - - - - - ); - }) - .add('example - used in ContentSwitcher light version ', () => { - const size = select( - 'Size', - Object.values(ICON_SWITCH_SIZES), - ICON_SWITCH_SIZES.default, - 'size' - ); - - return ( - - - - - ); - }); + }, +}; + +export const ExampleUsedInContentSwitcher = () => { + const size = select( + 'Size', + Object.values(ICON_SWITCH_SIZES), + ICON_SWITCH_SIZES.default, + 'size' + ); + + return ( + + + + + ); +}; + +ExampleUsedInContentSwitcher.story = { + name: 'example - used in ContentSwitcher', +}; + +export const ExampleUsedInContentSwitcherLightVersion = () => { + const size = select( + 'Size', + Object.values(ICON_SWITCH_SIZES), + ICON_SWITCH_SIZES.default, + 'size' + ); + + return ( + + + + + ); +}; + +ExampleUsedInContentSwitcherLightVersion.story = { + name: 'example - used in ContentSwitcher light version ', +}; diff --git a/src/components/ImageCard/HotspotContent.story.jsx b/src/components/ImageCard/HotspotContent.story.jsx index 98e5d3beb3..78009a5dae 100644 --- a/src/components/ImageCard/HotspotContent.story.jsx +++ b/src/components/ImageCard/HotspotContent.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text, object, select } from '@storybook/addon-knobs'; import { Tooltip } from 'carbon-components-react'; import { Warning16 } from '@carbon/icons-react'; @@ -9,178 +8,195 @@ import HotspotContent from './HotspotContent'; const mockValues = object('values', { temperature: 35.35, humidity: 99 }); -storiesOf('Watson IoT/HotspotContent', module) - .addParameters({ +export default { + title: 'Watson IoT/HotspotContent', + + parameters: { component: HotspotContent, - }) - .addParameters({ + info: 'This Hotspot content is recommended to be used with a Carbon tooltip', - }) - .add('basic', () => { - return ( - - - - ); - }) - .add('basic with units and precision', () => { - return ( - - - - ); - }) - .add( - 'with thresholds', - () => { - return ( - - ', value: 30, icon: 'Warning', color: red60 }, - ], - }, - { - dataSourceId: 'humidity', - label: 'Humidity', - precision: 0, - unit: '%', - }, - ])} - hotspotThreshold={object('hotspotThreshold', { - dataSourceId: 'humidity', - comparison: '<', - value: 100, - icon: 'Warning', - color: red60, - })} - renderIconByName={(icon, props) => - icon === 'Warning' ? ( - - {props.title} - - ) : null - } - /> - - ); + }, +}; + +export const Basic = () => { + return ( + + + + ); +}; + +Basic.story = { + name: 'basic', +}; + +export const BasicWithUnitsAndPrecision = () => { + return ( + + + + ); +}; + +BasicWithUnitsAndPrecision.story = { + name: 'basic with units and precision', +}; + +export const WithThresholds = () => { + return ( + + ', value: 30, icon: 'Warning', color: red60 }, + ], + }, + { + dataSourceId: 'humidity', + label: 'Humidity', + precision: 0, + unit: '%', + }, + ])} + hotspotThreshold={object('hotspotThreshold', { + dataSourceId: 'humidity', + comparison: '<', + value: 100, + icon: 'Warning', + color: red60, + })} + renderIconByName={(icon, props) => + icon === 'Warning' ? ( + + {props.title} + + ) : null + } + /> + + ); +}; + +WithThresholds.story = { + name: 'with thresholds', + + parameters: { + knobs: { + escapeHTML: false, // needed for greater than less than }, + }, +}; - { - knobs: { - escapeHTML: false, // needed for greater than less than - }, - } - ) - .add( - 'locale', - () => { - return ( - - { + return ( + + ', - value: 30.5, - icon: 'Warning', - color: red60, - }, - ], + comparison: '>', + value: 30.5, + icon: 'Warning', + color: red60, }, - { - dataSourceId: 'humidity', - label: 'Humidity', - precision: 0, - unit: '%', - }, - ])} - locale={select('locale', ['fr', 'en'], 'fr')} - hotspotThreshold={object('hotspotThreshold', { - dataSourceId: 'humidity', - comparison: '<', - value: 100.0, - icon: 'Warning', - color: red60, - })} - renderIconByName={(icon, props) => - icon === 'Warning' ? ( - - {props.title} - - ) : null - } - /> - - ); - }, - { - knobs: { - escapeHTML: false, // needed for greater than less than - }, - } + ], + }, + { + dataSourceId: 'humidity', + label: 'Humidity', + precision: 0, + unit: '%', + }, + ])} + locale={select('locale', ['fr', 'en'], 'fr')} + hotspotThreshold={object('hotspotThreshold', { + dataSourceId: 'humidity', + comparison: '<', + value: 100.0, + icon: 'Warning', + color: red60, + })} + renderIconByName={(icon, props) => + icon === 'Warning' ? ( + + {props.title} + + ) : null + } + /> + ); +}; + +Locale.story = { + name: 'locale', + + parameters: { + knobs: { + escapeHTML: false, // needed for greater than less than + }, + }, +}; diff --git a/src/components/ImageCard/ImageCard.story.jsx b/src/components/ImageCard/ImageCard.story.jsx index 1c8135c778..73c2dfe61b 100644 --- a/src/components/ImageCard/ImageCard.story.jsx +++ b/src/components/ImageCard/ImageCard.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text, select, object, boolean } from '@storybook/addon-knobs'; import omit from 'lodash/omit'; @@ -48,167 +47,200 @@ const values = { ], }; -storiesOf('Watson IoT/ImageCard', module) - .addParameters({ +export default { + title: 'Watson IoT/ImageCard', + + parameters: { component: ImageCard, - }) - .add('basic', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ -
- ); - }) - .add('custom renderIconByName', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- - name === 'arrowDown' ? ( - - {props.title} - - ) : name === 'arrowUp' ? ( - - {props.title} - - ) : ( - Unknown - ) - } - onCardAction={action('onCardAction')} - /> -
- ); - }) - .add('isEditable', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- -
- ); - }) - .add('hotspots are loading', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- -
- ); - }) - .add('error', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- -
- ); - }) - .add('error loading image', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); - return ( -
- -
- ); - }); + }, + ], + })} + breakpoint="lg" + size={size} + onCardAction={action('onCardAction')} + /> +
+ ); +}; + +Basic.story = { + name: 'basic', +}; + +export const CustomRenderIconByName = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ + name === 'arrowDown' ? ( + + {props.title} + + ) : name === 'arrowUp' ? ( + + {props.title} + + ) : ( + Unknown + ) + } + onCardAction={action('onCardAction')} + /> +
+ ); +}; + +CustomRenderIconByName.story = { + name: 'custom renderIconByName', +}; + +export const IsEditable = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ +
+ ); +}; + +IsEditable.story = { + name: 'isEditable', +}; + +export const HotspotsAreLoading = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ +
+ ); +}; + +HotspotsAreLoading.story = { + name: 'hotspots are loading', +}; + +export const Error = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ +
+ ); +}; + +Error.story = { + name: 'error', +}; + +export const ErrorLoadingImage = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.LARGEWIDE); + return ( +
+ +
+ ); +}; + +ErrorLoadingImage.story = { + name: 'error loading image', +}; diff --git a/src/components/ImageCard/ImageHotspots.story.jsx b/src/components/ImageCard/ImageHotspots.story.jsx index 0c6e385ef2..e32fa0c49d 100644 --- a/src/components/ImageCard/ImageHotspots.story.jsx +++ b/src/components/ImageCard/ImageHotspots.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text, object, boolean } from '@storybook/addon-knobs'; import { spacing03 } from '@carbon/layout'; @@ -42,98 +41,116 @@ const componentDescription = 'When the minimap is enabled, it will only appear when the image is being dragged ' + '/ panned, in which it will follow the overlaying panned position.'; -storiesOf('Watson IoT/ImageHotspots', module) - .addParameters({ +export default { + title: 'Watson IoT/ImageHotspots', + + parameters: { component: ImageHotspots, - }) - .addParameters({ info: componentDescription, - }) + }, +}; - .add('landscape image & landscape container', () => { - return ( -
- -
- ); - }) +export const LandscapeImageLandscapeContainer = () => { + return ( +
+ +
+ ); +}; - .add('landscape image & portrait container', () => { - return ( -
- -
- ); - }) +LandscapeImageLandscapeContainer.story = { + name: 'landscape image & landscape container', +}; - .add('portrait image & landscape container', () => { - return ( -
- -
- ); - }) +export const LandscapeImagePortraitContainer = () => { + return ( +
+ +
+ ); +}; - .add('portrait image & portrait container', () => { - return ( -
- -
- ); - }) +LandscapeImagePortraitContainer.story = { + name: 'landscape image & portrait container', +}; - .add( - 'image smaller than card, minimap and zoomcontrols should be hidden', - () => { - return ( -
- -
- ); - } +export const PortraitImageLandscapeContainer = () => { + return ( +
+ +
); +}; + +PortraitImageLandscapeContainer.story = { + name: 'portrait image & landscape container', +}; + +export const PortraitImagePortraitContainer = () => { + return ( +
+ +
+ ); +}; + +PortraitImagePortraitContainer.story = { + name: 'portrait image & portrait container', +}; + +export const ImageSmallerThanCardMinimapAndZoomcontrolsShouldBeHidden = () => { + return ( +
+ +
+ ); +}; + +ImageSmallerThanCardMinimapAndZoomcontrolsShouldBeHidden.story = { + name: 'image smaller than card, minimap and zoomcontrols should be hidden', +}; diff --git a/src/components/List/HierarchyList/HierarchyList.story.jsx b/src/components/List/HierarchyList/HierarchyList.story.jsx index d6190aa40a..7b4a496d20 100644 --- a/src/components/List/HierarchyList/HierarchyList.story.jsx +++ b/src/components/List/HierarchyList/HierarchyList.story.jsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text, select, boolean } from '@storybook/addon-knobs'; import { Add16 } from '@carbon/icons-react'; @@ -22,195 +21,21 @@ const addButton = ( /> ); -storiesOf('Watson IoT Experimental/HierarchyList', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/HierarchyList', + + parameters: { component: HierarchyList, - }) - .add('Stateful list with nested searching', () => ( -
- ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['American League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - secondaryValue: - sampleHierarchy.MLB['American League'][team][player], - }, - isSelectable: true, - })), - }) - ), - ...Object.keys(sampleHierarchy.MLB['National League']).map( - (team) => ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - secondaryValue: - sampleHierarchy.MLB['National League'][team][player], - }, - isSelectable: true, - })), - }) - ), - ]} - hasSearch - pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'sm')} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('With defaultSelectedId', () => ( -
- ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['American League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - }, - isSelectable: true, - })), - }) - ), - ...Object.keys(sampleHierarchy.MLB['National League']).map( - (team) => ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - }, - isSelectable: true, - })), - }) - ), - ]} - hasSearch - pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'lg')} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('With OverflowMenu', () => ( -
- ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['American League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - rowActions: [ - - console.log('Configure')} - /> - console.log('Delete')} - isDelete - hasDivider - /> - , - ], - }, - isSelectable: true, - })), - }) - ), - ...Object.keys(sampleHierarchy.MLB['National League']).map( - (team) => ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - rowActions: [ - - console.log('Configure')} - /> - console.log('Delete')} - isDelete - hasDivider - /> - , - ], - }, - isSelectable: true, - })), - }) - ), - ]} - hasSearch - pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'lg')} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('With Nested Reorder', () => { - const HierarchyListWithReorder = () => { - const [items, setItems] = useState([ + }, +}; + +export const StatefulListWithNestedSearching = () => ( +
+ ({ id: team, isCategory: true, @@ -223,6 +48,8 @@ storiesOf('Watson IoT Experimental/HierarchyList', module) id: `${team}_${player}`, content: { value: player, + secondaryValue: + sampleHierarchy.MLB['American League'][team][player], }, isSelectable: true, })), @@ -239,162 +66,347 @@ storiesOf('Watson IoT Experimental/HierarchyList', module) id: `${team}_${player}`, content: { value: player, + secondaryValue: + sampleHierarchy.MLB['National League'][team][player], }, isSelectable: true, })), })), - ]); + ]} + hasSearch + pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'sm')} + isLoading={boolean('isLoading', false)} + /> +
+); + +StatefulListWithNestedSearching.story = { + name: 'Stateful list with nested searching', +}; - const allowsEdit = boolean('Allow Item Movement', true); +export const WithDefaultSelectedId = () => ( +
+ ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys( + sampleHierarchy.MLB['American League'][team] + ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + }, + isSelectable: true, + })), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys( + sampleHierarchy.MLB['National League'][team] + ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + }, + isSelectable: true, + })), + })), + ]} + hasSearch + pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'lg')} + isLoading={boolean('isLoading', false)} + /> +
+); - return ( -
- { - setItems(updatedItems); - }} - itemWillMove={() => { - return allowsEdit; - }} - /> -
- ); - }; +WithDefaultSelectedId.story = { + name: 'With defaultSelectedId', +}; - return ; - }) - .add('With defaultExpandedIds', () => ( -
- ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['American League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - }, - isSelectable: true, - })), - }) - ), - ...Object.keys(sampleHierarchy.MLB['National League']).map( - (team) => ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - }, - isSelectable: true, - })), - }) - ), - ]} - hasSearch - pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'xl')} - isLoading={boolean('isLoading', false)} - defaultExpandedIds={['Chicago White Sox', 'New York Yankees']} - /> -
- )) - .add('with mixed hierarchies', () => ( -
- ( +
+ ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys( + sampleHierarchy.MLB['American League'][team] + ).map((player) => ({ + id: `${team}_${player}`, content: { - value: 'Tasks', + value: player, + rowActions: [ + + console.log('Configure')} + /> + console.log('Delete')} + isDelete + hasDivider + /> + , + ], }, - children: [ - { - id: 'Task 1', - content: { - value: 'Task 1', - }, - isSelectable: true, - }, - ], + isSelectable: true, + })), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, }, - { - id: 'My Reports', + children: Object.keys( + sampleHierarchy.MLB['National League'][team] + ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + rowActions: [ + + console.log('Configure')} + /> + console.log('Delete')} + isDelete + hasDivider + /> + , + ], + }, + isSelectable: true, + })), + })), + ]} + hasSearch + pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'lg')} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithOverflowMenu.story = { + name: 'With OverflowMenu', +}; + +export const WithNestedReorder = () => { + const HierarchyListWithReorder = () => { + const [items, setItems] = useState([ + ...Object.keys(sampleHierarchy.MLB['American League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys(sampleHierarchy.MLB['American League'][team]).map( + (player) => ({ + id: `${team}_${player}`, content: { - value: 'My Reports', + value: player, }, isSelectable: true, + }) + ), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys(sampleHierarchy.MLB['National League'][team]).map( + (player) => ({ + id: `${team}_${player}`, + content: { + value: player, + }, + isSelectable: true, + }) + ), + })), + ]); + + const allowsEdit = boolean('Allow Item Movement', true); + + return ( +
+ { + setItems(updatedItems); + }} + itemWillMove={() => { + return allowsEdit; + }} + /> +
+ ); + }; + + return ; +}; + +export const WithDefaultExpandedIds = () => ( +
+ ({ + id: team, + isCategory: true, + content: { + value: team, }, - { - id: 'Requests', - isCategory: true, + children: Object.keys( + sampleHierarchy.MLB['American League'][team] + ).map((player) => ({ + id: `${team}_${player}`, content: { - value: 'Requests', + value: player, }, - children: [ - { - id: 'Request 1', - content: { - value: 'Request 1', - }, - isSelectable: true, + isSelectable: true, + })), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys( + sampleHierarchy.MLB['National League'][team] + ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + }, + isSelectable: true, + })), + })), + ]} + hasSearch + pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'xl')} + isLoading={boolean('isLoading', false)} + defaultExpandedIds={['Chicago White Sox', 'New York Yankees']} + /> +
+); + +WithDefaultExpandedIds.story = { + name: 'With defaultExpandedIds', +}; + +export const WithMixedHierarchies = () => ( +
+ -
- )); + isSelectable: true, + }, + ], + }, + ]} + hasSearch + pageSize={select('Page Size', ['sm', 'lg', 'xl'], 'xl')} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithMixedHierarchies.story = { + name: 'with mixed hierarchies', +}; diff --git a/src/components/List/List.story.jsx b/src/components/List/List.story.jsx index b477b57985..263ca3dcbc 100644 --- a/src/components/List/List.story.jsx +++ b/src/components/List/List.story.jsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean, text } from '@storybook/addon-knobs'; import { Add16, Edit16, Star16 } from '@carbon/icons-react'; @@ -127,304 +126,341 @@ const headerButton = ( /> ); -storiesOf('Watson IoT Experimental/List', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/List', + + parameters: { component: List, - }) - .add('basic (single column)', () => ( -
- ({ - id: key, - content: { value: key }, - }))} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('with secondaryValue', () => ( -
- ({ - id: key, - content: { - value: key, - secondaryValue: value, - }, - }))} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('with isLargeRow and icon', () => ( -
- ({ - id: key, - content: { - value: key, - secondaryValue: value, - icon: , - }, - }))} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('with row actions (single)', () => ( -
- ({ - id: key, - content: { - value: key, - secondaryValue: value, - rowActions: [ -
- )) - .add('with row actions (multiple)', () => ( -
- ({ - id: key, - content: { - value: key, - secondaryValue: value, - rowActions: [ - - - - - , - ], - }, - }))} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('with hierarchy', () => ( -
- - level === 1 - ? [ -
- )) - .add('with categories, fixed height', () => ( -
- ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['American League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - secondaryValue: - sampleHierarchy.MLB['American League'][team][player], - }, - })), - }) - ), - ...Object.keys(sampleHierarchy.MLB['National League']).map( - (team) => ({ - id: team, - isCategory: true, - content: { - value: team, - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ - id: `${team}_${player}`, - content: { - value: player, - secondaryValue: - sampleHierarchy.MLB['National League'][team][player], - }, - })), - }) - ), - ]} - expandedIds={['New York Yankees', 'Atlanta Braves']} - isLoading={boolean('isLoading', false)} - /> -
- )) - .add('with empty state', () => ( -
- -
- )) - .add('with checkbox multi-selection', () => { - const MultiSelectList = () => { - const [selectedIds, setSelectedIds] = useState([]); - const [expandedIds, setExpandedIds] = useState([]); - - const searchNestedItems = (items, value, parentMatch) => { - let filteredItems = []; - cloneDeep(items).forEach((item) => { - if (item.id === value) { - filteredItems.push(item.id); - if (item.children) { - const children = searchNestedItems(item.children, item.id, true); - filteredItems = filteredItems.concat(children); - } - } else if (parentMatch) { - filteredItems.push(item.id); - } - }); - return filteredItems; - }; - - const handleCheckboxChange = (event, items, id) => { - // If checked, add to selectedIds - if (event.target.checked) { - // find item and children being changed - const nestedIds = searchNestedItems(items, id); - let tempSelectedIds = [...selectedIds]; - if (nestedIds.length > 0) { - tempSelectedIds = tempSelectedIds.concat(nestedIds); - } else { - tempSelectedIds.push(id); - } - setSelectedIds(tempSelectedIds); - } // If unchecked, remove from selectedIds - else { - // find children - const deselectedNestedIds = searchNestedItems(items, id); - let tempSelectedIds = [...selectedIds]; - if (deselectedNestedIds.length === 0) { - deselectedNestedIds.push(id); - } - deselectedNestedIds.forEach((deselectedId) => { - tempSelectedIds = tempSelectedIds.filter( - (id) => id !== deselectedId - ); - }); - setSelectedIds(tempSelectedIds); - } - }; + }, + + excludeStories: ['sampleHierarchy'], +}; + +export const BasicSingleColumn = () => ( +
+ ({ + id: key, + content: { value: key }, + }))} + isLoading={boolean('isLoading', false)} + /> +
+); - const checkSelectedChildren = (items, parent) => - someDeep(items, (value, key) => - selectedIds.some((id) => `${parent}-${key}` === id) - ); +BasicSingleColumn.story = { + name: 'basic (single column)', +}; - const nestedItems = [ +export const WithSecondaryValue = () => ( +
+ ({ + id: key, + content: { + value: key, + secondaryValue: value, + }, + }))} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithSecondaryValue.story = { + name: 'with secondaryValue', +}; + +export const WithIsLargeRowAndIcon = () => ( +
+ ({ + id: key, + content: { + value: key, + secondaryValue: value, + icon: , + }, + }))} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithIsLargeRowAndIcon.story = { + name: 'with isLargeRow and icon', +}; + +export const WithRowActionsSingle = () => ( +
+ ({ + id: key, + content: { + value: key, + secondaryValue: value, + rowActions: [ +
+); + +WithRowActionsSingle.story = { + name: 'with row actions (single)', +}; + +export const WithRowActionsMultiple = () => ( +
+ ({ + id: key, + content: { + value: key, + secondaryValue: value, + rowActions: [ + + + + + , + ], + }, + }))} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithRowActionsMultiple.story = { + name: 'with row actions (multiple)', +}; + +export const WithHierarchy = () => ( +
+ + level === 1 + ? [ +
+); + +WithHierarchy.story = { + name: 'with hierarchy', +}; + +export const WithCategoriesFixedHeight = () => ( +
+ ({ id: team, isCategory: true, - isSelectable: true, content: { value: team, - icon: ( - handleCheckboxChange(e, nestedItems, team)} - checked={selectedIds.some((id) => team === id)} - indeterminate={ - selectedIds.some((id) => team === id) - ? false - : checkSelectedChildren( - sampleHierarchy.MLB['American League'][team], - team - ) - } - /> - ), }, children: Object.keys( sampleHierarchy.MLB['American League'][team] ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + secondaryValue: + sampleHierarchy.MLB['American League'][team][player], + }, + })), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + content: { + value: team, + }, + children: Object.keys( + sampleHierarchy.MLB['National League'][team] + ).map((player) => ({ + id: `${team}_${player}`, + content: { + value: player, + secondaryValue: + sampleHierarchy.MLB['National League'][team][player], + }, + })), + })), + ]} + expandedIds={['New York Yankees', 'Atlanta Braves']} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithCategoriesFixedHeight.story = { + name: 'with categories, fixed height', +}; + +export const WithEmptyState = () => ( +
+ +
+); + +WithEmptyState.story = { + name: 'with empty state', +}; + +export const WithCheckboxMultiSelection = () => { + const MultiSelectList = () => { + const [selectedIds, setSelectedIds] = useState([]); + const [expandedIds, setExpandedIds] = useState([]); + + const searchNestedItems = (items, value, parentMatch) => { + let filteredItems = []; + cloneDeep(items).forEach((item) => { + if (item.id === value) { + filteredItems.push(item.id); + if (item.children) { + const children = searchNestedItems(item.children, item.id, true); + filteredItems = filteredItems.concat(children); + } + } else if (parentMatch) { + filteredItems.push(item.id); + } + }); + return filteredItems; + }; + + const handleCheckboxChange = (event, items, id) => { + // If checked, add to selectedIds + if (event.target.checked) { + // find item and children being changed + const nestedIds = searchNestedItems(items, id); + let tempSelectedIds = [...selectedIds]; + if (nestedIds.length > 0) { + tempSelectedIds = tempSelectedIds.concat(nestedIds); + } else { + tempSelectedIds.push(id); + } + setSelectedIds(tempSelectedIds); + } // If unchecked, remove from selectedIds + else { + // find children + const deselectedNestedIds = searchNestedItems(items, id); + let tempSelectedIds = [...selectedIds]; + if (deselectedNestedIds.length === 0) { + deselectedNestedIds.push(id); + } + deselectedNestedIds.forEach((deselectedId) => { + tempSelectedIds = tempSelectedIds.filter((id) => id !== deselectedId); + }); + setSelectedIds(tempSelectedIds); + } + }; + + const checkSelectedChildren = (items, parent) => + someDeep(items, (value, key) => + selectedIds.some((id) => `${parent}-${key}` === id) + ); + + const nestedItems = [ + ...Object.keys(sampleHierarchy.MLB['American League']).map((team) => ({ + id: team, + isCategory: true, + isSelectable: true, + content: { + value: team, + icon: ( + handleCheckboxChange(e, nestedItems, team)} + checked={selectedIds.some((id) => team === id)} + indeterminate={ + selectedIds.some((id) => team === id) + ? false + : checkSelectedChildren( + sampleHierarchy.MLB['American League'][team], + team + ) + } + /> + ), + }, + children: Object.keys(sampleHierarchy.MLB['American League'][team]).map( + (player) => ({ id: `${team}-${player}`, isSelectable: true, content: { @@ -443,35 +479,35 @@ storiesOf('Watson IoT Experimental/List', module) /> ), }, - })), - })), - ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ - id: team, - isCategory: true, - isSelectable: true, - content: { - value: team, - icon: ( - handleCheckboxChange(e, nestedItems, team)} - checked={selectedIds.some((id) => team === id)} - indeterminate={ - selectedIds.some((id) => team === id) - ? false - : checkSelectedChildren( - sampleHierarchy.MLB['National League'][team], - team - ) - } - /> - ), - }, - children: Object.keys( - sampleHierarchy.MLB['National League'][team] - ).map((player) => ({ + }) + ), + })), + ...Object.keys(sampleHierarchy.MLB['National League']).map((team) => ({ + id: team, + isCategory: true, + isSelectable: true, + content: { + value: team, + icon: ( + handleCheckboxChange(e, nestedItems, team)} + checked={selectedIds.some((id) => team === id)} + indeterminate={ + selectedIds.some((id) => team === id) + ? false + : checkSelectedChildren( + sampleHierarchy.MLB['National League'][team], + team + ) + } + /> + ), + }, + children: Object.keys(sampleHierarchy.MLB['National League'][team]).map( + (player) => ({ id: `${team}-${player}`, isSelectable: true, content: { @@ -490,52 +526,62 @@ storiesOf('Watson IoT Experimental/List', module) /> ), }, - })), - })), - ]; - - return ( -
- { - if (expandedIds.filter((rowId) => rowId === id).length > 0) { - // remove id from array - setExpandedIds(expandedIds.filter((rowId) => rowId !== id)); - } else { - setExpandedIds(expandedIds.concat([id])); - } - }} - isLoading={boolean('isLoading', false)} - /> -
- ); - }; - return ; - }) - .add('with tags', () => ( -
- ({ - id: key, - content: { - value: key, - tags: [ - - default - , - ], - }, - }))} - isLoading={boolean('isLoading', false)} - /> -
- )); + }) + ), + })), + ]; + + return ( +
+ { + if (expandedIds.filter((rowId) => rowId === id).length > 0) { + // remove id from array + setExpandedIds(expandedIds.filter((rowId) => rowId !== id)); + } else { + setExpandedIds(expandedIds.concat([id])); + } + }} + isLoading={boolean('isLoading', false)} + /> +
+ ); + }; + return ; +}; + +WithCheckboxMultiSelection.story = { + name: 'with checkbox multi-selection', +}; + +export const WithTags = () => ( +
+ ({ + id: key, + content: { + value: key, + tags: [ + + default + , + ], + }, + }))} + isLoading={boolean('isLoading', false)} + /> +
+); + +WithTags.story = { + name: 'with tags', +}; diff --git a/src/components/List/ListItem/ListItem.story.jsx b/src/components/List/ListItem/ListItem.story.jsx index 19ec34a6ff..ebbe976cfe 100644 --- a/src/components/List/ListItem/ListItem.story.jsx +++ b/src/components/List/ListItem/ListItem.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text, select, boolean } from '@storybook/addon-knobs'; import { Edit16, Star16, StarFilled16 } from '@carbon/icons-react'; @@ -23,291 +22,361 @@ const dndProps = { itemWillMove: identity, }; -storiesOf('Watson IoT Experimental/ListItem', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/ListItem', + + parameters: { component: ListItem, - }) - .add('basic w/ knobs', () => { - const value = text('value', 'List Item'); - const secondaryValue = text('secondaryValue', undefined); - const iconName = select('icon', ['none', 'Star16', 'StarFilled16']); - const iconComponent = - iconName === 'Star16' - ? Star16 - : iconName === 'StarFilled16' - ? StarFilled16 - : null; - const rowActionSet = select( - 'row action example', - ['none', 'single', 'multi'], - 'none' - ); - const tagsConfig = select( - 'tags example', - ['none', 'single', 'multi'], - 'none' - ); - - const rowActionComponent = - rowActionSet === 'single' - ? [ -
- )) - .add('with OverflowMenu row actions', () => ( -
- - - - - + ] + : rowActionSet === 'multi' + ? [ + + + + , - ]} - /> -
- )) - .add('with Tags', () => ( -
- + default + , + ] + : tagsConfig === 'multi' + ? [ default , disabled tag , - ]} + ] + : undefined; + return ( +
+
- )); + ); +}; + +BasicWKnobs.story = { + name: 'basic w/ knobs', +}; + +export const WithValue = () => ( +
+ +
+); + +WithValue.story = { + name: 'with value', +}; + +export const WithSecondaryValue = () => ( +
+ +
+); + +WithSecondaryValue.story = { + name: 'with secondaryValue', +}; + +export const TestingSecondaryValueOverflow = () => ( +
+ +
+); + +TestingSecondaryValueOverflow.story = { + name: 'testing secondaryValue overflow', +}; + +export const WithIcon = () => ( +
+ } + iconPosition={select('iconPosition', ['left', 'right'])} + /> +
+); + +WithIcon.story = { + name: 'with icon', +}; + +export const WithIsSelectable = () => ( +
+ +
+); + +WithIsSelectable.story = { + name: 'with isSelectable', +}; + +export const WithIsLargeRow = () => ( +
+ +
+); + +WithIsLargeRow.story = { + name: 'with isLargeRow', +}; + +export const TestingIsLargeRowOverflow = () => ( +
+ +
+); + +TestingIsLargeRowOverflow.story = { + name: 'testing isLargeRow overflow', +}; + +export const WithIsExpandable = () => ( +
+ +
+); + +WithIsExpandable.story = { + name: 'with isExpandable', +}; + +export const WithIsCategory = () => ( +
+ +
+); + +WithIsCategory.story = { + name: 'with isCategory', +}; + +export const WithSingleRowAction = () => ( +
+ action('row action clicked')} + iconDescription="Edit" + />, + ]} + /> +
+); + +WithSingleRowAction.story = { + name: 'with single row action', +}; + +export const WithDisabled = () => ( +
+ action('row action clicked')} + iconDescription="Edit" + />, + ]} + /> +
+); + +WithDisabled.story = { + name: 'with disabled', +}; + +export const WithOverflowMenuRowActions = () => ( +
+ + + + + + , + ]} + /> +
+); + +WithOverflowMenuRowActions.story = { + name: 'with OverflowMenu row actions', +}; + +export const WithTags = () => ( +
+ + default + , + + disabled tag + , + ]} + /> +
+); + +WithTags.story = { + name: 'with Tags', +}; diff --git a/src/components/List/SimpleList/SimpleList.story.jsx b/src/components/List/SimpleList/SimpleList.story.jsx index 69e96754e2..4c89622b27 100644 --- a/src/components/List/SimpleList/SimpleList.story.jsx +++ b/src/components/List/SimpleList/SimpleList.story.jsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { Add16, Close16, Edit16 } from '@carbon/icons-react'; -import { storiesOf } from '@storybook/react'; import { boolean, select, text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import { spacing03 } from '@carbon/layout'; @@ -129,332 +128,369 @@ const buttonsToRender = [ />, ]; -storiesOf('Watson IoT Experimental/SimpleList', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/SimpleList', + + parameters: { component: SimpleList, - }) - .add( - 'basic', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItems(30)} - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'tall list (isFullHeight = true)', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItems(3)} - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'tall list (isFullHeight = false)', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItems(3)} - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with overflow grow', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItems(20)} - pageSize="xl" - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with pageSize', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItems(20)} - pageSize="sm" - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with empty row', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={listItemsWithEmptyRow} - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with large row', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getFatRowListItems(20)} - pageSize="sm" - isLargeRow - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with multiple actions', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItemsWithActions(5)} - pageSize="sm" - isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add( - 'list with overflow menu', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getListItemsWithOverflowMenu(5)} - pageSize="sm" - isLoading={boolean('isLoading', false)} - /> -
- )) - ) + }, - .add( - 'large row list with multiple actions', - withReadme(SimpleListREADME, () => ( -
- `Page ${pageNumber}`, - }} - buttons={buttonsToRender} - items={getFatRowListItemsWithActions(5)} - pageSize="sm" - isLargeRow - isLoading={boolean('isLoading', false)} - /> -
- )) + excludeStories: ['getListItems'], +}; + +export const Basic = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItems(30)} + isLoading={boolean('isLoading', false)} + /> +
+)); + +Basic.story = { + name: 'basic', +}; + +export const TallListIsFullHeightTrue = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItems(3)} + isLoading={boolean('isLoading', false)} + /> +
+)); + +TallListIsFullHeightTrue.story = { + name: 'tall list (isFullHeight = true)', +}; + +export const TallListIsFullHeightFalse = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItems(3)} + isLoading={boolean('isLoading', false)} + /> +
+)); + +TallListIsFullHeightFalse.story = { + name: 'tall list (isFullHeight = false)', +}; + +export const ListWithOverflowGrow = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItems(20)} + pageSize="xl" + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithOverflowGrow.story = { + name: 'list with overflow grow', +}; + +export const ListWithPageSize = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItems(20)} + pageSize="sm" + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithPageSize.story = { + name: 'list with pageSize', +}; + +export const ListWithEmptyRow = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={listItemsWithEmptyRow} + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithEmptyRow.story = { + name: 'list with empty row', +}; + +export const ListWithLargeRow = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getFatRowListItems(20)} + pageSize="sm" + isLargeRow + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithLargeRow.story = { + name: 'list with large row', +}; + +export const ListWithMultipleActions = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItemsWithActions(5)} + pageSize="sm" + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithMultipleActions.story = { + name: 'list with multiple actions', +}; + +export const ListWithOverflowMenu = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getListItemsWithOverflowMenu(5)} + pageSize="sm" + isLoading={boolean('isLoading', false)} + /> +
+)); + +ListWithOverflowMenu.story = { + name: 'list with overflow menu', +}; + +export const LargeRowListWithMultipleActions = withReadme( + SimpleListREADME, + () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getFatRowListItemsWithActions(5)} + pageSize="sm" + isLargeRow + isLoading={boolean('isLoading', false)} + /> +
) - .add( - 'large row list with overflow menu', - withReadme(SimpleListREADME, () => ( -
+); + +LargeRowListWithMultipleActions.story = { + name: 'large row list with multiple actions', +}; + +export const LargeRowListWithOverflowMenu = withReadme(SimpleListREADME, () => ( +
+ `Page ${pageNumber}`, + }} + buttons={buttonsToRender} + items={getFatRowListItemsWithOverflowMenu(5)} + pageSize="sm" + isLargeRow + isLoading={boolean('isLoading', false)} + /> +
+)); + +LargeRowListWithOverflowMenu.story = { + name: 'large row list with overflow menu', +}; + +export const ListWithReorder = () => { + const SimpleListWithReorder = () => { + const [items, setItems] = useState(getListItems(15)); + + return ( +
`Page ${pageNumber}`, + items: '%d items', }} buttons={buttonsToRender} - items={getFatRowListItemsWithOverflowMenu(5)} - pageSize="sm" - isLargeRow + items={items} isLoading={boolean('isLoading', false)} - /> -
- )) - ) - .add('list with reorder', () => { - const SimpleListWithReorder = () => { - const [items, setItems] = useState(getListItems(15)); - - return ( -
- `Page ${pageNumber}`, - items: '%d items', - }} - buttons={buttonsToRender} - items={items} - isLoading={boolean('isLoading', false)} - editingStyle={select( - 'Editing Style', - [EditingStyle.Single, EditingStyle.Multiple], - EditingStyle.Single - )} - onListUpdated={(updatedItems) => { - setItems(updatedItems); - }} - /> -
- ); - }; - - return ; - }) - .add( - 'hidden pagination', - withReadme(SimpleListREADME, () => ( -
- { + setItems(updatedItems); }} - buttons={buttonsToRender} - items={getListItems(5)} - isLoading={boolean('isLoading', false)} - hasPagination={false} />
- )), - { - info: { - text: `Optionally hide the pagination by passing 'hasPagination: false'`, - }, - } - ); + ); + }; + + return ; +}; + +ListWithReorder.story = { + name: 'list with reorder', +}; + +export const HiddenPagination = withReadme(SimpleListREADME, () => ( +
+ +
+)); + +HiddenPagination.story = { + name: 'hidden pagination', + + parameters: { + info: { + text: `Optionally hide the pagination by passing 'hasPagination: false'`, + }, + }, +}; diff --git a/src/components/ListCard/ListCard.story.jsx b/src/components/ListCard/ListCard.story.jsx index 6c48b15d57..c550f0abe6 100644 --- a/src/components/ListCard/ListCard.story.jsx +++ b/src/components/ListCard/ListCard.story.jsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import { storiesOf } from '@storybook/react'; import { text, select } from '@storybook/addon-knobs'; import { spacing06 } from '@carbon/layout'; @@ -279,55 +278,78 @@ class ListCardExtraContentLong extends Component { }; } -storiesOf('Watson IoT Experimental/ListCard', module) - .addParameters({ +export default { + title: 'Watson IoT Experimental/ListCard', + + parameters: { component: ListCard, - }) - .add('basic', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + }, +}; - return ( - - ); - }) - .add('with extra content', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); +export const Basic = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( - - ); - }) - .add('with extra long content', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + return ( + + ); +}; - return ( - { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + + return ( + + ); +}; + +WithExtraContent.story = { + name: 'with extra content', +}; + +export const WithExtraLongContent = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUMWIDE); + + return ( + + ); +}; + +WithExtraLongContent.story = { + name: 'with extra long content', +}; + +export const Empty = () => { + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + + return ( +
+ {}} /> - ); - }) - .add('empty', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); +
+ ); +}; - return ( -
- {}} - /> -
- ); - }); +Empty.story = { + name: 'empty', +}; diff --git a/src/components/MultiSelect/MultiSelect.story.jsx b/src/components/MultiSelect/MultiSelect.story.jsx index 2a73dc702c..ff59df2efb 100644 --- a/src/components/MultiSelect/MultiSelect.story.jsx +++ b/src/components/MultiSelect/MultiSelect.story.jsx @@ -8,7 +8,6 @@ */ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, @@ -90,74 +89,83 @@ const props = () => ({ ), }); -storiesOf('MultiSelect', module) - .addParameters({ +export default { + title: 'MultiSelect', + decorators: [withKnobs], + + parameters: { component: MultiSelect, - }) - .addDecorator(withKnobs) - .add( - 'default', - withReadme(readme, () => { - const { - filterable, - listBoxMenuIconTranslationIds, - selectionFeedback, - ...multiSelectProps - } = props(); - const ComponentToUse = !filterable ? MultiSelect : MultiSelect.Filterable; - const placeholder = !filterable ? undefined : defaultPlaceholder; - return ( -
- (item ? item.text : '')} - placeholder={placeholder} - translateWithId={(id) => listBoxMenuIconTranslationIds[id]} - selectionFeedback={selectionFeedback} - /> -
- ); - }), - { - info: { - text: ` - MultiSelect - `, - }, - } - ) - .add( - 'with initial selected items', - withReadme(readme, () => { - const { - filterable, - listBoxMenuIconTranslationIds, - selectionFeedback, - ...multiSelectProps - } = props(); - const ComponentToUse = !filterable ? MultiSelect : MultiSelect.Filterable; - const placeholder = !filterable ? undefined : defaultPlaceholder; - - return ( -
- (item ? item.text : '')} - initialSelectedItems={[items[0], items[1]]} - placeholder={placeholder} - translateWithId={(id) => listBoxMenuIconTranslationIds[id]} - selectionFeedback={selectionFeedback} - /> -
- ); - }), - { - info: { - text: ` - Provide a set of items to initially select in the control - `, - }, - } + }, +}; + +export const Default = withReadme(readme, () => { + const { + filterable, + listBoxMenuIconTranslationIds, + selectionFeedback, + ...multiSelectProps + } = props(); + const ComponentToUse = !filterable ? MultiSelect : MultiSelect.Filterable; + const placeholder = !filterable ? undefined : defaultPlaceholder; + return ( +
+ (item ? item.text : '')} + placeholder={placeholder} + translateWithId={(id) => listBoxMenuIconTranslationIds[id]} + selectionFeedback={selectionFeedback} + /> +
); +}); + +Default.story = { + name: 'default', + + parameters: { + info: { + text: ` + MultiSelect + `, + }, + }, +}; + +export const WithInitialSelectedItems = withReadme(readme, () => { + const { + filterable, + listBoxMenuIconTranslationIds, + selectionFeedback, + ...multiSelectProps + } = props(); + const ComponentToUse = !filterable ? MultiSelect : MultiSelect.Filterable; + const placeholder = !filterable ? undefined : defaultPlaceholder; + + return ( +
+ (item ? item.text : '')} + initialSelectedItems={[items[0], items[1]]} + placeholder={placeholder} + translateWithId={(id) => listBoxMenuIconTranslationIds[id]} + selectionFeedback={selectionFeedback} + /> +
+ ); +}); + +WithInitialSelectedItems.story = { + name: 'with initial selected items', + + parameters: { + info: { + text: ` + Provide a set of items to initially select in the control + `, + }, + }, +}; diff --git a/src/components/NavigationBar/NavigationBar.story.jsx b/src/components/NavigationBar/NavigationBar.story.jsx index f5023e7ff0..02b336c540 100644 --- a/src/components/NavigationBar/NavigationBar.story.jsx +++ b/src/components/NavigationBar/NavigationBar.story.jsx @@ -1,5 +1,4 @@ import React, { Fragment, useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import styled from 'styled-components'; @@ -66,30 +65,53 @@ const StatefulNavigationBar = () => { ); }; -storiesOf('Watson IoT/NavigationBar', module) - .addParameters({ +export default { + title: 'Watson IoT/NavigationBar', + + parameters: { component: NavigationBar, - }) - .add('normal', () => ) - .add('start with tab 2 selected', () => ( - - )) - .add('with actions', () => ( - - )) - .add('example with workArea', () => ); + }, +}; + +export const Normal = () => ; + +Normal.story = { + name: 'normal', +}; + +export const StartWithTab2Selected = () => ( + +); + +StartWithTab2Selected.story = { + name: 'start with tab 2 selected', +}; + +export const WithActions = () => ( + +); + +WithActions.story = { + name: 'with actions', +}; + +export const ExampleWithWorkArea = () => ; + +ExampleWithWorkArea.story = { + name: 'example with workArea', +}; diff --git a/src/components/OverflowMenu/OverflowMenu.story.jsx b/src/components/OverflowMenu/OverflowMenu.story.jsx index e038b4d3c2..f07f6c4e37 100644 --- a/src/components/OverflowMenu/OverflowMenu.story.jsx +++ b/src/components/OverflowMenu/OverflowMenu.story.jsx @@ -8,7 +8,6 @@ */ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, boolean, select, text } from '@storybook/addon-knobs'; import { withReadme } from 'storybook-readme'; @@ -66,68 +65,80 @@ const OverflowMenuExample = ({ overflowMenuProps, overflowMenuItemProps }) => ( ); -storiesOf('OverflowMenu', module) - .addParameters({ +export default { + title: 'OverflowMenu', + decorators: [withKnobs], + + parameters: { component: OverflowMenu, - }) - .addDecorator(withKnobs) - .add( - 'basic', - withReadme(OverflowREADME, () => ( - - )), - { - info: { - text: ` - Overflow Menu is used when additional options are available to the user and there is a space constraint. - Create Overflow Menu Item components for each option on the menu. - `, - }, - } - ) - .add( - 'with links', - withReadme(OverflowREADME, () => ( - - )), - { - info: { - text: ` - Overflow Menu is used when additional options are available to the user and there is a space constraint. - Create Overflow Menu Item components for each option on the menu. - - When given \`href\` props, menu items render as tags to facilitate usability. - `, - }, - } - ) - .add( - 'custom trigger', - withReadme(OverflowREADME, () => ( -
Menu
, - }} - overflowMenuItemProps={props.menuItem()} - /> - )), - { - info: { - text: ` - Sometimes you just want to render something other than an icon - `, - }, - } - ); + }, +}; + +export const Basic = withReadme(OverflowREADME, () => ( + +)); + +Basic.story = { + name: 'basic', + + parameters: { + info: { + text: ` + Overflow Menu is used when additional options are available to the user and there is a space constraint. + Create Overflow Menu Item components for each option on the menu. + `, + }, + }, +}; + +export const WithLinks = withReadme(OverflowREADME, () => ( + +)); + +WithLinks.story = { + name: 'with links', + + parameters: { + info: { + text: ` + Overflow Menu is used when additional options are available to the user and there is a space constraint. + Create Overflow Menu Item components for each option on the menu. + + When given \`href\` props, menu items render as
tags to facilitate usability. + `, + }, + }, +}; + +export const CustomTrigger = withReadme(OverflowREADME, () => ( +
Menu
, + }} + overflowMenuItemProps={props.menuItem()} + /> +)); + +CustomTrigger.story = { + name: 'custom trigger', + + parameters: { + info: { + text: ` + Sometimes you just want to render something other than an icon + `, + }, + }, +}; diff --git a/src/components/Page/EditPage.story.jsx b/src/components/Page/EditPage.story.jsx index 3b8ee41a56..60ffc9e6be 100644 --- a/src/components/Page/EditPage.story.jsx +++ b/src/components/Page/EditPage.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text } from '@storybook/addon-knobs'; @@ -21,22 +20,39 @@ const breadcrumb = [
Type, Instance, ]; -storiesOf('Watson IoT/EditPage (Deprecated)', module) - .addDecorator((storyFn) => {storyFn()}) - .add(deprecatedStoryTitle, () => ( - - )) - .add('normal', () => ) - .add('isLoading', () => ) - .add('with blurb', () => ( - - )) - .add('with breadcrumb', () => ( - - )); + +export default { + title: 'Watson IoT/EditPage (Deprecated)', + decorators: [(storyFn) => {storyFn()}], +}; + +export const Normal = () => ; + +Normal.story = { + name: 'normal', +}; + +export const IsLoading = () => ; + +IsLoading.story = { + name: 'isLoading', +}; + +export const WithBlurb = () => ( + +); + +WithBlurb.story = { + name: 'with blurb', +}; + +export const WithBreadcrumb = () => ( + +); + +WithBreadcrumb.story = { + name: 'with breadcrumb', +}; diff --git a/src/components/Page/PageHero.story.jsx b/src/components/Page/PageHero.story.jsx index 4edcfd9369..4b2ea960b3 100644 --- a/src/components/Page/PageHero.story.jsx +++ b/src/components/Page/PageHero.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import FullWidthWrapper from '../../internal/FullWidthWrapper'; @@ -17,40 +16,61 @@ const commonPageHeroProps = { rightContent:
Right Content
, }; -storiesOf('Watson IoT/PageHero (Deprecated)', module) - .addDecorator((storyFn) => {storyFn()}) - .add(deprecatedStoryTitle, () => ( - - )) - .add('normal', () => ) - .add('normal with content switcher', () => ( - - )) - .add('with section', () => ( - - )) - .add('has breadcrumb', () => ( - breadcrumb/mybread
} /> - )) - .add('has left content', () => ( - Left Content
} /> - )); +export default { + title: 'Watson IoT/PageHero (Deprecated)', + decorators: [(storyFn) => {storyFn()}], +}; + +export const Normal = () => ; + +Normal.story = { + name: 'normal', +}; + +export const NormalWithContentSwitcher = () => ( + +); + +NormalWithContentSwitcher.story = { + name: 'normal with content switcher', +}; + +export const WithSection = () => ( + +); + +WithSection.story = { + name: 'with section', +}; + +export const HasBreadcrumb = () => ( + breadcrumb/mybread
} /> +); + +HasBreadcrumb.story = { + name: 'has breadcrumb', +}; + +export const HasLeftContent = () => ( + Left Content} /> +); + +HasLeftContent.story = { + name: 'has left content', +}; diff --git a/src/components/Page/__snapshots__/EditPage.story.storyshot b/src/components/Page/__snapshots__/EditPage.story.storyshot index 9c52393041..5bb2cef25f 100644 --- a/src/components/Page/__snapshots__/EditPage.story.storyshot +++ b/src/components/Page/__snapshots__/EditPage.story.storyshot @@ -472,100 +472,3 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/EditP `; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/EditPage (Deprecated) ️⛔ Deprecation Notice 1`] = ` -
-
-
-
- - - - - warning icon - - -
-

- Deprecation Notice -

-
- EditPage has been deprecated and will be removed in the next major version of carbon-addons-iot-react. -
-
- Refactor usages of EditPage to use PageWizard instead. -
-
-
-
-
- -
-`; diff --git a/src/components/Page/__snapshots__/PageHero.story.storyshot b/src/components/Page/__snapshots__/PageHero.story.storyshot index 3b6594fe83..1ab4c8b2ae 100644 --- a/src/components/Page/__snapshots__/PageHero.story.storyshot +++ b/src/components/Page/__snapshots__/PageHero.story.storyshot @@ -447,100 +447,3 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/PageH `; - -exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/PageHero (Deprecated) ️⛔ Deprecation Notice 1`] = ` -
-
-
-
- - - - - warning icon - - -
-

- Deprecation Notice -

-
- PageHero has been deprecated and will be removed in the next major version of carbon-addons-iot-react. -
-
- Refactor usages of PageHero to use Hero instead. -
-
-
-
-
- -
-`; diff --git a/src/components/PageTitleBar/PageTitleBar.story.jsx b/src/components/PageTitleBar/PageTitleBar.story.jsx index c6499a0975..f504da4608 100644 --- a/src/components/PageTitleBar/PageTitleBar.story.jsx +++ b/src/components/PageTitleBar/PageTitleBar.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { Add24, TrashCan24 } from '@carbon/icons-react'; import { spacing05 } from '@carbon/layout'; @@ -55,123 +54,179 @@ export const PageTitleBarNodeTooltip = () => ( ); -storiesOf('Watson IoT/PageTitleBar', module) - .addParameters({ +export default { + title: 'Watson IoT/PageTitleBar', + decorators: [(storyFn) => {storyFn()}], + + parameters: { component: PageTitleBar, - }) - .addDecorator((storyFn) => {storyFn()}) - .add('base', () => ) - .add('with breadcrumb', () => ( - - )) - .add('with description', () => ( - - )) - .add('with tooltip description with node', () => ( - } - breadcrumb={pageTitleBarBreadcrumb} - collapsed - /> - )) - .add('with content', () => ( - - -
Content for first tab.
-
- -
Content for second tab.
-
- -
Content for third tab.
-
- - } - /> - )) - .add('with editable title bar', () => ( - - )) - .add('with rich content', () => ( - - )) - .add('with everything', () => ( - -
- Last updated: yesterday -
-
- -
+ }, + + excludeStories: [ + 'commonPageTitleBarProps', + 'pageTitleBarBreadcrumb', + 'PageTitleBarNodeTooltip', + ], +}; + +export const Base = () => ( + +); + +Base.story = { + name: 'base', +}; + +export const WithBreadcrumb = () => ( + +); + +WithBreadcrumb.story = { + name: 'with breadcrumb', +}; + +export const WithDescription = () => ( + +); + +WithDescription.story = { + name: 'with description', +}; + +export const WithTooltipDescriptionWithNode = () => ( + } + breadcrumb={pageTitleBarBreadcrumb} + collapsed + /> +); + +WithTooltipDescriptionWithNode.story = { + name: 'with tooltip description with node', +}; + +export const WithContent = () => ( + + +
Content for first tab.
+
+ +
Content for second tab.
+
+ +
Content for third tab.
+
+ + } + /> +); + +WithContent.story = { + name: 'with content', +}; + +export const WithEditableTitleBar = () => ( + +); + +WithEditableTitleBar.story = { + name: 'with editable title bar', +}; + +export const WithRichContent = () => ( + +); + +WithRichContent.story = { + name: 'with rich content', +}; + +export const WithEverything = () => ( + +
+ Last updated: yesterday +
+
+
- } - editable - content={ - - -
Content for first tab.
-
- -
Content for second tab.
-
- -
Content for third tab.
-
-
- } - onEdit={action('edit')} - /> - )) - .add('isLoading', () => ( - - )); + + } + editable + content={ + + +
Content for first tab.
+
+ +
Content for second tab.
+
+ +
Content for third tab.
+
+
+ } + onEdit={action('edit')} + /> +); + +WithEverything.story = { + name: 'with everything', +}; + +export const IsLoading = () => ( + +); + +IsLoading.story = { + name: 'isLoading', +}; diff --git a/src/components/PageWizard/PageWizard.story.jsx b/src/components/PageWizard/PageWizard.story.jsx index bfa0159a58..7b6a1928bf 100644 --- a/src/components/PageWizard/PageWizard.story.jsx +++ b/src/components/PageWizard/PageWizard.story.jsx @@ -1,6 +1,5 @@ /* Used dependencies */ import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean, text } from '@storybook/addon-knobs'; import { @@ -317,203 +316,243 @@ export const StepValidationWizard = ({ ...props }) => { ); }; -storiesOf('Watson IoT/PageWizard', module) - .addParameters({ +export default { + title: 'Watson IoT/PageWizard', + + parameters: { component: PageWizard, - }) - .add('stateful example', () => ( -
- {})} - onSubmit={action('submit', () => {})} - onClearError={action('Clear error', () => {})} - onNext={action('next', () => {})} - onBack={action('back', () => {})} - setStep={action('step clicked', () => {})} - isClickable> - {content} - -
- )) - .add('stateful example w/ validation in PageTitleBar', () => ( -
- Home, - Something, - Something Else, - ]} - content={} - /> -
- )) - .add('wrapped in PageTitleBar', () => ( -
- Home, - Something, - Something Else, - ]} - content={ - {})} - onSubmit={action('submit', () => {})} - onClearError={action('Clear error', () => {})} - onNext={action('next', () => {})} - onBack={action('back', () => {})} - setStep={action('step clicked', () => {})} - isProgressIndicatorVertical={boolean( - 'Toggle Progress Indicator Alignment', - true - )}> - {content} - - } - /> -
- )) - .add('With Horizontal ProgressIndicator', () => ( -
- {})} - onSubmit={action('submit', () => {})} - onNext={action('next', () => {})} - onBack={action('back', () => {})} - setStep={action('step clicked', () => {})} - onClearError={action('Clear error', () => {})} - isProgressIndicatorVertical={boolean( - 'Toggle Progress Indicator Alignment', - false - )} - isClickable> - {content} - -
- )) - .add('only one step, in PageTitleBar', () => ( -
- Home, - Something, - Something Else, - ]} - content={ - {})} - onSubmit={action('submit', () => {})} - onNext={action('next', () => {})} - onBack={action('back', () => {})} - onClearError={() => {}} - setStep={action('step clicked', () => {})} - sendingData={boolean('sendingData', false)} - hasStickyFooter={boolean('hasStickyFooter', false)}> - {[content[0]]} - - } - /> -
- )) - .add( - 'With Sticky Footer: stateful example w/ validation in PageTitleBar', - () => ( -
- Home, - Something, - Something Else, - ]} - content={ - + }, + + excludeStories: ['content', 'StepValidationWizard', 'StepValidation'], +}; + +export const StatefulExample = () => ( +
+ {})} + onSubmit={action('submit', () => {})} + onClearError={action('Clear error', () => {})} + onNext={action('next', () => {})} + onBack={action('back', () => {})} + setStep={action('step clicked', () => {})} + isClickable> + {content} + +
+); + +StatefulExample.story = { + name: 'stateful example', +}; + +export const StatefulExampleWValidationInPageTitleBar = () => ( +
+ Home, + Something, + Something Else, + ]} + content={} + /> +
+); + +StatefulExampleWValidationInPageTitleBar.story = { + name: 'stateful example w/ validation in PageTitleBar', +}; + +export const WrappedInPageTitleBar = () => ( +
+ Home, + Something, + Something Else, + ]} + content={ + {})} + onSubmit={action('submit', () => {})} + onClearError={action('Clear error', () => {})} + onNext={action('next', () => {})} + onBack={action('back', () => {})} + setStep={action('step clicked', () => {})} + isProgressIndicatorVertical={boolean( + 'Toggle Progress Indicator Alignment', + true + )}> + {content} + + } + /> +
+); + +WrappedInPageTitleBar.story = { + name: 'wrapped in PageTitleBar', +}; + +export const WithHorizontalProgressIndicator = () => ( +
+ {})} + onSubmit={action('submit', () => {})} + onNext={action('next', () => {})} + onBack={action('back', () => {})} + setStep={action('step clicked', () => {})} + onClearError={action('Clear error', () => {})} + isProgressIndicatorVertical={boolean( + 'Toggle Progress Indicator Alignment', + false + )} + isClickable> + {content} + +
+); + +WithHorizontalProgressIndicator.story = { + name: 'With Horizontal ProgressIndicator', +}; + +export const OnlyOneStepInPageTitleBar = () => ( +
+ Home, + Something, + Something Else, + ]} + content={ + {})} + onSubmit={action('submit', () => {})} + onNext={action('next', () => {})} + onBack={action('back', () => {})} + onClearError={() => {}} + setStep={action('step clicked', () => {})} + sendingData={boolean('sendingData', false)} + hasStickyFooter={boolean('hasStickyFooter', false)}> + {[content[0]]} + + } + /> +
+); + +OnlyOneStepInPageTitleBar.story = { + name: 'only one step, in PageTitleBar', +}; + +export const WithStickyFooterStatefulExampleWValidationInPageTitleBar = () => ( +
+ Home, + Something, + Something Else, + ]} + content={ + + } + /> +
+); + +WithStickyFooterStatefulExampleWValidationInPageTitleBar.story = { + name: 'With Sticky Footer: stateful example w/ validation in PageTitleBar', +}; + +export const WithAdditionalFooterContent = () => ( +
+ Home, + Something, + Something Else, + ]} + content={ + Save and close} + isClickable + afterFooterContent={ +
+ + {text('Additional footer content', 'Additional footer content')} +
} /> -
- ) - ) - .add('With additional footer content', () => ( -
- Home, - Something, - Something Else, - ]} - content={ - Save and close - } - isClickable - afterFooterContent={ -
- - {text('Additional footer content', 'Additional footer content')} -
- } - /> - } - /> -
- )) - .add('w/ i18n', () => ( -
- {})} - onSubmit={action('submit', () => {})} - onNext={action('next', () => {})} - onBack={action('back', () => {})} - onClearError={() => {}} - setStep={action('step clicked', () => {})} - sendingData={boolean('sendingData', false)} - hasStickyFooter={boolean('hasStickyFooter', false)} - i18={{ - close: text('Close label', 'Close'), - cancel: text('Cancel label', 'Cancel'), - back: text('Back label', 'Back'), - next: text('Next label', 'Next'), - submit: text('Submit label', 'Submit'), - }}> - {content} - -
- )); + } + /> +
+); + +WithAdditionalFooterContent.story = { + name: 'With additional footer content', +}; + +export const WI18N = () => ( +
+ {})} + onSubmit={action('submit', () => {})} + onNext={action('next', () => {})} + onBack={action('back', () => {})} + onClearError={() => {}} + setStep={action('step clicked', () => {})} + sendingData={boolean('sendingData', false)} + hasStickyFooter={boolean('hasStickyFooter', false)} + i18={{ + close: text('Close label', 'Close'), + cancel: text('Cancel label', 'Cancel'), + back: text('Back label', 'Back'), + next: text('Next label', 'Next'), + submit: text('Submit label', 'Submit'), + }}> + {content} + +
+); + +WI18N.story = { + name: 'w/ i18n', +}; export const StepValidation = ({ ...props }) => { const [firstName, setFirstName] = useState(''); diff --git a/src/components/PieChartCard/PieChartCard.story.jsx b/src/components/PieChartCard/PieChartCard.story.jsx index d851b5dac6..d002184708 100644 --- a/src/components/PieChartCard/PieChartCard.story.jsx +++ b/src/components/PieChartCard/PieChartCard.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { text, select, object, boolean } from '@storybook/addon-knobs'; import { SettingsAdjust16 } from '@carbon/icons-react'; @@ -48,277 +47,295 @@ const chartDataExample = [ }, ]; -storiesOf('Watson IoT/PieChartCard', module) - .addParameters({ +export default { + title: 'Watson IoT/PieChartCard', + + parameters: { component: PieChartCard, - }) - .add('basic', () => { - const size = select('size', acceptableSizes, CARD_SIZES.MEDIUM); - const groupDataSourceId = select( - 'groupDataSourceId', - ['category', 'group'], - 'category' - ); - const chartData = object('chartData', chartDataExample); + }, +}; - return ( -
- -
- ); - }) - .add('with CardVariables', () => { - const size = select('size', acceptableSizes, CARD_SIZES.LARGE); - const groupDataSourceId = select( - 'groupDataSourceId', - ['category', 'group'], - 'group' - ); - const chartDataExampleWithVars = chartDataExample.map((data) => ({ - ...data, - group: `{var1} ${data.group}`, - })); - const chartData = object('chartData', chartDataExampleWithVars); - const cardVariables = object('cardVariables', { var1: 'Inserted' }); +export const Basic = () => { + const size = select('size', acceptableSizes, CARD_SIZES.MEDIUM); + const groupDataSourceId = select( + 'groupDataSourceId', + ['category', 'group'], + 'category' + ); + const chartData = object('chartData', chartDataExample); - return ( -
- -
- ); - }) - .add('custom colors', () => { - const size = select('size', acceptableSizes, CARD_SIZES.LARGE); - const groupDataSourceId = select( - 'groupDataSourceId', - ['category', 'group'], - 'category' - ); - const chartData = object('chartData', chartDataExample); - const colorsMap = object('colors', { - A: 'red', - B: 'green', - C: 'blue', - D: 'yellow', - E: 'purple', - F: 'orange', - }); - return ( -
- -
- ); - }) - .add('custom labels', () => { - const size = select('size', acceptableSizes, CARD_SIZES.LARGE); - const groupDataSourceId = select( - 'groupDataSourceId', - ['category', 'group'], - 'category' - ); - const chartData = object('chartData', chartDataExample); + return ( +
+ +
+ ); +}; - return ( -
- { - return `${wrapper.data[groupDataSourceId]} (${wrapper.value})`; - }, - legendPosition: select( - 'legendPosition', - ['bottom', 'top'], - 'bottom' - ), - }} - id="basicCardStory" - isLoading={boolean('isLoading', false)} - isEditable={boolean('isEditable', false)} - isExpanded={boolean('isExpanded', false)} - onCardAction={action('onCardAction')} - size={size} - title={text('title', 'Schools')} - testID="basicCardStoryTest" - values={chartData} - /> -
- ); - }) - .add('custom tooltip', () => { - const size = select('size', acceptableSizes, CARD_SIZES.LARGE); - const groupDataSourceId = select( - 'groupDataSourceId', - ['category', 'group'], - 'category' - ); - const chartData = object('chartData', chartDataExample); +Basic.story = { + name: 'basic', +}; - return ( -
- { - return pieData - ? `label: ${pieData.label} - Value: ${pieData.value}` - : html; - }, - groupDataSourceId, - legendPosition: select( - 'legendPosition', - ['bottom', 'top'], - 'bottom' - ), - }} - id="basicCardStory" - isLoading={boolean('isLoading', false)} - isEditable={boolean('isEditable', false)} - isExpanded={boolean('isExpanded', false)} - onCardAction={action('onCardAction')} - size={size} - title={text('title', 'Schools')} - testID="basicCardStoryTest" - values={chartData} - /> -
- ); - }) - .add('no data', () => { - const size = select('size', acceptableSizes, CARD_SIZES.LARGE); - const chartData = object('chartData', []); - const i18n = object('i18n', { noDataLabel: 'No data for this card' }); - return ( -
- -
- ); - }) - .add('advanced customisation using overrides', () => { - return ( -
- { - const props = cloneDeep(originalPieChartProps); - props.options.animations = true; - return props; - }, +export const WithCardVariables = () => { + const size = select('size', acceptableSizes, CARD_SIZES.LARGE); + const groupDataSourceId = select( + 'groupDataSourceId', + ['category', 'group'], + 'group' + ); + const chartDataExampleWithVars = chartDataExample.map((data) => ({ + ...data, + group: `{var1} ${data.group}`, + })); + const chartData = object('chartData', chartDataExampleWithVars); + const cardVariables = object('cardVariables', { var1: 'Inserted' }); + + return ( +
+ +
+ ); +}; + +WithCardVariables.story = { + name: 'with CardVariables', +}; + +export const CustomColors = () => { + const size = select('size', acceptableSizes, CARD_SIZES.LARGE); + const groupDataSourceId = select( + 'groupDataSourceId', + ['category', 'group'], + 'category' + ); + const chartData = object('chartData', chartDataExample); + const colorsMap = object('colors', { + A: 'red', + B: 'green', + C: 'blue', + D: 'yellow', + E: 'purple', + F: 'orange', + }); + return ( +
+ +
+ ); +}; + +CustomColors.story = { + name: 'custom colors', +}; + +export const CustomLabels = () => { + const size = select('size', acceptableSizes, CARD_SIZES.LARGE); + const groupDataSourceId = select( + 'groupDataSourceId', + ['category', 'group'], + 'category' + ); + const chartData = object('chartData', chartDataExample); + + return ( +
+ { + return `${wrapper.data[groupDataSourceId]} (${wrapper.value})`; + }, + legendPosition: select('legendPosition', ['bottom', 'top'], 'bottom'), + }} + id="basicCardStory" + isLoading={boolean('isLoading', false)} + isEditable={boolean('isEditable', false)} + isExpanded={boolean('isExpanded', false)} + onCardAction={action('onCardAction')} + size={size} + title={text('title', 'Schools')} + testID="basicCardStoryTest" + values={chartData} + /> +
+ ); +}; + +CustomLabels.story = { + name: 'custom labels', +}; + +export const CustomTooltip = () => { + const size = select('size', acceptableSizes, CARD_SIZES.LARGE); + const groupDataSourceId = select( + 'groupDataSourceId', + ['category', 'group'], + 'category' + ); + const chartData = object('chartData', chartDataExample); + + return ( +
+ { + return pieData + ? `label: ${pieData.label} - Value: ${pieData.value}` + : html; + }, + groupDataSourceId, + legendPosition: select('legendPosition', ['bottom', 'top'], 'bottom'), + }} + id="basicCardStory" + isLoading={boolean('isLoading', false)} + isEditable={boolean('isEditable', false)} + isExpanded={boolean('isExpanded', false)} + onCardAction={action('onCardAction')} + size={size} + title={text('title', 'Schools')} + testID="basicCardStoryTest" + values={chartData} + /> +
+ ); +}; + +CustomTooltip.story = { + name: 'custom tooltip', +}; + +export const NoData = () => { + const size = select('size', acceptableSizes, CARD_SIZES.LARGE); + const chartData = object('chartData', []); + const i18n = object('i18n', { noDataLabel: 'No data for this card' }); + return ( +
+ +
+ ); +}; + +NoData.story = { + name: 'no data', +}; + +export const AdvancedCustomisationUsingOverrides = () => { + return ( +
+ { + const props = cloneDeep(originalPieChartProps); + props.options.animations = true; + return props; }, - table: { - props: (originalTableProps) => { - const props = cloneDeep(originalTableProps); - props.view.toolbar = { - customToolbarContent: ( - - Example Flyout Content - - ), - }; - return props; - }, + }, + table: { + props: (originalTableProps) => { + const props = cloneDeep(originalTableProps); + props.view.toolbar = { + customToolbarContent: ( + + Example Flyout Content + + ), + }; + return props; }, - }} - /> -
- ); - }); + }, + }} + /> +
+ ); +}; + +AdvancedCustomisationUsingOverrides.story = { + name: 'advanced customisation using overrides', +}; diff --git a/src/components/ProgressIndicator/ProgressIndicator.story.jsx b/src/components/ProgressIndicator/ProgressIndicator.story.jsx index d667ad8a0e..e9e262a9ab 100644 --- a/src/components/ProgressIndicator/ProgressIndicator.story.jsx +++ b/src/components/ProgressIndicator/ProgressIndicator.story.jsx @@ -1,7 +1,6 @@ /* Used dependencies */ import React from 'react'; import { boolean, number, select } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { ProgressIndicatorSkeleton } from 'carbon-components-react'; @@ -44,67 +43,96 @@ const items = [ { id: 'step5', label: 'Fifth Step' }, ]; -/* Adds the stories */ -storiesOf('Watson IoT/ProgressIndicator', module) - .addParameters({ +export default { + title: 'Watson IoT/ProgressIndicator', + + parameters: { component: ProgressIndicator, - }) - .add('stateful', () => ( - - )) - .add('presentation', () => ( - item.id), - items[0].id - )} - onClickItem={action('onClickItem')} - stepWidth={number('stepWidth', 6)} - showLabels={boolean('showlabels', true)} - isVerticalMode={boolean('isVerticalMode', false)} - isClickable={boolean('isClickable', true)} - /> - )) - .add('presentation vertical', () => ( - item.id), - items[1].id - )} - showLabels={boolean('showlabels', true)} - isClickable={boolean('isClickable', true)} - isVerticalMode={boolean('isVerticalMode', true)} - /> - )) - .add('hideLabels and default stepWidth', () => ( - item.id), - items[1].id - )} - onClickItem={action('onClickItem')} - showLabels={boolean('showlabels', false)} - isVerticalMode={boolean('isVerticalMode', false)} - isClickable={boolean('isClickable', true)} - /> - )) - .add('skeleton', () => , { + }, +}; + +export const Stateful = () => ( + +); + +Stateful.story = { + name: 'stateful', +}; + +export const Presentation = () => ( + item.id), + items[0].id + )} + onClickItem={action('onClickItem')} + stepWidth={number('stepWidth', 6)} + showLabels={boolean('showlabels', true)} + isVerticalMode={boolean('isVerticalMode', false)} + isClickable={boolean('isClickable', true)} + /> +); + +Presentation.story = { + name: 'presentation', +}; + +export const PresentationVertical = () => ( + item.id), + items[1].id + )} + showLabels={boolean('showlabels', true)} + isClickable={boolean('isClickable', true)} + isVerticalMode={boolean('isVerticalMode', true)} + /> +); + +PresentationVertical.story = { + name: 'presentation vertical', +}; + +export const HideLabelsAndDefaultStepWidth = () => ( + item.id), + items[1].id + )} + onClickItem={action('onClickItem')} + showLabels={boolean('showlabels', false)} + isVerticalMode={boolean('isVerticalMode', false)} + isClickable={boolean('isClickable', true)} + /> +); + +HideLabelsAndDefaultStepWidth.story = { + name: 'hideLabels and default stepWidth', +}; + +export const Skeleton = () => ; + +Skeleton.story = { + name: 'skeleton', + + parameters: { info: { text: ` - Placeholder skeleton state to use when content is loading. - `, + Placeholder skeleton state to use when content is loading. + `, }, - }); + }, +}; diff --git a/src/components/ResourceList/ResourceList.story.jsx b/src/components/ResourceList/ResourceList.story.jsx index ce455e8f17..38eb75a9c4 100644 --- a/src/components/ResourceList/ResourceList.story.jsx +++ b/src/components/ResourceList/ResourceList.story.jsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { select } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; -import { storiesOf } from '@storybook/react'; import { Bee32, Edit16 } from '@carbon/icons-react'; import ResourceList from './ResourceList'; @@ -51,29 +50,47 @@ class ResourceListSimple extends Component { }; } -storiesOf('Watson IoT/ResourceList', module) - .addParameters({ +export default { + title: 'Watson IoT/ResourceList', + + parameters: { component: ResourceList, - }) - .add('default', () => ) - .add('with extra content', () => ( - ( -
-
{i.id}
- -
- ))} - /> - )) - .add('with action', () => ( - - )); + }, +}; + +export const Default = () => ; + +Default.story = { + name: 'default', +}; + +export const WithExtraContent = () => ( + ( +
+
{i.id}
+ +
+ ))} + /> +); + +WithExtraContent.story = { + name: 'with extra content', +}; + +export const WithAction = () => ( + +); + +WithAction.story = { + name: 'with action', +}; diff --git a/src/components/SideNav/SideNav.story.jsx b/src/components/SideNav/SideNav.story.jsx index 9eae726af3..5394fb0276 100644 --- a/src/components/SideNav/SideNav.story.jsx +++ b/src/components/SideNav/SideNav.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { Switcher24 } from '@carbon/icons-react'; import Chip from '@carbon/icons-react/lib/chip/24'; @@ -141,45 +140,17 @@ const HeaderProps = { ], }; -storiesOf('Watson IoT/SideNav', module) - .addParameters({ - component: SideNav, - }) - .add( - 'SideNav component', - () => ( - - ( - <> -
- -
- -
- - )} - /> - - ), - { - info: { - text: ` - When implementing the Header and SideNav components you must utilized the HeaderContainer component +export default { + title: 'Watson IoT/SideNav', -
+ parameters: { + component: SideNav, + }, +}; - ~~~js - ( + + ( <>
+
+ +
)} /> + +); - ~~~ +SideNavComponent.story = { + name: 'SideNav component', -
+ parameters: { + info: { + text: ` + When implementing the Header and SideNav components you must utilized the HeaderContainer component - If you want to style your main content to "push over" instead of being overlayed by the sidenav you can use the ".iot--side-nav--expanded" class. It could look something like this. +
-
+ ~~~js + ( + <> +
+ + + )} + /> - ~~~scss - .iot--main-content { - width: calc(100% - 3rem); - transform: translateX(3rem); - transition: all .2s ease-in; - } + ~~~ - .iot--side-nav--expanded + .iot--main-content { - width: calc(100% - 16rem); - transform: translateX(16rem); - } +
- html[dir='rtl'] { - .iot--main-content, - .iot--side-nav--expanded + .iot--main-content { - transform: translateX(0); - } + If you want to style your main content to "push over" instead of being overlayed by the sidenav you can use the ".iot--side-nav--expanded" class. It could look something like this. - } +
- ~~~ + ~~~scss + .iot--main-content { + width: calc(100% - 3rem); + transform: translateX(3rem); + transition: all .2s ease-in; + } + + .iot--side-nav--expanded + .iot--main-content { + width: calc(100% - 16rem); + transform: translateX(16rem); + } + + html[dir='rtl'] { + .iot--main-content, + .iot--side-nav--expanded + .iot--main-content { + transform: translateX(0); + } - `, - }, } - ); + + ~~~ + + `, + }, + }, +}; diff --git a/src/components/SimplePagination/SimplePagination.story.jsx b/src/components/SimplePagination/SimplePagination.story.jsx index e045588260..5f8e195892 100644 --- a/src/components/SimplePagination/SimplePagination.story.jsx +++ b/src/components/SimplePagination/SimplePagination.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { number } from '@storybook/addon-knobs'; import styled from 'styled-components'; @@ -12,16 +11,24 @@ const StyledSimplePagination = styled.div` } `; -storiesOf('Watson IoT/SimplePagination', module) - .addParameters({ +export default { + title: 'Watson IoT/SimplePagination', + + parameters: { component: SimplePagination, - }) - .add('default', () => ( - - - - )); + }, +}; + +export const Default = () => ( + + + +); + +Default.story = { + name: 'default', +}; diff --git a/src/components/SuiteHeader/SuiteHeader.story.jsx b/src/components/SuiteHeader/SuiteHeader.story.jsx index 5c63eaa321..f622bf0cfc 100644 --- a/src/components/SuiteHeader/SuiteHeader.story.jsx +++ b/src/components/SuiteHeader/SuiteHeader.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text, object, boolean, select } from '@storybook/addon-knobs'; import { Switcher24 } from '@carbon/icons-react'; import Chip from '@carbon/icons-react/lib/chip/24'; @@ -93,92 +92,108 @@ const sideNavLinks = [ }, ]; -/* Sample of SuiteHeader usage with data hook -const HeaderWithHook = () => { - const [data, isLoading, error, refreshData] = useSuiteHeaderData({ - // baseApiUrl: 'http://localhost:3001/internal', - domain: 'mydomain.com', - isTest: true, - surveyConfig: { - id: 'suite', - delayIntervalDays: 30, - frequencyDays: 90, - }, - lang: 'en', - }); - const surveyData = data.showSurvey - ? { - surveyLink: 'https://www.ibm.com', - privacyLink: 'https://www.ibm.com', - } - : null; +export default { + title: 'Watson IoT/SuiteHeader', + + parameters: { + component: SuiteHeader, + }, +}; + +export const Default = () => { + const language = select('Language', Object.keys(SuiteHeaderI18N), 'en'); return ( ); }; -*/ -storiesOf('Watson IoT/SuiteHeader', module) - .addParameters({ - component: SuiteHeader, - }) - .add('default', () => { - const language = select('Language', Object.keys(SuiteHeaderI18N), 'en'); - return ( - - ); - }) - .add('Header with side nav', () => ( +Default.story = { + name: 'default', +}; + +export const HeaderWithSideNav = () => ( + +); + +HeaderWithSideNav.story = { + name: 'Header with side nav', +}; + +export const HeaderWithSurveyNotification = () => { + const language = select('Language', Object.keys(SuiteHeaderI18N), 'en'); + return ( - )) - .add('Header with survey notification', () => { - const language = select('Language', Object.keys(SuiteHeaderI18N), 'en'); - return ( - - ); - }); -// .add('Header with data hook', () => ); + ); +}; + +HeaderWithSurveyNotification.story = { + name: 'Header with survey notification', +}; diff --git a/src/components/SuiteHeader/SuiteHeaderAppSwitcher/SuiteHeaderAppSwitcher.story.jsx b/src/components/SuiteHeader/SuiteHeaderAppSwitcher/SuiteHeaderAppSwitcher.story.jsx index 66af8eb41c..42ebec8726 100644 --- a/src/components/SuiteHeader/SuiteHeaderAppSwitcher/SuiteHeaderAppSwitcher.story.jsx +++ b/src/components/SuiteHeader/SuiteHeaderAppSwitcher/SuiteHeaderAppSwitcher.story.jsx @@ -1,40 +1,52 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text, object } from '@storybook/addon-knobs'; import SuiteHeaderAppSwitcher from './SuiteHeaderAppSwitcher'; -storiesOf('Watson IoT/SuiteHeader/SuiteHeaderAppSwitcher', module) - .addParameters({ +export default { + title: 'Watson IoT/SuiteHeader/SuiteHeaderAppSwitcher', + + parameters: { component: SuiteHeaderAppSwitcher, - }) - .add('default', () => ( -
- -
- )) - .add('No applications', () => ( -
- -
- )); + }, +}; + +export const Default = () => ( +
+ +
+); + +Default.story = { + name: 'default', +}; + +export const NoApplications = () => ( +
+ +
+); + +NoApplications.story = { + name: 'No applications', +}; diff --git a/src/components/SuiteHeader/SuiteHeaderLogoutModal/SuiteHeaderLogoutModal.story.jsx b/src/components/SuiteHeader/SuiteHeaderLogoutModal/SuiteHeaderLogoutModal.story.jsx index c8acd14fab..94cfff0606 100644 --- a/src/components/SuiteHeader/SuiteHeaderLogoutModal/SuiteHeaderLogoutModal.story.jsx +++ b/src/components/SuiteHeader/SuiteHeaderLogoutModal/SuiteHeaderLogoutModal.story.jsx @@ -1,20 +1,27 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import SuiteHeaderLogoutModal from './SuiteHeaderLogoutModal'; -storiesOf('Watson IoT/SuiteHeader/SuiteHeaderLogoutModal', module) - .addParameters({ +export default { + title: 'Watson IoT/SuiteHeader/SuiteHeaderLogoutModal', + + parameters: { component: SuiteHeaderLogoutModal, - }) - .add('default', () => ( - - )); + }, +}; + +export const Default = () => ( + +); + +Default.story = { + name: 'default', +}; diff --git a/src/components/SuiteHeader/SuiteHeaderProfile/SuiteHeaderProfile.story.jsx b/src/components/SuiteHeader/SuiteHeaderProfile/SuiteHeaderProfile.story.jsx index 4c3d40d785..df2ca9f4f9 100644 --- a/src/components/SuiteHeader/SuiteHeaderProfile/SuiteHeaderProfile.story.jsx +++ b/src/components/SuiteHeader/SuiteHeaderProfile/SuiteHeaderProfile.story.jsx @@ -1,23 +1,30 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import SuiteHeaderProfile from './SuiteHeaderProfile'; -storiesOf('Watson IoT/SuiteHeader/SuiteHeaderProfile', module) - .addParameters({ +export default { + title: 'Watson IoT/SuiteHeader/SuiteHeaderProfile', + + parameters: { component: SuiteHeaderProfile, - }) - .add('default', () => ( -
- { - window.location.href = 'https://www.ibm.com'; - }} - onRequestLogout={action('onRequestLogout')} - /> -
- )); + }, +}; + +export const Default = () => ( +
+ { + window.location.href = 'https://www.ibm.com'; + }} + onRequestLogout={action('onRequestLogout')} + /> +
+); + +Default.story = { + name: 'default', +}; diff --git a/src/components/Table/Table.story.jsx b/src/components/Table/Table.story.jsx index e3eb25ef53..39c275fc8e 100644 --- a/src/components/Table/Table.story.jsx +++ b/src/components/Table/Table.story.jsx @@ -6,8 +6,6 @@ import React, { useEffect, useCallback, } from 'react'; -// import PropTypes from 'prop-types'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean, text, number, select, array } from '@storybook/addon-knobs'; import styled from 'styled-components'; @@ -625,158 +623,678 @@ export const StatefulTableWithNestedRowItems = (props) => { ); }; -storiesOf('Watson IoT/Table', module) - .addParameters({ +export default { + title: 'Watson IoT/Table', + + parameters: { component: Table, - }) - .add( - 'Simple Stateful Example', - () => ( - - ( + + + +); + +SimpleStatefulExample.story = { + parameters: { + info: { + text: + 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const SimpleStatefulExampleWithAlignment = () => ( + + + +); + +SimpleStatefulExampleWithAlignment.story = { + name: 'Simple Stateful Example with alignment', + + parameters: { + info: { + text: + 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const StatefulExampleWithEveryThirdRowUnselectable = () => ( + ({ + ...eachRow, + isSelectable: index % 3 !== 0, + }))} + actions={actions} + lightweight={boolean('lightweight', false)} + options={{ + hasRowSelection: select('hasRowSelection', ['multi', 'single'], 'multi'), + hasRowExpansion: false, + }} + view={{ table: { selectedIds: array('selectedIds', []) } }} + /> +); + +StatefulExampleWithEveryThirdRowUnselectable.story = { + name: 'Stateful Example with every third row unselectable', + + parameters: { + info: { + text: + 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const StatefulExampleWithExpansionMaxPagesAndColumnResize = () => ( + + + Example Flyout Content + + ), + }, + }} + secondaryTitle={text( + 'Secondary Title', + `Row count: ${initialState.data.length}` + )} + actions={{ + ...actions, + toolbar: { + ...actions.toolbar, + onDownloadCSV: (filteredData) => + csvDownloadHandler(filteredData, 'my table data'), + }, + }} + isSortable + lightweight={boolean('lightweight', false)} + options={{ + ...initialState.options, + hasResize: true, + hasFilter: select( + 'hasFilter', + ['onKeyPress', 'onEnterAndBlur'], + 'onKeyPress' + ), + wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), + hasSingleRowEdit: true, + }} + /> + +); + +StatefulExampleWithExpansionMaxPagesAndColumnResize.story = { + name: 'Stateful Example with expansion, maxPages, and column resize', + + parameters: { + info: { + text: ` + + This table has expanded rows. To support expanded rows, make sure to pass the expandedData prop to the table and set options.hasRowExpansion=true. + +
+ + ~~~js + expandedData={[ + {rowId: 'row-0',content: }, + {rowId: 'row-1',content: }, + {rowId: 'row-2',content: }, + … + ]} + + options = { + hasRowExpansion:true + } + + view={{ + pagination: { + maxPages: 5, + } + }} + + ~~~ + +
+ + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const StatefulExampleWithCreateSaveViews = () => { + return React.createElement(() => { + // The initial default state for this story is one with no active filters + // and no default search value etc, i.e. a view all scenario. + const defaultState = { + ...initialState, + columns: initialState.columns.map((col) => ({ + ...col, + width: '150px', + })), + view: { + ...initialState.view, + filters: [], + toolbar: { + activeBar: 'filter', + search: { defaultValue: '' }, + }, + }, + }; + + // Create some mockdata to represent previously saved views. + // The props can be any subset of the view and columns prop that + // you need in order to successfully save and load your views. + const viewExample = { + description: 'Columns: 7, Filters: 0, Search: pinoc', + id: 'view1', + isPublic: true, + isDeleteable: true, + isEditable: true, + title: 'My view 1', + props: { + view: { + filters: [], + table: { + ordering: defaultState.view.table.ordering, + sort: {}, + }, + toolbar: { + activeBar: 'column', + search: { defaultValue: 'pinoc' }, + }, + }, + columns: defaultState.columns, + }, + }; + const viewExample2 = { + description: 'Columns: 7, Filters: 1, Search:', + id: 'view2', + isPublic: false, + isDeleteable: true, + isEditable: true, + title: 'My view 2', + props: { + view: { + filters: [{ columnId: 'string', value: 'helping' }], + table: { + ordering: defaultState.view.table.ordering, + sort: { + columnId: 'select', + direction: 'DESC', + }, + }, + toolbar: { + activeBar: 'filter', + search: { defaultValue: '' }, + }, + }, + columns: defaultState.columns, + }, + }; + + /** The "store" that holds all the existing views */ + const [viewsStorage, setViewsStorage] = useState([ + viewExample, + viewExample2, + ]); + /** Tracks if the user has modified the view since it was selected */ + const [selectedViewEdited, setSelectedViewEdited] = useState(false); + /** The props & metadata of the view currently selected */ + const [selectedView, setSelectedView] = useState(viewExample2); + /** The props & metadata representing the current state needed by SaveViewModal */ + const [viewToSave, setViewToSave] = useState(undefined); + /** The id of the view that is currently the default */ + const [defaultViewId, setDefaultViewId] = useState('view2'); + /** Number of views per page in the TableManageViewModal */ + const manageViewsRowsPerPage = 10; + /** Current page number in the TableManageViewModal */ + const [ + manageViewsCurrentPageNumber, + setManageViewsCurrentPageNumber, + ] = useState(1); + /** Current filters in the TableManageViewModal. Can hold 'searchTerm' and 'showPublic' */ + const [manageViewsCurrentFilters, setManageViewsCurrentFilters] = useState({ + searchTerm: '', + showPublic: true, + }); + /** Flag needed to open and close the TableManageViewModal */ + const [manageViewsModalOpen, setManageViewsModalOpen] = useState(false); + /** Collection of filtered views needed for the pagination in the TableManageViewModal */ + const [manageViewsFilteredViews, setManageViewsFilteredViews] = useState( + viewsStorage + ); + /** Collection of views on the current page in the TableManageViewModal */ + const [ + manageViewsCurrentPageItems, + setManageViewsCurrentPageItems, + ] = useState(viewsStorage.slice(0, manageViewsRowsPerPage)); + + // The seletable items to be presented by the ViewDropDown. + const selectableViews = useMemo( + () => viewsStorage.map(({ id, title }) => ({ id, text: title })), + [viewsStorage] + ); + + // A helper method for currentUserViewRef that extracts a relevant subset of the + // properties avilable in the "view" prop. It also extracts the columns since they + // potentially hold the column widths. + const extractViewRefData = ({ view, columns }) => { + return { + columns, + view: { + filters: view.filters, + table: { + ordering: view.table.ordering, + sort: view.table.sort || {}, + }, + toolbar: { + activeBar: view.toolbar.activeBar, + search: { ...view.toolbar.search }, + }, + }, + }; + }; + + // The table's current user view configuration (inlcuding unsaved changes to the selected view). + // useRef is preferred over useState so that the value can be updated without causing a + // rerender of the table. + const currentUserViewRef = useRef({ + props: { + ...(selectedView + ? selectedView.props + : extractViewRefData(defaultState)), + }, + }); + + // Callback from the StatefulTable when view, columns or search value have + // been modified and we need to update our ref that holds the latest view config. + const onUserViewModified = (newState) => { + const { + view, + columns, + // The default search value is not updated just because the user modifies + // the actual search input so in order to set the defaultValue we can access + // the internal "currentSearchValue" via a special state prop + state: { currentSearchValue }, + } = newState; + + const props = extractViewRefData({ view, columns }); + props.view.toolbar.search = { + ...props.view.toolbar.search, + defaultValue: currentSearchValue, + }; + currentUserViewRef.current = { props }; + + if (!selectedView) { + setSelectedViewEdited( + !isEqual(props, extractViewRefData(defaultState)) + ); + } else { + setSelectedViewEdited(!isEqual(props, selectedView.props)); + } + }; + + /** + * The TableManageViewsModal is an external component that can be placed outside + * the table. It is highly customizable and is used to list existing views and + * provide the used the option to delete and edit the view's metadata. See the + * TableManageViewsModal story for a more detailed documentation. + */ + const renderManageViewsModal = () => { + const showPage = (pageNumber, views) => { + const rowUpperLimit = pageNumber * manageViewsRowsPerPage; + const currentItemsOnPage = views.slice( + rowUpperLimit - manageViewsRowsPerPage, + rowUpperLimit + ); + setManageViewsCurrentPageNumber(pageNumber); + setManageViewsCurrentPageItems(currentItemsOnPage); + }; + + const applyFiltering = ({ searchTerm, showPublic }) => { + const views = viewsStorage + .filter( + (view) => + searchTerm === '' || + view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 + ) + .filter((view) => (showPublic ? view : !view.isPublic)); + + setManageViewsFilteredViews(views); + showPage(1, views); + }; + + const onDelete = (viewId) => { + if (selectedView?.id === viewId) { + currentUserViewRef.current = { + props: { ...extractViewRefData(defaultState) }, + }; + setSelectedViewEdited(false); + setSelectedView(undefined); + } + + const deleteIndex = viewsStorage.findIndex( + (view) => view.id === viewId + ); + setViewsStorage((existingViews) => { + const modifiedViews = [...existingViews]; + modifiedViews.splice(deleteIndex, 1); + setManageViewsFilteredViews(modifiedViews); + showPage(1, modifiedViews); + return modifiedViews; + }); + }; + + return ( + { + const newFilters = { + ...manageViewsCurrentFilters, + showPublic, + }; + setManageViewsCurrentFilters(newFilters); + applyFiltering(newFilters); + }, + onSearchChange: (searchTerm = '') => { + const newFilters = { + ...manageViewsCurrentFilters, + searchTerm, + }; + setManageViewsCurrentFilters(newFilters); + applyFiltering(newFilters); + }, + onEdit: (viewId) => { + setManageViewsModalOpen(false); + const viewToEdit = viewsStorage.find( + (view) => view.id === viewId + ); + setSelectedView(viewToEdit); + setViewToSave(viewToEdit); + }, + onDelete, + onClearError: action('onClearManageViewsModalError'), + onClose: () => setManageViewsModalOpen(false), + }} + defaultViewId={defaultViewId} + error={select('error', [undefined, 'My error msg'], undefined)} + isLoading={boolean('isLoading', false)} + open={manageViewsModalOpen} + views={manageViewsCurrentPageItems} + pagination={{ + page: manageViewsCurrentPageNumber, + onPage: (pageNumber) => + showPage(pageNumber, manageViewsFilteredViews), + maxPage: Math.ceil( + manageViewsFilteredViews.length / manageViewsRowsPerPage ), - hasRowExpansion: false, - wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), + pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, }} - view={{ table: { selectedIds: array('selectedIds', []) } }} /> - - ), - { - info: { - text: - 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Simple Stateful Example with alignment', - () => ( - - { + return ( + { + setViewToSave({ + id: undefined, + ...currentUserViewRef.current, + }); + }, + onManageViews: () => { + setManageViewsModalOpen(true); + setManageViewsCurrentPageItems( + viewsStorage.slice(0, manageViewsRowsPerPage) + ); + }, + onChangeView: ({ id }) => { + const selected = viewsStorage.find((view) => view.id === id); + setSelectedView(selected); + setSelectedViewEdited(false); + currentUserViewRef.current = selected?.props || { + props: extractViewRefData(defaultState), + }; + }, + onSaveChanges: () => { + setViewToSave({ + ...selectedView, + ...currentUserViewRef.current, + }); + }, }} - view={{ table: { selectedIds: array('selectedIds', []) } }} /> - - ), - { - info: { - text: - 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) + ); + }; + + /** + * The TableSaveViewModal is a an external component that can be placed + * outside the table. Is is used both for saving new views and for + * updating existing ones. See the TableSaveViewModal story for a more + * detailed documentation. + */ + const renderSaveViewModal = () => { + const saveView = (viewMetaData) => { + setViewsStorage((existingViews) => { + const modifiedStorage = []; + const saveNew = viewToSave.id === undefined; + const { isDefault, ...metaDataToSave } = viewMetaData; + const generatedId = new Date().getTime().toString(); + + if (saveNew) { + const newViewToStore = { + ...viewToSave, + ...metaDataToSave, + id: generatedId, + isDeleteable: true, + isEditable: true, + }; + modifiedStorage.push(...existingViews, newViewToStore); + setSelectedView(newViewToStore); + } else { + const indexToUpdate = existingViews.findIndex( + (view) => view.id === viewToSave.id + ); + const viewsCopy = [...existingViews]; + const modifiedViewToStore = { + ...viewToSave, + ...metaDataToSave, + }; + viewsCopy[indexToUpdate] = modifiedViewToStore; + setSelectedView(modifiedViewToStore); + modifiedStorage.push(...viewsCopy); + } - .add( - 'Stateful Example with every third row unselectable', - () => ( - ({ - ...eachRow, - isSelectable: index % 3 !== 0, - }))} - actions={actions} - lightweight={boolean('lightweight', false)} - options={{ - hasRowSelection: select( - 'hasRowSelection', - ['multi', 'single'], - 'multi' - ), - hasRowExpansion: false, - }} - view={{ table: { selectedIds: array('selectedIds', []) } }} - /> - ), - { - info: { - text: - 'This is an example of the component that uses local state to handle all the table actions. This is produced by wrapping the
in a container component and managing the state associated with features such the toolbar, filters, row select, etc. For more robust documentation on the prop model and source, see the other "with function" stories.', - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Stateful Example with expansion, maxPages, and column resize', - () => ( + if (isDefault) { + setDefaultViewId(saveNew ? generatedId : viewToSave.id); + } + + setSelectedViewEdited(false); + return modifiedStorage; + }); + setViewToSave(undefined); + }; + + // Simple description example that can be replaced by any string or node. + // See the TableSaveViewModal story for more examples. + const getDescription = ({ table, filters, toolbar }) => + `Columns: ${table.ordering.filter((col) => !col.isHidden).length}, + Filters: ${filters?.length || 0}, + Search: ${toolbar?.search?.defaultValue}`; + + return ( + viewToSave && ( + { + setViewToSave(undefined); + }, + onClearError: action('onClearError'), + onChange: action('onChange'), + }} + sendingData={boolean('sendingData', false)} + error={select('error', [undefined, 'My error msg'], undefined)} + open + titleInputInvalid={boolean('titleInputInvalid', false)} + titleInputInvalidText={text('titleInputInvalidText', undefined)} + viewDescription={getDescription(viewToSave.props.view)} + initialFormValues={{ + title: viewToSave.title, + isPublic: viewToSave.isPublic, + isDefault: viewToSave.id === defaultViewId, + }} + i18n={{ + modalTitle: viewToSave.id ? 'Update view' : 'Save new view', + }} + /> + ) + ); + }; + + // We need to merge (using assign) the view properties from a few sources as + // explained below in order to get the desired result. This is written as a + // more general function, but it can just as well be written as an explicit + // object literal picking the right properties from the differentsources. + const mergedViewProp = useMemo(() => { + const merged = assign( + {}, + // The default state view contains properties that are not + // part of this Save View example, e.g. pagination, so we include + // the default state as a baseline view configuration. + defaultState.view, + // These are the properties specific for the currently selected view + selectedView?.props?.view, + // These are the properties of an unsaved modified view that already + // have to be rendered before they become part of the selected view. + viewToSave?.props?.view + ); + return merged; + }, [defaultState, selectedView, viewToSave]); + + return ( + {renderManageViewsModal()} + {renderSaveViewModal()} - Example Flyout Content - - ), + ...mergedViewProp.toolbar, + customToolbarContent: renderViewDropdown(), }, }} - secondaryTitle={text( - 'Secondary Title', - `Row count: ${initialState.data.length}` - )} + secondaryTitle="Table with user view management" actions={{ ...actions, - toolbar: { - ...actions.toolbar, - onDownloadCSV: (filteredData) => - csvDownloadHandler(filteredData, 'my table data'), - }, + onUserViewModified, }} isSortable lightweight={boolean('lightweight', false)} options={{ - ...initialState.options, + ...defaultState.options, hasResize: true, hasFilter: select( 'hasFilter', @@ -784,1990 +1302,1538 @@ storiesOf('Watson IoT/Table', module) 'onKeyPress' ), wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), - hasSingleRowEdit: true, + // Enables the behaviour in StatefulTable and Table required + // to fully implement Create and Save Views + hasUserViewManagement: true, }} /> - ), - { - info: { - text: ` - - This table has expanded rows. To support expanded rows, make sure to pass the expandedData prop to the table and set options.hasRowExpansion=true. - -
- - ~~~js - expandedData={[ - {rowId: 'row-0',content: }, - {rowId: 'row-1',content: }, - {rowId: 'row-2',content: }, - … - ]} - - options = { - hasRowExpansion:true - } - - view={{ - pagination: { - maxPages: 5, - } - }} - - ~~~ + ); + }); +}; -
+StatefulExampleWithCreateSaveViews.story = { + name: 'Stateful Example with Create & Save Views', + + parameters: { + info: { + text: ` + This story shows a complete implementation of user configurable View Management. + The story's source code is too complex to successfully be shown here, please view + the actual source code. + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; - `, - propTables: [Table], - propTablesExclude: [StatefulTable], +export const TableExampleWithCreateSaveViews = () => { + return React.createElement(() => { + // The initial default state for this story is one with no active filters + // and no default search value etc, i.e. a view all scenario. + const baseState = { + ...initialState, + columns: initialState.columns.map((col) => ({ + ...col, + width: '150px', + })), + view: { + ...initialState.view, + filters: [], + toolbar: { + activeBar: 'filter', + search: { defaultValue: '' }, + }, }, - } - ) - .add( - 'Stateful Example with Create & Save Views', - () => { - return React.createElement(() => { - // The initial default state for this story is one with no active filters - // and no default search value etc, i.e. a view all scenario. - const defaultState = { - ...initialState, - columns: initialState.columns.map((col) => ({ - ...col, - width: '150px', - })), - view: { - ...initialState.view, - filters: [], - toolbar: { - activeBar: 'filter', - search: { defaultValue: '' }, - }, - }, - }; - - // Create some mockdata to represent previously saved views. - // The props can be any subset of the view and columns prop that - // you need in order to successfully save and load your views. - const viewExample = { - description: 'Columns: 7, Filters: 0, Search: pinoc', - id: 'view1', - isPublic: true, - isDeleteable: true, - isEditable: true, - title: 'My view 1', - props: { - view: { - filters: [], - table: { - ordering: defaultState.view.table.ordering, - sort: {}, - }, - toolbar: { - activeBar: 'column', - search: { defaultValue: 'pinoc' }, - }, - }, - columns: defaultState.columns, - }, - }; - const viewExample2 = { - description: 'Columns: 7, Filters: 1, Search:', - id: 'view2', - isPublic: false, - isDeleteable: true, - isEditable: true, - title: 'My view 2', - props: { - view: { - filters: [{ columnId: 'string', value: 'helping' }], - table: { - ordering: defaultState.view.table.ordering, - sort: { - columnId: 'select', - direction: 'DESC', - }, - }, - toolbar: { - activeBar: 'filter', - search: { defaultValue: '' }, - }, - }, - columns: defaultState.columns, - }, - }; - - /** The "store" that holds all the existing views */ - const [viewsStorage, setViewsStorage] = useState([ - viewExample, - viewExample2, - ]); - /** Tracks if the user has modified the view since it was selected */ - const [selectedViewEdited, setSelectedViewEdited] = useState(false); - /** The props & metadata of the view currently selected */ - const [selectedView, setSelectedView] = useState(viewExample2); - /** The props & metadata representing the current state needed by SaveViewModal */ - const [viewToSave, setViewToSave] = useState(undefined); - /** The id of the view that is currently the default */ - const [defaultViewId, setDefaultViewId] = useState('view2'); - /** Number of views per page in the TableManageViewModal */ - const manageViewsRowsPerPage = 10; - /** Current page number in the TableManageViewModal */ - const [ - manageViewsCurrentPageNumber, - setManageViewsCurrentPageNumber, - ] = useState(1); - /** Current filters in the TableManageViewModal. Can hold 'searchTerm' and 'showPublic' */ - const [ - manageViewsCurrentFilters, - setManageViewsCurrentFilters, - ] = useState({ - searchTerm: '', - showPublic: true, - }); - /** Flag needed to open and close the TableManageViewModal */ - const [manageViewsModalOpen, setManageViewsModalOpen] = useState(false); - /** Collection of filtered views needed for the pagination in the TableManageViewModal */ - const [ - manageViewsFilteredViews, - setManageViewsFilteredViews, - ] = useState(viewsStorage); - /** Collection of views on the current page in the TableManageViewModal */ - const [ - manageViewsCurrentPageItems, - setManageViewsCurrentPageItems, - ] = useState(viewsStorage.slice(0, manageViewsRowsPerPage)); - - // The seletable items to be presented by the ViewDropDown. - const selectableViews = useMemo( - () => viewsStorage.map(({ id, title }) => ({ id, text: title })), - [viewsStorage] - ); - - // A helper method for currentUserViewRef that extracts a relevant subset of the - // properties avilable in the "view" prop. It also extracts the columns since they - // potentially hold the column widths. - const extractViewRefData = ({ view, columns }) => { - return { - columns, - view: { - filters: view.filters, - table: { - ordering: view.table.ordering, - sort: view.table.sort || {}, - }, - toolbar: { - activeBar: view.toolbar.activeBar, - search: { ...view.toolbar.search }, - }, - }, - }; - }; - - // The table's current user view configuration (inlcuding unsaved changes to the selected view). - // useRef is preferred over useState so that the value can be updated without causing a - // rerender of the table. - const currentUserViewRef = useRef({ - props: { - ...(selectedView - ? selectedView.props - : extractViewRefData(defaultState)), + }; + + // Create some mockdata to represent previously saved views. + // The props can be any subset of the view and columns prop that + // you need in order to successfully save and load your views. + const viewExample = { + description: 'Columns: 7, Filters: 0, Search: pinoc', + id: 'view1', + isPublic: true, + isDeleteable: true, + isEditable: true, + title: 'My view 1', + props: { + view: { + filters: [], + table: { + ordering: baseState.view.table.ordering, + sort: {}, }, - }); - - // Callback from the StatefulTable when view, columns or search value have - // been modified and we need to update our ref that holds the latest view config. - const onUserViewModified = (newState) => { - const { - view, - columns, - // The default search value is not updated just because the user modifies - // the actual search input so in order to set the defaultValue we can access - // the internal "currentSearchValue" via a special state prop - state: { currentSearchValue }, - } = newState; - - const props = extractViewRefData({ view, columns }); - props.view.toolbar.search = { - ...props.view.toolbar.search, - defaultValue: currentSearchValue, - }; - currentUserViewRef.current = { props }; - - if (!selectedView) { - setSelectedViewEdited( - !isEqual(props, extractViewRefData(defaultState)) - ); - } else { - setSelectedViewEdited(!isEqual(props, selectedView.props)); - } - }; - - /** - * The TableManageViewsModal is an external component that can be placed outside - * the table. It is highly customizable and is used to list existing views and - * provide the used the option to delete and edit the view's metadata. See the - * TableManageViewsModal story for a more detailed documentation. - */ - const renderManageViewsModal = () => { - const showPage = (pageNumber, views) => { - const rowUpperLimit = pageNumber * manageViewsRowsPerPage; - const currentItemsOnPage = views.slice( - rowUpperLimit - manageViewsRowsPerPage, - rowUpperLimit - ); - setManageViewsCurrentPageNumber(pageNumber); - setManageViewsCurrentPageItems(currentItemsOnPage); - }; - - const applyFiltering = ({ searchTerm, showPublic }) => { - const views = viewsStorage - .filter( - (view) => - searchTerm === '' || - view.title.toLowerCase().search(searchTerm.toLowerCase()) !== - -1 - ) - .filter((view) => (showPublic ? view : !view.isPublic)); - - setManageViewsFilteredViews(views); - showPage(1, views); - }; - - const onDelete = (viewId) => { - if (selectedView?.id === viewId) { - currentUserViewRef.current = { - props: { ...extractViewRefData(defaultState) }, - }; - setSelectedViewEdited(false); - setSelectedView(undefined); - } - - const deleteIndex = viewsStorage.findIndex( - (view) => view.id === viewId - ); - setViewsStorage((existingViews) => { - const modifiedViews = [...existingViews]; - modifiedViews.splice(deleteIndex, 1); - setManageViewsFilteredViews(modifiedViews); - showPage(1, modifiedViews); - return modifiedViews; - }); - }; - - return ( - { - const newFilters = { - ...manageViewsCurrentFilters, - showPublic, - }; - setManageViewsCurrentFilters(newFilters); - applyFiltering(newFilters); - }, - onSearchChange: (searchTerm = '') => { - const newFilters = { - ...manageViewsCurrentFilters, - searchTerm, - }; - setManageViewsCurrentFilters(newFilters); - applyFiltering(newFilters); - }, - onEdit: (viewId) => { - setManageViewsModalOpen(false); - const viewToEdit = viewsStorage.find( - (view) => view.id === viewId - ); - setSelectedView(viewToEdit); - setViewToSave(viewToEdit); - }, - onDelete, - onClearError: action('onClearManageViewsModalError'), - onClose: () => setManageViewsModalOpen(false), - }} - defaultViewId={defaultViewId} - error={select('error', [undefined, 'My error msg'], undefined)} - isLoading={boolean('isLoading', false)} - open={manageViewsModalOpen} - views={manageViewsCurrentPageItems} - pagination={{ - page: manageViewsCurrentPageNumber, - onPage: (pageNumber) => - showPage(pageNumber, manageViewsFilteredViews), - maxPage: Math.ceil( - manageViewsFilteredViews.length / manageViewsRowsPerPage - ), - pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, - }} - /> - ); - }; - - /** - * The TableViewDropdown is an external component that needs to be passed in - * via the customToolbarContent and positioned according to the applications needs. - * Most of the functionality in the TableViewDropdown can be overwritten. See the - * TableViewDropdown story for a more detailed documentation. - */ - - const renderViewDropdown = () => { - return ( - { - setViewToSave({ - id: undefined, - ...currentUserViewRef.current, - }); - }, - onManageViews: () => { - setManageViewsModalOpen(true); - setManageViewsCurrentPageItems( - viewsStorage.slice(0, manageViewsRowsPerPage) - ); - }, - onChangeView: ({ id }) => { - const selected = viewsStorage.find((view) => view.id === id); - setSelectedView(selected); - setSelectedViewEdited(false); - currentUserViewRef.current = selected?.props || { - props: extractViewRefData(defaultState), - }; - }, - onSaveChanges: () => { - setViewToSave({ - ...selectedView, - ...currentUserViewRef.current, - }); - }, - }} - /> - ); - }; - - /** - * The TableSaveViewModal is a an external component that can be placed - * outside the table. Is is used both for saving new views and for - * updating existing ones. See the TableSaveViewModal story for a more - * detailed documentation. - */ - const renderSaveViewModal = () => { - const saveView = (viewMetaData) => { - setViewsStorage((existingViews) => { - const modifiedStorage = []; - const saveNew = viewToSave.id === undefined; - const { isDefault, ...metaDataToSave } = viewMetaData; - const generatedId = new Date().getTime().toString(); - - if (saveNew) { - const newViewToStore = { - ...viewToSave, - ...metaDataToSave, - id: generatedId, - isDeleteable: true, - isEditable: true, - }; - modifiedStorage.push(...existingViews, newViewToStore); - setSelectedView(newViewToStore); - } else { - const indexToUpdate = existingViews.findIndex( - (view) => view.id === viewToSave.id - ); - const viewsCopy = [...existingViews]; - const modifiedViewToStore = { - ...viewToSave, - ...metaDataToSave, - }; - viewsCopy[indexToUpdate] = modifiedViewToStore; - setSelectedView(modifiedViewToStore); - modifiedStorage.push(...viewsCopy); - } - - if (isDefault) { - setDefaultViewId(saveNew ? generatedId : viewToSave.id); - } - - setSelectedViewEdited(false); - return modifiedStorage; - }); - setViewToSave(undefined); - }; - - // Simple description example that can be replaced by any string or node. - // See the TableSaveViewModal story for more examples. - const getDescription = ({ table, filters, toolbar }) => - `Columns: ${table.ordering.filter((col) => !col.isHidden).length}, - Filters: ${filters?.length || 0}, - Search: ${toolbar?.search?.defaultValue}`; - - return ( - viewToSave && ( - { - setViewToSave(undefined); - }, - onClearError: action('onClearError'), - onChange: action('onChange'), - }} - sendingData={boolean('sendingData', false)} - error={select('error', [undefined, 'My error msg'], undefined)} - open - titleInputInvalid={boolean('titleInputInvalid', false)} - titleInputInvalidText={text('titleInputInvalidText', undefined)} - viewDescription={getDescription(viewToSave.props.view)} - initialFormValues={{ - title: viewToSave.title, - isPublic: viewToSave.isPublic, - isDefault: viewToSave.id === defaultViewId, - }} - i18n={{ - modalTitle: viewToSave.id ? 'Update view' : 'Save new view', - }} - /> - ) - ); - }; - - // We need to merge (using assign) the view properties from a few sources as - // explained below in order to get the desired result. This is written as a - // more general function, but it can just as well be written as an explicit - // object literal picking the right properties from the differentsources. - const mergedViewProp = useMemo(() => { - const merged = assign( - {}, - // The default state view contains properties that are not - // part of this Save View example, e.g. pagination, so we include - // the default state as a baseline view configuration. - defaultState.view, - // These are the properties specific for the currently selected view - selectedView?.props?.view, - // These are the properties of an unsaved modified view that already - // have to be rendered before they become part of the selected view. - viewToSave?.props?.view - ); - return merged; - }, [defaultState, selectedView, viewToSave]); - - return ( - - {renderManageViewsModal()} - {renderSaveViewModal()} - - - ); - }); - }, - { - info: { - text: ` - This story shows a complete implementation of user configurable View Management. - The story's source code is too complex to successfully be shown here, please view - the actual source code. - `, - propTables: [Table], - propTablesExclude: [StatefulTable], + toolbar: { + activeBar: 'column', + search: { defaultValue: 'pinoc' }, + }, + }, + columns: baseState.columns, }, - } - ) - .add( - 'Table Example with Create & Save Views', - () => { - return React.createElement(() => { - // The initial default state for this story is one with no active filters - // and no default search value etc, i.e. a view all scenario. - const baseState = { - ...initialState, - columns: initialState.columns.map((col) => ({ - ...col, - width: '150px', - })), - view: { - ...initialState.view, - filters: [], - toolbar: { - activeBar: 'filter', - search: { defaultValue: '' }, + }; + const viewExample2 = { + description: 'Columns: 7, Filters: 1, Search:', + id: 'view2', + isPublic: false, + isDeleteable: true, + isEditable: true, + title: 'My view 2', + props: { + view: { + filters: [{ columnId: 'string', value: 'helping' }], + table: { + ordering: baseState.view.table.ordering, + sort: { + columnId: 'select', + direction: 'DESC', }, }, - }; - - // Create some mockdata to represent previously saved views. - // The props can be any subset of the view and columns prop that - // you need in order to successfully save and load your views. - const viewExample = { - description: 'Columns: 7, Filters: 0, Search: pinoc', - id: 'view1', - isPublic: true, - isDeleteable: true, - isEditable: true, - title: 'My view 1', - props: { - view: { - filters: [], - table: { - ordering: baseState.view.table.ordering, - sort: {}, - }, - toolbar: { - activeBar: 'column', - search: { defaultValue: 'pinoc' }, - }, - }, - columns: baseState.columns, + toolbar: { + activeBar: 'filter', + search: { defaultValue: '' }, }, - }; - const viewExample2 = { - description: 'Columns: 7, Filters: 1, Search:', - id: 'view2', - isPublic: false, - isDeleteable: true, - isEditable: true, - title: 'My view 2', - props: { - view: { - filters: [{ columnId: 'string', value: 'helping' }], - table: { - ordering: baseState.view.table.ordering, - sort: { - columnId: 'select', - direction: 'DESC', - }, - }, - toolbar: { - activeBar: 'filter', - search: { defaultValue: '' }, + }, + columns: baseState.columns, + }, + }; + + /** The "store" that holds all the existing views */ + const [viewsStorage, setViewsStorage] = useState([ + viewExample, + viewExample2, + ]); + /** Tracks if the user has modified the view since it was selected */ + const [selectedViewEdited, setSelectedViewEdited] = useState(false); + /** The props & metadata of the view currently selected */ + const [selectedView, setSelectedView] = useState(viewExample2); + /** The props & metadata representing the current state needed by SaveViewModal */ + const [viewToSave, setViewToSave] = useState(undefined); + /** The id of the view that is currently the default */ + const [defaultViewId, setDefaultViewId] = useState('view2'); + /** Number of views per page in the TableManageViewModal */ + const manageViewsRowsPerPage = 10; + /** Current page number in the TableManageViewModal */ + const [ + manageViewsCurrentPageNumber, + setManageViewsCurrentPageNumber, + ] = useState(1); + /** Current filters in the TableManageViewModal. Can hold 'searchTerm' and 'showPublic' */ + const [manageViewsCurrentFilters, setManageViewsCurrentFilters] = useState({ + searchTerm: '', + showPublic: true, + }); + /** Flag needed to open and close the TableManageViewModal */ + const [manageViewsModalOpen, setManageViewsModalOpen] = useState(false); + /** Collection of filtered views needed for the pagination in the TableManageViewModal */ + const [manageViewsFilteredViews, setManageViewsFilteredViews] = useState( + viewsStorage + ); + /** Collection of views on the current page in the TableManageViewModal */ + const [ + manageViewsCurrentPageItems, + setManageViewsCurrentPageItems, + ] = useState(viewsStorage.slice(0, manageViewsRowsPerPage)); + + // This is the state of the current table. + const [currentTableState, setCurrentTableState] = useState( + assign( + {}, + baseState, + viewsStorage.find((view) => view.id === defaultViewId)?.props + ) + ); + + // The seletable items to be presented by the ViewDropDown. + const selectableViews = useMemo( + () => viewsStorage.map(({ id, title }) => ({ id, text: title })), + [viewsStorage] + ); + + // A helper method used to extract the relevat properties from the view and column + // props. For our example story this is what we store in a saved view. + const extractCurrentUserView = useCallback( + ({ view, columns }) => ({ + props: { + columns, + view: { + filters: view.filters, + table: { + ordering: view.table.ordering, + sort: view.table.sort || {}, + }, + toolbar: { + activeBar: view.toolbar.activeBar, + search: { + ...view.toolbar.search, + defaultValue: + currentTableState.view.toolbar?.search?.defaultValue || '', }, }, - columns: baseState.columns, }, - }; - - /** The "store" that holds all the existing views */ - const [viewsStorage, setViewsStorage] = useState([ - viewExample, - viewExample2, - ]); - /** Tracks if the user has modified the view since it was selected */ - const [selectedViewEdited, setSelectedViewEdited] = useState(false); - /** The props & metadata of the view currently selected */ - const [selectedView, setSelectedView] = useState(viewExample2); - /** The props & metadata representing the current state needed by SaveViewModal */ - const [viewToSave, setViewToSave] = useState(undefined); - /** The id of the view that is currently the default */ - const [defaultViewId, setDefaultViewId] = useState('view2'); - /** Number of views per page in the TableManageViewModal */ - const manageViewsRowsPerPage = 10; - /** Current page number in the TableManageViewModal */ - const [ - manageViewsCurrentPageNumber, - setManageViewsCurrentPageNumber, - ] = useState(1); - /** Current filters in the TableManageViewModal. Can hold 'searchTerm' and 'showPublic' */ - const [ - manageViewsCurrentFilters, - setManageViewsCurrentFilters, - ] = useState({ - searchTerm: '', - showPublic: true, - }); - /** Flag needed to open and close the TableManageViewModal */ - const [manageViewsModalOpen, setManageViewsModalOpen] = useState(false); - /** Collection of filtered views needed for the pagination in the TableManageViewModal */ - const [ - manageViewsFilteredViews, - setManageViewsFilteredViews, - ] = useState(viewsStorage); - /** Collection of views on the current page in the TableManageViewModal */ - const [ - manageViewsCurrentPageItems, - setManageViewsCurrentPageItems, - ] = useState(viewsStorage.slice(0, manageViewsRowsPerPage)); - - // This is the state of the current table. - const [currentTableState, setCurrentTableState] = useState( - assign( - {}, - baseState, - viewsStorage.find((view) => view.id === defaultViewId)?.props - ) - ); + }, + }), + [currentTableState] + ); - // The seletable items to be presented by the ViewDropDown. - const selectableViews = useMemo( - () => viewsStorage.map(({ id, title }) => ({ id, text: title })), - [viewsStorage] + // This effect is needed to determine if the current view has been changed + // so that this can be reflected in the TableViewDropdown. + useEffect(() => { + const currentUserView = extractCurrentUserView(currentTableState); + const compareView = selectedView || extractCurrentUserView(baseState); + setSelectedViewEdited(!isEqual(currentUserView.props, compareView.props)); + }, [baseState, currentTableState, extractCurrentUserView, selectedView]); + + /** + * The TableManageViewsModal is an external component that can be placed outside + * the table. It is highly customizable and is used to list existing views and + * provide the used the option to delete and edit the view's metadata. See the + * TableManageViewsModal story for a more detailed documentation. + */ + const renderManageViewsModal = () => { + const showPage = (pageNumber, views) => { + const rowUpperLimit = pageNumber * manageViewsRowsPerPage; + const currentItemsOnPage = views.slice( + rowUpperLimit - manageViewsRowsPerPage, + rowUpperLimit ); + setManageViewsCurrentPageNumber(pageNumber); + setManageViewsCurrentPageItems(currentItemsOnPage); + }; - // A helper method used to extract the relevat properties from the view and column - // props. For our example story this is what we store in a saved view. - const extractCurrentUserView = useCallback( - ({ view, columns }) => ({ - props: { - columns, - view: { - filters: view.filters, - table: { - ordering: view.table.ordering, - sort: view.table.sort || {}, - }, - toolbar: { - activeBar: view.toolbar.activeBar, - search: { - ...view.toolbar.search, - defaultValue: - currentTableState.view.toolbar?.search?.defaultValue || - '', - }, - }, - }, - }, - }), - [currentTableState] - ); + const applyFiltering = ({ searchTerm, showPublic }) => { + const views = viewsStorage + .filter( + (view) => + searchTerm === '' || + view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 + ) + .filter((view) => (showPublic ? view : !view.isPublic)); - // This effect is needed to determine if the current view has been changed - // so that this can be reflected in the TableViewDropdown. - useEffect(() => { - const currentUserView = extractCurrentUserView(currentTableState); - const compareView = selectedView || extractCurrentUserView(baseState); - setSelectedViewEdited( - !isEqual(currentUserView.props, compareView.props) - ); - }, [ - baseState, - currentTableState, - extractCurrentUserView, - selectedView, - ]); - - /** - * The TableManageViewsModal is an external component that can be placed outside - * the table. It is highly customizable and is used to list existing views and - * provide the used the option to delete and edit the view's metadata. See the - * TableManageViewsModal story for a more detailed documentation. - */ - const renderManageViewsModal = () => { - const showPage = (pageNumber, views) => { - const rowUpperLimit = pageNumber * manageViewsRowsPerPage; - const currentItemsOnPage = views.slice( - rowUpperLimit - manageViewsRowsPerPage, - rowUpperLimit - ); - setManageViewsCurrentPageNumber(pageNumber); - setManageViewsCurrentPageItems(currentItemsOnPage); - }; + setManageViewsFilteredViews(views); + showPage(1, views); + }; - const applyFiltering = ({ searchTerm, showPublic }) => { - const views = viewsStorage - .filter( - (view) => - searchTerm === '' || - view.title.toLowerCase().search(searchTerm.toLowerCase()) !== - -1 - ) - .filter((view) => (showPublic ? view : !view.isPublic)); - - setManageViewsFilteredViews(views); - showPage(1, views); - }; + const onDelete = (viewId) => { + if (viewId === selectedView?.id) { + setSelectedViewEdited(false); + setSelectedView(undefined); + setCurrentTableState(baseState); + } - const onDelete = (viewId) => { - if (viewId === selectedView?.id) { - setSelectedViewEdited(false); - setSelectedView(undefined); - setCurrentTableState(baseState); - } + const deleteIndex = viewsStorage.findIndex( + (view) => view.id === viewId + ); + setViewsStorage((existingViews) => { + const modifiedViews = [...existingViews]; + modifiedViews.splice(deleteIndex, 1); + setManageViewsFilteredViews(modifiedViews); + showPage(1, modifiedViews); + return modifiedViews; + }); + }; - const deleteIndex = viewsStorage.findIndex( - (view) => view.id === viewId + return ( + { + const newFilters = { + ...manageViewsCurrentFilters, + showPublic, + }; + setManageViewsCurrentFilters(newFilters); + applyFiltering(newFilters); + }, + onSearchChange: (searchTerm = '') => { + const newFilters = { + ...manageViewsCurrentFilters, + searchTerm, + }; + setManageViewsCurrentFilters(newFilters); + applyFiltering(newFilters); + }, + onEdit: (viewId) => { + setManageViewsModalOpen(false); + const viewToEdit = viewsStorage.find( + (view) => view.id === viewId + ); + setSelectedView(viewToEdit); + setViewToSave(viewToEdit); + }, + onDelete, + onClearError: action('onClearManageViewsModalError'), + onClose: () => setManageViewsModalOpen(false), + }} + defaultViewId={defaultViewId} + error={select('error', [undefined, 'My error msg'], undefined)} + isLoading={boolean('isLoading', false)} + open={manageViewsModalOpen} + views={manageViewsCurrentPageItems} + pagination={{ + page: manageViewsCurrentPageNumber, + onPage: (pageNumber) => + showPage(pageNumber, manageViewsFilteredViews), + maxPage: Math.ceil( + manageViewsFilteredViews.length / manageViewsRowsPerPage + ), + pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, + }} + /> + ); + }; + + /** + * The TableViewDropdown is an external component that needs to be passed in + * via the customToolbarContent and positioned according to the applications needs. + * Most of the functionality in the TableViewDropdown can be overwritten. See the + * TableViewDropdown story for a more detailed documentation. + */ + const renderViewDropdown = () => { + return ( + { + setViewToSave({ + id: undefined, + ...extractCurrentUserView(currentTableState), + }); + }, + onManageViews: () => { + setManageViewsModalOpen(true); + setManageViewsCurrentPageItems( + viewsStorage.slice(0, manageViewsRowsPerPage) + ); + }, + onChangeView: ({ id }) => { + const selectedView = viewsStorage.find((view) => view.id === id); + setCurrentTableState(assign({}, baseState, selectedView?.props)); + setSelectedView(selectedView); + setSelectedViewEdited(false); + }, + onSaveChanges: () => { + setViewToSave({ + ...selectedView, + ...extractCurrentUserView(currentTableState), + }); + }, + }} + /> + ); + }; + + /** + * The TableSaveViewModal is a an external component that can be placed + * outside the table. Is is used both for saving new views and for + * updating existing ones. See the TableSaveViewModal story for a more + * detailed documentation. + */ + const renderSaveViewModal = () => { + const saveView = (viewMetaData) => { + setViewsStorage((existingViews) => { + const modifiedStorage = []; + const saveNew = viewToSave.id === undefined; + const { isDefault, ...metaDataToSave } = viewMetaData; + const generatedId = new Date().getTime().toString(); + + if (saveNew) { + const newViewToStore = { + ...viewToSave, + ...metaDataToSave, + id: generatedId, + isDeleteable: true, + isEditable: true, + }; + modifiedStorage.push(...existingViews, newViewToStore); + setSelectedView(newViewToStore); + } else { + const indexToUpdate = existingViews.findIndex( + (view) => view.id === viewToSave.id ); - setViewsStorage((existingViews) => { - const modifiedViews = [...existingViews]; - modifiedViews.splice(deleteIndex, 1); - setManageViewsFilteredViews(modifiedViews); - showPage(1, modifiedViews); - return modifiedViews; - }); - }; + const viewsCopy = [...existingViews]; + const modifiedViewToStore = { + ...viewToSave, + ...metaDataToSave, + }; + viewsCopy[indexToUpdate] = modifiedViewToStore; + setSelectedView(modifiedViewToStore); + modifiedStorage.push(...viewsCopy); + } - return ( - { - const newFilters = { - ...manageViewsCurrentFilters, - showPublic, - }; - setManageViewsCurrentFilters(newFilters); - applyFiltering(newFilters); - }, - onSearchChange: (searchTerm = '') => { - const newFilters = { - ...manageViewsCurrentFilters, - searchTerm, - }; - setManageViewsCurrentFilters(newFilters); - applyFiltering(newFilters); - }, - onEdit: (viewId) => { - setManageViewsModalOpen(false); - const viewToEdit = viewsStorage.find( - (view) => view.id === viewId - ); - setSelectedView(viewToEdit); - setViewToSave(viewToEdit); - }, - onDelete, - onClearError: action('onClearManageViewsModalError'), - onClose: () => setManageViewsModalOpen(false), - }} - defaultViewId={defaultViewId} - error={select('error', [undefined, 'My error msg'], undefined)} - isLoading={boolean('isLoading', false)} - open={manageViewsModalOpen} - views={manageViewsCurrentPageItems} - pagination={{ - page: manageViewsCurrentPageNumber, - onPage: (pageNumber) => - showPage(pageNumber, manageViewsFilteredViews), - maxPage: Math.ceil( - manageViewsFilteredViews.length / manageViewsRowsPerPage - ), - pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, - }} - /> - ); - }; - - /** - * The TableViewDropdown is an external component that needs to be passed in - * via the customToolbarContent and positioned according to the applications needs. - * Most of the functionality in the TableViewDropdown can be overwritten. See the - * TableViewDropdown story for a more detailed documentation. - */ - const renderViewDropdown = () => { - return ( - { - setViewToSave({ - id: undefined, - ...extractCurrentUserView(currentTableState), - }); - }, - onManageViews: () => { - setManageViewsModalOpen(true); - setManageViewsCurrentPageItems( - viewsStorage.slice(0, manageViewsRowsPerPage) - ); - }, - onChangeView: ({ id }) => { - const selectedView = viewsStorage.find( - (view) => view.id === id - ); - setCurrentTableState( - assign({}, baseState, selectedView?.props) - ); - setSelectedView(selectedView); - setSelectedViewEdited(false); - }, - onSaveChanges: () => { - setViewToSave({ - ...selectedView, - ...extractCurrentUserView(currentTableState), - }); - }, - }} - /> - ); - }; - - /** - * The TableSaveViewModal is a an external component that can be placed - * outside the table. Is is used both for saving new views and for - * updating existing ones. See the TableSaveViewModal story for a more - * detailed documentation. - */ - const renderSaveViewModal = () => { - const saveView = (viewMetaData) => { - setViewsStorage((existingViews) => { - const modifiedStorage = []; - const saveNew = viewToSave.id === undefined; - const { isDefault, ...metaDataToSave } = viewMetaData; - const generatedId = new Date().getTime().toString(); - - if (saveNew) { - const newViewToStore = { - ...viewToSave, - ...metaDataToSave, - id: generatedId, - isDeleteable: true, - isEditable: true, - }; - modifiedStorage.push(...existingViews, newViewToStore); - setSelectedView(newViewToStore); - } else { - const indexToUpdate = existingViews.findIndex( - (view) => view.id === viewToSave.id - ); - const viewsCopy = [...existingViews]; - const modifiedViewToStore = { - ...viewToSave, - ...metaDataToSave, - }; - viewsCopy[indexToUpdate] = modifiedViewToStore; - setSelectedView(modifiedViewToStore); - modifiedStorage.push(...viewsCopy); - } + if (isDefault) { + setDefaultViewId(saveNew ? generatedId : viewToSave.id); + } - if (isDefault) { - setDefaultViewId(saveNew ? generatedId : viewToSave.id); - } + setSelectedViewEdited(false); + return modifiedStorage; + }); + setViewToSave(undefined); + }; - setSelectedViewEdited(false); - return modifiedStorage; - }); - setViewToSave(undefined); - }; + // Simple description example that can be replaced by any string or node. + // See the TableSaveViewModal story for more examples. + const getDescription = ({ table, filters, toolbar }) => + `Columns: ${table.ordering.filter((col) => !col.isHidden).length}, + Filters: ${filters?.length || 0}, + Search: ${toolbar?.search?.defaultValue}`; - // Simple description example that can be replaced by any string or node. - // See the TableSaveViewModal story for more examples. - const getDescription = ({ table, filters, toolbar }) => - `Columns: ${table.ordering.filter((col) => !col.isHidden).length}, - Filters: ${filters?.length || 0}, - Search: ${toolbar?.search?.defaultValue}`; - - return ( - viewToSave && ( - { - setViewToSave(undefined); - }, - onClearError: action('onClearError'), - onChange: action('onChange'), - }} - sendingData={boolean('sendingData', false)} - error={select('error', [undefined, 'My error msg'], undefined)} - open - titleInputInvalid={boolean('titleInputInvalid', false)} - titleInputInvalidText={text('titleInputInvalidText', undefined)} - viewDescription={getDescription(viewToSave.props.view)} - initialFormValues={{ - title: viewToSave.title, - isPublic: viewToSave.isPublic, - isDefault: viewToSave.id === defaultViewId, - }} - i18n={{ - modalTitle: viewToSave.id ? 'Update view' : 'Save new view', - }} - /> - ) - ); - }; - - return ( - - {renderManageViewsModal()} - {renderSaveViewModal()} -
{ - setCurrentTableState((state) => ({ - ...state, - columns, - })); - }, - // Simplified sorting for this story. It does not update the data of the table - // and it ignores direction. - onChangeSort: (sortOnColumnId) => { - setCurrentTableState((state) => ({ - ...state, - view: { - ...state.view, - table: { - ...state.view.table, - sort: { - columnId: sortOnColumnId, - direction: 'DESC', - }, - }, - }, - })); - }, - }, - toolbar: { - ...actions.toolbar, - onApplySearch: (currentSearchValue) => { - // Here you can use debounce and call the backend to properly filter - // your data. For this story we simply update the search defaultValue. - setCurrentTableState((state) => ({ - ...state, - view: { - ...state.view, - toolbar: { - ...state.view.toolbar, - search: { defaultValue: currentSearchValue }, - }, - }, - })); - }, - onApplyFilter: (filters) => { - // Simplified filtering for this story. It does not update the data of - // the table only the actual filters. - setCurrentTableState((state) => ({ - ...state, - view: { - ...state.view, - filters: Object.entries(filters) - .filter(([, value]) => value !== '') - .map(([key, value]) => ({ - columnId: key, - value, - })), - }, - })); - }, - }, - }} - isSortable - lightweight={boolean('lightweight', false)} - options={{ - ...baseState.options, - hasResize: true, - hasFilter: select( - 'hasFilter', - ['onKeyPress', 'onEnterAndBlur'], - 'onKeyPress' - ), - wrapCellText: select( - 'wrapCellText', - selectTextWrapping, - 'always' - ), - // Enables the behaviour in Table required - // to fully implement Create and Save Views - hasUserViewManagement: true, - }} - /> - - ); - }); - }, - { - info: { - text: ` - This story shows a partial implementation of how to add user View Management, - but the implemented examples should be enough to give you an idea on how to use it - together with your own state manager. We examplify by providing shallow implementations - for onChangeSort, onApplySearch and onApplyFilter. The story is using a simple state - object currentTableState and the data objects in the callbacks are just appended to that - state using the same ref, but in a real situation the state management would be more complex. - The story's source code is too complex to successfully be shown here, please view - the actual source code. - `, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Stateful Example with pre-set multiselect filtering', - () => ( + return ( + viewToSave && ( + { + setViewToSave(undefined); + }, + onClearError: action('onClearError'), + onChange: action('onChange'), + }} + sendingData={boolean('sendingData', false)} + error={select('error', [undefined, 'My error msg'], undefined)} + open + titleInputInvalid={boolean('titleInputInvalid', false)} + titleInputInvalidText={text('titleInputInvalidText', undefined)} + viewDescription={getDescription(viewToSave.props.view)} + initialFormValues={{ + title: viewToSave.title, + isPublic: viewToSave.isPublic, + isDefault: viewToSave.id === defaultViewId, + }} + i18n={{ + modalTitle: viewToSave.id ? 'Update view' : 'Save new view', + }} + /> + ) + ); + }; + + return ( - { - if (column.filter) { - return { - ...column, - filter: { - ...column.filter, - isMultiselect: !!column.filter?.options, - }, - }; - } - return column; - })} + {...baseState} + columns={currentTableState.columns} view={{ - ...initialState.view, - pagination: { - ...initialState.view.pagination, - maxPages: 5, - }, + ...currentTableState.view, + // The TableViewDropdown should be inserted as customToolbarContent toolbar: { - activeBar: 'filter', + ...currentTableState.view.toolbar, + customToolbarContent: renderViewDropdown(), }, }} - secondaryTitle={text( - 'Secondary Title', - `Row count: ${initialState.data.length}` - )} - actions={actions} - isSortable - lightweight={boolean('lightweight', false)} - options={{ - ...initialState.options, - hasFilter: select( - 'hasFilter', - ['onKeyPress', 'onEnterAndBlur'], - 'onKeyPress' - ), - wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), - hasSingleRowEdit: true, - }} - /> - - ), - { - info: { - text: `This table has a multiselect filter. To support multiselect filtering, make sure to pass isMultiselect: true to the filter prop on the table.`, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Stateful Example with multiselect filtering', - () => ( - - { - if (column.filter) { - return { - ...column, - filter: { - ...column.filter, - isMultiselect: !!column.filter?.options, - }, - }; - } - return column; - })} - view={{ - ...initialState.view, - pagination: { - ...initialState.view.pagination, - maxPages: 5, + secondaryTitle="Table with user view management" + actions={{ + ...actions, + table: { + ...action.table, + onColumnResize: (columns) => { + setCurrentTableState((state) => ({ + ...state, + columns, + })); + }, + // Simplified sorting for this story. It does not update the data of the table + // and it ignores direction. + onChangeSort: (sortOnColumnId) => { + setCurrentTableState((state) => ({ + ...state, + view: { + ...state.view, + table: { + ...state.view.table, + sort: { + columnId: sortOnColumnId, + direction: 'DESC', + }, + }, + }, + })); + }, }, toolbar: { - activeBar: 'filter', + ...actions.toolbar, + onApplySearch: (currentSearchValue) => { + // Here you can use debounce and call the backend to properly filter + // your data. For this story we simply update the search defaultValue. + setCurrentTableState((state) => ({ + ...state, + view: { + ...state.view, + toolbar: { + ...state.view.toolbar, + search: { defaultValue: currentSearchValue }, + }, + }, + })); + }, + onApplyFilter: (filters) => { + // Simplified filtering for this story. It does not update the data of + // the table only the actual filters. + setCurrentTableState((state) => ({ + ...state, + view: { + ...state.view, + filters: Object.entries(filters) + .filter(([, value]) => value !== '') + .map(([key, value]) => ({ + columnId: key, + value, + })), + }, + })); + }, }, - filters: [], }} - secondaryTitle={text( - 'Secondary Title', - `Row count: ${initialState.data.length}` - )} - actions={actions} isSortable lightweight={boolean('lightweight', false)} options={{ - ...initialState.options, + ...baseState.options, + hasResize: true, hasFilter: select( 'hasFilter', ['onKeyPress', 'onEnterAndBlur'], 'onKeyPress' ), wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), - hasSingleRowEdit: true, + // Enables the behaviour in Table required + // to fully implement Create and Save Views + hasUserViewManagement: true, }} /> - ), - { - info: { - text: `This table has a multiselect filter. To support multiselect filtering, make sure to pass isMultiselect: true to the filter prop on the table.`, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Stateful Example with row nesting and fixed columns', - () => , - { - info: { - text: ` + ); + }); +}; + +TableExampleWithCreateSaveViews.story = { + name: 'Table Example with Create & Save Views', + + parameters: { + info: { + text: ` + This story shows a partial implementation of how to add user View Management, + but the implemented examples should be enough to give you an idea on how to use it + together with your own state manager. We examplify by providing shallow implementations + for onChangeSort, onApplySearch and onApplyFilter. The story is using a simple state + object currentTableState and the data objects in the callbacks are just appended to that + state using the same ref, but in a real situation the state management would be more complex. + The story's source code is too complex to successfully be shown here, please view + the actual source code. + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const StatefulExampleWithPreSetMultiselectFiltering = () => ( + + { + if (column.filter) { + return { + ...column, + filter: { + ...column.filter, + isMultiselect: !!column.filter?.options, + }, + }; + } + return column; + })} + view={{ + ...initialState.view, + pagination: { + ...initialState.view.pagination, + maxPages: 5, + }, + toolbar: { + activeBar: 'filter', + }, + }} + secondaryTitle={text( + 'Secondary Title', + `Row count: ${initialState.data.length}` + )} + actions={actions} + isSortable + lightweight={boolean('lightweight', false)} + options={{ + ...initialState.options, + hasFilter: select( + 'hasFilter', + ['onKeyPress', 'onEnterAndBlur'], + 'onKeyPress' + ), + wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), + hasSingleRowEdit: true, + }} + /> + +); - This stateful table has nested rows. To setup your table this way you must pass a children prop along with each of your data rows. +StatefulExampleWithPreSetMultiselectFiltering.story = { + name: 'Stateful Example with pre-set multiselect filtering', -
+ parameters: { + info: { + text: `This table has a multiselect filter. To support multiselect filtering, make sure to pass isMultiselect: true to the filter prop on the table.`, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; - ~~~js - data=[ - { - id: 'rowid', - values: { - col1: 'value1 +export const StatefulExampleWithMultiselectFiltering = () => ( + + { + if (column.filter) { + return { + ...column, + filter: { + ...column.filter, + isMultiselect: !!column.filter?.options, }, - children: [ - { - id: 'child-rowid, - values: { - col1: 'nested-value1' - } + }; + } + return column; + })} + view={{ + ...initialState.view, + pagination: { + ...initialState.view.pagination, + maxPages: 5, + }, + toolbar: { + activeBar: 'filter', + }, + filters: [], + }} + secondaryTitle={text( + 'Secondary Title', + `Row count: ${initialState.data.length}` + )} + actions={actions} + isSortable + lightweight={boolean('lightweight', false)} + options={{ + ...initialState.options, + hasFilter: select( + 'hasFilter', + ['onKeyPress', 'onEnterAndBlur'], + 'onKeyPress' + ), + wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), + hasSingleRowEdit: true, + }} + /> + +); + +StatefulExampleWithMultiselectFiltering.story = { + name: 'Stateful Example with multiselect filtering', + + parameters: { + info: { + text: `This table has a multiselect filter. To support multiselect filtering, make sure to pass isMultiselect: true to the filter prop on the table.`, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const StatefulExampleWithRowNestingAndFixedColumns = () => ( + +); + +StatefulExampleWithRowNestingAndFixedColumns.story = { + name: 'Stateful Example with row nesting and fixed columns', + + parameters: { + info: { + text: ` + + This stateful table has nested rows. To setup your table this way you must pass a children prop along with each of your data rows. + +
+ + ~~~js + data=[ + { + id: 'rowid', + values: { + col1: 'value1 + }, + children: [ + { + id: 'child-rowid, + values: { + col1: 'nested-value1' } - ] - } - ] - ~~~ + } + ] + } + ] + ~~~ + +
-
+ You must also set hasRowExpansion and hasRowNesting to true in your table options + +
+ + ~~~js + options={ + hasRowExpansion: true, + hasRowNesting: true + } + ~~~ - You must also set hasRowExpansion and hasRowNesting to true in your table options +
-
+ `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; - ~~~js - options={ - hasRowExpansion: true, - hasRowNesting: true - } - ~~~ +export const StatefulExampleWithSingleNestedHierarchy = () => { + const tableData = initialState.data.map((i, idx) => ({ + ...i, + children: [getNewRow(idx, 'A', true), getNewRow(idx, 'B', true)], + })); + return ( +
+ +
+ ); +}; -
+StatefulExampleWithSingleNestedHierarchy.story = { + name: 'Stateful Example with single nested hierarchy', - `, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Stateful Example with single nested hierarchy', - () => { - const tableData = initialState.data.map((i, idx) => ({ - ...i, - children: [getNewRow(idx, 'A', true), getNewRow(idx, 'B', true)], - })); - return ( -
- -
- ); - }, - { - info: { - text: ` + parameters: { + info: { + text: ` - This stateful table has nested rows. To setup your table this way you must pass a children prop along with each of your data rows. - In addition, if there is a single level of row nesting, hasRowNesting can be customized to add additional styling seen in this story + This stateful table has nested rows. To setup your table this way you must pass a children prop along with each of your data rows. + In addition, if there is a single level of row nesting, hasRowNesting can be customized to add additional styling seen in this story -
+
- ~~~js - data=[ - { - id: 'rowid', - values: { - col1: 'value1 - }, - children: [ - { - id: 'child-rowid, - values: { - col1: 'nested-value1' - } + ~~~js + data=[ + { + id: 'rowid', + values: { + col1: 'value1 + }, + children: [ + { + id: 'child-rowid, + values: { + col1: 'nested-value1' } - ] - } - ] - ~~~ + } + ] + } + ] + ~~~ -
+
- You must also set hasRowExpansion to true and hasRowNesting to an object with hasSingleLevelRowNesting to true in your table options + You must also set hasRowExpansion to true and hasRowNesting to an object with hasSingleLevelRowNesting to true in your table options -
+
- ~~~js - options={ - hasRowExpansion: true, - hasRowNesting: { - hasSingleLevelRowNesting: true - } + ~~~js + options={ + hasRowExpansion: true, + hasRowNesting: { + hasSingleLevelRowNesting: true } - ~~~ + } + ~~~ -
+
- `, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'Basic table with full rowEdit example', - () => { - return React.createElement(() => { - const [showRowEditBar, setShowRowEditBar] = useState(false); - const startingData = tableData.map((i) => ({ - ...i, - rowActions: [ - { - id: 'edit', - renderIcon: Edit, - iconDescription: 'Edit', - labelText: 'Edit', - isOverflow: true, - isEdit: true, - }, - ], - })); - const [currentData, setCurrentData] = useState(startingData); - const [rowEditedData, setRowEditedData] = useState([]); - const [previousData, setPreviousData] = useState([]); - const [showToast, setShowToast] = useState(false); - const [rowActionsState, setRowActionsState] = useState([]); - - const onDataChange = (e, columnId, rowId) => { - const newValue = e.currentTarget ? e.currentTarget.value : e; - rowEditedData.find((row) => row.id === rowId).values[ - columnId - ] = newValue; - }; - - const onShowMultiRowEdit = () => { - setRowEditedData(cloneDeep(currentData)); - setShowRowEditBar(true); - setShowToast(false); - }; - const onCancelRowEdit = () => { - setRowEditedData([]); - setShowRowEditBar(false); - setRowActionsState([]); - }; - const onSaveRowEdit = () => { - setShowToast(true); - setPreviousData(currentData); - setCurrentData(rowEditedData); - setRowEditedData([]); - setShowRowEditBar(false); - setRowActionsState([]); - }; - const onUndoRowEdit = () => { - setCurrentData(previousData); - setPreviousData([]); - setShowToast(false); - }; - - const onApplyRowAction = (action, rowId) => { - if (action === 'edit') { - setRowEditedData(cloneDeep(currentData)); - setRowActionsState([ - ...rowActionsState, - { rowId, isEditMode: true }, - ]); - } - }; + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const BasicTableWithFullRowEditExample = () => { + return React.createElement(() => { + const [showRowEditBar, setShowRowEditBar] = useState(false); + const startingData = tableData.map((i) => ({ + ...i, + rowActions: [ + { + id: 'edit', + renderIcon: Edit, + iconDescription: 'Edit', + labelText: 'Edit', + isOverflow: true, + isEdit: true, + }, + ], + })); + const [currentData, setCurrentData] = useState(startingData); + const [rowEditedData, setRowEditedData] = useState([]); + const [previousData, setPreviousData] = useState([]); + const [showToast, setShowToast] = useState(false); + const [rowActionsState, setRowActionsState] = useState([]); + + const onDataChange = (e, columnId, rowId) => { + const newValue = e.currentTarget ? e.currentTarget.value : e; + rowEditedData.find((row) => row.id === rowId).values[columnId] = newValue; + }; + + const onShowMultiRowEdit = () => { + setRowEditedData(cloneDeep(currentData)); + setShowRowEditBar(true); + setShowToast(false); + }; + const onCancelRowEdit = () => { + setRowEditedData([]); + setShowRowEditBar(false); + setRowActionsState([]); + }; + const onSaveRowEdit = () => { + setShowToast(true); + setPreviousData(currentData); + setCurrentData(rowEditedData); + setRowEditedData([]); + setShowRowEditBar(false); + setRowActionsState([]); + }; + const onUndoRowEdit = () => { + setCurrentData(previousData); + setPreviousData([]); + setShowToast(false); + }; + + const onApplyRowAction = (action, rowId) => { + if (action === 'edit') { + setRowEditedData(cloneDeep(currentData)); + setRowActionsState([...rowActionsState, { rowId, isEditMode: true }]); + } + }; + + // The app should handle i18n and button enable state, e.g. that the save button + // is disabled when the input controls are pristine. + const saveCancelButtons = ( + + + + + ); - // The app should handle i18n and button enable state, e.g. that the save button - // is disabled when the input controls are pristine. - const saveCancelButtons = ( - + // This is a simplified example. + // The app should handle input validation and types like dates, select etc + const editDataFunction = ({ value, columnId, rowId }) => { + const id = `${columnId}-${rowId}`; + return React.isValidElement(value) ? ( + value + ) : typeof value === 'boolean' ? ( + onDataChange(e, columnId, rowId)} + /> + ) : ( + onDataChange(e, columnId, rowId)} + type="text" + light + defaultValue={value} + labelText="" + hideLabel + /> + ); + }; + + const myToast = ( + + Changed your mind? - - - ); + + } + timeout={5000} + title="Your changes have been saved." + /> + ); - // This is a simplified example. - // The app should handle input validation and types like dates, select etc - const editDataFunction = ({ value, columnId, rowId }) => { - const id = `${columnId}-${rowId}`; - return React.isValidElement(value) ? ( - value - ) : typeof value === 'boolean' ? ( - onDataChange(e, columnId, rowId)} - /> - ) : ( - onDataChange(e, columnId, rowId)} - type="text" - light - defaultValue={value} - labelText="" - hideLabel - /> - ); - }; - - const myToast = ( - - Changed your mind? - - - } - timeout={5000} - title="Your changes have been saved." - /> - ); + return ( +
+ {showToast ? myToast : null} +
({ ...i, editDataFunction }))} + /> + + ); + }); +}; - return ( -
- {showToast ? myToast : null} -
({ ...i, editDataFunction }))} - /> - - ); - }); - }, - { - info: { - text: ` +BasicTableWithFullRowEditExample.story = { + name: 'Basic table with full rowEdit example', - This table has editable rows. It is wrapped in a component that handles the state of the table data and - the active bar to serve as a simple example of how to use the 'hasRowEdit' and the 'hasSingleRowEdit' - functionality with your own data store. + parameters: { + info: { + text: ` - When the 'hasRowEdit' is true an edit icon will be shown in the - table toolbar. Clicking the edit icon should enable row edit for all rows, but it requires the - columns to have an 'editDataFunction' prop defined. For StatefulTable this is handled automatically, for normal tables it - should be handled manually as shown in this story. + This table has editable rows. It is wrapped in a component that handles the state of the table data and + the active bar to serve as a simple example of how to use the 'hasRowEdit' and the 'hasSingleRowEdit' + functionality with your own data store. - The 'hasSingleRowEdit' must be combined with a row action that has the "isEdit" property set to true. - Clicking that row action shoulf turn that specific row editable, and it also requires the columns to have - provided a 'editDataFunction'. For StatefulTable the row action state is automatically updated with - isEditMode:true but for normal tables it should be handled manually as shown in this story. + When the 'hasRowEdit' is true an edit icon will be shown in the + table toolbar. Clicking the edit icon should enable row edit for all rows, but it requires the + columns to have an 'editDataFunction' prop defined. For StatefulTable this is handled automatically, for normal tables it + should be handled manually as shown in this story. + The 'hasSingleRowEdit' must be combined with a row action that has the "isEdit" property set to true. + Clicking that row action shoulf turn that specific row editable, and it also requires the columns to have + provided a 'editDataFunction'. For StatefulTable the row action state is automatically updated with + isEditMode:true but for normal tables it should be handled manually as shown in this story. - ~~~js - view = { - toolbar: { - activeBar: // conditionally set to 'rowEdit' using onShowRowEdit action - rowEditBarButtons: // JSX to show save and cancel buttons in the rowEdit bar - } - } + ~~~js - actions = { - table: { onApplyRowAction: (action, rowId) => { - // Handle action === 'edit' to enable the rows edit mode - } }, - toolbar: { onShowRowEdit: (action, rowId) => { - // Update your state to enable full table edit mode - } }, + view = { + toolbar: { + activeBar: // conditionally set to 'rowEdit' using onShowRowEdit action + rowEditBarButtons: // JSX to show save and cancel buttons in the rowEdit bar } + } - options = { hasRowEdit: true, hasSingleRowEdit: true } + actions = { + table: { onApplyRowAction: (action, rowId) => { + // Handle action === 'edit' to enable the rows edit mode + } }, + toolbar: { onShowRowEdit: (action, rowId) => { + // Update your state to enable full table edit mode + } }, + } - columns={columns.map(i => ({ - ...i, - editDataFunction: () => { - // Your edit data function here.. - }, - }))} + options = { hasRowEdit: true, hasSingleRowEdit: true } - The editDataFunction is called with this payload - { - value: PropTypes.any (current cell value), - columnId: PropTypes.string, - rowId: PropTypes.string, - row: the full data for this rowPropTypes.object like this {col: value, col2: value} - } - ~~~ + columns={columns.map(i => ({ + ...i, + editDataFunction: () => { + // Your edit data function here.. + }, + }))} - `, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'basic `dumb` table', - () => ( -
- ), - { - info: { - text: ` + The editDataFunction is called with this payload + { + value: PropTypes.any (current cell value), + columnId: PropTypes.string, + rowId: PropTypes.string, + row: the full data for this rowPropTypes.object like this {col: value, col2: value} + } + ~~~ + + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; - For basic table support, you can render the functional
component with only the columns and data props. This table does not have any state management built in. If you want that, use the component or you will need to implement your own listeners and state management. You can reuse our tableReducer and tableActions with the useReducer hook to update state. +export const BasicDumbTable = () => ( +
+); -
+BasicDumbTable.story = { + name: 'basic `dumb` table', - To enable simple search on a table, simply set the prop options.hasSearch=true. We wouldn't recommend enabling column filters on a table and simple search for UX reasons, but it is supported. + parameters: { + info: { + text: ` -
+ For basic table support, you can render the functional
component with only the columns and data props. This table does not have any state management built in. If you want that, use the component or you will need to implement your own listeners and state management. You can reuse our tableReducer and tableActions with the useReducer hook to update state. - Warning: Searching, filtering, and sorting is only enabled for strings, numbers, and booleans. +
-
+ To enable simple search on a table, simply set the prop options.hasSearch=true. We wouldn't recommend enabling column filters on a table and simple search for UX reasons, but it is supported. - ~~~js - import { tableReducer, tableActions } from 'carbon-addons-iot-react'; +
- const [state, dispatch] = useReducer(tableReducer, { data: initialData, view: initialState }); + Warning: Searching, filtering, and sorting is only enabled for strings, numbers, and booleans. - const actions = { - table: { - onChangeSort: column => { - dispatch(tableActions.tableColumnSort(column)); - }, - } +
+ + ~~~js + import { tableReducer, tableActions } from 'carbon-addons-iot-react'; + + const [state, dispatch] = useReducer(tableReducer, { data: initialData, view: initialState }); + + const actions = { + table: { + onChangeSort: column => { + dispatch(tableActions.tableColumnSort(column)); + }, } + } -
- `, - }, - } - ) - .add( - 'minitable', - () => ( - - ), - { - info: { - text: `The table will automatically adjust to narrow mode if you set a style or class that makes max-width smaller than 600 pixels (which is the width needed to render the full pagination controls) `, - }, - } - ) - .add( - 'with pre-filled search', - () => { - return React.createElement(() => { - const [defaultValue, setDefaultValue] = useState('toyota'); - const sampleDefaultValues = ['whiteboard', 'scott', 'helping']; - return ( - <> -

- Click the button below to demonstrate updating the pre-filled - search (defaultValue) via state/props -

- - - - - ); - }); +
+ `, }, - { - info: { - text: `The table will pre-fill a search value, expand the search input and trigger a search`, - }, - } - ) - .add('with multi select and batch actions', () => { - const selectedTableType = select( - 'Type of Table', - ['Table', 'StatefulTable'], - 'StatefulTable' - ); - const MyTable = - selectedTableType === 'StatefulTable' ? StatefulTable : Table; + }, +}; + +export const Minitable = () => ( + +); + +Minitable.story = { + name: 'minitable', + parameters: { + info: { + text: `The table will automatically adjust to narrow mode if you set a style or class that makes max-width smaller than 600 pixels (which is the width needed to render the full pagination controls) `, + }, + }, +}; + +export const WithPreFilledSearch = () => { + return React.createElement(() => { + const [defaultValue, setDefaultValue] = useState('toyota'); + const sampleDefaultValues = ['whiteboard', 'scott', 'helping']; return ( - +

+ Click the button below to demonstrate updating the pre-filled search + (defaultValue) via state/props +

+ + + + }, + }} + /> + ); - }) - .add('with single select', () => ( -
{ + const selectedTableType = select( + 'Type of Table', + ['Table', 'StatefulTable'], + 'StatefulTable' + ); + const MyTable = selectedTableType === 'StatefulTable' ? StatefulTable : Table; + + return ( + - )) - .add('with single select and nested table rows ', () => ( -
({ - ...i, - children: - idx === 3 - ? [getNewRow(idx, 'A'), getNewRow(idx, 'B')] - : idx === 7 - ? [ - getNewRow(idx, 'A'), - { - ...getNewRow(idx, 'B'), - children: [getNewRow(idx, 'B-1'), getNewRow(idx, 'B-2')], - }, - getNewRow(idx, 'C'), - { - ...getNewRow(idx, 'D'), - children: [ - getNewRow(idx, 'D-1'), - getNewRow(idx, 'D-2'), - getNewRow(idx, 'D-3'), - ], - }, - ] - : undefined, - }))} options={{ + hasFilter: true, hasPagination: true, hasRowSelection: 'multi', - hasRowExpansion: true, - hasRowNesting: true, - }} - actions={actions} - view={{ - table: { - expandedIds: ['row-3', 'row-7', 'row-7_B'], - selectedIds: ['row-3_A'], - }, - }} - /> - )) - .add('with row expansion and on row click expands', () => ( -
, - }, + toolbar: { + batchActions: [ { - rowId: 'row-5', - content: , + id: 'delete', + labelText: 'Delete', + renderIcon: TrashCan16, + iconDescription: 'Delete Item', }, ], }, + table: { + ordering: defaultOrdering, + isSelectAllSelected: select( + 'isSelectAllSelected', + [undefined, true, false], + undefined + ), + isSelectAllIndeterminate: select( + 'isSelectAllIndeterminate', + [undefined, true, false], + undefined + ), + selectedIds: array('selectedIds', [ + 'row-3', + 'row-4', + 'row-6', + 'row-7', + ]), + }, }} /> - )) - .add( - 'with row expansion and actions', - () => ( -
({ - ...i, - rowActions: - idx % 4 === 0 // every 4th row shouldn't have any actions - ? [] - : [ - { - id: 'drilldown', - renderIcon: Arrow, - iconDescription: 'See more', - labelText: 'See more', - }, - { - id: 'add', - renderIcon: Add, - iconDescription: 'Add', - labelText: 'Add', - isOverflow: true, - hasDivider: true, - }, - { - id: 'delete', - renderIcon: TrashCan16, - iconDescription: 'Delete', - labelText: 'Delete', - isOverflow: true, - isDelete: true, - }, - ].filter((i) => i), - }))} - actions={actions} - options={{ - hasRowExpansion: true, - hasRowActions: true, - }} - view={{ - filters: [], - table: { - ordering: defaultOrdering, - expandedRows: [ + ); +}; + +WithMultiSelectAndBatchActions.story = { + name: 'with multi select and batch actions', +}; + +export const WithSingleSelect = () => ( +
+); + +WithSingleSelect.story = { + name: 'with single select', +}; + +export const WithSingleSelectAndNestedTableRows = () => ( +
({ + ...i, + children: + idx === 3 + ? [getNewRow(idx, 'A'), getNewRow(idx, 'B')] + : idx === 7 + ? [ + getNewRow(idx, 'A'), + { + ...getNewRow(idx, 'B'), + children: [getNewRow(idx, 'B-1'), getNewRow(idx, 'B-2')], + }, + getNewRow(idx, 'C'), { - rowId: 'row-2', - content: , + ...getNewRow(idx, 'D'), + children: [ + getNewRow(idx, 'D-1'), + getNewRow(idx, 'D-2'), + getNewRow(idx, 'D-3'), + ], }, + ] + : undefined, + }))} + options={{ + hasPagination: true, + hasRowSelection: 'multi', + hasRowExpansion: true, + hasRowNesting: true, + }} + actions={actions} + view={{ + table: { + expandedIds: ['row-3', 'row-7', 'row-7_B'], + selectedIds: ['row-3_A'], + }, + }} + /> +); + +WithSingleSelectAndNestedTableRows.story = { + name: 'with single select and nested table rows ', +}; + +export const WithRowExpansionAndOnRowClickExpands = () => ( +
, + }, + { + rowId: 'row-5', + content: , + }, + ], + }, + }} + /> +); + +WithRowExpansionAndOnRowClickExpands.story = { + name: 'with row expansion and on row click expands', +}; + +export const WithRowExpansionAndActions = () => ( +
({ + ...i, + rowActions: + idx % 4 === 0 // every 4th row shouldn't have any actions + ? [] + : [ { - rowId: 'row-5', - content: , + id: 'drilldown', + renderIcon: Arrow, + iconDescription: 'See more', + labelText: 'See more', }, - ], - rowActions: [ { - rowId: 'row-1', - isRunning: true, + id: 'add', + renderIcon: Add, + iconDescription: 'Add', + labelText: 'Add', + isOverflow: true, + hasDivider: true, }, { - rowId: 'row-3', - error: { - title: 'Import failed', - message: 'Contact your administrator', - }, + id: 'delete', + renderIcon: TrashCan16, + iconDescription: 'Delete', + labelText: 'Delete', + isOverflow: true, + isDelete: true, }, - ], + ].filter((i) => i), + }))} + actions={actions} + options={{ + hasRowExpansion: true, + hasRowActions: true, + }} + view={{ + filters: [], + table: { + ordering: defaultOrdering, + expandedRows: [ + { + rowId: 'row-2', + content: , }, - }} - /> - ), - { - info: { - text: ` - - To add custom row actions to each row you need to pass a rowActions array along with every row of your data. The RowActionsPropTypes is defined as: - -
- - ~~~js - RowActionPropTypes = PropTypes.arrayOf( - PropTypes.shape({ - /** Unique id of the action */ - id: PropTypes.string.isRequired, - /** icon ultimately gets passed through all the way to
({ + ...i, + isSortable: idx !== 1, + }))} + data={getSortedData(tableData, 'string', 'ASC')} + actions={actions} + options={{ + hasFilter: false, + hasPagination: true, + hasRowSelection: 'multi', + }} + view={{ + filters: [], + table: { + ordering: defaultOrdering, + sort: { + columnId: 'string', + direction: 'ASC', + }, }, - } - ) - .add('with sorting', () => ( + }} + /> +); + +WithSorting.story = { + name: 'with sorting', +}; + +export const WithCustomCellRenderer = () => { + const renderDataFunction = ({ value }) => ( +
{value}
+ ); + return (
({ + id="table" + columns={tableColumns.map((i) => ({ ...i, - isSortable: idx !== 1, + renderDataFunction, }))} - data={getSortedData(tableData, 'string', 'ASC')} + data={tableData} actions={actions} options={{ - hasFilter: false, + hasFilter: true, hasPagination: true, hasRowSelection: 'multi', }} @@ -2782,712 +2848,710 @@ storiesOf('Watson IoT/Table', module) }, }} /> - )) - .add( - 'with custom cell renderer', - () => { - const renderDataFunction = ({ value }) => ( -
{value}
- ); - return ( -
({ - ...i, - renderDataFunction, - }))} - data={tableData} - actions={actions} - options={{ - hasFilter: true, - hasPagination: true, - hasRowSelection: 'multi', - }} - view={{ - filters: [], - table: { - ordering: defaultOrdering, - sort: { - columnId: 'string', - direction: 'ASC', - }, - }, - }} - /> - ); + ); +}; + +WithCustomCellRenderer.story = { + name: 'with custom cell renderer', + + parameters: { + info: { + text: `To render a custom widget in a table cell, pass a renderDataFunction prop along with your column metadata. + +
+ + ~~~js + The renderDataFunction is called with this payload + { + value: PropTypes.any (current cell value), + columnId: PropTypes.string, + rowId: PropTypes.string, + row: the full data for this rowPropTypes.object like this {col: value, col2: value} + } + ~~~ + +
+ `, }, - { - info: { - text: `To render a custom widget in a table cell, pass a renderDataFunction prop along with your column metadata. - -
- - ~~~js - The renderDataFunction is called with this payload - { - value: PropTypes.any (current cell value), - columnId: PropTypes.string, - rowId: PropTypes.string, - row: the full data for this rowPropTypes.object like this {col: value, col2: value} - } - ~~~ - -
- `, + }, +}; + +export const WithFilters = () => { + const filteredData = tableData.filter(({ values }) => + // return false if a value doesn't match a valid filter + [ + { + columnId: 'string', + value: 'whiteboard', }, - } - ) - .add('with filters', () => { - const filteredData = tableData.filter(({ values }) => - // return false if a value doesn't match a valid filter - [ - { - columnId: 'string', - value: 'whiteboard', - }, - { - columnId: 'select', - value: 'option-B', - }, - ].reduce( - (acc, { columnId, value }) => - acc && values[columnId].toString().includes(value), - true - ) - ); - return ( -
- ); - }) - .add('with column selection', () => ( -
- )) - .add('with no results', () => ( -
- )) - .add('with no data', () => ( + { + columnId: 'select', + value: 'option-B', + }, + ].reduce( + (acc, { columnId, value }) => + acc && values[columnId].toString().includes(value), + true + ) + ); + return (
- )) - .add('with nested table rows', () => ( -
({ - ...i, - children: - idx === 3 - ? [getNewRow(idx, 'A'), getNewRow(idx, 'B')] - : idx === 7 - ? [ - getNewRow(idx, 'A'), - { - ...getNewRow(idx, 'B'), - children: [getNewRow(idx, 'B-1'), getNewRow(idx, 'B-2')], - }, - getNewRow(idx, 'C'), - { - ...getNewRow(idx, 'D'), - children: [ - getNewRow(idx, 'D-1'), - getNewRow(idx, 'D-2'), - getNewRow(idx, 'D-3'), - ], - }, - ] - : undefined, - }))} options={{ + hasFilter: true, hasPagination: true, hasRowSelection: 'multi', - hasRowExpansion: true, - hasRowNesting: true, }} - actions={actions} view={{ - table: { - expandedIds: ['row-3', 'row-7', 'row-7_B'], + filters: [ + { + columnId: 'string', + value: 'whiteboard', + }, + { + columnId: 'select', + value: 'option-B', + }, + ], + pagination: { + totalItems: filteredData.length, + }, + toolbar: { + activeBar: 'filter', }, - }} - /> - )) - .add('with no data and custom empty state', () => ( -
-

Custom empty state

-

Hey, no data!

- - ), }, }} - options={{ hasPagination: true }} /> - )) - .add('with loading state', () => ( + ); +}; + +WithFilters.story = { + name: 'with filters', +}; + +export const WithColumnSelection = () => ( +
+); + +WithColumnSelection.story = { + name: 'with column selection', +}; + +export const WithNoResults = () => ( +
+); + +WithNoResults.story = { + name: 'with no results', +}; + +export const WithNoData = () => ( +
+); + +WithNoData.story = { + name: 'with no data', +}; + +export const WithNestedTableRows = () => ( +
({ + ...i, + children: + idx === 3 + ? [getNewRow(idx, 'A'), getNewRow(idx, 'B')] + : idx === 7 + ? [ + getNewRow(idx, 'A'), + { + ...getNewRow(idx, 'B'), + children: [getNewRow(idx, 'B-1'), getNewRow(idx, 'B-2')], + }, + getNewRow(idx, 'C'), + { + ...getNewRow(idx, 'D'), + children: [ + getNewRow(idx, 'D-1'), + getNewRow(idx, 'D-2'), + getNewRow(idx, 'D-3'), + ], + }, + ] + : undefined, + }))} + options={{ + hasPagination: true, + hasRowSelection: 'multi', + hasRowExpansion: true, + hasRowNesting: true, + }} + actions={actions} + view={{ + table: { + expandedIds: ['row-3', 'row-7', 'row-7_B'], + }, + }} + /> +); + +WithNestedTableRows.story = { + name: 'with nested table rows', +}; + +export const WithNoDataAndCustomEmptyState = () => ( +
+

Custom empty state

+

Hey, no data!

+ + ), + }, + }} + options={{ hasPagination: true }} + /> +); + +WithNoDataAndCustomEmptyState.story = { + name: 'with no data and custom empty state', +}; + +export const WithLoadingState = () => ( +
+); + +WithLoadingState.story = { + name: 'with loading state', +}; + +export const WithZebraStriping = () => ( +
+); + +WithZebraStriping.story = { + name: 'with zebra striping', +}; + +export const WithResizeAndInitialColumnWidthsOnSimpleStatefulWithRowSelectionSort = () => ( + ({ + width: idx % 2 === 0 ? '100px' : '200px', + isSortable: true, + ...i, + }))} + options={{ + hasRowSelection: select('hasRowSelection', ['multi', 'single'], 'multi'), + hasRowExpansion: false, + hasResize: true, + wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), + }} + view={{ table: { selectedIds: array('selectedIds', []) } }} + /> +); + +WithResizeAndInitialColumnWidthsOnSimpleStatefulWithRowSelectionSort.story = { + name: + 'with resize and initial column widths on Simple Stateful with row selection & sort', +}; + +export const WithResizeAndInitialColumnWidthsAndHiddenColumn = () => ( +
({ + width: idx % 2 === 0 ? '100px' : '200px', + ...i, + }))} data={tableData} actions={actions} view={{ table: { ordering: defaultOrdering, - loadingState: { - isLoading: true, - rowCount: 7, - }, }, }} /> - )) - .add('with zebra striping', () => ( -
- )) - .add( - 'with resize and initial column widths on Simple Stateful with row selection & sort', - () => ( - ({ - width: idx % 2 === 0 ? '100px' : '200px', - isSortable: true, - ...i, - }))} - options={{ - hasRowSelection: select( - 'hasRowSelection', - ['multi', 'single'], - 'multi' - ), - hasRowExpansion: false, - hasResize: true, - wrapCellText: select('wrapCellText', selectTextWrapping, 'always'), - }} - view={{ table: { selectedIds: array('selectedIds', []) } }} - /> - ) - ) - .add( - 'with resize and initial column widths and hidden column', - () => ( - + +); + +WithResizeAndInitialColumnWidthsAndHiddenColumn.story = { + name: 'with resize and initial column widths and hidden column', + + parameters: { + info: { + source: true, + propTables: false, + }, + }, +}; + +export const WithResizeHasColumnSelectionAndInitialColumnWidths = () => ( + ({ + width: idx % 2 === 0 ? '100px' : '200px', + ...i, + }))} + data={tableData} + actions={actions} + view={{ + table: { + ordering: defaultOrdering, + }, + }} + /> +); + +WithResizeHasColumnSelectionAndInitialColumnWidths.story = { + name: 'with resize, hasColumnSelection and initial column widths', + + parameters: { + info: { + source: true, + propTables: false, + }, + }, +}; + +export const WithResizeOnColumnResizeCallbackNoInitialColumnWidthAndColumnManagement = () => { + const ColumnsModifier = ({ onAdd, onRemove, columns, ordering }) => { + const [colsToAddField, setColsToAddField] = useState('colX, colY'); + const [colsToAddWidthField, setColsToAddWidthField] = useState( + '100px, 150px' + ); + const [colsToDeleteField, setColsToDeleteField] = useState( + 'select, status' + ); + const [isHidden, setIsHidden] = useState(false); + + return ( +
+
+ setColsToAddField(evt.currentTarget.value)} + /> + + setIsHidden(!isHidden)} + /> + + setColsToAddWidthField(evt.currentTarget.value)} + /> + + +
+
+

COLUMNS prop

+ {JSON.stringify(columns)} +
+
+

ORDERING prop

+ {JSON.stringify(ordering)} +
+
+ +
+ setColsToDeleteField(evt.currentTarget.value)} + /> + + +
+ ); + }; + + return React.createElement(() => { + const [myColumns, setMyColumns] = useState( + tableColumns.map(({ filter, ...rest }) => rest) + ); + const [myOrdering, setMyOrdering] = useState(defaultOrdering); + + const onAdd = (colIds, colWidths, isHidden) => { + const colsToAdd = colIds.split(', '); + const widths = colWidths.split(', '); + const newColumns = []; + const newOrdering = []; + colsToAdd.forEach((colToAddId, index) => { + newColumns.push({ + id: colToAddId, + name: colToAddId, + width: widths[index] || undefined, + }); + newOrdering.push({ columnId: colToAddId, isHidden }); + }); + setMyColumns([...myColumns, ...newColumns]); + setMyOrdering([...myOrdering, ...newOrdering]); + }; + + const onRemove = (colIds) => { + const colsToDelete = colIds.split(', '); + setMyColumns(myColumns.filter((col) => !colsToDelete.includes(col.id))); + setMyOrdering( + myOrdering.filter((col) => !colsToDelete.includes(col.columnId)) + ); + }; + const onColumnResize = (cols) => setMyColumns(cols); + + return ( + <> +
({ - width: idx % 2 === 0 ? '100px' : '200px', - ...i, - }))} - data={tableData} - actions={actions} + columns={myColumns} view={{ + filters: [], table: { - ordering: defaultOrdering, + ordering: myOrdering, }, }} + data={tableData} + actions={{ + ...actions, + table: { ...actions.table, onColumnResize }, + }} /> - - ), - { - info: { - source: true, - propTables: false, - }, - } - ) - .add( - 'with resize, hasColumnSelection and initial column widths', - () => ( - + ); + }); +}; + +WithResizeOnColumnResizeCallbackNoInitialColumnWidthAndColumnManagement.story = { + name: + 'with resize, onColumnResize callback, no initial column width and column management', + + parameters: { + info: { + source: true, + propTables: false, + }, + }, +}; + +export const WithResizeAndNoInitialColumnWidthAndAutoAdjustedColumnWidths = () => ( + +

+ Note!
+ For this configuration to work, the table must be wrapped in a container + that has a width defined in other than %.
+ E.g. the FullWidthWrapper used by the storybook examples. +

+ +
({ - width: idx % 2 === 0 ? '100px' : '200px', - ...i, - }))} + columns={tableColumns} data={tableData} actions={actions} - view={{ - table: { - ordering: defaultOrdering, - }, - }} /> - ), - { - info: { - source: true, - propTables: false, - }, - } - ) - .add( - 'with resize, onColumnResize callback, no initial column width and column management', - () => { - const ColumnsModifier = ({ onAdd, onRemove, columns, ordering }) => { - const [colsToAddField, setColsToAddField] = useState('colX, colY'); - const [colsToAddWidthField, setColsToAddWidthField] = useState( - '100px, 150px' - ); - const [colsToDeleteField, setColsToDeleteField] = useState( - 'select, status' - ); - const [isHidden, setIsHidden] = useState(false); - - return ( -
-
- setColsToAddField(evt.currentTarget.value)} - /> - - setIsHidden(!isHidden)} - /> - - - setColsToAddWidthField(evt.currentTarget.value) - } - /> - - -
-
-

COLUMNS prop

- {JSON.stringify(columns)} -
-
-

ORDERING prop

- {JSON.stringify(ordering)} -
-
- -
- - setColsToDeleteField(evt.currentTarget.value) - } - /> - - -
- ); - }; + + +); - return React.createElement(() => { - const [myColumns, setMyColumns] = useState( - tableColumns.map(({ filter, ...rest }) => rest) - ); - const [myOrdering, setMyOrdering] = useState(defaultOrdering); - - const onAdd = (colIds, colWidths, isHidden) => { - const colsToAdd = colIds.split(', '); - const widths = colWidths.split(', '); - const newColumns = []; - const newOrdering = []; - colsToAdd.forEach((colToAddId, index) => { - newColumns.push({ - id: colToAddId, - name: colToAddId, - width: widths[index] || undefined, - }); - newOrdering.push({ columnId: colToAddId, isHidden }); - }); - setMyColumns([...myColumns, ...newColumns]); - setMyOrdering([...myOrdering, ...newOrdering]); - }; - - const onRemove = (colIds) => { - const colsToDelete = colIds.split(', '); - setMyColumns( - myColumns.filter((col) => !colsToDelete.includes(col.id)) - ); - setMyOrdering( - myOrdering.filter((col) => !colsToDelete.includes(col.columnId)) - ); - }; - const onColumnResize = (cols) => setMyColumns(cols); - - return ( - <> - -
- - ); - }); - }, - { - info: { - source: true, - propTables: false, - }, - } - ) - .add( +WithResizeAndNoInitialColumnWidthAndAutoAdjustedColumnWidths.story = { + name: 'with resize and no initial column width and auto adjusted column widths', - () => ( - -

- Note!
- For this configuration to work, the table must be wrapped in a - container that has a width defined in other than %.
- E.g. the FullWidthWrapper used by the storybook examples. -

- -
- - - ), - { - info: { - source: true, - propTables: false, - }, - } - ) - .add( - 'with fixed column width and no resize', - () => ( - // You don't need to use styled components, just pass a className to the Table component and use selectors to find the correct column - -
({ - width: idx % 2 === 0 ? '20rem' : '10rem', - ...i, - }))} - data={tableData} - actions={actions} - /> - - ), - { - info: { - source: true, - propTables: false, - }, - } - ) - .add('with resize and no initial columns', () => - React.createElement(() => { - // Initial render is an empty columns array, which is updated after the first render - const [columns, setColumns] = useState([]); - useLayoutEffect(() => { - setColumns( - tableColumns.map((i, idx) => ({ - width: idx % 2 === 0 ? '100px' : '100px', - ...i, - })) - ); - }, []); - return ( -
+ + parameters: { + info: { + source: true, + propTables: false, + }, + }, +}; + +export const WithFixedColumnWidthAndNoResize = () => ( + // You don't need to use styled components, just pass a className to the Table component and use selectors to find the correct column + +
({ + width: idx % 2 === 0 ? '20rem' : '10rem', + ...i, + }))} + data={tableData} + actions={actions} + /> + +); + +WithFixedColumnWidthAndNoResize.story = { + name: 'with fixed column width and no resize', + + parameters: { + info: { + source: true, + propTables: false, + }, + }, +}; + +export const WithResizeAndNoInitialColumns = () => + React.createElement(() => { + // Initial render is an empty columns array, which is updated after the first render + const [columns, setColumns] = useState([]); + useLayoutEffect(() => { + setColumns( + tableColumns.map((i, idx) => ({ + width: idx % 2 === 0 ? '100px' : '100px', + ...i, + })) ); - }) - ) - .add( - 'with custom row height', - () => ( - // You don't need to use styled components, just pass a className to the Table component and use selectors to find the correct column - - - - ), - { - info: { - source: false, - text: `This is an example of the
component that has a custom row height. Pass a custom className prop to the Table component and use a css selector to change the height of all the rows. - `, - propTables: false, - }, - } - ) - .add('with lightweight design', () => ( -
+ ); + }); + +WithResizeAndNoInitialColumns.story = { + name: 'with resize and no initial columns', +}; + +export const WithCustomRowHeight = () => ( + // You don't need to use styled components, just pass a className to the Table component and use selectors to find the correct column + + - )) - .add( - 'with hasOnlyPageData', - () => { - return ( -
- ); + +); + +WithCustomRowHeight.story = { + name: 'with custom row height', + + parameters: { + info: { + source: false, + text: `This is an example of the
component that has a custom row height. Pass a custom className prop to the Table component and use a css selector to change the height of all the rows. + `, + propTables: false, + }, + }, +}; + +export const WithLightweightDesign = () => ( +
+); + +WithLightweightDesign.story = { + name: 'with lightweight design', +}; + +export const WithHasOnlyPageData = () => { + return ( +
+ ); +}; + +WithHasOnlyPageData.story = { + name: 'with hasOnlyPageData', + + parameters: { + info: { + text: + 'By default, tables with pagination will expect the entire table data to be passed in on the `data` prop; the visible data for a page is calculated dynamically by the table based on the page size and page number. In the case where the table is rendering a large data set, the `options.hasOnlyPageData` prop can be used change this behavior. With `options.hasOnlyPageData = true`, the `data` prop will be expected to contain only the rows for the visible page.', + source: true, }, + }, +}; + +export const HorizontalScrollCustomWidth = () => { + const tableColumnsConcat = [ + { id: 'test2', name: 'Test 2' }, + { id: 'test3', name: 'Test 3' }, { - info: { - text: - 'By default, tables with pagination will expect the entire table data to be passed in on the `data` prop; the visible data for a page is calculated dynamically by the table based on the page size and page number. In the case where the table is rendering a large data set, the `options.hasOnlyPageData` prop can be used change this behavior. With `options.hasOnlyPageData = true`, the `data` prop will be expected to contain only the rows for the visible page.', - source: true, - }, - } - ) - .add('horizontal scroll - custom width', () => { - const tableColumnsConcat = [ - { id: 'test2', name: 'Test 2' }, - { id: 'test3', name: 'Test 3' }, - { - id: 'test4', - name: 'Test 4', - }, - ]; - // You don't n,eed to use styled components, just pass a className to the Table component and use selectors to find the correct column - return ( -
-
- - ); - }) - .add('horizontal scroll - full width - no wrap', () => { - const tableColumnsConcat = [ - { id: 'test2', name: 'Test 2' }, - { id: 'test3', name: 'Test 3' }, - { - id: 'test4', - name: 'Test 4', - }, - ]; - // You don't n,eed to use styled components, just pass a className to the Table component and use selectors to find the correct column - return ( + id: 'test4', + name: 'Test 4', + }, + ]; + // You don't n,eed to use styled components, just pass a className to the Table component and use selectors to find the correct column + return ( +
- ); - }) - .add( - 'Filtered/Sorted/Paginated table with asynchronous data source', - () => { - const apiClient = new MockApiClient( - 100, - number('Fetch Duration (ms)', 500) - ); - return ; - }, + + ); +}; + +HorizontalScrollCustomWidth.story = { + name: 'horizontal scroll - custom width', +}; + +export const HorizontalScrollFullWidthNoWrap = () => { + const tableColumnsConcat = [ + { id: 'test2', name: 'Test 2' }, + { id: 'test3', name: 'Test 3' }, { - info: { - text: - 'This is an example of how to use the
component to present data fetched asynchronously from an HTTP API supporting pagination, filtering and sorting. Refer to the source files under /src/components/Table/AsyncTable for details. ', - source: false, - }, - } - ) - .add('Custom toolbar content', () => ( + id: 'test4', + name: 'Test 4', + }, + ]; + // You don't n,eed to use styled components, just pass a className to the Table component and use selectors to find the correct column + return (
my custom - ), - }, + toolbar: { activeBar: 'filter' }, }} /> - )) - .add( - 'Stateful Example with I18N strings', - () => ( - { + const apiClient = new MockApiClient(100, number('Fetch Duration (ms)', 500)); + return ; +}; + +FilteredSortedPaginatedTableWithAsynchronousDataSource.story = { + name: 'Filtered/Sorted/Paginated table with asynchronous data source', + + parameters: { + info: { + text: + 'This is an example of how to use the
component to present data fetched asynchronously from an HTTP API supporting pagination, filtering and sorting. Refer to the source files under /src/components/Table/AsyncTable for details. ', + source: false, + }, + }, +}; + +export const CustomToolbarContent = () => ( +
my custom + ), + }, + }} + /> +); + +CustomToolbarContent.story = { + name: 'Custom toolbar content', +}; + +export const StatefulExampleWithI18NStrings = () => ( + `__${min}–${max} items__`, + currentPage: (page) => `__page ${page}__`, + itemsRangeWithTotal: (min, max, total) => + `__${min}–${max} of ${total} items__`, + pageRange: (current, total) => `__${current} of ${total} pages__`, + /** table body */ + overflowMenuAria: text('i18n.overflowMenuAria', '__More actions__'), + clickToExpandAria: text( + 'i18n.clickToExpandAria', + '__Click to expand content__' + ), + clickToCollapseAria: text( + 'i18n.clickToCollapseAria', + '__Click to collapse content__' + ), + selectAllAria: text('i18n.selectAllAria', '__Select all items__'), + selectRowAria: text('i18n.selectRowAria', '__Select row__'), + /** toolbar */ + clearAllFilters: text('i18n.clearAllFilters', '__Clear all filters__'), + searchLabel: text('i18n.searchLabel', '__Search__'), + searchPlaceholder: text('i18n.searchPlaceholder', '__Search__'), + columnSelectionButtonAria: text( + 'i18n.columnSelectionButtonAria', + '__Column Selection__' + ), + filterButtonAria: text('i18n.filterButtonAria', '__Filters__'), + editButtonAria: text('i18n.editButtonAria', '__Edit rows__'), + clearFilterAria: text('i18n.clearFilterAria', '__Clear filter__'), + filterAria: text('i18n.filterAria', '__Filter__'), + openMenuAria: text('i18n.openMenuAria', '__Open menu__'), + closeMenuAria: text('i18n.closeMenuAria', '__Close menu__'), + clearSelectionAria: text( + 'i18n.clearSelectionAria', + '__Clear selection__' + ), + batchCancel: text('i18n.batchCancel', '__Cancel__'), + itemsSelected: text('i18n.itemsSelected', '__items selected__'), + itemSelected: text('i18n.itemSelected', '__item selected__'), + filterNone: text('i18n.filterNone', '__filterNone__'), + filterAscending: text('i18n.filterAscending', '__filterAscending__'), + filterDescending: text('i18n.filterDescending', '__filterDescending__'), + /** empty state */ + emptyMessage: text('i18n.emptyMessage', '__There is no data__'), + emptyMessageWithFilters: text( + 'i18n.emptyMessageWithFilters', + '__No results match the current filters__' + ), + emptyButtonLabel: text('i18n.emptyButtonLabel', '__Create some data__'), + emptyButtonLabelWithFilters: text( + 'i18n.emptyButtonLabel', + '__Clear all filters__' + ), + inProgressText: text('i18n.inProgressText', '__In Progress__'), + actionFailedText: text('i18n.actionFailedText', '__Action Failed__'), + learnMoreText: text('i18n.learnMoreText', '__Learn More__'), + dismissText: text('i18n.dismissText', '__Dismiss__'), + }} + /> +); + +StatefulExampleWithI18NStrings.story = { + name: 'Stateful Example with I18N strings', + + parameters: { + info: { + text: ` + + By default the table shows all of its internal strings in English. If you want to support multiple languages, you must populate these i18n keys with the appropriate label for the selected UI language. + +
+ + ~~~js + i18n={ + + /** pagination */ + pageBackwardAria, + pageForwardAria, + pageNumberAria, + itemsPerPage, + itemsRange, + currentPage, + itemsRangeWithTotal, + pageRange, + + /** table body */ + overflowMenuAria, + clickToExpandAria, + clickToCollapseAria, + selectAllAria, + selectRowAria, + + /** toolbar */ + clearAllFilters, + searchPlaceholder, + columnSelectionButtonAria, + filterButtonAria, + editButtonAria, + clearFilterAria, + filterAria, + openMenuAria, + closeMenuAria, + clearSelectionAria, + + /** empty state */ + emptyMessage, + emptyMessageWithFilters, + emptyButtonLabel, + emptyButtonLabelWithFilters, + inProgressText, + actionFailedText, + learnMoreText, + dismissText, + } + +
+ + `, + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; + +export const WithStickyHeaderExperimentalAndCellTooltipCalculation = () => { + const renderDataFunction = ({ value }) => ( +
+ {value} + { + const container = menuBody.closest('[data-floating-menu-container]'); + return { + top: -container.getBoundingClientRect().y - window.pageYOffset + 4, + left: + -container.getBoundingClientRect().x - window.pageXOffset + 10, + }; + }}> +

This scroll with the table body

+
+
+ ); + return ( +
+
({ + ...i, + renderDataFunction, + }))} + data={tableData} actions={actions} + stickyHeader options={{ - hasRowActions: true, + hasFilter: true, + hasPagination: true, + hasRowSelection: 'multi', }} view={{ filters: [], table: { + ordering: defaultOrdering, sort: { - columnId: 'number', - direction: 'DESC', + columnId: 'string', + direction: 'ASC', }, - rowActions: [ - { - rowId: 'row-1', - isRunning: true, - }, - { - rowId: 'row-3', - error: { - title: 'Import failed', - message: 'Contact your administrator', - }, - }, - ], }, }} - locale={select('locale', ['fr', 'en'], 'fr')} - i18n={{ - /** pagination */ - pageBackwardAria: text('i18n.pageBackwardAria', '__Previous page__'), - pageForwardAria: text('i18n.pageForwardAria', '__Next page__'), - pageNumberAria: text('i18n.pageNumberAria', '__Page Number__'), - itemsPerPage: text('i18n.itemsPerPage', '__Items per page:__'), - itemsRange: (min, max) => `__${min}–${max} items__`, - currentPage: (page) => `__page ${page}__`, - itemsRangeWithTotal: (min, max, total) => - `__${min}–${max} of ${total} items__`, - pageRange: (current, total) => `__${current} of ${total} pages__`, - /** table body */ - overflowMenuAria: text('i18n.overflowMenuAria', '__More actions__'), - clickToExpandAria: text( - 'i18n.clickToExpandAria', - '__Click to expand content__' - ), - clickToCollapseAria: text( - 'i18n.clickToCollapseAria', - '__Click to collapse content__' - ), - selectAllAria: text('i18n.selectAllAria', '__Select all items__'), - selectRowAria: text('i18n.selectRowAria', '__Select row__'), - /** toolbar */ - clearAllFilters: text( - 'i18n.clearAllFilters', - '__Clear all filters__' - ), - searchLabel: text('i18n.searchLabel', '__Search__'), - searchPlaceholder: text('i18n.searchPlaceholder', '__Search__'), - columnSelectionButtonAria: text( - 'i18n.columnSelectionButtonAria', - '__Column Selection__' - ), - filterButtonAria: text('i18n.filterButtonAria', '__Filters__'), - editButtonAria: text('i18n.editButtonAria', '__Edit rows__'), - clearFilterAria: text('i18n.clearFilterAria', '__Clear filter__'), - filterAria: text('i18n.filterAria', '__Filter__'), - openMenuAria: text('i18n.openMenuAria', '__Open menu__'), - closeMenuAria: text('i18n.closeMenuAria', '__Close menu__'), - clearSelectionAria: text( - 'i18n.clearSelectionAria', - '__Clear selection__' - ), - batchCancel: text('i18n.batchCancel', '__Cancel__'), - itemsSelected: text('i18n.itemsSelected', '__items selected__'), - itemSelected: text('i18n.itemSelected', '__item selected__'), - filterNone: text('i18n.filterNone', '__filterNone__'), - filterAscending: text('i18n.filterAscending', '__filterAscending__'), - filterDescending: text( - 'i18n.filterDescending', - '__filterDescending__' - ), - /** empty state */ - emptyMessage: text('i18n.emptyMessage', '__There is no data__'), - emptyMessageWithFilters: text( - 'i18n.emptyMessageWithFilters', - '__No results match the current filters__' - ), - emptyButtonLabel: text( - 'i18n.emptyButtonLabel', - '__Create some data__' - ), - emptyButtonLabelWithFilters: text( - 'i18n.emptyButtonLabel', - '__Clear all filters__' - ), - inProgressText: text('i18n.inProgressText', '__In Progress__'), - actionFailedText: text('i18n.actionFailedText', '__Action Failed__'), - learnMoreText: text('i18n.learnMoreText', '__Learn More__'), - dismissText: text('i18n.dismissText', '__Dismiss__'), - }} /> - ), - { - info: { - text: ` - - By default the table shows all of its internal strings in English. If you want to support multiple languages, you must populate these i18n keys with the appropriate label for the selected UI language. - -
- - ~~~js - i18n={ - - /** pagination */ - pageBackwardAria, - pageForwardAria, - pageNumberAria, - itemsPerPage, - itemsRange, - currentPage, - itemsRangeWithTotal, - pageRange, - - /** table body */ - overflowMenuAria, - clickToExpandAria, - clickToCollapseAria, - selectAllAria, - selectRowAria, - - /** toolbar */ - clearAllFilters, - searchPlaceholder, - columnSelectionButtonAria, - filterButtonAria, - editButtonAria, - clearFilterAria, - filterAria, - openMenuAria, - closeMenuAria, - clearSelectionAria, - - /** empty state */ - emptyMessage, - emptyMessageWithFilters, - emptyButtonLabel, - emptyButtonLabelWithFilters, - inProgressText, - actionFailedText, - learnMoreText, - dismissText, - } + + ); +}; -
+WithStickyHeaderExperimentalAndCellTooltipCalculation.story = { + name: 'with sticky header (experimental) and cell tooltip calculation', - `, - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ) - .add( - 'with sticky header (experimental) and cell tooltip calculation', - () => { - const renderDataFunction = ({ value }) => ( -
- {value} - { - const container = menuBody.closest( - '[data-floating-menu-container]' - ); - return { - top: - -container.getBoundingClientRect().y - window.pageYOffset + 4, - left: - -container.getBoundingClientRect().x - - window.pageXOffset + - 10, - }; - }}> -

This scroll with the table body

-
-
- ); - return ( -
-
({ - ...i, - renderDataFunction, - }))} - data={tableData} - actions={actions} - stickyHeader - options={{ - hasFilter: true, - hasPagination: true, - hasRowSelection: 'multi', - }} - view={{ - filters: [], - table: { - ordering: defaultOrdering, - sort: { - columnId: 'string', - direction: 'ASC', - }, - }, - }} - /> - - ); + parameters: { + centered: { disable: true }, + info: { + text: `StickyHeader is experimental. To properly render a tooltip in a table with sticky headers you need to pass a menuOffset or menuOffsetFlip calculation to `, }, - { - centered: { disable: true }, - info: { - text: `StickyHeader is experimental. To properly render a tooltip in a table with sticky headers you need to pass a menuOffset or menuOffsetFlip calculation to `, - }, - } - ) - .add( - 'Simple Stateful Example with column overflow menu', - () => ( - - - - ), - { - info: { - text: - 'This is an example of the component that implements the overflow menu in the column header. Refer to the source files under /src/components/Table/TableHead for details. ', - propTables: [Table], - propTablesExclude: [StatefulTable], - }, - } - ); + }, +}; + +export const SimpleStatefulExampleWithColumnOverflowMenu = () => ( + + + +); + +SimpleStatefulExampleWithColumnOverflowMenu.story = { + name: 'Simple Stateful Example with column overflow menu', + + parameters: { + info: { + text: + 'This is an example of the component that implements the overflow menu in the column header. Refer to the source files under /src/components/Table/TableHead for details. ', + propTables: [Table], + propTablesExclude: [StatefulTable], + }, + }, +}; diff --git a/src/components/Table/TableBody/TableBodyRow/TableBodyRow.story.jsx b/src/components/Table/TableBody/TableBodyRow/TableBodyRow.story.jsx index b3f8c5a97b..7930c05323 100644 --- a/src/components/Table/TableBody/TableBodyRow/TableBodyRow.story.jsx +++ b/src/components/Table/TableBody/TableBodyRow/TableBodyRow.story.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { actions } from '@storybook/addon-actions'; import { boolean } from '@storybook/addon-knobs'; import { DataTable } from 'carbon-components-react'; @@ -36,116 +35,154 @@ const TableDecorator = (storyFn) => (
); -// Table rows need to be rendered in a tbody or else they'll throw an error -storiesOf('Watson IoT/TableBodyRow', module) - .addParameters({ + +export default { + title: 'Watson IoT/TableBodyRow', + decorators: [TableDecorator], + + parameters: { component: TableBodyRow, - }) - .addDecorator(TableDecorator) - .add('normal', () => ) - .add('row actions', () => ( - - )) - .add('row actions with overflow', () => ( - - )) - .add('is not selectable', () => ( - - )) - .add('is selectable', () => ( - - )) - .add('rowActions running', () => ( - - )) - .add('rowActions error', () => ( - - )); + }, +}; + +export const Normal = () => ; + +Normal.story = { + name: 'normal', +}; + +export const RowActions = () => ( + +); + +RowActions.story = { + name: 'row actions', +}; + +export const RowActionsWithOverflow = () => ( + +); + +RowActionsWithOverflow.story = { + name: 'row actions with overflow', +}; + +export const IsNotSelectable = () => ( + +); + +IsNotSelectable.story = { + name: 'is not selectable', +}; + +export const IsSelectable = () => ( + +); + +IsSelectable.story = { + name: 'is selectable', +}; + +export const RowActionsRunning = () => ( + +); + +RowActionsRunning.story = { + name: 'rowActions running', +}; + +export const RowActionsError = () => ( + +); + +RowActionsError.story = { + name: 'rowActions error', +}; diff --git a/src/components/Table/TableDetailWizard/TableDetailWizard.story.jsx b/src/components/Table/TableDetailWizard/TableDetailWizard.story.jsx index 20cb02ec00..49e2a085b7 100644 --- a/src/components/Table/TableDetailWizard/TableDetailWizard.story.jsx +++ b/src/components/Table/TableDetailWizard/TableDetailWizard.story.jsx @@ -1,6 +1,5 @@ /* Used dependencies */ import React from 'react'; -import { storiesOf } from '@storybook/react'; import { boolean, text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; @@ -161,51 +160,67 @@ export const itemsAndComponents = items.map((item, i) => ({ component: itemComponents[i], })); -storiesOf('Watson IoT/TableDetailWizard', module) - .addParameters({ +export default { + title: 'Watson IoT/TableDetailWizard', + + parameters: { component: TableDetailWizard, - }) - .add('Stateful example', () => ( - - )) - .add('Static', () => ( - - )) - .add('with error', () => ( - - )); + }, + + excludeStories: ['itemsAndComponents'], +}; + +export const StatefulExample = () => ( + +); + +StatefulExample.story = { + name: 'Stateful example', +}; + +export const Static = () => ( + +); + +export const WithError = () => ( + +); + +WithError.story = { + name: 'with error', +}; diff --git a/src/components/Table/TableManageViewsModal/TableManageViewsModal.story.jsx b/src/components/Table/TableManageViewsModal/TableManageViewsModal.story.jsx index 8a76815bd5..919f3b241a 100644 --- a/src/components/Table/TableManageViewsModal/TableManageViewsModal.story.jsx +++ b/src/components/Table/TableManageViewsModal/TableManageViewsModal.story.jsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { boolean, select } from '@storybook/addon-knobs'; import { Button } from 'carbon-components-react'; @@ -22,328 +21,331 @@ const demoViews = Array(100) isPublic: !!(index % 2), })); -storiesOf('Watson IoT/Table/TableManageViewsModal', module) - .addParameters({ +export default { + title: 'Watson IoT/Table/TableManageViewsModal', + + parameters: { component: TableManageViewsModal, - }) - .add( - 'With callbacks implemented', - () => { - return React.createElement(() => { - const rowPerPage = 10; - const [currentPageNumber, setCurrentPageNumber] = useState(1); - const [currentFilters, setCurrentFilters] = useState({ - searchTerm: '', - showPublic: true, - }); - const [isOpen, setIsOpen] = useState(true); - const [filteredViews, setFilteredViews] = useState(demoViews); - const [viewsToShow, setViewsToShow] = useState( - demoViews.slice(0, rowPerPage) - ); + }, +}; - const showPage = (pageNumber, views) => { - const rowUpperLimit = pageNumber * rowPerPage; - const currentItemsOnPage = views.slice( - rowUpperLimit - rowPerPage, - rowUpperLimit - ); - setCurrentPageNumber(pageNumber); - setViewsToShow(currentItemsOnPage); - }; +export const WithCallbacksImplemented = () => { + return React.createElement(() => { + const rowPerPage = 10; + const [currentPageNumber, setCurrentPageNumber] = useState(1); + const [currentFilters, setCurrentFilters] = useState({ + searchTerm: '', + showPublic: true, + }); + const [isOpen, setIsOpen] = useState(true); + const [filteredViews, setFilteredViews] = useState(demoViews); + const [viewsToShow, setViewsToShow] = useState( + demoViews.slice(0, rowPerPage) + ); + + const showPage = (pageNumber, views) => { + const rowUpperLimit = pageNumber * rowPerPage; + const currentItemsOnPage = views.slice( + rowUpperLimit - rowPerPage, + rowUpperLimit + ); + setCurrentPageNumber(pageNumber); + setViewsToShow(currentItemsOnPage); + }; - const applyFiltering = ({ searchTerm, showPublic }) => { - const views = demoViews - .filter( - (view) => - searchTerm === '' || - view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 - ) - .filter((view) => (showPublic ? view : !view.isPublic)); + const applyFiltering = ({ searchTerm, showPublic }) => { + const views = demoViews + .filter( + (view) => + searchTerm === '' || + view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 + ) + .filter((view) => (showPublic ? view : !view.isPublic)); - setFilteredViews(views); - showPage(1, views); - }; + setFilteredViews(views); + showPage(1, views); + }; - const onDelete = (viewId) => { - const deleteIndex = demoViews.findIndex((view) => view.id === viewId); - demoViews.splice(deleteIndex, 1); - setFilteredViews(demoViews); - showPage(1, demoViews); - }; + const onDelete = (viewId) => { + const deleteIndex = demoViews.findIndex((view) => view.id === viewId); + demoViews.splice(deleteIndex, 1); + setFilteredViews(demoViews); + showPage(1, demoViews); + }; - const onClose = () => { - setIsOpen(false); - }; + const onClose = () => { + setIsOpen(false); + }; - const onEdit = (viewId) => { - /* eslint-disable-next-line no-alert */ - alert( - `EDIT ${viewId} \nThis action should close this modal and open TableSaveViewModal with the data of this view prefilled.` - ); - }; + const onEdit = (viewId) => { + /* eslint-disable-next-line no-alert */ + alert( + `EDIT ${viewId} \nThis action should close this modal and open TableSaveViewModal with the data of this view prefilled.` + ); + }; - const onPage = (pageNumber) => showPage(pageNumber, filteredViews); + const onPage = (pageNumber) => showPage(pageNumber, filteredViews); - const onSearchChange = (val) => { - const searchTerm = val === undefined ? '' : val; - const newFilters = { ...currentFilters, searchTerm }; - setCurrentFilters(newFilters); - applyFiltering(newFilters); - }; + const onSearchChange = (val) => { + const searchTerm = val === undefined ? '' : val; + const newFilters = { ...currentFilters, searchTerm }; + setCurrentFilters(newFilters); + applyFiltering(newFilters); + }; - const onDisplayPublicChange = (showPublic) => { - const newFilters = { ...currentFilters, showPublic }; - setCurrentFilters(newFilters); - applyFiltering(newFilters); - }; + const onDisplayPublicChange = (showPublic) => { + const newFilters = { ...currentFilters, showPublic }; + setCurrentFilters(newFilters); + applyFiltering(newFilters); + }; - const pagination = { - page: currentPageNumber, - onPage, - maxPage: Math.ceil(filteredViews.length / rowPerPage), - pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, - }; + const pagination = { + page: currentPageNumber, + onPage, + maxPage: Math.ceil(filteredViews.length / rowPerPage), + pageOfPagesText: (pageNumber) => `Page ${pageNumber}`, + }; - return ( - - ); - }); - }, - { - info: { - text: ` - This TableManageViewsModal story demonstrates a fully implemented example with data - and actions working properly in the default configuration. The avaiability of - 'Delete' and 'Edit' actions are determined by the respective views data properties. + return ( + + ); + }); +}; - ~~~js - return React.createElement(() => { - const rowPerPage = 10; - const [currentPageNumber, setCurrentPageNumber] = useState(1); - const [currentFilters, setCurrentFilters] = useState({ searchTerm: '', showPublic: true }); - const [isOpen, setIsOpen] = useState(true); - const [filteredViews, setFilteredViews] = useState(demoViews); - const [viewsToShow, setViewsToShow] = useState(demoViews.slice(0, rowPerPage)); - - const showPage = (pageNumber, views) => { - const rowUpperLimit = pageNumber * rowPerPage; - const currentItemsOnPage = views.slice(rowUpperLimit - rowPerPage, rowUpperLimit); - setCurrentPageNumber(pageNumber); - setViewsToShow(currentItemsOnPage); - }; - - const applyFiltering = ({ searchTerm, showPublic }) => { - const views = demoViews - .filter( - view => - searchTerm === '' || - view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 - ) - .filter(view => (showPublic ? view : !view.isPublic)); - - setFilteredViews(views); - showPage(1, views); - }; - - const onDelete = viewId => { - const deleteIndex = demoViews.findIndex(view => view.id === viewId); - demoViews.splice(deleteIndex, 1); - setFilteredViews(demoViews); - showPage(1, demoViews); - }; - - const onClose = () => { - setIsOpen(false); - }; - - const onEdit = viewId => { - /* eslint-disable-next-line no-alert */ - alert( - 'This action should close this modal and open TableSaveViewModal with the data of this view prefilled.' - ); - }; - - const onPage = pageNumber => showPage(pageNumber, filteredViews); - - const onSearchChange = val => { - const searchTerm = val === undefined ? '' : val; - const newFilters = { ...currentFilters, searchTerm }; - setCurrentFilters(newFilters); - applyFiltering(newFilters); - }; - - const onDisplayPublicChange = showPublic => { - const newFilters = { ...currentFilters, showPublic }; - setCurrentFilters(newFilters); - applyFiltering(newFilters); - }; - - const pagination = { - page: currentPageNumber, - onPage, - maxPage: Math.ceil(filteredViews.length / rowPerPage), - pageOfPagesText: pageNumber => 'Page ' + pageNumber, - }; - - return ( - +WithCallbacksImplemented.story = { + name: 'With callbacks implemented', + + parameters: { + info: { + text: ` + This TableManageViewsModal story demonstrates a fully implemented example with data + and actions working properly in the default configuration. The avaiability of + 'Delete' and 'Edit' actions are determined by the respective views data properties. + + ~~~js + return React.createElement(() => { + const rowPerPage = 10; + const [currentPageNumber, setCurrentPageNumber] = useState(1); + const [currentFilters, setCurrentFilters] = useState({ searchTerm: '', showPublic: true }); + const [isOpen, setIsOpen] = useState(true); + const [filteredViews, setFilteredViews] = useState(demoViews); + const [viewsToShow, setViewsToShow] = useState(demoViews.slice(0, rowPerPage)); + + const showPage = (pageNumber, views) => { + const rowUpperLimit = pageNumber * rowPerPage; + const currentItemsOnPage = views.slice(rowUpperLimit - rowPerPage, rowUpperLimit); + setCurrentPageNumber(pageNumber); + setViewsToShow(currentItemsOnPage); + }; + + const applyFiltering = ({ searchTerm, showPublic }) => { + const views = demoViews + .filter( + view => + searchTerm === '' || + view.title.toLowerCase().search(searchTerm.toLowerCase()) !== -1 + ) + .filter(view => (showPublic ? view : !view.isPublic)); + + setFilteredViews(views); + showPage(1, views); + }; + + const onDelete = viewId => { + const deleteIndex = demoViews.findIndex(view => view.id === viewId); + demoViews.splice(deleteIndex, 1); + setFilteredViews(demoViews); + showPage(1, demoViews); + }; + + const onClose = () => { + setIsOpen(false); + }; + + const onEdit = viewId => { + /* eslint-disable-next-line no-alert */ + alert( + 'This action should close this modal and open TableSaveViewModal with the data of this view prefilled.' ); - }); - ~~~ - `, - propTables: [TableManageViewsModal], - }, + }; + + const onPage = pageNumber => showPage(pageNumber, filteredViews); + + const onSearchChange = val => { + const searchTerm = val === undefined ? '' : val; + const newFilters = { ...currentFilters, searchTerm }; + setCurrentFilters(newFilters); + applyFiltering(newFilters); + }; + + const onDisplayPublicChange = showPublic => { + const newFilters = { ...currentFilters, showPublic }; + setCurrentFilters(newFilters); + applyFiltering(newFilters); + }; + + const pagination = { + page: currentPageNumber, + onPage, + maxPage: Math.ceil(filteredViews.length / rowPerPage), + pageOfPagesText: pageNumber => 'Page ' + pageNumber, + }; + + return ( + + ); + }); + ~~~ + `, + propTables: [TableManageViewsModal], + }, + }, +}; + +export const WithCustomRowActionsCustomRenderingAndNoPagination = () => { + const defaultViewId = 'id1'; + const myViews = demoViews.map((view) => ({ ...view, isClonable: true })); + const renderButton = (id, onClick, icon, key, iconText) => ( +