Skip to content

Commit

Permalink
fix(combobox): component optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Tianhui-Han authored and jor-row committed Apr 2, 2024
1 parent fde23d4 commit 95d16c0
Show file tree
Hide file tree
Showing 11 changed files with 779 additions and 280 deletions.
23 changes: 19 additions & 4 deletions src/components/ComboBox/ComboBox.constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const CLASS_PREFIX = 'md-combobox';
const CLASS_PREFIX = 'md-combo-box';

const DEFAULTS = {
WIDTH:'16.25rem',
PLACEHOLDER:'',
NO_RESULT_TEXT:'No results found',
SHOULDFILTERONARROWBUTTON:true,
SHOULD_FILTER_ON_ARROW_BUTTON:true,
ERROR:false,
SELECTEDKEY:'',
DISABLEDKEYS:[],
Expand All @@ -29,7 +29,22 @@ const KEYS = {
INPUT_SEARCH_NO_RESULT: 'input_search_no_result',
};

export{STYLE,DEFAULTS,KEYS};

const ELEMENT = {
PROPS: {
SELECTION_CONTAINER_MAX_HEIGHT:244,
},
};

const EVENT = {
KEY:{
KEYCODE:{
ESCAPE: 'Escape',
ENTER: 'Enter',
TAB: 'Tab',
ARROW_DOWN: 'ArrowDown',
ARROW_UP: 'ArrowUp',
}
}
};

export{STYLE,DEFAULTS,KEYS,ELEMENT,EVENT};
8 changes: 8 additions & 0 deletions src/components/ComboBox/ComboBox.documentation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ The component is composed of three parts: Button, Menu and Input. In the compone
passed in are filtered based on the value of the Input and then passed to the internal Menu
to implement the search function.


Tips:

For dynamic data, it is recommended to render in conjunction with the ‘openStateChange’ property.
This is because when the list is open, dynamic data can affect the automatic rendering of the list.
If not controlled, it may confuse the user. Therefore, it is suggested to perform dynamic rendering only when the list is closed.


## Content

The ComboBox element follows the
Expand Down
24 changes: 19 additions & 5 deletions src/components/ComboBox/ComboBox.stories.args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default {
},
},
error: {
description: 'Sets whether the ComboBox is in error state',
description: 'Sets whether the ComboBox is in error state.',
control: { type: 'boolean' },
table: {
type: {
Expand All @@ -30,7 +30,7 @@ export default {
},
},
placeholder: {
description: 'Text to display inside the input when there is no inputValue or item selected',
description: 'Text to display inside the input when there is no inputValue or item selected.',
control: { type: 'text' },
table: {
category: 'React Aria - Input',
Expand Down Expand Up @@ -82,13 +82,13 @@ export default {
summary: 'boolean',
},
defaultValue: {
summary: CONSTANTS.DEFAULTS.SHOULDFILTERONARROWBUTTON,
summary: CONSTANTS.DEFAULTS.SHOULD_FILTER_ON_ARROW_BUTTON,
},
},
},
selectedKey: {
description:
'It also affects the value of the input (displayed as the label of the corresponding item',
'It also affects the value of the input (displayed as the label of the corresponding item.',
control: { type: 'text' },
table: {
category: 'React Aria - Select',
Expand Down Expand Up @@ -143,7 +143,7 @@ export default {
},
onSelectionChange: {
description:
'Handler that is called when an item is selected(if the selected item matches the selectedKey, the parameter is undefined).',
'Handler that is called when an item is selected. (if the selected item matches the selectedKey, the parameter is undefined)',
control: { type: 'function' },
table: {
category: 'React Aria - Select',
Expand All @@ -155,6 +155,20 @@ export default {
},
},
},
openStateChange: {
description:
'Handler that is called when the selection list is expanded or collapsed.',
control: { type: 'function' },
table: {
category: 'React Aria - Select',
type: {
summary: '(isOpen: boolean) => void',
},
defaultValue: {
summary: 'undefined',
},
},
},
children: {
description: 'Provides the items nodes for this selection list element.',
table: {
Expand Down
73 changes: 65 additions & 8 deletions src/components/ComboBox/ComboBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/* eslint-disable react/display-name */
/* eslint-disable @typescript-eslint/ban-types */
import React from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { Item, Section } from '@react-stately/collections';
import ComboBox from '.';

Expand Down Expand Up @@ -43,7 +41,7 @@ const ComboBoxWrapper = (props:Props) => {
{itemsEle}
</Section>
) : (
<Section key="noSection">{itemsEle}</Section>
<Section key="withoutSection">{itemsEle}</Section>
);
}}
</ComboBox>
Expand Down Expand Up @@ -112,8 +110,8 @@ Sections.argTypes = { ...argTypes };
const InListItemTemplate = (props:Props) => {
return (
<OverlayAlert>
<div style={{ overflowY: 'scroll' }}>
<ComboBoxWrapper {...props} />
<div style={{ overflowY: 'scroll', height:'100px'}}>
<ComboBoxWrapper {...props} />
</div>
</OverlayAlert>
);
Expand Down Expand Up @@ -143,10 +141,69 @@ const MultipleComboBoxTemplate = (props:Props) => {
const MultipleComboBox = Template(MultipleComboBoxTemplate).bind({});

MultipleComboBox.args = {
comboBoxGroups: withSection,
comboBoxGroups:withSection,
label:'MultipleComboBox',
};

MultipleComboBox.argTypes = { ...argTypes };

export { Example, Sections, InListItem, MultipleComboBox };
const DynamicDataTemplate = (props:Props) => {
const mockDynamicData = [{section:'section1',items:[{key:'key-1',label:'label-1'}]},{section:'section2',items:[{key:'key-2',label:'label-2'}]}];
const [dynamicData,setDynamicData] = useState(mockDynamicData);
const [dynamicDataLength,setDynamicDataLength] = useState<number>(0);
const [comboBoxGroups,setComboBoxGroups] = useState<IComboBoxGroup[]>(dynamicData);
const [isListOpen,setIsListOpen] = useState<boolean>(false);

useEffect(()=>{
const timer = setInterval(()=>{
const randomText = Math.random().toString(36).substr(2, 10);
mockDynamicData[Math.round(Math.random())].items.push({key:'key-'+randomText,label:'label-'+randomText});
setDynamicData(JSON.parse(JSON.stringify(mockDynamicData)));
},2000);
return ()=>{
clearInterval(timer);
};
},[]);

useEffect(()=>{
const length = dynamicData.reduce((total, current) => {
return total + current.items.length;
}, 0);
setDynamicDataLength(length);
},[dynamicData]);

const openStateChange = useCallback((isOpen)=>{
setIsListOpen(isOpen);
},[]);

const onInputChange = useCallback(()=>{
setComboBoxGroups(dynamicData);
},[dynamicData]);

useEffect(()=>{
if(!isListOpen){
setComboBoxGroups(dynamicData);
}
},[isListOpen,dynamicData]);

return (
<>
<div style={{margin:'20px 12px'}}>DynamicDataLength:{dynamicDataLength}</div>
<div style={{margin:'20px 12px'}}>For dynamic data, it is relatively controllable to not re-render when the list is opened, and to re-render when input changes.</div>
<ComboBoxWrapper {...props} comboBoxGroups={comboBoxGroups} openStateChange={openStateChange} onInputChange={onInputChange}/>
<ComboBoxWrapper {...props} comboBoxGroups={comboBoxGroups} openStateChange={openStateChange} onInputChange={onInputChange}/>
<ComboBoxWrapper {...props} comboBoxGroups={comboBoxGroups} openStateChange={openStateChange} onInputChange={onInputChange}/>
</>
);
};

const DynamicData = Template(DynamicDataTemplate).bind({});

DynamicData.args = {
label:'DynamicData',
};

DynamicData.argTypes = { ...argTypes };


export { Example, Sections, InListItem, MultipleComboBox, DynamicData };
47 changes: 21 additions & 26 deletions src/components/ComboBox/ComboBox.style.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
.md-combobox-wrapper {
.md-combo-box-wrapper {
position: relative;
width: var(--local-width);
height: 2rem;
min-height: 1.71rem;

:global {
.md-menu-item-wrapper {
position: static;
}
Expand All @@ -30,13 +29,9 @@
line-height: 2rem;
}

ul.md-menu-wrapper ul[role='presentation']:not(:first-child) > span {
display: block;
}

ul.md-menu-wrapper ul[role='group'] {
margin: 0;
}
}

ul.md-menu-wrapper ul[role='presentation']:not(:first-child)::before {
display: block;
Expand Down Expand Up @@ -68,44 +63,40 @@
padding: 0;
overflow: hidden;
}
}

&[data-input-have-value='false'] {
:global {
.md-text-input-container > input {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.md-text-input-container > input {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}

&[data-error='true'] {
.md-text-input-container,
.md-button-pill-wrapper,
.md-combobox-divider {
.md-combo-box-divider {
border-color: var(--mds-color-theme-text-error-normal);
}
}

.md-combobox-input-section {
.md-combo-box-input-section {
display: flex;
height: 100%;
}

.md-combobox-input {
.md-combo-box-input {
width: 100%;
}

.md-combobox-selection-position{
.md-combo-box-selection-position{
width: var(--local-width);
z-index: 10000;
position: fixed;
}

.md-combobox-selection-container {
.md-combo-box-selection-container {
width: 100%;
max-height: 15.25rem;
padding: 0.5rem;
margin-top: 0.25rem;
overflow: auto;
Expand All @@ -115,41 +106,45 @@
box-sizing: border-box;
}

.md-combobox-no-result-text {
.md-combo-box-no-result-text {
color: var(--mds-color-theme-text-primary-normal);
}

.md-combobox-button {
.md-combo-box-button {
width: var(--local-height);
height: 100%;
min-height: 1.71rem;
position: relative;

.md-combobox-arrow-icon {
.md-combo-box-arrow-icon {
position: absolute;
top: 50%;
right: 50%;
transform: translate(50%, -50%);
}
}

.md-combobox-divider {
.md-combo-box-divider {
width: 2px;
height: 100%;
border-top: 1px solid;
border-bottom: 1px solid;
border-color: var(--mds-color-theme-outline-input-normal);
background-color: var(--mds-color-theme-background-solid-primary-normal);
}

.md-combo-box-input:hover ~.md-combo-box-divider {
background-color: var(--mds-color-theme-background-primary-hover);
}
}

.md-combobox-label {
.md-combo-box-label {
margin-left: 0.75rem;
margin-bottom: 0.5rem;
letter-spacing: 0.01563rem;
}

.md-combobox-description {
.md-combo-box-description {
font-size: 0.875rem;
line-height: 1.375rem;
fill: var(--mds-color-theme-text-secondary-normal);
Expand Down
Loading

0 comments on commit 95d16c0

Please sign in to comment.