From 9195e18f8e9ba611b7c2bf4439acd52a413af6aa Mon Sep 17 00:00:00 2001 From: David Conner Date: Thu, 22 Oct 2020 11:02:33 -0400 Subject: [PATCH 1/5] feat(card): add selection state styles and prop --- src/components/Card/Card.jsx | 26 +++++++++++++++++++++++++- src/components/Card/Card.story.jsx | 5 +++-- src/components/Card/_card.scss | 6 ++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/components/Card/Card.jsx b/src/components/Card/Card.jsx index e572697e96..aab05e157c 100644 --- a/src/components/Card/Card.jsx +++ b/src/components/Card/Card.jsx @@ -32,6 +32,7 @@ const OptimizedSkeletonText = React.memo(SkeletonText); /** Full card */ const CardWrapper = ({ + isSelected, children, dimensions, id, @@ -51,6 +52,20 @@ const CardWrapper = ({ ...others }) => { const validOthers = filterValidAttributes(others); + const [isCardSelected, setIsCardSelected] = useState(false); + useEffect(() => { + setIsCardSelected(isSelected); + }, [isSelected, setIsCardSelected]); + + const handleSelection = (e) => { + console.log({ e }); + if ( + (e.type === 'click' && isSelected && isSelected(e)) || + e.key === 'Enter' + ) { + setIsCardSelected(!isCardSelected); + } + }; return (
{children}
@@ -111,6 +130,10 @@ const EmptyMessageWrapper = (props) => { }; CardWrapper.propTypes = { + /** + * Is given the event as argument. Should return true or false if event should trigger selection + */ + isSelected: PropTypes.func, children: PropTypes.node.isRequired, dimensions: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }) .isRequired, @@ -128,6 +151,7 @@ CardWrapper.propTypes = { tabIndex: PropTypes.number, }; CardWrapper.defaultProps = { + isSelected: null, id: undefined, style: undefined, testID: 'Card', diff --git a/src/components/Card/Card.story.jsx b/src/components/Card/Card.story.jsx index a5aab3e53f..937c9e0f22 100644 --- a/src/components/Card/Card.story.jsx +++ b/src/components/Card/Card.story.jsx @@ -40,6 +40,7 @@ storiesOf('Watson IoT/Card', module) id="facilitycard-basic" size={size} isLoading={boolean('isLoading', false)} + isSelected={boolean('isSelected', false)} isEmpty={boolean('isEmpty', false)} isEditable={boolean('isEditable', false)} isExpanded={boolean('isExpanded', false)} @@ -371,11 +372,11 @@ storiesOf('Watson IoT/Card', module) - 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. + for each card (if it exists), then will call the onFetchData function for the dashboard. `, }, } diff --git a/src/components/Card/_card.scss b/src/components/Card/_card.scss index f9b191320c..ca00fddeed 100644 --- a/src/components/Card/_card.scss +++ b/src/components/Card/_card.scss @@ -12,6 +12,12 @@ $iot-header-padding: $spacing-05; display: flex; flex-direction: column; overflow: hidden; + + &__selected { + border: solid $spacing-01 $interactive-04; + box-sizing: content-box; + margin: -$spacing-01; + } } .#{$iot-prefix}--card--title { From 2e5800d554e9110ab2c8275bdc69887f0dad3c6d Mon Sep 17 00:00:00 2001 From: David Conner Date: Wed, 28 Oct 2020 10:25:39 -0400 Subject: [PATCH 2/5] feat(card): change selections colors from design --- src/components/Card/Card.jsx | 24 ++++++++++++++++-------- src/components/Card/_card.scss | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/Card/Card.jsx b/src/components/Card/Card.jsx index aab05e157c..f7f144703d 100644 --- a/src/components/Card/Card.jsx +++ b/src/components/Card/Card.jsx @@ -58,14 +58,22 @@ const CardWrapper = ({ }, [isSelected, setIsCardSelected]); const handleSelection = (e) => { - console.log({ e }); + if (e.type === 'click' || e.key === 'Enter') { + setIsCardSelected(true); + } + }; + + const handleBlur = (e) => { if ( - (e.type === 'click' && isSelected && isSelected(e)) || - e.key === 'Enter' + e.relatedTarget.classList.contains( + `${iotPrefix}--card iot--card--wrapper` + ) ) { - setIsCardSelected(!isCardSelected); + setIsCardSelected(false); } + onBlur(); }; + return (
handleBlur(e)} + onKeyDown={handleSelection} onClick={handleSelection} tabIndex={tabIndex} className={classnames(className, `${iotPrefix}--card--wrapper`, { @@ -133,7 +141,7 @@ CardWrapper.propTypes = { /** * Is given the event as argument. Should return true or false if event should trigger selection */ - isSelected: PropTypes.func, + isSelected: PropTypes.bool, children: PropTypes.node.isRequired, dimensions: PropTypes.shape({ x: PropTypes.number, y: PropTypes.number }) .isRequired, @@ -151,7 +159,7 @@ CardWrapper.propTypes = { tabIndex: PropTypes.number, }; CardWrapper.defaultProps = { - isSelected: null, + isSelected: false, id: undefined, style: undefined, testID: 'Card', diff --git a/src/components/Card/_card.scss b/src/components/Card/_card.scss index ca00fddeed..06b45d16c9 100644 --- a/src/components/Card/_card.scss +++ b/src/components/Card/_card.scss @@ -14,7 +14,7 @@ $iot-header-padding: $spacing-05; overflow: hidden; &__selected { - border: solid $spacing-01 $interactive-04; + border: solid $spacing-01 $interactive-02; box-sizing: content-box; margin: -$spacing-01; } From 557209c8ca7f0d74e232cb0464c29b5ab45b9276 Mon Sep 17 00:00:00 2001 From: David Conner Date: Tue, 3 Nov 2020 09:36:35 -0500 Subject: [PATCH 3/5] feat(card): fix story for selection --- src/components/Card/Card.jsx | 21 +------ src/components/Card/Card.story.jsx | 59 ++++++++++++------- .../Card/__snapshots__/Card.story.storyshot | 1 + 3 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/components/Card/Card.jsx b/src/components/Card/Card.jsx index 25949ff03f..91fa7d166d 100644 --- a/src/components/Card/Card.jsx +++ b/src/components/Card/Card.jsx @@ -60,23 +60,6 @@ const CardWrapper = ({ setIsCardSelected(isSelected); }, [isSelected, setIsCardSelected]); - const handleSelection = (e) => { - if (e.type === 'click' || e.key === 'Enter') { - setIsCardSelected(true); - } - }; - - const handleBlur = (e) => { - if ( - e.relatedTarget.classList.contains( - `${iotPrefix}--card iot--card--wrapper` - ) - ) { - setIsCardSelected(false); - } - onBlur(); - }; - return (
handleBlur(e)} - onKeyDown={handleSelection} - onClick={handleSelection} + onBlur={onBlur} tabIndex={tabIndex} className={classnames(className, `${iotPrefix}--card--wrapper`, { [`${iotPrefix}--card--wrapper__selected`]: isCardSelected, diff --git a/src/components/Card/Card.story.jsx b/src/components/Card/Card.story.jsx index 937c9e0f22..2c6910a196 100644 --- a/src/components/Card/Card.story.jsx +++ b/src/components/Card/Card.story.jsx @@ -32,27 +32,44 @@ export const getDataStateProp = () => ({ storiesOf('Watson IoT/Card', module) .add('basic', () => { - const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); - return ( -
- -
- ); + const StatefulExample = () => { + const [selected, setSelected] = React.useState(false); + const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); + const handleClick = () => { + setSelected(true); + }; + const handleBlur = (e) => { + if ( + !e.currentTarget.contains(e.relatedTarget) || + (e.target === e.currentTarget && e.relatedTarget === null) + ) { + setSelected(false); + } + action('onBlur'); + }; + return ( +
+ +
+ ); + }; + return ; }) .add('with ellipsed title tooltip & external tooltip', () => { const size = select('size', Object.keys(CARD_SIZES), CARD_SIZES.MEDIUM); diff --git a/src/components/Card/__snapshots__/Card.story.storyshot b/src/components/Card/__snapshots__/Card.story.storyshot index 1951fdc142..760954b607 100644 --- a/src/components/Card/__snapshots__/Card.story.storyshot +++ b/src/components/Card/__snapshots__/Card.story.storyshot @@ -25,6 +25,7 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT/Card data-testid="Card" id="facilitycard-basic" onBlur={[Function]} + onClick={[Function]} onFocus={[Function]} role="presentation" style={ From 450f9b48e2b1e6c1ef30db2ea49dcba0c0082b9f Mon Sep 17 00:00:00 2001 From: David Conner Date: Wed, 4 Nov 2020 16:16:08 -0500 Subject: [PATCH 4/5] fix(card): remove unnecessary state hooks --- src/components/Card/Card.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Card/Card.jsx b/src/components/Card/Card.jsx index 91fa7d166d..26900778a3 100644 --- a/src/components/Card/Card.jsx +++ b/src/components/Card/Card.jsx @@ -55,10 +55,6 @@ const CardWrapper = ({ ...others }) => { const validOthers = filterValidAttributes(others); - const [isCardSelected, setIsCardSelected] = useState(false); - useEffect(() => { - setIsCardSelected(isSelected); - }, [isSelected, setIsCardSelected]); return (
{children} From cbbacecd17f6713047aa11c13ed641291dc040e5 Mon Sep 17 00:00:00 2001 From: David Conner Date: Wed, 4 Nov 2020 20:01:27 -0500 Subject: [PATCH 5/5] fix(card): remove negative margins --- src/components/Card/_card.scss | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Card/_card.scss b/src/components/Card/_card.scss index 7c2ff2e9ed..34280c8ca1 100644 --- a/src/components/Card/_card.scss +++ b/src/components/Card/_card.scss @@ -8,6 +8,7 @@ $iot-header-padding: $spacing-05; .#{$iot-prefix}--card--wrapper { background: white; + border: solid $spacing-01 transparent; height: var(--card-default-height); display: flex; flex-direction: column; @@ -16,20 +17,17 @@ $iot-header-padding: $spacing-05; &__selected { border: solid $spacing-01 $interactive-02; box-sizing: content-box; - margin: -$spacing-01; } } .#{$iot-prefix}--card__selected { border: $spacing-01 solid $interactive-02; box-sizing: content-box; - margin: -$spacing-01; } .#{$iot-prefix}--card--resizing { border: $spacing-01 solid $interactive-02; box-sizing: content-box; - margin: -$spacing-01; } .#{$iot-prefix}--card--title {