diff --git a/.storybook/__snapshots__/Welcome.story.storyshot b/.storybook/__snapshots__/Welcome.story.storyshot index 1c7bb991a2..2f57a63291 100644 --- a/.storybook/__snapshots__/Welcome.story.storyshot +++ b/.storybook/__snapshots__/Welcome.story.storyshot @@ -2868,6 +2868,18 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte IconDropdown +
+
+
+ HotspotTextStyleTab +
+
diff --git a/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.jsx b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.jsx new file mode 100644 index 0000000000..fcfa2bddb4 --- /dev/null +++ b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.jsx @@ -0,0 +1,256 @@ +import React, { useRef } from 'react'; +import PropTypes from 'prop-types'; +import merge from 'lodash/merge'; +import { NumberInput } from 'carbon-components-react'; +import { + TextBold16 as TextBold, + TextItalic16 as TextItalic, + TextUnderline16 as TextUnderline, +} from '@carbon/icons-react'; + +import { settings } from '../../../constants/Settings'; +import IconSwitch from '../../IconSwitch/IconSwitch'; +import ColorDropdown from '../../ColorDropdown/ColorDropdown'; + +const { iotPrefix } = settings; + +const colorPropType = PropTypes.shape({ + carbonColor: PropTypes.string, + name: PropTypes.string, +}); + +const propTypes = { + light: PropTypes.bool, + i18n: PropTypes.shape({ + boldLabel: PropTypes.string, + italicLabel: PropTypes.string, + underlineLabel: PropTypes.string, + fontLabel: PropTypes.string, + fontSize: PropTypes.string, + fontSizeInvalid: PropTypes.string, + backgroundLabel: PropTypes.string, + fillOpacityLabel: PropTypes.string, + fillOpacityInvalid: PropTypes.string, + borderLabel: PropTypes.string, + borderWidth: PropTypes.string, + borderWidthInvalid: PropTypes.string, + }), + /** Callback for when any of the form element's value changes */ + onChange: PropTypes.func.isRequired, + /** The state values of the controlled form elements e.g. { title: 'My hotspot 1', description: 'Lorem ipsum' } */ + formValues: PropTypes.objectOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + PropTypes.bool, + colorPropType, + ]) + ), + fontColors: PropTypes.arrayOf(colorPropType), + backgroundColors: PropTypes.arrayOf(colorPropType), + borderColors: PropTypes.arrayOf(colorPropType), +}; + +const defaultProps = { + light: true, + i18n: { + boldLabel: 'Text Bold', + italicLabel: 'Text Italic', + underlineLabel: 'Text Underline', + fontLabel: 'Font', + fontSizeLabel: 'Font Size', + fontSizeInvalid: 'Font Size is invalid', + backgroundLabel: 'Background', + fillOpacityLabel: 'Fill Opacity', + fillOpacityInvalid: 'Fill Opacity is invalid', + borderLabel: 'Border', + borderWidthLabel: 'Border Width', + borderWidthInvalid: 'Border Width is invalid', + }, + formValues: { + bold: false, + italic: false, + underline: false, + font: { + color: undefined, + size: 12, + }, + background: { + color: undefined, + opacity: 100, + }, + border: { + color: undefined, + width: 1, + }, + }, + fontColors: undefined, + backgroundColors: undefined, + borderColors: undefined, +}; + +/* this component is only used internally where props are defined and set. */ +const HotspotTextStyleTab = ({ + className, + fontColors, + backgroundColors, + borderColors, + formValues, + light, + i18n, + onChange, +}) => { + const { + boldLabel, + italicLabel, + underlineLabel, + fontLabel, + fontSizeLabel, + fontSizeInvalid, + backgroundLabel, + fillOpacityLabel, + fillOpacityInvalid, + borderLabel, + borderWidthLabel, + borderWidthInvalid, + } = merge({}, defaultProps.i18n, i18n); + + const fontSizeRef = useRef(null); + const fillOpacityRef = useRef(null); + const borderWidthRef = useRef(null); + + const { bold, italic, underline, font, background, border } = merge( + {}, + defaultProps.formValues, + formValues + ); + + return ( +
+
+ onChange({ bold: !bold })} + data-testid="hotspot-bold" + selected={bold} + text={boldLabel} + renderIcon={TextBold} + index={0} + light={light} + /> + onChange({ italic: !italic })} + data-testid="hotspot-italic" + selected={italic} + text={italicLabel} + renderIcon={TextItalic} + index={1} + light={light} + /> + onChange({ underline: !underline })} + data-testid="hotspot-underline" + selected={underline} + text={underlineLabel} + renderIcon={TextUnderline} + index={2} + light={light} + /> +
+ +
+ { + onChange({ font: selected }); + }} + /> + + { + onChange({ font: { size: fontSizeRef.current.value } }); + }} + /> +
+ +
+ { + onChange({ + background: selected, + }); + }} + /> + + { + onChange({ + background: { opacity: fillOpacityRef.current.value }, + }); + }} + /> +
+ +
+ { + onChange({ border: selected }); + }} + selectedColor={border.color ?? borderColors?.[0]} + /> + + { + onChange({ border: { width: borderWidthRef.current.value } }); + }} + /> +
+
+ ); +}; + +HotspotTextStyleTab.propTypes = propTypes; +HotspotTextStyleTab.defaultProps = defaultProps; + +export default HotspotTextStyleTab; diff --git a/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.story.jsx b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.story.jsx new file mode 100644 index 0000000000..fd49f8601e --- /dev/null +++ b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.story.jsx @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import merge from 'lodash/merge'; +import { action } from '@storybook/addon-actions'; +import { withKnobs } from '@storybook/addon-knobs'; +import { + purple70, + cyan50, + teal70, + magenta70, + red50, + red90, + green60, + blue80, + magenta50, + purple50, + teal50, + cyan90, +} from '@carbon/colors'; + +import HotspotTextStyleTab from './HotspotTextStyleTab'; + +export default { + title: 'Watson IoT Experimental/HotSpotEditorModal/HotspotTextStyleTab', + decorators: [withKnobs], + + parameters: { + component: HotspotTextStyleTab, + }, +}; + +const colors = [ + { carbonColor: purple70, name: 'purple70' }, + { carbonColor: cyan50, name: 'cyan50' }, + { carbonColor: teal70, name: 'teal70' }, + { carbonColor: magenta70, name: 'magenta70' }, + { carbonColor: red50, name: 'red50' }, + { carbonColor: red90, name: 'red90' }, + { carbonColor: green60, name: 'green60' }, + { carbonColor: blue80, name: 'blue80' }, + { carbonColor: magenta50, name: 'magenta50' }, + { carbonColor: purple50, name: 'purple50' }, + { carbonColor: teal50, name: 'teal50' }, + { carbonColor: cyan90, name: 'cyan90' }, +]; + +export const Default = () => { + const WithState = () => { + const [formValues, setFormValues] = useState({}); + + return ( + { + setFormValues(merge({}, formValues, change)); + action('onChange')(change); + }} + /> + ); + }; + + return ( +
+ +
+ ); +}; + +Default.story = { + name: 'default', + + parameters: { + info: { + text: 'TODO', + }, + }, +}; diff --git a/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.test.jsx b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.test.jsx new file mode 100644 index 0000000000..3b710bac47 --- /dev/null +++ b/src/components/HotspotEditorModal/HotspotTextStyleTab/HotspotTextStyleTab.test.jsx @@ -0,0 +1,108 @@ +import React from 'react'; +import merge from 'lodash/merge'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { purple70, cyan50, teal70 } from '@carbon/colors'; + +import HotspotTextStyleTab from './HotspotTextStyleTab'; + +const colors = [ + { carbonColor: purple70, name: 'purple70' }, + { carbonColor: cyan50, name: 'cyan50' }, + { carbonColor: teal70, name: 'teal70' }, +]; + +describe('HotspotTextStyleTab', () => { + it('handles text styling', () => { + let formValues = {}; + + render( + { + formValues = merge({}, formValues, change); + }} + /> + ); + + const boldButton = screen.getByTestId('hotspot-bold'); + const italicButton = screen.getByTestId('hotspot-italic'); + const underlineButton = screen.getByTestId('hotspot-underline'); + + fireEvent.click(boldButton); + fireEvent.click(italicButton); + fireEvent.click(underlineButton); + + expect(formValues.bold).toBe(true); + expect(formValues.italic).toBe(true); + expect(formValues.underline).toBe(true); + }); + + it('handles dropdown updates', () => { + let formValues = {}; + + render( + { + formValues = merge({}, formValues, change); + }} + /> + ); + + const dropdowns = screen.getAllByText(colors[0].name); + + fireEvent.click(dropdowns[0]); + fireEvent.click(screen.getAllByText(colors[1].name)[0]); + + fireEvent.click(dropdowns[1]); + fireEvent.click(screen.getAllByText(colors[1].name)[0]); + + fireEvent.click(dropdowns[2]); + fireEvent.click(screen.getAllByText(colors[1].name)[0]); + + expect(formValues.font.color).toEqual(colors[1]); + expect(formValues.background.color).toEqual(colors[1]); + expect(formValues.border.color).toEqual(colors[1]); + }); + + it('handles number input updates', () => { + let formValues = {}; + + render( + { + formValues = merge({}, formValues, change); + }} + /> + ); + + const incrementButtons = screen.getAllByTitle('Increment number'); + const decrementButtons = screen.getAllByTitle('Decrement number'); + + fireEvent.click(incrementButtons[0]); + fireEvent.click(incrementButtons[1]); + fireEvent.click(incrementButtons[2]); + + expect(formValues.font.size).toEqual('13'); + expect(formValues.background).toBeUndefined(); // Invalid update, will be undefined + expect(formValues.border.width).toEqual('2'); + + fireEvent.click(decrementButtons[0]); + fireEvent.click(decrementButtons[1]); + fireEvent.click(decrementButtons[2]); + + expect(formValues.font.size).toEqual('12'); + expect(formValues.background.opacity).toEqual('99'); + expect(formValues.border.width).toEqual('1'); + }); +}); diff --git a/src/components/HotspotEditorModal/HotspotTextStyleTab/__snapshots__/HotspotTextStyleTab.story.storyshot b/src/components/HotspotEditorModal/HotspotTextStyleTab/__snapshots__/HotspotTextStyleTab.story.storyshot new file mode 100644 index 0000000000..d1744ec72b --- /dev/null +++ b/src/components/HotspotEditorModal/HotspotTextStyleTab/__snapshots__/HotspotTextStyleTab.story.storyshot @@ -0,0 +1,696 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT Experimental/HotSpotEditorModal/HotspotTextStyleTab default 1`] = ` +
+
+
+
+
+ + + +
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ + +
+
+
+
+
+
+
+
+ +
+`; diff --git a/src/components/HotspotEditorModal/HotspotTextStyleTab/_hotspot-text-style-tab.scss b/src/components/HotspotEditorModal/HotspotTextStyleTab/_hotspot-text-style-tab.scss new file mode 100644 index 0000000000..13a0a97cf2 --- /dev/null +++ b/src/components/HotspotEditorModal/HotspotTextStyleTab/_hotspot-text-style-tab.scss @@ -0,0 +1,32 @@ +@import '../../../globals/vars'; + +.#{$iot-prefix}--hotspot-text-style-tab { + &__text-style { + :first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + + :last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + } + + &__dropdown { + width: 100%; + } + + &__row { + display: flex; + flex-direction: row; + align-items: baseline; + gap: $spacing-06; + + margin-top: $spacing-06; + + .#{$prefix}--dropdown__wrapper.#{$prefix}--list-box__wrapper { + width: 100%; + } + } +} diff --git a/src/components/IconDropdown/__snapshots__/IconDropdown.story.storyshot b/src/components/IconDropdown/__snapshots__/IconDropdown.story.storyshot index e3ed95130f..16f7a876bd 100644 --- a/src/components/IconDropdown/__snapshots__/IconDropdown.story.storyshot +++ b/src/components/IconDropdown/__snapshots__/IconDropdown.story.storyshot @@ -36,8 +36,8 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT Exper > @@ -52,10 +52,10 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT Exper aria-disabled={false} aria-expanded={false} aria-haspopup="listbox" - aria-labelledby="downshift-22-label downshift-22-toggle-button" + aria-labelledby="downshift-25-label downshift-25-toggle-button" className="bx--list-box__field" disabled={false} - id="downshift-22-toggle-button" + id="downshift-25-toggle-button" onClick={[Function]} onKeyDown={[Function]} type="button" @@ -90,9 +90,9 @@ exports[`Storybook Snapshot tests and console checks Storyshots Watson IoT Exper
Object { + "defaultProps": Object { + "backgroundColors": undefined, + "borderColors": undefined, + "fontColors": undefined, + "formValues": Object { + "background": Object { + "color": undefined, + "opacity": 100, + }, + "bold": false, + "border": Object { + "color": undefined, + "width": 1, + }, + "font": Object { + "color": undefined, + "size": 12, + }, + "italic": false, + "underline": false, + }, + "i18n": Object { + "backgroundLabel": "Background", + "boldLabel": "Text Bold", + "borderLabel": "Border", + "borderWidthInvalid": "Border Width is invalid", + "borderWidthLabel": "Border Width", + "fillOpacityInvalid": "Fill Opacity is invalid", + "fillOpacityLabel": "Fill Opacity", + "fontLabel": "Font", + "fontSizeInvalid": "Font Size is invalid", + "fontSizeLabel": "Font Size", + "italicLabel": "Text Italic", + "underlineLabel": "Text Underline", + }, + "light": true, + }, + "propTypes": Object { + "backgroundColors": Object { + "args": Array [ + Object { + "args": Array [ + Object { + "carbonColor": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + }, + ], + "type": "shape", + }, + ], + "type": "arrayOf", + }, + "borderColors": Object { + "args": Array [ + Object { + "args": Array [ + Object { + "carbonColor": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + }, + ], + "type": "shape", + }, + ], + "type": "arrayOf", + }, + "fontColors": Object { + "args": Array [ + Object { + "args": Array [ + Object { + "carbonColor": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + }, + ], + "type": "shape", + }, + ], + "type": "arrayOf", + }, + "formValues": Object { + "args": Array [ + Object { + "args": Array [ + Array [ + Object { + "type": "string", + }, + Object { + "type": "number", + }, + Object { + "type": "bool", + }, + Object { + "args": Array [ + Object { + "carbonColor": Object { + "type": "string", + }, + "name": Object { + "type": "string", + }, + }, + ], + "type": "shape", + }, + ], + ], + "type": "oneOfType", + }, + ], + "type": "objectOf", + }, + "i18n": Object { + "args": Array [ + Object { + "backgroundLabel": Object { + "type": "string", + }, + "boldLabel": Object { + "type": "string", + }, + "borderLabel": Object { + "type": "string", + }, + "borderWidth": Object { + "type": "string", + }, + "borderWidthInvalid": Object { + "type": "string", + }, + "fillOpacityInvalid": Object { + "type": "string", + }, + "fillOpacityLabel": Object { + "type": "string", + }, + "fontLabel": Object { + "type": "string", + }, + "fontSize": Object { + "type": "string", + }, + "fontSizeInvalid": Object { + "type": "string", + }, + "italicLabel": Object { + "type": "string", + }, + "underlineLabel": Object { + "type": "string", + }, + }, + ], + "type": "shape", + }, + "light": Object { + "type": "bool", + }, + "onChange": Object { + "isRequired": true, + "type": "func", + }, + }, + }, "baseTableReducer" => Object {}, "tableReducer" => Object {}, "tileCatalogReducer" => Object {},