From 1b13eaa862bd27fff6fb4b053e6e36d15f215d7c Mon Sep 17 00:00:00 2001 From: Kishore Torlakonda Date: Tue, 31 May 2022 09:52:39 +0530 Subject: [PATCH 01/22] feat: added select component with basic, disabled features --- src/components/Select/Option/Option.tsx | 0 src/components/Select/Select.stories.tsx | 111 +++++++++++++++++++++++ src/components/Select/Select.tsx | 74 +++++++++++++++ src/components/Select/Select.types.ts | 26 ++++++ src/components/Select/index.ts | 2 + src/components/Select/select.module.scss | 63 +++++++++++++ 6 files changed, 276 insertions(+) create mode 100644 src/components/Select/Option/Option.tsx create mode 100644 src/components/Select/Select.stories.tsx create mode 100644 src/components/Select/Select.tsx create mode 100644 src/components/Select/Select.types.ts create mode 100644 src/components/Select/index.ts create mode 100644 src/components/Select/select.module.scss diff --git a/src/components/Select/Option/Option.tsx b/src/components/Select/Option/Option.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx new file mode 100644 index 000000000..a8a81a95b --- /dev/null +++ b/src/components/Select/Select.stories.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { Stories } from '@storybook/addon-docs'; +import { ComponentStory, ComponentMeta } from '@storybook/react'; +import { IconName } from '../Icon'; +import { Select } from './'; +import { Option } from './Select'; +import { SelectOption, SelectProps } from './Select.types'; + +export default { + title: 'Select', + parameters: {}, + argTypes: { + type: { + options: ['round', 'square'], + control: { type: 'inline-radio' }, + }, + }, +} as ComponentMeta; + +const Select_Basic_Story: ComponentStory = (args) => ( + +); + +export type T = ComponentStory> +export const Select_Basic: T = Select_Basic_Story.bind({}); +export const Select_With_DefaultValue: T = Select_Basic_Story.bind({}); +export const Select_Disabled: T = Select_Basic_Story.bind({}); +export const Select_With_Option_Disabled: T = Select_Basic_Story.bind({}); + +const defaultOptions: SelectOption[] = [ + { + iconProps: { path: IconName.mdiCalendar }, + text: 'Date', + value: 'date 1', + }, + { + iconProps: { path: IconName.mdiThumbUpOutline }, + text: 'Thumbs up', + value: 'date 2', + }, + { + iconProps: { path: IconName.mdiSchool }, + text: 'School', + value: 'date 3', + }, + { + iconProps: { path: IconName.mdiCalendar }, + text: 'Date', + value: 'date 4', + }, + { + iconProps: { path: IconName.mdiThumbUpOutline }, + text: 'Thumbs up', + value: 'date 5', + }, + { + iconProps: { path: IconName.mdiSchool }, + text: 'School', + value: 'date 6', + }, + { + iconProps: { path: IconName.mdiCalendar }, + text: 'Date', + value: 'date 7', + }, + { + iconProps: { path: IconName.mdiThumbUpOutline }, + text: 'Thumbs up', + value: 'date 8', + }, + { + iconProps: { path: IconName.mdiSchool }, + text: 'School', + value: 'date 9', + }, +]; +const SelectArgs: SelectProps = { + children: 'JD', + classNames: 'my-Select-class', + 'data-test-id': 'my-Select-test-id', + style: {}, + options: defaultOptions, +}; + +Select_Basic.args = { + ...SelectArgs, +}; + +Select_With_DefaultValue.args = { + ...Select_Basic.args, + defaultValue: 'date 2', +} + +Select_Disabled.args = { + ...Select_With_DefaultValue.args, + disabled: true, +} + +Select_With_Option_Disabled.args = { + ...Select_Basic.args, + options: [ + { + iconProps: { path: IconName.mdiShare }, + text: 'option taken', + value: 'option1', + disabled: true, + }, + ...defaultOptions + ] +} \ No newline at end of file diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx new file mode 100644 index 000000000..a5799da8a --- /dev/null +++ b/src/components/Select/Select.tsx @@ -0,0 +1,74 @@ +import React, { Ref, FC, useState } from 'react'; + +// Styles: +import { Dropdown } from '../Dropdown'; +import { ButtonIconAlign, DefaultButton } from '../Button'; +import { IconName } from '../Icon'; +import { Props } from '@storybook/addon-docs'; +import { Menu } from '../Menu'; +import { SearchBox, TextInput } from '../Inputs'; +import { SelectOption, SelectProps } from './Select.types'; + +const Options = (props: SelectProps, onOptionChange: Function) => { + const { options } = props; + return ( { + const option = options.find(option => option.value === value); + onOptionChange(option); + }} + />) +}; + +export const Select: FC = React.forwardRef( + ( + { + options = [], + ...rest + }, + _ref: Ref + ) => { + const { defaultValue, disabled } = rest; + const [visible, setVisibility] = useState(false); + const [selectedOption, setSelectedOption] = useState({text: '', value: ''}); + const onOptionChange = (item: any) => { + setSelectedOption(item) + } + + const onClear = () => { + setSelectedOption({text: '', value: ''}); + } + if(!selectedOption.value && defaultValue) { + const defaultOption = options.find(option => option.value === defaultValue); + setSelectedOption(defaultOption); + } + return ( + setVisibility(isVisible)} + overlay={Options({options, ...rest}, onOptionChange)} + trigger="click" + classNames="my-dropdown-class" + dropdownClassNames="my-dropdown-class" + placement="bottom-start" + positionStrategy="absolute" + disabled={false} + > + + + ); + } +); + +export const Option: FC = React.forwardRef( + (props, _ref: Ref) => { + const { key, label } = props; + return
  • {label}
  • ; + } +); diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts new file mode 100644 index 000000000..ddd963929 --- /dev/null +++ b/src/components/Select/Select.types.ts @@ -0,0 +1,26 @@ +import { Ref } from 'react'; +import { IconProps } from '../Icon'; +import { MenuItem } from '../Menu'; +import { OcBaseProps } from '../OcBase'; + +export interface SelectOption extends MenuItem {} + +export interface SelectProps extends OcBaseProps { + /** + * Default Value + * @default '' + */ + defaultValue?: string, + + /** + * Default options + * @default '' + */ + options?: SelectOption[], + + /** + * The select disabled state. + * @default false + */ + disabled?: boolean; +} diff --git a/src/components/Select/index.ts b/src/components/Select/index.ts new file mode 100644 index 000000000..066f15c93 --- /dev/null +++ b/src/components/Select/index.ts @@ -0,0 +1,2 @@ +export * from './Select.types'; +export * from './Select'; diff --git a/src/components/Select/select.module.scss b/src/components/Select/select.module.scss new file mode 100644 index 000000000..e1243876b --- /dev/null +++ b/src/components/Select/select.module.scss @@ -0,0 +1,63 @@ +.wrapper-style { + align-items: center; + display: flex; + justify-content: center; + + &.disruptive { + background-color: var(--disruptive-color-20); + border: solid 1px var(--disruptive-color-30); + color: var(--disruptive-color); + } + + &.grey { + background-color: var(--grey-color-20); + border: solid 1px var(--grey-color-30); + color: var(--grey-color); + } + + &.blue { + background-color: var(--blue-color-20); + border: solid 1px var(--blue-color-30); + color: var(--blue-color); + } + + &.orange { + background-color: var(--orange-color-20); + border: solid 1px var(--orange-color-30); + color: var(--orange-color); + } + + &.green { + background-color: var(--green-color-20); + border: solid 1px var(--green-color-30); + color: var(--green-color); + } + + &.violet { + background-color: var(--violet-color-20); + border: solid 1px var(--violet-color-30); + color: var(--violet-color); + } + + &.yellow { + background-color: var(--yellow-color-20); + border: solid 1px var(--yellow-color-30); + color: var(--yellow-color); + } + + &.bluegreen { + background-color: var(--bluegreen-color-20); + border: solid 1px var(--bluegreen-color-30); + color: var(--bluegreen-color); + } +} + +.round-image { + border-radius: 50%; +} + +.image-style { + height: 100%; + width: 100%; + object-fit: cover; +} From 6b9e45bffb4f6d2a1d17622b12f10b694aabe521 Mon Sep 17 00:00:00 2001 From: Kishore Torlakonda Date: Tue, 31 May 2022 10:50:46 +0530 Subject: [PATCH 02/22] feat: updating input comp to support select --- src/components/Inputs/Input.types.ts | 11 ++++++++++ src/components/Inputs/TextInput/TextInput.tsx | 20 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/components/Inputs/Input.types.ts b/src/components/Inputs/Input.types.ts index 9c2b59bb6..797eb001d 100644 --- a/src/components/Inputs/Input.types.ts +++ b/src/components/Inputs/Input.types.ts @@ -179,6 +179,17 @@ export interface TextInputProps extends InputProps { * @default false */ required?: boolean; + + /** + * The input required attribute. + * @default false + */ + clearable?: boolean; + + /** + * onclear event handler. + */ + onClear?: React.MouseEventHandler; } export interface InputProps diff --git a/src/components/Inputs/TextInput/TextInput.tsx b/src/components/Inputs/TextInput/TextInput.tsx index 1d01a7f98..1f6e7e91d 100644 --- a/src/components/Inputs/TextInput/TextInput.tsx +++ b/src/components/Inputs/TextInput/TextInput.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import { ButtonSize, DefaultButton } from '../../Button'; import { Icon, IconName, IconSize } from '../../Icon'; import { Label } from '../../Label'; @@ -32,6 +32,7 @@ export const TextInput: FC = ({ numbersOnly = false, onBlur, onChange, + onClear, onFocus, onKeyDown, placeholder, @@ -41,9 +42,10 @@ export const TextInput: FC = ({ theme = TextInputTheme.light, value, waitInterval = 10, + clearable = true, ...rest }) => { - const [clearButtonShown, setClearButtonShown] = useState(false); + const [clearButtonShown, _setClearButtonShown] = useState(false); const [inputId] = useState(uniqueId(id || 'input-')); const inputField: HTMLElement = document.getElementById(inputId); @@ -52,6 +54,19 @@ export const TextInput: FC = ({ styles.leftIcon, ]); + useEffect(() => { + if (value !== undefined && value.toString().length > 0) { + return setClearButtonShown(true); + } + setClearButtonShown(false); + }, [value]); + + const setClearButtonShown = (showClear: boolean) => { + return !clearable + ? _setClearButtonShown(false) + : _setClearButtonShown(showClear); + }; + const iconButtonClassNames: string = mergeClasses([ styles.iconButton, styles.leftIcon, @@ -117,6 +132,7 @@ export const TextInput: FC = ({ if (!!inputField) { (inputField as HTMLInputElement).value = ''; } + onClear?.(_event); setClearButtonShown(false); }; From 84ef021739f5676a31a36e98ebe2ebb3df1f2455 Mon Sep 17 00:00:00 2001 From: Kishore Torlakonda Date: Tue, 31 May 2022 11:57:26 +0530 Subject: [PATCH 03/22] feat: clearable feature on select --- src/components/Select/Select.stories.tsx | 23 +++++++---- src/components/Select/Select.tsx | 52 +++++++++++++----------- src/components/Select/Select.types.ts | 10 ++++- 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index a8a81a95b..ef16aacee 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -18,15 +18,15 @@ export default { } as ComponentMeta; const Select_Basic_Story: ComponentStory = (args) => ( - + ); -export type T = ComponentStory> -export const Select_Basic: T = Select_Basic_Story.bind({}); +export type T = ComponentStory>; +export const Select_Basic: T = Select_Basic_Story.bind({}); export const Select_With_DefaultValue: T = Select_Basic_Story.bind({}); export const Select_Disabled: T = Select_Basic_Story.bind({}); export const Select_With_Option_Disabled: T = Select_Basic_Story.bind({}); +export const Select_With_Clear: T = Select_Basic_Story.bind({}); const defaultOptions: SelectOption[] = [ { @@ -90,12 +90,12 @@ Select_Basic.args = { Select_With_DefaultValue.args = { ...Select_Basic.args, defaultValue: 'date 2', -} +}; Select_Disabled.args = { ...Select_With_DefaultValue.args, disabled: true, -} +}; Select_With_Option_Disabled.args = { ...Select_Basic.args, @@ -106,6 +106,11 @@ Select_With_Option_Disabled.args = { value: 'option1', disabled: true, }, - ...defaultOptions - ] -} \ No newline at end of file + ...defaultOptions, + ], +}; + +Select_With_Clear.args = { + ...Select_With_DefaultValue.args, + clearable: true, +}; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index a5799da8a..550e3f639 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -11,41 +11,44 @@ import { SelectOption, SelectProps } from './Select.types'; const Options = (props: SelectProps, onOptionChange: Function) => { const { options } = props; - return ( { - const option = options.find(option => option.value === value); - onOptionChange(option); - }} - />) + return ( + { + const option = options.find((option) => option.value === value); + onOptionChange(option); + }} + /> + ); }; export const Select: FC = React.forwardRef( - ( - { - options = [], - ...rest - }, - _ref: Ref - ) => { - const { defaultValue, disabled } = rest; + ({ options = [], ...rest }, _ref: Ref) => { + const { defaultValue, disabled, clearable = false } = rest; const [visible, setVisibility] = useState(false); - const [selectedOption, setSelectedOption] = useState({text: '', value: ''}); + const [defaultValueShown, setDefaultValueShown] = useState(false); + const [selectedOption, setSelectedOption] = useState({ + text: '', + value: '', + }); const onOptionChange = (item: any) => { - setSelectedOption(item) - } + setSelectedOption(item); + }; const onClear = () => { - setSelectedOption({text: '', value: ''}); - } - if(!selectedOption.value && defaultValue) { - const defaultOption = options.find(option => option.value === defaultValue); + setSelectedOption({ text: '', value: '' }); + }; + if (defaultValue && !defaultValueShown) { + const defaultOption = options.find( + (option) => option.value === defaultValue + ); setSelectedOption(defaultOption); + setDefaultValueShown(true); } return ( setVisibility(isVisible)} - overlay={Options({options, ...rest}, onOptionChange)} + overlay={Options({ options, ...rest }, onOptionChange)} trigger="click" classNames="my-dropdown-class" dropdownClassNames="my-dropdown-class" @@ -54,12 +57,13 @@ export const Select: FC = React.forwardRef( disabled={false} > ); diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts index ddd963929..052593b39 100644 --- a/src/components/Select/Select.types.ts +++ b/src/components/Select/Select.types.ts @@ -10,17 +10,23 @@ export interface SelectProps extends OcBaseProps { * Default Value * @default '' */ - defaultValue?: string, + defaultValue?: string; /** * Default options * @default '' */ - options?: SelectOption[], + options?: SelectOption[]; /** * The select disabled state. * @default false */ disabled?: boolean; + + /** + * clearable. + * @default false + */ + clearable?: boolean; } From d3f4842090b456678d3706adf7c9aeb78cadd856 Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Tue, 31 May 2022 15:24:10 +0530 Subject: [PATCH 04/22] feat: added filterable options --- src/components/Select/Select.stories.tsx | 78 ++++++++++++------------ src/components/Select/Select.tsx | 72 ++++++++++++++++++---- src/components/Select/Select.types.ts | 6 ++ 3 files changed, 105 insertions(+), 51 deletions(-) diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index ef16aacee..29b4ca037 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -17,62 +17,58 @@ export default { }, } as ComponentMeta; -const Select_Basic_Story: ComponentStory = (args) => ( +const Basic_Story: ComponentStory = (args) => ( ); export type T = ComponentStory>; -export const Select_Basic: T = Select_Basic_Story.bind({}); -export const Select_With_DefaultValue: T = Select_Basic_Story.bind({}); -export const Select_Disabled: T = Select_Basic_Story.bind({}); -export const Select_With_Option_Disabled: T = Select_Basic_Story.bind({}); -export const Select_With_Clear: T = Select_Basic_Story.bind({}); +export const Basic: T = Basic_Story.bind({}); +export const With_DefaultValue: T = Basic_Story.bind({}); +export const Disabled: T = Basic_Story.bind({}); +export const With_Option_Disabled: T = Basic_Story.bind({}); +export const With_Clear: T = Basic_Story.bind({}); +export const With_Filterable: T = Basic_Story.bind({}); const defaultOptions: SelectOption[] = [ - { - iconProps: { path: IconName.mdiCalendar }, - text: 'Date', - value: 'date 1', - }, { iconProps: { path: IconName.mdiThumbUpOutline }, text: 'Thumbs up', - value: 'date 2', + value: 'thumbsup', }, { iconProps: { path: IconName.mdiSchool }, text: 'School', - value: 'date 3', + value: 'school', }, { iconProps: { path: IconName.mdiCalendar }, text: 'Date', - value: 'date 4', + value: 'date', }, { - iconProps: { path: IconName.mdiThumbUpOutline }, - text: 'Thumbs up', - value: 'date 5', + iconProps: { path: IconName.mdiAccount }, + text: 'Account', + value: 'account', }, { - iconProps: { path: IconName.mdiSchool }, - text: 'School', - value: 'date 6', + iconProps: { path: IconName.mdiAccountHardHat }, + text: 'Hat', + value: 'hat', }, { - iconProps: { path: IconName.mdiCalendar }, - text: 'Date', - value: 'date 7', + iconProps: { path: IconName.mdiAccountTie }, + text: 'Tie', + value: 'tie', }, { - iconProps: { path: IconName.mdiThumbUpOutline }, - text: 'Thumbs up', - value: 'date 8', + iconProps: { path: IconName.mdiCalendarAlert }, + text: 'Date alert', + value: 'datealert', }, { - iconProps: { path: IconName.mdiSchool }, - text: 'School', - value: 'date 9', + iconProps: { path: IconName.mdiBell }, + text: 'Bell', + value: 'bell', }, ]; const SelectArgs: SelectProps = { @@ -83,22 +79,22 @@ const SelectArgs: SelectProps = { options: defaultOptions, }; -Select_Basic.args = { +Basic.args = { ...SelectArgs, }; -Select_With_DefaultValue.args = { - ...Select_Basic.args, +With_DefaultValue.args = { + ...Basic.args, defaultValue: 'date 2', }; -Select_Disabled.args = { - ...Select_With_DefaultValue.args, +Disabled.args = { + ...With_DefaultValue.args, disabled: true, }; -Select_With_Option_Disabled.args = { - ...Select_Basic.args, +With_Option_Disabled.args = { + ...Basic.args, options: [ { iconProps: { path: IconName.mdiShare }, @@ -110,7 +106,13 @@ Select_With_Option_Disabled.args = { ], }; -Select_With_Clear.args = { - ...Select_With_DefaultValue.args, +With_Clear.args = { + ...With_DefaultValue.args, + clearable: true, +}; + +With_Filterable.args = { + ...Basic.args, + filterable: true, clearable: true, }; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 550e3f639..fa39f9372 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -9,8 +9,7 @@ import { Menu } from '../Menu'; import { SearchBox, TextInput } from '../Inputs'; import { SelectOption, SelectProps } from './Select.types'; -const Options = (props: SelectProps, onOptionChange: Function) => { - const { options } = props; +const Options = (options: SelectOption[], onOptionChange: Function) => { return ( { export const Select: FC = React.forwardRef( ({ options = [], ...rest }, _ref: Ref) => { - const { defaultValue, disabled, clearable = false } = rest; + const { + defaultValue, + disabled, + clearable = false, + filterable = false, + } = rest; const [visible, setVisibility] = useState(false); const [defaultValueShown, setDefaultValueShown] = useState(false); const [selectedOption, setSelectedOption] = useState({ text: '', value: '', }); + const [filteredOptions, setFilteredOptions] = useState(options); + const [searchQuery, setSearchQuery] = useState(''); + const onOptionChange = (item: any) => { + setSearchQuery(''); setSelectedOption(item); }; const onClear = () => { + setSearchQuery(''); + setFilteredOptions(options); setSelectedOption({ text: '', value: '' }); }; + + const onChange = ( + event: React.ChangeEvent + ) => { + const { target } = event; + console.log('@@@@@ kishore ', target?.value); + if (target?.value) { + const value = target.value.toLowerCase(); + const filteredOptions = options.filter((option) => + option.value.toLowerCase().includes(value) + ); + setSearchQuery(target.value); + setFilteredOptions(filteredOptions); + } else { + setFilteredOptions(options); + } + }; + if (defaultValue && !defaultValueShown) { const defaultOption = options.find( (option) => option.value === defaultValue @@ -45,10 +73,11 @@ export const Select: FC = React.forwardRef( setSelectedOption(defaultOption); setDefaultValueShown(true); } + return ( setVisibility(isVisible)} - overlay={Options({ options, ...rest }, onOptionChange)} + overlay={Options(filteredOptions, onOptionChange)} trigger="click" classNames="my-dropdown-class" dropdownClassNames="my-dropdown-class" @@ -56,15 +85,32 @@ export const Select: FC = React.forwardRef( positionStrategy="absolute" disabled={false} > - + {filterable ? ( + + ) : ( + + )} ); } diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts index 052593b39..09b7a6cae 100644 --- a/src/components/Select/Select.types.ts +++ b/src/components/Select/Select.types.ts @@ -29,4 +29,10 @@ export interface SelectProps extends OcBaseProps { * @default false */ clearable?: boolean; + + /** + * clearable. + * @default false + */ + filterable?: boolean; } From 1a204457e329c961910729d2ea539c582de4e4ca Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Tue, 31 May 2022 18:20:24 +0530 Subject: [PATCH 05/22] feat: added support for multi selection --- src/components/Dropdown/Dropdown.tsx | 25 +++- src/components/Dropdown/Dropdown.types.ts | 9 ++ src/components/Select/Select.stories.tsx | 18 ++- src/components/Select/Select.tsx | 147 ++++++++++++++-------- src/components/Select/Select.types.ts | 6 + 5 files changed, 143 insertions(+), 62 deletions(-) diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index 35a818798..83191fbd9 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -5,7 +5,7 @@ import React, { useState, SyntheticEvent, } from 'react'; -import { DropdownProps } from './Dropdown.types'; +import { DropdownProps, ToggleState } from './Dropdown.types'; import { autoUpdate, shift, useFloating } from '@floating-ui/react-dom'; import { offset as fOffset } from '@floating-ui/core'; import { mergeClasses, uniqueId } from '../../shared/utilities'; @@ -41,6 +41,7 @@ export const Dropdown: FC = ({ offset = 0, positionStrategy = 'absolute', onVisibleChange, + onToggle, disabled, }) => { const [visible, setVisible] = useState(false); @@ -55,16 +56,23 @@ export const Dropdown: FC = ({ }); const toggle: Function = - (show: boolean): Function => + ( + show: boolean, + onToggle = (_: any, changes: Partial) => changes + ): Function => (e: SyntheticEvent): void => { + const newState = onToggle( + { closing, visible }, + { closing: !show, visible: show } + ); if (PREVENT_DEFAULT_TRIGGERS.includes(trigger)) { e.preventDefault(); } - setClosing(!show); + setClosing(newState.closing); timeout && clearTimeout(timeout); timeout = setTimeout( () => { - setVisible(show); + setVisible(newState.visible); }, !show ? ANIMATION_DURATION : 0 ); @@ -136,7 +144,7 @@ export const Dropdown: FC = ({ style={dropdownStyles} className={dropdownClasses} tabIndex={0} - onClick={toggle(false)} + onClick={toggle(false, onToggle)} id={dropdownId} > {overlay} @@ -149,7 +157,12 @@ export const Dropdown: FC = ({ style={style} ref={reference} {...(TRIGGER_TO_HANDLER_MAP_ON_LEAVE[trigger] - ? { [TRIGGER_TO_HANDLER_MAP_ON_LEAVE[trigger]]: toggle(false) } + ? { + [TRIGGER_TO_HANDLER_MAP_ON_LEAVE[trigger]]: toggle( + false, + onToggle + ), + } : {})} > {getReference()} diff --git a/src/components/Dropdown/Dropdown.types.ts b/src/components/Dropdown/Dropdown.types.ts index 56c81fd90..0579656ea 100644 --- a/src/components/Dropdown/Dropdown.types.ts +++ b/src/components/Dropdown/Dropdown.types.ts @@ -1,6 +1,7 @@ import { Placement, Strategy } from '@floating-ui/react-dom'; import React from 'react'; +export type ToggleState = { visible: boolean; closing: boolean }; export interface DropdownProps { /** * The trigger mode that opens the dropdown @@ -11,6 +12,14 @@ export interface DropdownProps { * @param visible {boolean} */ onVisibleChange?: (visible: boolean) => void; + /** + * Callback called when the visibility of the dropdown changes + * @param visible {boolean} + */ + onToggle?: ( + currentState: ToggleState, + changes: Partial + ) => ToggleState; /** * The dropdown content */ diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index 29b4ca037..fd10d6bf3 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -3,7 +3,6 @@ import { Stories } from '@storybook/addon-docs'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { IconName } from '../Icon'; import { Select } from './'; -import { Option } from './Select'; import { SelectOption, SelectProps } from './Select.types'; export default { @@ -25,9 +24,10 @@ export type T = ComponentStory>; export const Basic: T = Basic_Story.bind({}); export const With_DefaultValue: T = Basic_Story.bind({}); export const Disabled: T = Basic_Story.bind({}); -export const With_Option_Disabled: T = Basic_Story.bind({}); +export const Options_Disabled: T = Basic_Story.bind({}); export const With_Clear: T = Basic_Story.bind({}); -export const With_Filterable: T = Basic_Story.bind({}); +export const Filterable: T = Basic_Story.bind({}); +export const Multiple: T = Basic_Story.bind({}); const defaultOptions: SelectOption[] = [ { @@ -85,7 +85,7 @@ Basic.args = { With_DefaultValue.args = { ...Basic.args, - defaultValue: 'date 2', + defaultValue: 'school', }; Disabled.args = { @@ -93,7 +93,7 @@ Disabled.args = { disabled: true, }; -With_Option_Disabled.args = { +Options_Disabled.args = { ...Basic.args, options: [ { @@ -111,8 +111,14 @@ With_Clear.args = { clearable: true, }; -With_Filterable.args = { +Filterable.args = { ...Basic.args, filterable: true, clearable: true, }; + +Multiple.args = { + ...Basic.args, + filterable: true, + multiple: true, +}; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index fa39f9372..0031eb91f 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,13 +1,14 @@ import React, { Ref, FC, useState } from 'react'; // Styles: -import { Dropdown } from '../Dropdown'; +import { Dropdown, ToggleState } from '../Dropdown'; import { ButtonIconAlign, DefaultButton } from '../Button'; import { IconName } from '../Icon'; import { Props } from '@storybook/addon-docs'; import { Menu } from '../Menu'; import { SearchBox, TextInput } from '../Inputs'; import { SelectOption, SelectProps } from './Select.types'; +import { Pill, PillSize } from '../Pills'; const Options = (options: SelectOption[], onOptionChange: Function) => { return ( @@ -28,6 +29,7 @@ export const Select: FC = React.forwardRef( disabled, clearable = false, filterable = false, + multiple = false, } = rest; const [visible, setVisibility] = useState(false); const [defaultValueShown, setDefaultValueShown] = useState(false); @@ -37,10 +39,32 @@ export const Select: FC = React.forwardRef( }); const [filteredOptions, setFilteredOptions] = useState(options); const [searchQuery, setSearchQuery] = useState(''); + const [selectedOptions, setSelectedOptions] = useState( + [] + ); - const onOptionChange = (item: any) => { + const onOptionChange = (option: SelectOption) => { setSearchQuery(''); - setSelectedOption(item); + if (multiple) { + const index = selectedOptions.findIndex( + (selectedOption) => selectedOption.value === option.value + ); + if (index > -1) { + const selectionOptionsSet: { [key: string]: SelectOption } = + selectedOptions.reduce((acc, opt) => { + return { + ...acc, + [opt.value]: opt, + }; + }, {}); + delete selectionOptionsSet[option.value]; + return setSelectedOptions( + Object.values(selectionOptionsSet) + ); + } + return setSelectedOptions([...selectedOptions, option]); + } + setSelectedOption(option); }; const onClear = () => { @@ -53,7 +77,6 @@ export const Select: FC = React.forwardRef( event: React.ChangeEvent ) => { const { target } = event; - console.log('@@@@@ kishore ', target?.value); if (target?.value) { const value = target.value.toLowerCase(); const filteredOptions = options.filter((option) => @@ -66,59 +89,83 @@ export const Select: FC = React.forwardRef( } }; + const onDropdownToggle = ( + current: ToggleState, + changes: Partial + ) => { + if (multiple) { + return { + visible: true, + closing: false, + }; + } + return { ...current, ...changes }; + }; + if (defaultValue && !defaultValueShown) { const defaultOption = options.find( (option) => option.value === defaultValue ); - setSelectedOption(defaultOption); + if (multiple) { + setSelectedOptions([...selectedOptions, defaultOption]); + } else { + setSelectedOption(defaultOption); + } setDefaultValueShown(true); } - return ( - setVisibility(isVisible)} - overlay={Options(filteredOptions, onOptionChange)} - trigger="click" - classNames="my-dropdown-class" - dropdownClassNames="my-dropdown-class" - placement="bottom-start" - positionStrategy="absolute" - disabled={false} - > - {filterable ? ( - - ) : ( - - )} - + <> + {multiple ? ( +
    + {selectedOptions.map((option) => ( + + ))} +
    + ) : null} + setVisibility(isVisible)} + onToggle={onDropdownToggle} + overlay={Options(filteredOptions, onOptionChange)} + trigger="click" + classNames="my-dropdown-class" + dropdownClassNames="my-dropdown-class" + placement="bottom-start" + positionStrategy="absolute" + disabled={false} + > + {filterable ? ( + + ) : ( + + )} + + ); } ); - -export const Option: FC = React.forwardRef( - (props, _ref: Ref) => { - const { key, label } = props; - return
  • {label}
  • ; - } -); diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts index 09b7a6cae..3af04b00e 100644 --- a/src/components/Select/Select.types.ts +++ b/src/components/Select/Select.types.ts @@ -35,4 +35,10 @@ export interface SelectProps extends OcBaseProps { * @default false */ filterable?: boolean; + + /** + * select multiple. + * @default false + */ + multiple?: boolean; } From d403b4d4954f742d4a83e9c5b5df5ca2f14fce51 Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Tue, 31 May 2022 20:58:06 +0530 Subject: [PATCH 06/22] feat: added dynamic options --- src/components/Select/Select.stories.tsx | 104 ++++++++++++++++++++++- src/components/Select/Select.tsx | 28 ++++-- src/components/Select/Select.types.ts | 12 +++ 3 files changed, 136 insertions(+), 8 deletions(-) diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index fd10d6bf3..6035f69f7 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { FC, Ref, useState } from 'react'; import { Stories } from '@storybook/addon-docs'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { IconName } from '../Icon'; @@ -29,6 +29,103 @@ export const With_Clear: T = Basic_Story.bind({}); export const Filterable: T = Basic_Story.bind({}); export const Multiple: T = Basic_Story.bind({}); +const usAllStates = [ + 'Alabama', + 'Alaska', + 'Arizona', + 'Arkansas', + 'California', + 'Colorado', + 'Connecticut', + 'Delaware', + 'Florida', + 'Georgia', + 'Hawaii', + 'Idaho', + 'Illinois', + 'Indiana', + 'Iowa', + 'Kansas', + 'Kentucky', + 'Louisiana', + 'Maine', + 'Maryland', + 'Massachusetts', + 'Michigan', + 'Minnesota', + 'Mississippi', + 'Missouri', + 'Montana', + 'Nebraska', + 'Nevada', + 'New Hampshire', + 'New Jersey', + 'New Mexico', + 'New York', + 'North Carolina', + 'North Dakota', + 'Ohio', + 'Oklahoma', + 'Oregon', + 'Pennsylvania', + 'Rhode Island', + 'South Carolina', + 'South Dakota', + 'Tennessee', + 'Texas', + 'Utah', + 'Vermont', + 'Virginia', + 'Washington', + 'West Virginia', + 'Wisconsin', + 'Wyoming', +]; + +const DynamicSelect: FC = React.forwardRef( + ({ ...rest }, _ref: Ref) => { + let timer: any; + const [usStates, setUsStates] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const loadUsStates = (searchString: string) => { + console.log('async', searchString); + setIsLoading(true); + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + const filtered = usAllStates.filter((state) => + state.toLowerCase().includes(searchString.toLowerCase()) + ); + const options: SelectOption[] = filtered.map((state) => { + return { + text: state, + value: state, + }; + }); + console.log('async2', filtered); + setUsStates(options); + setIsLoading(false); + }, 2000); + }; + return ( + + ); + } +); + +const Dynamic_Story: ComponentStory = (args) => ( + +); + +export const Dynamic: T = Dynamic_Story.bind({}); + const defaultOptions: SelectOption[] = [ { iconProps: { path: IconName.mdiThumbUpOutline }, @@ -122,3 +219,8 @@ Multiple.args = { filterable: true, multiple: true, }; + +Dynamic.args = { + ...Basic.args, + clearable: true, +}; diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 0031eb91f..50649ecc4 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,4 +1,4 @@ -import React, { Ref, FC, useState } from 'react'; +import React, { Ref, FC, useState, useEffect } from 'react'; // Styles: import { Dropdown, ToggleState } from '../Dropdown'; @@ -30,6 +30,8 @@ export const Select: FC = React.forwardRef( clearable = false, filterable = false, multiple = false, + loadOptions, + isLoading = false, } = rest; const [visible, setVisibility] = useState(false); const [defaultValueShown, setDefaultValueShown] = useState(false); @@ -43,6 +45,13 @@ export const Select: FC = React.forwardRef( [] ); + useEffect(() => { + console.log('came here', options, searchQuery); + if (!isLoading) { + setFilteredOptions(options); + } + }, [JSON.stringify(options)]); + const onOptionChange = (option: SelectOption) => { setSearchQuery(''); if (multiple) { @@ -67,13 +76,13 @@ export const Select: FC = React.forwardRef( setSelectedOption(option); }; - const onClear = () => { + const onInputClear = () => { setSearchQuery(''); setFilteredOptions(options); setSelectedOption({ text: '', value: '' }); }; - const onChange = ( + const onInputChange = ( event: React.ChangeEvent ) => { const { target } = event; @@ -83,7 +92,11 @@ export const Select: FC = React.forwardRef( option.value.toLowerCase().includes(value) ); setSearchQuery(target.value); - setFilteredOptions(filteredOptions); + if (loadOptions) { + loadOptions(target.value); + } else { + setFilteredOptions(filteredOptions); + } } else { setFilteredOptions(options); } @@ -94,6 +107,7 @@ export const Select: FC = React.forwardRef( changes: Partial ) => { if (multiple) { + // return { visible: true, closing: false, @@ -149,8 +163,8 @@ export const Select: FC = React.forwardRef( } role="button" disabled={disabled} - onClear={onClear} - onChange={onChange} + onClear={onInputClear} + onChange={onInputChange} clearable={clearable} /> ) : ( @@ -160,7 +174,7 @@ export const Select: FC = React.forwardRef( value={selectedOption?.text} role="button" disabled={disabled} - onClear={onClear} + onClear={onInputClear} clearable={clearable} /> )} diff --git a/src/components/Select/Select.types.ts b/src/components/Select/Select.types.ts index 3af04b00e..9c3d32805 100644 --- a/src/components/Select/Select.types.ts +++ b/src/components/Select/Select.types.ts @@ -41,4 +41,16 @@ export interface SelectProps extends OcBaseProps { * @default false */ multiple?: boolean; + + /** + * select multiple. + * @default false + */ + loadOptions?: (inputValue: string) => void; + + /** + * select multiple. + * @default false + */ + isLoading?: boolean; } From b01a8bf747f64b1c567068696189020d887911ce Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Wed, 1 Jun 2022 13:59:05 +0530 Subject: [PATCH 07/22] feat: added styling --- src/components/Select/Option/Option.tsx | 0 src/components/Select/Select.stories.tsx | 41 +++++++++++---- src/components/Select/Select.tsx | 42 ++++++++++----- src/components/Select/select.module.scss | 66 +++--------------------- 4 files changed, 68 insertions(+), 81 deletions(-) delete mode 100644 src/components/Select/Option/Option.tsx diff --git a/src/components/Select/Option/Option.tsx b/src/components/Select/Option/Option.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index 6035f69f7..0d55740f4 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -1,5 +1,4 @@ import React, { FC, Ref, useState } from 'react'; -import { Stories } from '@storybook/addon-docs'; import { ComponentStory, ComponentMeta } from '@storybook/react'; import { IconName } from '../Icon'; import { Select } from './'; @@ -16,18 +15,38 @@ export default { }, } as ComponentMeta; +const Wrapper: FC = React.forwardRef( + ({ ...rest }, _ref: Ref) => { + return ( +
    + +
    + ); + } +); + const Basic_Story: ComponentStory = (args) => ( - +
    + +
    ); -export type T = ComponentStory>; -export const Basic: T = Basic_Story.bind({}); -export const With_DefaultValue: T = Basic_Story.bind({}); -export const Disabled: T = Basic_Story.bind({}); -export const Options_Disabled: T = Basic_Story.bind({}); -export const With_Clear: T = Basic_Story.bind({}); -export const Filterable: T = Basic_Story.bind({}); -export const Multiple: T = Basic_Story.bind({}); +export type SelectStory = ComponentStory>; +export const Basic: SelectStory = Basic_Story.bind({}); +export const With_DefaultValue: SelectStory = Basic_Story.bind({}); +export const Disabled: SelectStory = Basic_Story.bind({}); +export const Options_Disabled: SelectStory = Basic_Story.bind({}); +export const With_Clear: SelectStory = Basic_Story.bind({}); +export const Filterable: SelectStory = Basic_Story.bind({}); +export const Multiple: SelectStory = Basic_Story.bind({}); const usAllStates = [ 'Alabama', @@ -124,7 +143,7 @@ const Dynamic_Story: ComponentStory = (args) => ( ); -export const Dynamic: T = Dynamic_Story.bind({}); +export const Dynamic: SelectStory = Dynamic_Story.bind({}); const defaultOptions: SelectOption[] = [ { diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 50649ecc4..80ce909df 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -2,13 +2,14 @@ import React, { Ref, FC, useState, useEffect } from 'react'; // Styles: import { Dropdown, ToggleState } from '../Dropdown'; -import { ButtonIconAlign, DefaultButton } from '../Button'; -import { IconName } from '../Icon'; -import { Props } from '@storybook/addon-docs'; import { Menu } from '../Menu'; -import { SearchBox, TextInput } from '../Inputs'; +import { TextInput, TextInputWidth } from '../Inputs'; import { SelectOption, SelectProps } from './Select.types'; import { Pill, PillSize } from '../Pills'; +import { IconName } from '../Icon'; +import { mergeClasses } from '../../shared/utilities'; + +import styles from './select.module.scss'; const Options = (options: SelectOption[], onOptionChange: Function) => { return ( @@ -32,6 +33,8 @@ export const Select: FC = React.forwardRef( multiple = false, loadOptions, isLoading = false, + classNames, + style, } = rest; const [visible, setVisibility] = useState(false); const [defaultValueShown, setDefaultValueShown] = useState(false); @@ -46,10 +49,7 @@ export const Select: FC = React.forwardRef( ); useEffect(() => { - console.log('came here', options, searchQuery); - if (!isLoading) { - setFilteredOptions(options); - } + setFilteredOptions(options); }, [JSON.stringify(options)]); const onOptionChange = (option: SelectOption) => { @@ -127,8 +127,14 @@ export const Select: FC = React.forwardRef( } setDefaultValueShown(true); } + + const componentClasses: string = mergeClasses([ + styles.selectWrapper, + classNames, + ]); + return ( - <> +
    {multiple ? (
    {selectedOptions.map((option) => ( @@ -146,8 +152,8 @@ export const Select: FC = React.forwardRef( onToggle={onDropdownToggle} overlay={Options(filteredOptions, onOptionChange)} trigger="click" - classNames="my-dropdown-class" - dropdownClassNames="my-dropdown-class" + classNames={styles.selectDropdownMainWrapper} + dropdownClassNames={styles.myDropdownClass} placement="bottom-start" positionStrategy="absolute" disabled={false} @@ -166,6 +172,12 @@ export const Select: FC = React.forwardRef( onClear={onInputClear} onChange={onInputChange} clearable={clearable} + inputWidth={TextInputWidth.fill} + iconProps={{ + path: IconName.mdiMenuDown, + rotate: visible ? 180 : 0, + }} + classNames={styles.selectInputFit} /> ) : ( = React.forwardRef( disabled={disabled} onClear={onInputClear} clearable={clearable} + inputWidth={TextInputWidth.fill} + iconProps={{ + path: IconName.mdiMenuDown, + rotate: visible ? 180 : 0, + }} + classNames={styles.selectInputFit} /> )} - +
    ); } ); diff --git a/src/components/Select/select.module.scss b/src/components/Select/select.module.scss index e1243876b..cd667bd75 100644 --- a/src/components/Select/select.module.scss +++ b/src/components/Select/select.module.scss @@ -1,63 +1,13 @@ -.wrapper-style { - align-items: center; - display: flex; - justify-content: center; - - &.disruptive { - background-color: var(--disruptive-color-20); - border: solid 1px var(--disruptive-color-30); - color: var(--disruptive-color); - } - - &.grey { - background-color: var(--grey-color-20); - border: solid 1px var(--grey-color-30); - color: var(--grey-color); - } - - &.blue { - background-color: var(--blue-color-20); - border: solid 1px var(--blue-color-30); - color: var(--blue-color); - } - - &.orange { - background-color: var(--orange-color-20); - border: solid 1px var(--orange-color-30); - color: var(--orange-color); - } - - &.green { - background-color: var(--green-color-20); - border: solid 1px var(--green-color-30); - color: var(--green-color); - } +.select-wrapper { + width: 100%; - &.violet { - background-color: var(--violet-color-20); - border: solid 1px var(--violet-color-30); - color: var(--violet-color); + .my-dropdown-class { + width: 400px; } - - &.yellow { - background-color: var(--yellow-color-20); - border: solid 1px var(--yellow-color-30); - color: var(--yellow-color); + .select-dropdown-main-wrapper { + width: 100%; } - - &.bluegreen { - background-color: var(--bluegreen-color-20); - border: solid 1px var(--bluegreen-color-30); - color: var(--bluegreen-color); + .select-input-fit { + min-width: unset !important; } } - -.round-image { - border-radius: 50%; -} - -.image-style { - height: 100%; - width: 100%; - object-fit: cover; -} From 3f36681932c461df661518977e54ccb9fc7d9e04 Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Thu, 2 Jun 2022 11:41:32 +0530 Subject: [PATCH 08/22] feat: added more styling for multi select --- src/components/Pills/Pill.tsx | 1 + src/components/Select/Select.stories.tsx | 18 +- src/components/Select/Select.tsx | 416 ++++++++++++++--------- src/components/Select/select.module.scss | 24 ++ 4 files changed, 287 insertions(+), 172 deletions(-) diff --git a/src/components/Pills/Pill.tsx b/src/components/Pills/Pill.tsx index 43d7d65a7..75e79d491 100644 --- a/src/components/Pills/Pill.tsx +++ b/src/components/Pills/Pill.tsx @@ -40,6 +40,7 @@ export const Pill: FC = React.forwardRef( ]); const tagClassName: string = mergeClasses([ styles.tagPills, + rest.classNames, { [styles.red]: theme === 'red' }, { [styles.orange]: theme === 'orange' }, { [styles.yellow]: theme === 'yellow' }, diff --git a/src/components/Select/Select.stories.tsx b/src/components/Select/Select.stories.tsx index 0d55740f4..a7498467f 100644 --- a/src/components/Select/Select.stories.tsx +++ b/src/components/Select/Select.stories.tsx @@ -47,6 +47,7 @@ export const Options_Disabled: SelectStory = Basic_Story.bind({}); export const With_Clear: SelectStory = Basic_Story.bind({}); export const Filterable: SelectStory = Basic_Story.bind({}); export const Multiple: SelectStory = Basic_Story.bind({}); +export const Multiple_With_NoFilter: SelectStory = Basic_Story.bind({}); const usAllStates = [ 'Alabama', @@ -146,11 +147,6 @@ const Dynamic_Story: ComponentStory = (args) => ( export const Dynamic: SelectStory = Dynamic_Story.bind({}); const defaultOptions: SelectOption[] = [ - { - iconProps: { path: IconName.mdiThumbUpOutline }, - text: 'Thumbs up', - value: 'thumbsup', - }, { iconProps: { path: IconName.mdiSchool }, text: 'School', @@ -161,6 +157,11 @@ const defaultOptions: SelectOption[] = [ text: 'Date', value: 'date', }, + { + iconProps: { path: IconName.mdiFlagVariant }, + text: 'Supercalifragilisticexpialidocious', + value: 'verylarge', + }, { iconProps: { path: IconName.mdiAccount }, text: 'Account', @@ -237,6 +238,13 @@ Multiple.args = { ...Basic.args, filterable: true, multiple: true, + clearable: true, +}; + +Multiple_With_NoFilter.args = { + ...Basic.args, + filterable: false, + multiple: true, }; Dynamic.args = { diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index 80ce909df..c195787bc 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -1,203 +1,285 @@ -import React, { Ref, FC, useState, useEffect } from 'react'; +import React, { FC, useState, useEffect } from 'react'; // Styles: import { Dropdown, ToggleState } from '../Dropdown'; import { Menu } from '../Menu'; import { TextInput, TextInputWidth } from '../Inputs'; import { SelectOption, SelectProps } from './Select.types'; -import { Pill, PillSize } from '../Pills'; +import { Pill, PillSize, PillType } from '../Pills'; import { IconName } from '../Icon'; import { mergeClasses } from '../../shared/utilities'; import styles from './select.module.scss'; -const Options = (options: SelectOption[], onOptionChange: Function) => { +const Options = ({ + options, + onOptionChange, + multiple, + selectedOptions, + selectedOption, +}: any) => { + const updated = options.map((option: any) => { + if (multiple) { + const selected = selectedOptions.some( + (selectedOption: any) => selectedOption.value === option.value + ); + if (selected) { + return { + ...option, + classNames: styles.selectedOption, + }; + } + return option; + } + return { + ...option, + classNames: + selectedOption.value === option.value + ? styles.selectedOption + : '', + }; + }); return ( { - const option = options.find((option) => option.value === value); + const option = updated.find( + (option: any) => option.value === value + ); onOptionChange(option); }} /> ); }; -export const Select: FC = React.forwardRef( - ({ options = [], ...rest }, _ref: Ref) => { - const { - defaultValue, - disabled, - clearable = false, - filterable = false, - multiple = false, - loadOptions, - isLoading = false, - classNames, - style, - } = rest; - const [visible, setVisibility] = useState(false); - const [defaultValueShown, setDefaultValueShown] = useState(false); - const [selectedOption, setSelectedOption] = useState({ - text: '', - value: '', - }); - const [filteredOptions, setFilteredOptions] = useState(options); - const [searchQuery, setSearchQuery] = useState(''); - const [selectedOptions, setSelectedOptions] = useState( - [] - ); - - useEffect(() => { - setFilteredOptions(options); - }, [JSON.stringify(options)]); +export const Select: FC = ({ options = [], ...rest }) => { + const { + defaultValue, + disabled, + clearable = false, + filterable = false, + multiple = false, + loadOptions, + isLoading = false, + classNames, + style, + } = rest; + const [dropdownVisible, setDropdownVisibility] = useState(false); + const [defaultValueShown, setDefaultValueShown] = useState(false); + const [selectedOption, setSelectedOption] = useState({ + text: '', + value: '', + }); + const [filteredOptions, setFilteredOptions] = useState(options); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedOptions, setSelectedOptions] = useState([]); + useEffect(() => { + setOptions(options); + }, [JSON.stringify(options)]); - const onOptionChange = (option: SelectOption) => { - setSearchQuery(''); - if (multiple) { - const index = selectedOptions.findIndex( - (selectedOption) => selectedOption.value === option.value - ); - if (index > -1) { - const selectionOptionsSet: { [key: string]: SelectOption } = - selectedOptions.reduce((acc, opt) => { - return { - ...acc, - [opt.value]: opt, - }; - }, {}); - delete selectionOptionsSet[option.value]; - return setSelectedOptions( - Object.values(selectionOptionsSet) - ); - } - return setSelectedOptions([...selectedOptions, option]); + const onOptionChange = (option: SelectOption) => { + setSearchQuery(''); + if (multiple) { + const index = selectedOptions.findIndex( + (selectedOption) => selectedOption.value === option.value + ); + if (index > -1) { + const selectionOptionsSet: { [key: string]: SelectOption } = + selectedOptions.reduce((acc, opt) => { + return { + ...acc, + [opt.value]: opt, + }; + }, {}); + delete selectionOptionsSet[option.value]; + return setSelectedOptions(Object.values(selectionOptionsSet)); } + return setSelectedOptions([...selectedOptions, option]); + } else { setSelectedOption(option); - }; + } + }; - const onInputClear = () => { - setSearchQuery(''); - setFilteredOptions(options); - setSelectedOption({ text: '', value: '' }); - }; + const onInputClear = () => { + setSearchQuery(''); + setOptions(options); + setSelectedOption({ text: '', value: '' }); + }; - const onInputChange = ( - event: React.ChangeEvent - ) => { - const { target } = event; - if (target?.value) { - const value = target.value.toLowerCase(); - const filteredOptions = options.filter((option) => - option.value.toLowerCase().includes(value) - ); - setSearchQuery(target.value); - if (loadOptions) { - loadOptions(target.value); - } else { - setFilteredOptions(filteredOptions); - } + const onInputChange = ( + event: React.ChangeEvent + ) => { + const { target } = event; + if (target?.value) { + const value = target.value.toLowerCase(); + const filteredOptions = options.filter((option) => + option.value.toLowerCase().includes(value) + ); + setSearchQuery(target.value); + if (loadOptions) { + loadOptions(target.value); } else { - setFilteredOptions(options); + setOptions(filteredOptions); } - }; + } else { + setSearchQuery(''); + setOptions(options); + } + }; - const onDropdownToggle = ( - current: ToggleState, - changes: Partial - ) => { - if (multiple) { - // + const setOptions = (options: SelectOption[]) => { + setFilteredOptions( + options.map((option) => { + if (multiple) { + const selected = selectedOptions.some( + (selectedOption) => + selectedOption.value === option.value + ); + if (selected) { + return { + ...option, + classNames: styles.selectedOption, + }; + } + return option; + } return { - visible: true, - closing: false, + ...option, + classNames: + selectedOption.value === option.value + ? styles.selectedOption + : '', }; - } - return { ...current, ...changes }; - }; + }) + ); + }; - if (defaultValue && !defaultValueShown) { - const defaultOption = options.find( - (option) => option.value === defaultValue - ); - if (multiple) { - setSelectedOptions([...selectedOptions, defaultOption]); - } else { - setSelectedOption(defaultOption); - } - setDefaultValueShown(true); + const onDropdownToggle = ( + current: ToggleState, + changes: Partial + ) => { + if (multiple) { + // + return { + visible: true, + closing: false, + }; + } + return { ...current, ...changes }; + }; + + if (defaultValue && !defaultValueShown) { + const defaultOption = options.find( + (option) => option.value === defaultValue + ); + if (multiple) { + setSelectedOptions([...selectedOptions, defaultOption]); + } else { + setSelectedOption(defaultOption); } + setDefaultValueShown(true); + } - const componentClasses: string = mergeClasses([ - styles.selectWrapper, - classNames, - ]); + const componentClasses: string = mergeClasses([ + styles.selectWrapper, + classNames, + ]); - return ( -
    - {multiple ? ( -
    - {selectedOptions.map((option) => ( - - ))} -
    + const selectedPills = () => { + const firstOption = selectedOptions[0]; + const moreOptionsCount = selectedOptions.length - 1; + const hidePills = + selectedOptions.length === 0 || (filterable && dropdownVisible); + return !hidePills ? ( +
    + + {moreOptionsCount ? ( + ) : null} - setVisibility(isVisible)} - onToggle={onDropdownToggle} - overlay={Options(filteredOptions, onOptionChange)} - trigger="click" - classNames={styles.selectDropdownMainWrapper} - dropdownClassNames={styles.myDropdownClass} - placement="bottom-start" - positionStrategy="absolute" - disabled={false} - > - {filterable ? ( - - ) : ( - - )} -
    - ); - } -); + ) : null; + }; + + const onDropdownVisibilityChange = (isVisible: boolean) => { + setDropdownVisibility(isVisible); + setSearchQuery(''); + setOptions(options); + }; + + return ( +
    + {multiple ? selectedPills() : null} + + onDropdownVisibilityChange(isVisible) + } + onToggle={onDropdownToggle} + overlay={ + + } + trigger="click" + classNames={styles.selectDropdownMainWrapper} + dropdownClassNames={styles.myDropdownClass} + placement="bottom-start" + positionStrategy="absolute" + disabled={false} + > + {filterable ? ( + + ) : ( + + )} + +
    + ); +}; diff --git a/src/components/Select/select.module.scss b/src/components/Select/select.module.scss index cd667bd75..2d3c0ca84 100644 --- a/src/components/Select/select.module.scss +++ b/src/components/Select/select.module.scss @@ -1,5 +1,6 @@ .select-wrapper { width: 100%; + position: relative; .my-dropdown-class { width: 400px; @@ -10,4 +11,27 @@ .select-input-fit { min-width: unset !important; } + + .multi-select-pills { + position: absolute; + z-index: 1; + left: 26px; + top: 6px; + display: flex; + flex-direction: row; + align-items: center; + } + + .multi-select-pill { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 80px; + display: inline-block; + } + + .selected-option { + background-color: var(--button-active-background-color); + color: var(--button-active-foreground-color); + } } From d1f6f1206fd7b342d21613922944537d77c29edd Mon Sep 17 00:00:00 2001 From: ktorlakonda-eightfold Date: Thu, 2 Jun 2022 11:52:42 +0530 Subject: [PATCH 09/22] feat: readonly prop to input --- src/components/Inputs/Input.types.ts | 6 ++++++ src/components/Inputs/TextInput/TextInput.tsx | 2 ++ src/components/Select/Select.tsx | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/Inputs/Input.types.ts b/src/components/Inputs/Input.types.ts index 797eb001d..9348a8871 100644 --- a/src/components/Inputs/Input.types.ts +++ b/src/components/Inputs/Input.types.ts @@ -294,4 +294,10 @@ export interface InputProps * @default 10 */ waitInterval?: number; + + /** + * input readonly. + * @default false + */ + readonly?: boolean; } diff --git a/src/components/Inputs/TextInput/TextInput.tsx b/src/components/Inputs/TextInput/TextInput.tsx index 1f6e7e91d..98d8290ac 100644 --- a/src/components/Inputs/TextInput/TextInput.tsx +++ b/src/components/Inputs/TextInput/TextInput.tsx @@ -37,6 +37,7 @@ export const TextInput: FC = ({ onKeyDown, placeholder, required = false, + readonly = false, shape = TextInputShape.Rectangle, style, theme = TextInputTheme.light, @@ -177,6 +178,7 @@ export const TextInput: FC = ({ tabIndex={0} type={numbersOnly ? 'number' : htmlType} value={value} + readOnly={readonly} /> {iconProps && (
    diff --git a/src/components/Select/Select.tsx b/src/components/Select/Select.tsx index c195787bc..8cfe61dfd 100644 --- a/src/components/Select/Select.tsx +++ b/src/components/Select/Select.tsx @@ -244,7 +244,7 @@ export const Select: FC = ({ options = [], ...rest }) => { {filterable ? ( = ({ options = [], ...rest }) => { ) : ( Date: Thu, 2 Jun 2022 21:35:13 +0530 Subject: [PATCH 10/22] feat: cleanup --- src/__snapshots__/storybook.test.js.snap | 679 +++++++++++++++++++++++ src/components/Select/Select.stories.tsx | 103 ++-- src/components/Select/Select.tsx | 435 ++++++++------- src/components/Select/select.module.scss | 8 +- 4 files changed, 956 insertions(+), 269 deletions(-) diff --git a/src/__snapshots__/storybook.test.js.snap b/src/__snapshots__/storybook.test.js.snap index a619dde54..f47d179ac 100644 --- a/src/__snapshots__/storybook.test.js.snap +++ b/src/__snapshots__/storybook.test.js.snap @@ -4653,6 +4653,7 @@ exports[`Storyshots Input Search Box 1`] = ` name="mySearchBox" onChange={[Function]} placeholder="Search" + readOnly={false} required={false} role="textbox" style={Object {}} @@ -4949,6 +4950,7 @@ exports[`Storyshots Input Text Input 1`] = ` name="myTextInput" onChange={[Function]} placeholder="Placeholder text" + readOnly={false} required={false} role="textbox" style={Object {}} @@ -5798,6 +5800,7 @@ exports[`Storyshots Pagination All Combined 1`] = ` id="input-15" minLength={1} onChange={[Function]} + readOnly={false} required={false} role="textbox" tabIndex={0} @@ -6573,6 +6576,7 @@ exports[`Storyshots Pagination Jump To 1`] = ` id="input-17" minLength={1} onChange={[Function]} + readOnly={false} required={false} role="textbox" tabIndex={0} @@ -8292,6 +8296,681 @@ exports[`Storyshots Radio Button Radio Group 1`] = `
    `; +exports[`Storyshots Select Basic 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select Disabled 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select Dynamic 1`] = ` +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +`; + +exports[`Storyshots Select Filterable 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select Multiple 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select Multiple With No Filter 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select Options Disabled 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select With Clear 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + +exports[`Storyshots Select With Default Value 1`] = ` +
    +
    +
    +
    +
    + +
    + + + + + +
    +
    +
    +
    +
    +
    +`; + exports[`Storyshots Snack Bar Closable 1`] = `