diff --git a/CHANGELOG.react.md b/CHANGELOG.react.md index f71f9b24d..01c0b90d9 100644 --- a/CHANGELOG.react.md +++ b/CHANGELOG.react.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Text field enhancements (`chips`, `onFocus`, `onBlur`, `textfieldRef`, `inputRef` props) [#199](https://github.com/lumapps/design-system/pull/199) + ### Changed - Fix stroke-width property on react progress bar. diff --git a/demo/react/doc/product/components/chip.mdx b/demo/react/doc/product/components/chip.mdx index b84a8bbfa..f3890ef21 100644 --- a/demo/react/doc/product/components/chip.mdx +++ b/demo/react/doc/product/components/chip.mdx @@ -1,6 +1,6 @@ ```javascript import import { useState } from 'react'; -import { Chip, Icon, Size } from 'LumX'; +import { Chip, ChipGroup, Icon, Size } from 'LumX'; import { mdiEmail, mdiClose, mdiCloseCircle, mdiMenuDown, mdiFilterVariant } from 'LumX/icons'; ``` @@ -140,6 +140,34 @@ Use small when space is a constraint, as in areas like text fields, selects, dro }; ``` -# Properties +# Properties + + +# Chip Group + +```javascript jsx withThemeSwitcher +(theme) => { + return ( + + + Default + + }> + Rich + + }> + Dismissible + + + ); +}; +``` + +# Properties + + + + + diff --git a/demo/react/doc/product/components/text-field.mdx b/demo/react/doc/product/components/text-field.mdx index e7ea85ac9..98a823a7e 100644 --- a/demo/react/doc/product/components/text-field.mdx +++ b/demo/react/doc/product/components/text-field.mdx @@ -1,7 +1,7 @@ ```javascript import import { ReactElement, useState } from 'react'; -import { TextField, Theme } from 'LumX'; -import { mdiMagnify } from 'LumX/icons'; +import { Chip, ChipGroup, TextField, Theme, IconButton, Emphasis, Size } from 'LumX'; +import { mdiMagnify, mdiClose } from 'LumX/icons'; ``` # Text field @@ -50,7 +50,7 @@ There are three states: _disabled_, _valid_, and _invalid_. ## Options -There are three options: _helper text_, _placeholder_ and _icon_. +There are five options: _helper text_, _placeholder_, _icon_, _is clearable_ and _with chips_. ### Helper text @@ -90,33 +90,72 @@ There are three options: _helper text_, _placeholder_ and _icon_. }; ``` -### Text Area +### Clearable ```javascript jsx withThemeSwitcher disableGrid (theme) => { const [value, setValue] = useState(''); - return ; + return ; }; ``` -### Valid Text Area +### With Chips ```javascript jsx withThemeSwitcher disableGrid (theme) => { const [value, setValue] = useState(''); - return ; + return ( + + + First + + + Second + + + Third + + + } + /> + ); }; ``` -### Error Text Area +### Text Area ```javascript jsx withThemeSwitcher disableGrid (theme) => { const [value, setValue] = useState(''); - return ; + return ; +}; +``` + +### Valid + +```javascript jsx withThemeSwitcher disableGrid +(theme) => { + const [value, setValue] = useState('Valid value'); + + return ; +}; +``` + +### Invalid + +```javascript jsx withThemeSwitcher disableGrid +(theme) => { + const [value, setValue] = useState('Invalid value'); + + return ; }; ``` diff --git a/src/angularjs.index.js b/src/angularjs.index.js index de8bd21bc..a51b1f405 100644 --- a/src/angularjs.index.js +++ b/src/angularjs.index.js @@ -7,6 +7,7 @@ import './components/button/angularjs/button_directive'; import './components/button/angularjs/button-group_directive'; import './components/checkbox/angularjs/checkbox_directive'; import './components/chip/angularjs/chip_directive'; +import './components/chip/angularjs/chip-group_directive'; import './components/comment-block/angularjs/comment-block_directive'; import './components/dialog/angularjs/dialog_directive'; import './components/dialog/angularjs/dialog_service'; diff --git a/src/components/button/style/lumapps/_mixins.scss b/src/components/button/style/lumapps/_mixins.scss index f692590d6..fc0f919ff 100644 --- a/src/components/button/style/lumapps/_mixins.scss +++ b/src/components/button/style/lumapps/_mixins.scss @@ -33,7 +33,7 @@ } } @else if $variant == 'variant-icon' { width: map-get($lumx-sizes, $size); - border-radius: 50%; + border-radius: $lumx-button-variant-icon-border-radius; } } diff --git a/src/components/button/style/lumapps/_variables.scss b/src/components/button/style/lumapps/_variables.scss index 4b81812fd..5552a0429 100644 --- a/src/components/button/style/lumapps/_variables.scss +++ b/src/components/button/style/lumapps/_variables.scss @@ -10,3 +10,5 @@ $lumx-button-icon-sizes: ( 'm': map-get($lumx-sizes, 'xs'), 's': map-get($lumx-sizes, 'xxs') ) !default; + +$lumx-button-variant-icon-border-radius: $lumx-theme-border-radius !default; diff --git a/src/components/button/style/material/_variables.scss b/src/components/button/style/material/_variables.scss index 3218acc07..6f2d427c4 100644 --- a/src/components/button/style/material/_variables.scss +++ b/src/components/button/style/material/_variables.scss @@ -5,3 +5,5 @@ $lumx-button-text-sizes: ( $lumx-button-text-weight: 500; $lumx-button-text-transform: uppercase; + +$lumx-button-variant-icon-border-radius: 50%; diff --git a/src/components/chip/angularjs/chip-group_directive.js b/src/components/chip/angularjs/chip-group_directive.js new file mode 100644 index 000000000..eb15dea51 --- /dev/null +++ b/src/components/chip/angularjs/chip-group_directive.js @@ -0,0 +1,82 @@ +import { CSS_PREFIX } from 'LumX/core/constants'; +import { COMPONENT_PREFIX, MODULE_NAME } from 'LumX/angularjs/constants/common_constants'; + +///////////////////////////// + +function ChipGroupController() { + 'ngInject'; + + // eslint-disable-next-line consistent-this + const lumx = this; + + ///////////////////////////// + // // + // Private attributes // + // // + ///////////////////////////// + + /** + * The default props. + * + * @type {Object} + * @constant + * @readonly + */ + const _DEFAULT_PROPS = { + align: 'left', + }; + + ///////////////////////////// + // // + // Public functions // + // // + ///////////////////////////// + + /** + * Get button classes. + * + * @return {Array} The list of button classes. + */ + function getClasses() { + const classes = []; + + if (angular.isDefined(lumx.align) && lumx.align) { + classes.push(`${CSS_PREFIX}-chip-group--align-${lumx.align}`); + } else { + classes.push(`${CSS_PREFIX}-chip-group--align-${_DEFAULT_PROPS.align}`); + } + + return classes; + } + + ///////////////////////////// + + lumx.getClasses = getClasses; +} + +///////////////////////////// + +function ChipGroupDirective() { + 'ngInject'; + + return { + bindToController: true, + controller: ChipGroupController, + controllerAs: 'lumx', + replace: true, + restrict: 'E', + scope: { + align: '@?lumxAlign', + }, + template: `
`, + transclude: true, + }; +} + +///////////////////////////// + +angular.module(`${MODULE_NAME}.chip`).directive(`${COMPONENT_PREFIX}ChipGroup`, ChipGroupDirective); + +///////////////////////////// + +export { ChipGroupDirective }; diff --git a/src/components/chip/react/ChipGroup.test.tsx b/src/components/chip/react/ChipGroup.test.tsx new file mode 100644 index 000000000..ba81f6827 --- /dev/null +++ b/src/components/chip/react/ChipGroup.test.tsx @@ -0,0 +1,38 @@ +import { shallow } from 'enzyme'; +import React from 'react'; + +import { ICommonSetup } from 'LumX/core/testing/utils.test'; +import { Chip } from './Chip'; +import { ChipGroup, ChipGroupProps } from './ChipGroup'; + +interface ISetup extends ICommonSetup {} + +/** + * Mounts the component and returns common DOM elements / data needed in multiple tests further down. + * + * @param propOverrides An object that will extend the default properties. + * @return An object with some shortcuts to elements or data required in tests. + */ +const setup = (propOverrides: Partial = {}): ISetup => { + const props = { + children: [Chip 1, Chip 2, Chip 3], + ...propOverrides, + }; + + const wrapper = shallow(); + + return { + props, + wrapper, + }; +}; + +describe('', () => { + // 1. Test render via snapshot (default state of component). + describe('Snapshot', () => { + it('should render correctly Chip Group component', () => { + const { wrapper } = setup(); + expect(wrapper).toMatchSnapshot(); + }); + }); +}); diff --git a/src/components/chip/react/ChipGroup.tsx b/src/components/chip/react/ChipGroup.tsx new file mode 100644 index 000000000..b996d0ce6 --- /dev/null +++ b/src/components/chip/react/ChipGroup.tsx @@ -0,0 +1,82 @@ +import React, { ReactElement } from 'react'; + +import classNames from 'classnames'; + +import { COMPONENT_PREFIX } from 'LumX/core/react/constants'; + +import { IGenericProps, getRootClassName } from 'LumX/core/react/utils'; +import { handleBasicClasses } from 'LumX/core/utils'; + +///////////////////////////// + +/** + * Defines the props of the component. + */ +interface IChipGroupProps extends IGenericProps { + /** Children of the ChipGroup. This should be a list of Chips */ + children: React.ReactNode; + + /** Chip group alignment */ + align?: string; +} +type ChipGroupProps = IChipGroupProps; + +///////////////////////////// + +/** + * Define the types of the default props. + */ +interface IDefaultPropsType extends Partial {} + +///////////////////////////// +// // +// Public attributes // +// // +///////////////////////////// + +/** + * The default value of props. + */ +const DEFAULT_PROPS: IDefaultPropsType = { + align: 'left', +}; + +/** + * The display name of the component. + */ +const COMPONENT_NAME = `${COMPONENT_PREFIX}ChipGroup`; + +/** + * The default class name and classes prefix for this component. + */ +const CLASSNAME: string = getRootClassName(COMPONENT_NAME); + +///////////////////////////// + +/** + * Displays a list of Chips in a grouped fashion. + * @return The Chip Group component. + */ +const ChipGroup: React.FC = ({ + className, + align = DEFAULT_PROPS.align, + children, + ...props +}: ChipGroupProps): ReactElement => { + const chipGroupClassName = handleBasicClasses({ + align, + prefix: CLASSNAME, + }); + + return ( +
+ {children} +
+ ); +}; + +ChipGroup.displayName = COMPONENT_NAME; + +///////////////////////////// + +export { CLASSNAME, ChipGroup, ChipGroupProps }; diff --git a/src/components/chip/react/__snapshots__/ChipGroup.test.tsx.snap b/src/components/chip/react/__snapshots__/ChipGroup.test.tsx.snap new file mode 100644 index 000000000..488b8b490 --- /dev/null +++ b/src/components/chip/react/__snapshots__/ChipGroup.test.tsx.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Snapshot should render correctly Chip Group component 1`] = ` +
+ + Chip 1 + + + Chip 2 + + + Chip 3 + +
+`; diff --git a/src/components/chip/style/lumapps/_index.scss b/src/components/chip/style/lumapps/_index.scss index 14d113297..e28b6e8fc 100644 --- a/src/components/chip/style/lumapps/_index.scss +++ b/src/components/chip/style/lumapps/_index.scss @@ -121,3 +121,28 @@ .#{$lumx-base-prefix}-chip--is-disabled { @include lumx-state-disabled-input; } + +/* Chip group + ========================================================================== */ + +.#{$lumx-base-prefix}-chip-group { + display: flex; + flex-wrap: wrap; + + .#{$lumx-base-prefix}-chip { + margin: $lumx-chip-group-spacing 0; + } + + &--align-left .#{$lumx-base-prefix}-chip { + margin-right: $lumx-chip-group-spacing * 2; + } + + &--align-center .#{$lumx-base-prefix}-chip { + margin-right: $lumx-chip-group-spacing; + margin-left: $lumx-chip-group-spacing; + } + + &--align-right .#{$lumx-base-prefix}-chip { + margin-left: $lumx-chip-group-spacing * 2; + } +} diff --git a/src/components/chip/style/lumapps/_variables.scss b/src/components/chip/style/lumapps/_variables.scss index ad77affb4..c3c58ce3d 100644 --- a/src/components/chip/style/lumapps/_variables.scss +++ b/src/components/chip/style/lumapps/_variables.scss @@ -2,3 +2,5 @@ $lumx-chip-sizes: ( 'm': map-get($lumx-sizes, 'm'), 's': map-get($lumx-sizes, 's') ) !default; + +$lumx-chip-group-spacing: 3px; diff --git a/src/components/select/angularjs/select.html b/src/components/select/angularjs/select.html index d38b493c8..39f7152fe 100644 --- a/src/components/select/angularjs/select.html +++ b/src/components/select/angularjs/select.html @@ -15,6 +15,7 @@ 'lumx-select--has-error': lumx.hasError, 'lumx-select--has-placeholder': lumx.placeholder, 'lumx-select--is-focus': lumx.isFocus, + 'lumx-select--has-input-clear': lumx.isClearable && !lumx.multiple && !lumx.isModelEmpty(), 'lumx-custom-colors': lumx.customColors }" > @@ -22,7 +23,7 @@
+
+ + + + + + + + + +
+
-
-
- - - - -
-
-
-
- -
+ +
diff --git a/src/components/select/react/Select.tsx b/src/components/select/react/Select.tsx index 40ee51057..660bf44aa 100644 --- a/src/components/select/react/Select.tsx +++ b/src/components/select/react/Select.tsx @@ -7,7 +7,7 @@ import { mdiAlertCircle, mdiCheckCircle, mdiClose, mdiCloseCircle, mdiMenuDown } import { CSS_PREFIX } from 'LumX/core/constants'; import { COMPONENT_PREFIX } from 'LumX/core/react/constants'; -import { Chip, Dropdown, Icon, Placement, Size, Theme } from 'LumX'; +import { Chip, ChipGroup, Dropdown, Emphasis, Icon, IconButton, Placement, Size, Theme } from 'LumX'; import { ENTER_KEY_CODE, SPACE_KEY_CODE } from 'LumX/core/constants'; import { IGenericProps, getRootClassName } from 'LumX/core/react/utils'; @@ -254,6 +254,7 @@ const Select: React.FC = ({ const isEmpty = value.length === 0; const targetUuid = 'uuid'; const anchorRef = useRef(null); + const hasInputClear = onClear && !isMultiple && !isEmpty; useFocusOnClose(anchorRef.current, isOpen); useHandleElementFocus(anchorRef.current, setIsFocus); @@ -275,11 +276,21 @@ const Select: React.FC = ({
} id={targetUuid} - className={`${CLASSNAME}__input-wrapper`} + className={`${CLASSNAME}__wrapper`} onClick={onInputClick} onKeyPress={handleKeyboardNav} tabIndex={0} > +
+ {!isEmpty && isMultiple && ( + + {value.map((val: string, index: number) => + selectedChipRender!(val, index, onClear, isDisabled), + )} + + )} +
+ {isEmpty && placeholder && (
= ({
)} -
- {!isEmpty && - isMultiple && - value.map((val: string, index: number) => ( -
- {selectedChipRender!(val, index, onClear, isDisabled)} -
- ))} -
- {(isValid || hasError) && (
- +
)} - {onClear && !isMultiple && !isEmpty && ( -
- -
+ {hasInputClear && ( + )}
@@ -359,6 +365,7 @@ const Select: React.FC = ({ className, handleBasicClasses({ hasError, + hasInputClear, hasLabel: Boolean(label), hasMultiple: !isEmpty && isMultiple, hasPlaceholder: Boolean(placeholder), diff --git a/src/components/select/react/__snapshots__/Select.test.tsx.snap b/src/components/select/react/__snapshots__/Select.test.tsx.snap index 08db53f0f..af5540732 100644 --- a/src/components/select/react/__snapshots__/Select.test.tsx.snap +++ b/src/components/select/react/__snapshots__/Select.test.tsx.snap @@ -5,13 +5,13 @@ exports[` +
+ +
@@ -27,18 +31,22 @@ exports[` Snapshots and structure should render textarea 1`] = ` className="lumx-text-field lumx-text-field--has-textarea lumx-text-field--theme-light" >
-