From dfbbdedf56fa458b46e136885409c4d090f7eb74 Mon Sep 17 00:00:00 2001 From: Marcos Conceicao Date: Fri, 11 Oct 2024 15:43:04 -0300 Subject: [PATCH] feat(Dropdown.jsx): Migrated to css, removed theme prop of d.ts and updated snapshots --- .../AutoComplete/AutoComplete.module.css | 8 +- components/Dropdown/Dropdown.jsx | 303 +++------- components/Dropdown/Dropdown.module.css | 100 +++ .../__snapshots__/Dropdown.unit.test.jsx.snap | 570 ++++++------------ components/Dropdown/index.css | 1 + components/Dropdown/index.d.ts | 7 +- components/index.css | 1 + 7 files changed, 381 insertions(+), 609 deletions(-) create mode 100644 components/Dropdown/Dropdown.module.css create mode 100644 components/Dropdown/index.css diff --git a/components/AutoComplete/AutoComplete.module.css b/components/AutoComplete/AutoComplete.module.css index 745ef6dfd..965e1801a 100644 --- a/components/AutoComplete/AutoComplete.module.css +++ b/components/AutoComplete/AutoComplete.module.css @@ -1,6 +1,6 @@ :root { - --max-items-visibility: 7; - --item-height: 44px; + --max-items-visibility-autocomplete: 7; + --item-height-autocomplete: 44px; --dropitem-font-size: calc(var(--qtm-base-font-size) * 0.875); } @@ -52,7 +52,7 @@ border-radius: 4px; box-sizing: border-box; list-style: none; - max-height: calc(var(--item-height) * var(--max-items-visibility)); + max-height: calc(var(--item-height-autocomplete) * var(--max-items-visibility-autocomplete)); overflow: auto; padding: 0; position: absolute; @@ -68,7 +68,7 @@ justify-content: space-between; box-sizing: border-box; cursor: pointer; - height: var(--item-height); + height: var(--item-height-autocomplete); color: var(--qtm-colors-neutral-700); font-size: var(--dropitem-font-size); background-color: var(--qtm-colors-neutral-0); diff --git a/components/Dropdown/Dropdown.jsx b/components/Dropdown/Dropdown.jsx index 3943816ff..1d69ffe40 100644 --- a/components/Dropdown/Dropdown.jsx +++ b/components/Dropdown/Dropdown.jsx @@ -1,10 +1,9 @@ import { useState, forwardRef } from 'react'; import PropTypes from 'prop-types'; -import styled, { css } from 'styled-components'; import Downshift from 'downshift'; +import classNames from 'classnames'; import Icon from '../Icon/Icon'; -import { FieldGroup, shadow, createUniqId, normalizeChars } from '../shared'; -import { colors, spacing, baseFontSize } from '../shared/theme'; +import { FieldGroup, createUniqId, normalizeChars } from '../shared'; import { InputLabel, @@ -14,15 +13,12 @@ import { HelperText, } from '../Input/sub-components'; import { useTextInputClass } from '../Input/sub-components/TextInput'; +import styles from './Dropdown.module.css'; const uniqId = createUniqId('dropdown-'); -const ITEM_HEIGHT = '44px'; const MAX_ITEMS_VISIBILITY = 7; -const DROPITEM_FONT_SIZE = baseFontSize * 0.875; -const DROPITEM_IMAGE_SIZE = '24px'; -const ICON_DEFAULT_SIZE = `${baseFontSize * 1.5}px`; -const DropButtonBase = forwardRef( +const DropButton = forwardRef( ( { children, className, hasDefaultValue, hasLabel, skin, error, ...rest }, ref, @@ -42,173 +38,15 @@ const DropButtonBase = forwardRef( ); }, ); -const propsNotContainedInDropButtonBase = ['text']; -const DropButton = styled(DropButtonBase).withConfig({ - shouldForwardProp: (prop) => - !propsNotContainedInDropButtonBase.includes(prop), -})` - align-items: center; - cursor: pointer; - display: flex; - justify-content: space-between; - text-align: left; - - ${({ text }) => ` - ${!text ? 'flex-direction: row-reverse;' : ''} - color: inherit; - `}; -`; - -const propsNotContainedInInput = ['theme', 'autocomplete']; - -const DropInput = styled(TextInput).withConfig({ - shouldForwardProp: (prop) => !propsNotContainedInInput.includes(prop), -})` - align-items: center; - cursor: pointer; - display: flex; - justify-content: space-between; - text-align: left; - - ${({ - text, - autocomplete, - theme: { - spacing: { xsmall, medium, xxxlarge }, - }, - }) => ` - ${ - autocomplete - ? `padding: ${xsmall}px ${xxxlarge}px ${xsmall}px ${medium}px;` - : '' - } - ${!text ? 'flex-direction: row-reverse;' : ''} - color: inherit; - `}; -`; - -const ArrowIcon = styled(Icon)` - pointer-events: none; - width: ${ICON_DEFAULT_SIZE}; - - ${({ selectedItem }) => - !selectedItem && - ` - color: inherit; - `} -`; - -const DropList = styled.ul` - border-radius: 4px; - box-sizing: border-box; - list-style: none; - max-height: calc(${ITEM_HEIGHT} * ${MAX_ITEMS_VISIBILITY}); - overflow: auto; - padding: 0; - position: absolute; - width: 100%; - z-index: 9999; - - ${({ theme }) => { - const { - spacing: { xxsmall }, - colors: { neutral }, - } = theme; - - return css` - background-color: ${neutral[0]}; - color: ${neutral[700]}; - margin-top: ${xxsmall}px; - ${shadow(5, neutral[300])({ theme })}; - `; - }} -`; - -const CheckIcon = styled(Icon).attrs({ - name: 'check', -})` - ${({ - selectedItem, - theme: { - colors: { - primary: { 700: primary700 }, - }, - }, - }) => - !selectedItem && - ` - color: ${primary700}; - `} -`; - -const DropItem = styled.li` - display: flex; - align-items: center; - justify-content: space-between; - box-sizing: border-box; - cursor: pointer; - min-height: 42px; - ${({ - theme: { - spacing: { xsmall, medium }, - colors: { - neutral: { 0: neutral0 }, - }, - }, - }) => ` - font-size: ${DROPITEM_FONT_SIZE}px; - background-color: ${neutral0}; - padding: ${xsmall}px ${medium}px; - `} - &[aria-selected= 'true' ] { - ${({ - theme: { - colors: { - neutral: { 100: neutral100 }, - }, - }, - }) => ` - background-color: ${neutral100}; - `} - } - - ${({ isSelected }) => - isSelected && - ` - display: flex; - justify-content: space-between; - `} -`; - -const DropContainer = styled.div` - position: relative; -`; - -const SelectedItemLabel = styled.span` - ${({ - theme: { - colors: { - primary: { 700: primary700 }, - }, - }, - }) => ` - color: ${primary700} - `} -`; - -const DropItemImage = styled.img` - width: ${DROPITEM_IMAGE_SIZE}; - height: ${DROPITEM_IMAGE_SIZE}; - margin-left: ${spacing.xsmall}px; -`; - -DropInput.displayName = 'DropInput'; -DropItem.displayName = 'DropItem'; -ArrowIcon.displayName = 'ArrowIcon'; -SelectedItemLabel.displayName = 'SelectedItemLabel'; -DropItemImage.displayName = 'DropItemImage'; -DropContainer.displayName = 'DropContainer'; +const DropContainer = forwardRef(({ children, className, ...rest }, ref) => { + const dropContainerClass = classNames(styles['drop-container'], className); + return ( +
+ {children} +
+ ); +}); const _getValue = (item) => (item ? item.value || item.label || item : ''); const _getLabel = (item) => (item ? item.label || item.value || item : ''); @@ -224,48 +62,59 @@ const itemPropType = PropTypes.oneOfType([ }), ]); -const List = ({ theme, items, selectedItem = null, getItemProps }) => ( - - {items.map((item) => ( - - {_isEqual(selectedItem, item) ? ( - <> - - {_getLabel(item)} - +const List = ({ items, selectedItem = null, getItemProps }) => { + const imageItemClass = classNames(styles['drop-image-item']); + const checkIconClass = classNames(styles['check-icon-without-selected-item']); + const dropListClass = classNames(styles['drop-list']); + const itemClass = classNames(styles['drop-item']); + const itemSelectedLabelClass = classNames( + styles['drop-item-item-selected-label'], + ); - {_getImage(item) ? ( - - ) : ( - - )} - - ) : ( - <> - {_getLabel(item)} - {_getImage(item) && ( - - )} - - )} - - ))} - -); + return ( + + ); +}; List.propTypes = { selectedItem: itemPropType, - theme: PropTypes.shape({ - colors: PropTypes.object, - spacing: PropTypes.object, - }).isRequired, items: PropTypes.arrayOf(itemPropType).isRequired, getItemProps: PropTypes.func.isRequired, }; @@ -281,7 +130,6 @@ const Dropdown = ({ selectedItem = null, onChange = () => {}, autocomplete = false, - theme = { colors, spacing, baseFontSize }, id = '', name = '', ignoreSpecialChars = false, @@ -289,6 +137,14 @@ const Dropdown = ({ ...rest }) => { const _buttonLabel = selectedItem ? _getLabel(selectedItem) : placeholder; + const dropdownButtonClass = classNames(styles['dropdown-button'], { + [styles['without-text']]: !_buttonLabel, + }); + const dropdownInputClass = classNames(styles['dropdown-input'], { + [styles['without-text']]: !_buttonLabel, + [styles['dropdown-input-autocomplete']]: autocomplete, + }); + const arrowIconClass = classNames(styles['arrow-icon']); const _highlightedReducer = ({ selectedItem: selected }, changes) => { if (changes.isOpen !== undefined && changes.isOpen) { @@ -342,7 +198,7 @@ const Dropdown = ({ const filteredInput = isOpen ? inputFilter(inputValue) : []; return ( - + {label && ( {autocomplete ? ( <> - - + @@ -384,7 +240,6 @@ const Dropdown = ({ {filteredInput.length > 0 && ( @@ -396,22 +251,21 @@ const Dropdown = ({ {...getToggleButtonProps()} disabled={disabled} error={error} - text={_buttonLabel} hasLabel={hasLabel} skin={skin} id={_id} + className={dropdownButtonClass} > {_buttonLabel} - {isOpen && ( @@ -449,11 +303,6 @@ Dropdown.propTypes = { onChange: PropTypes.func, /** A list of string or objects with value and label keys */ items: PropTypes.arrayOf(itemPropType), - theme: PropTypes.shape({ - colors: PropTypes.object, - spacing: PropTypes.object, - baseFontSize: PropTypes.number, - }), ignoreSpecialChars: PropTypes.bool, skin: PropTypes.oneOf(['default', 'dark']), }; diff --git a/components/Dropdown/Dropdown.module.css b/components/Dropdown/Dropdown.module.css new file mode 100644 index 000000000..935305b0a --- /dev/null +++ b/components/Dropdown/Dropdown.module.css @@ -0,0 +1,100 @@ +:root { + --max-items-visibility-dropdown: 7; + --item-height-dropdown: 44px; + + --qtm-shadow-umbra-neutral-300-color: rgba(224, 224, 224, var(--qtm-shadow-umbra-opacity)); + --qtm-shadow-penumbra-neutral-300-color: rgba(224, 224, 224, var(--qtm-shadow-penumbra-opacity)); + --qtm-shadow-ambient-neutral-300-color: rgba(224, 224, 224, var(--qtm-shadow-ambient-opacity)); + + --shadow-5-neutral-300: + 0px 3px 5px -1px var(--qtm-shadow-umbra-neutral-300-color), + 0px 5px 8px 0px var(--qtm-shadow-penumbra-neutral-300-color), + 0px 1px 14px 0px var(--qtm-shadow-ambient-neutral-300-color); +} + +.dropdown-button { + align-items: center; + cursor: pointer; + display: flex; + justify-content: space-between; + text-align: left; + color: inherit; +} + +.without-text { + flex-direction: row-reverse; +} + +.dropdown-input { + align-items: center; + cursor: pointer; + display: flex; + justify-content: space-between; + text-align: left; + color: inherit; +} + +.dropdown-input-autocomplete { + padding: var(--qtm-spacing-xsmall) var(--qtm-spacing-xxxlarge) var(--qtm-spacing-xsmall) var(--qtm-spacing-medium); +} + +.arrow-icon { + pointer-events: none; + width: calc(var(--qtm-base-font-size) * 1.5); + color: inherit; +} + +.check-icon-without-selected-item { + color: var(--qtm-colors-primary-700); +} + +.drop-container { + position: relative; +} + +.drop-list { + border-radius: 4px; + box-sizing: border-box; + list-style: none; + max-height: calc(var(--item-height-dropdown) * var(--max-items-visibility-dropdown)); + overflow: auto; + padding: 0; + position: absolute; + width: 100%; + z-index: 9999; + background-color: var(--qtm-colors-neutral-0); + color: var(--qtm-colors-neutral-700); + margin-top: var(--qtm-spacing-xxsmall); + box-shadow: var(--shadow-5-neutral-300); +} + +.drop-image-item { + width: 24px; + height: 24px; + margin-left: var(--qtm-spacing-xsmall); +} + +.drop-item { + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + cursor: pointer; + min-height: 42px; + font-size: calc(var(--qtm-base-font-size) * 0.875); + background-color: var(--qtm-colors-neutral-0); + padding: var(--qtm-spacing-xsmall) var(--qtm-spacing-medium); +} + +.drop-item[aria-selected='true'] { + background-color: var(--qtm-colors-neutral-100); +} + +.drop-item-item-selected { + display: flex; + justify-content: space-between; +} + +.drop-item-item-selected-label { + color: var(--qtm-colors-primary-700); +} \ No newline at end of file diff --git a/components/Dropdown/__snapshots__/Dropdown.unit.test.jsx.snap b/components/Dropdown/__snapshots__/Dropdown.unit.test.jsx.snap index 4c385e03f..226f26ee3 100644 --- a/components/Dropdown/__snapshots__/Dropdown.unit.test.jsx.snap +++ b/components/Dropdown/__snapshots__/Dropdown.unit.test.jsx.snap @@ -25,31 +25,22 @@ exports[`Dropdown component should match the snapshot 1`] = ` width: 100%; } -.c1 { - -webkit-align-items: center; - -webkit-box-align: center; - -ms-flex-align: center; +.Dropdown-module__dropdown-button___DZE6T { align-items: center; + color: inherit; cursor: pointer; - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; display: flex; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; justify-content: space-between; text-align: left; - color: inherit; } -.c2 { - pointer-events: none; - width: 24px; +.Dropdown-module__arrow-icon___lvU4r { color: inherit; + pointer-events: none; + width: calc(var(--qtm-base-font-size)*1.5); } -.c0 { +.Dropdown-module__drop-container___SjYi- { position: relative; } @@ -60,7 +51,7 @@ exports[`Dropdown component should match the snapshot 1`] = ` aria-expanded="false" aria-haspopup="listbox" aria-labelledby="downshift-0-label" - class="c0" + class="Dropdown-module__drop-container___SjYi-" role="combobox" > * @@ -747,7 +685,7 @@ exports[`Dropdown component should match the snapshot 7`] = `