Skip to content

Commit

Permalink
Merge branch 'improvement/ARTESCA-10955-disabled-select-option-toolti…
Browse files Browse the repository at this point in the history
…p' into q/1.0
  • Loading branch information
bert-e committed Jan 22, 2024
2 parents 79055be + 466735a commit 81ef754
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 49 deletions.
3 changes: 3 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,7 @@
.storybook-mutiselect-container {
width: 400px;
}
.sbdocs.sbdocs-wrapper{
padding-bottom: 0;
}
</style>
3 changes: 3 additions & 0 deletions src/lib/components/selectv2/SelectStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ const SelectStyle = styled(Select)`
)};
}
}
.sc-tooltip {
width: 100%;
}
div > .react-window-option > .sc-select__option,
.sc-select__option {
Expand Down
35 changes: 23 additions & 12 deletions src/lib/components/selectv2/Selectv2.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, {
useEffect,
useRef,
} from 'react';
import { ScrollbarWrapper } from '../../index';
import { ScrollbarWrapper, Tooltip } from '../../index';
import { components } from 'react-select';
import { Icon } from '../icon/Icon.component';
import { SelectStyle } from './SelectStyle';
Expand All @@ -24,6 +24,7 @@ export type OptionProps = {
icon?: React.ReactNode;
children?: React.ReactNode;
value: string;
disabledReason?: React.ReactNode;
};
const usePreviousValue = (value) => {
const ref = useRef(null);
Expand All @@ -47,6 +48,7 @@ export function Option({
children,
disabled,
icon,
disabledReason,
...rest
}: OptionProps): JSX.Element {
const optionContext = useContext(OptionContext);
Expand All @@ -64,6 +66,7 @@ export function Option({
label: children || '',
isDisabled: disabled || false,
icon: icon,
disabledReason: disabledReason,
optionProps: { ...rest },
});
return () => {
Expand Down Expand Up @@ -168,17 +171,23 @@ const InternalOption = (width, isDefaultVariant) => (props) => {
'aria-selected': props.isSelected,
};
return (
<components.Option
{...props}
innerProps={innerProps}
isFocused={props.isFocused && props.selectProps.keyboardFocusEnabled}
<Tooltip
overlay={props.data.isDisabled && props.data.disabledReason}
placement="right"
overlayStyle={{ marginLeft: '0.5rem', maxWidth: '15rem' }}
>
<div className="option-value-wrapper">
<div className="option-icon">{props.data.icon}</div>
{formatOptionLabel()}
</div>
<div>{props.isDisabled && <Icon name="Deletion-marker" />}</div>
</components.Option>
<components.Option
{...props}
innerProps={innerProps}
isFocused={props.isFocused && props.selectProps.keyboardFocusEnabled}
>
<div className="option-value-wrapper">
<div className="option-icon">{props.data.icon}</div>
{formatOptionLabel()}
</div>
<div>{props.isDisabled && <Icon name="Deletion-marker" />}</div>
</components.Option>
</Tooltip>
);
};

Expand Down Expand Up @@ -256,6 +265,8 @@ const MenuList = (props) => {
itemCount={children.length}
itemSize={optionHeight}
initialScrollOffset={initialOffset}
// css prop willChange used by react-window causes display issues with tooltip
style={{ willChange: undefined }}
>
{({ index, style }) => {
return (
Expand Down Expand Up @@ -303,6 +314,7 @@ type SelectOptionProps = {
isDisabled: boolean;
icon?: React.ReactNode;
optionProps: any;
disabledReason?: React.ReactNode;
};

const OptionContext = createContext<{
Expand Down Expand Up @@ -341,7 +353,6 @@ function SelectWithOptionContext(props: SelectProps) {
function SelectBox({
placeholder = 'Select...',
disabled = false,

defaultValue,
value,
onChange,
Expand Down
7 changes: 6 additions & 1 deletion src/lib/components/tooltip/Tooltip.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,18 @@ const TooltipOverLayContainer = styled.div<{

const TooltipText = styled.div`
width: 100%;
text-align: justify;
ul,
ol {
padding-inline-start: ${spacing.r16};
margin-block-start: 0;
margin-block-end: 0;
}
li {
margin-bottom: ${spacing.r8};
}
li:last-child {
margin-bottom: 0;
}
`;

function Tooltip({
Expand Down
28 changes: 23 additions & 5 deletions stories/Select/select.guideline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Select } from '../../src/lib/components/selectv2/Selectv2.component';

import * as SelectStories from './selectv2.stories';

<Meta title="Guideline" of={SelectStories} />
<Meta name="Guideline" of={SelectStories} />

# Select

Expand Down Expand Up @@ -58,16 +58,34 @@ a search bar is automatically added, enabling users to quickly find and select o

### Disable state

#### Select

The Select component can be easily disabled, preventing user interaction.
This is useful in scenarios where user input is not allowed or required

<Story of={SelectStories.DisabledSelect} />

#### Options

The Option component can also be disabled, preventing its selection by the user.

<Canvas
of={SelectStories.WithDisabledOptionsWithoutMessage}
sourceState="hidden"
layout="fullscreen"
/>

You can also give more information to the user by adding a disabledReason prop to Option.

<Canvas
of={SelectStories.WithDisabledOptionsWithMessage}
sourceState="hidden"
layout="fullscreen"
/>

### Dropdown Placement

In cases where there is limited space at the bottom of the viewport,
the dropdown menu intelligently opens toward the top to ensure visibility and usability.
the dropdown menu opens toward the top to ensure visibility and usability.

<Unstyled>
<Story of={SelectStories.NotEnoughPlaceAtTheBottom} />
</Unstyled>
<Canvas of={SelectStories.NotEnoughPlaceAtTheBottom} sourceState="hidden" />
111 changes: 80 additions & 31 deletions stories/Select/selectv2.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import { Icon } from '../../src/lib/components/icon/Icon.component';
import { Modal } from '../../src/lib/components/modal/Modal.component';
import { Select } from '../../src/lib/components/selectv2/Selectv2.component';
import { Wrapper } from '../common';
import { Meta, StoryObj } from '@storybook/react';

export default {
type SelectStory = StoryObj<typeof Select>;
const meta: Meta<typeof Select> = {
title: 'Components/Inputs/Select',
component: Select,
decorators: [
(story) => <Wrapper className="storybook-select">{story()}</Wrapper>,
(story) => <Wrapper style={{ minHeight: '15rem' }}>{story()}</Wrapper>,
],
};
const sizes = ['1/3', '1/2', '2/3', '1'];

export default meta;
const sizes = ['1/3', '1/2', '2/3', '1'] as const;

const SelectWrapper = styled.div`
display: flex;
justify-content: space-around;
height: 15rem;
flex-direction: column;
min-height: 20rem;
height: 100%;
justify-content: space-between;
`;

const generateOptions = (n = 10) =>
Array.from(new Array(n), (_, index) => (
<Select.Option
key={index}
value={`Option${index + 1}`}
disabled={index !== 0 && index % 8 === 0}
icon={index % 5 === 0 ? <Icon name={'Check'} /> : null}
>{`Option ${index + 1}`}</Select.Option>
));
Expand All @@ -34,49 +39,84 @@ const optionsWithSearchBar = generateOptions(25);
const optionsWithoutSearchBar = generateOptions(7);
const defaultOptions = generateOptions(4);
const thousandsOfOptions = generateOptions(1000);
const optionsWithDisabledWithoutMessage = optionsWithSearchBar.map(
(option, index) => {
if (index % 3 === 0) {
return React.cloneElement(option, {
disabled: true,
});
}
return option;
},
);
const optionsWithDisabledWithMessage = optionsWithDisabledWithoutMessage.map(
(option, index) => {
if (index % 3 === 0) {
return React.cloneElement(option, {
disabled: true,
disabledReason: "This option can't be selected for some reason",
});
}
return option;
},
);

export const Playground = {
export const Playground: SelectStory = {
args: {
children: defaultOptions,
placeholder: 'Playground',
},
};

export const WithoutOptions = {
export const WithoutOptions: SelectStory = {
args: {
options: [],
children: [],
placeholder: 'No options',
},
};

export const DisabledSelect = {
export const DisabledSelect: SelectStory = {
args: {
disabled: true,
defaultValue: defaultOptions[0].props.value,
children: defaultOptions,
},
};

export const WithScrollbar = {
export const WithScrollbar: SelectStory = {
name: 'More than 4 items',
args: {
children: optionsWithoutSearchBar,
},
};

export const WithSearchBar = {
export const WithSearchBar: SelectStory = {
args: {
children: optionsWithSearchBar,
},
};

export const LotsOfOptions = {
export const LotsOfOptions: SelectStory = {
args: {
children: thousandsOfOptions,
},
};
export const WithDisabledOptionsWithoutMessage: SelectStory = {
name: 'Options disabled',
args: {
children: optionsWithDisabledWithoutMessage,
},
};

export const DifferentSizes = {
export const WithDisabledOptionsWithMessage: SelectStory = {
name: 'Options disabled with message',
args: {
children: optionsWithDisabledWithMessage,
},
};

export const DifferentSizes: SelectStory = {
name: 'Sizes',
render: (args) => (
<SelectWrapper>
{sizes.map((size) => (
Expand All @@ -89,15 +129,35 @@ export const DifferentSizes = {
},
};

export const NotEnoughPlaceAtTheBottom = {
export const InsideModal: SelectStory = {
render: (args) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open modal</button>

{isOpen && (
<Modal isOpen title="select" close={() => setIsOpen(false)}>
<Select menuPosition="fixed" {...args}></Select>
</Modal>
)}
</>
);
},
args: {
children: optionsWithoutSearchBar,
},
};

export const NotEnoughPlaceAtTheBottom: SelectStory = {
name: 'Menu open at the top',
render: (args) => (
<div
style={{
display: 'flex',
justifyContent: 'flex-end',
height: '100vh',
flex: '1',
flexDirection: 'column',
minHeight: '12rem',
height: '100%',
alignItems: 'flex-end',
}}
>
<Select {...args}></Select>
Expand All @@ -107,14 +167,3 @@ export const NotEnoughPlaceAtTheBottom = {
children: optionsWithSearchBar,
},
};

export const InsideModal = {
render: (args) => (
<Modal isOpen title="select">
<Select menuPosition="fixed" {...args}></Select>
</Modal>
),
args: {
children: optionsWithoutSearchBar,
},
};
1 change: 1 addition & 0 deletions stories/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const StyledWrapper = styled.div`
height: 100%;
background-color: ${(props) => props.theme.backgroundLevel1};
color: ${(props) => props.theme.textPrimary};
box-sizing: border-box;
`;
const StyledTitle = styled.h3`
color: ${getThemePropSelector('textPrimary')};
Expand Down

0 comments on commit 81ef754

Please sign in to comment.