Skip to content

Commit

Permalink
Merge pull request #1357 from City-of-Helsinki/hds-2069-select-part-2…
Browse files Browse the repository at this point in the history
…-rebase

Release-4.0.0: (HDS-2069) New Select part 2
  • Loading branch information
NikoHelle authored Sep 10, 2024
2 parents 4b824a4 + 8df1f54 commit bbd91ea
Show file tree
Hide file tree
Showing 38 changed files with 1,676 additions and 245 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 56 additions & 22 deletions packages/react/src/components/select/Select.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,7 @@
flex-direction: column;
max-width: 420px;

&.open {
.angleIcon {
transform: rotateX(180deg);
}
}

&.error {
&.invalid {
border-color: var(--color-error);
}

Expand All @@ -23,21 +17,36 @@
--menu-item-background-selected: var(--color-bus-light);
--menu-item-background-selected-hover: var(--color-bus-light);
--menu-item-background-disabled: var(--color-white);
--dropdown-border-color-hover-invalid: var(--color-error);
--dropdown-border-color-invalid: var(--color-error);
}

.selectAndListContainer {
@extend %dropdownWrapper;

&.open {
.angleIcon {
transform: rotateX(180deg);
}
}

&.invalid {
border-color: var(--dropdown-border-color-invalid);

&:hover {
border-color: var(--dropdown-border-color-hover-invalid);
}
}

margin-bottom: var(--spacing-2-xs);
}

.selectedOptionsContainer {
align-items: center;
box-sizing: border-box;
display: flex;
min-height: var(--menu-item-height);

.button {
.dropdownButton {
@extend %buttonReset;

align-items: center;
Expand All @@ -48,6 +57,19 @@
padding: var(--spacing-s);
padding-right: var(--spacing-2-xs);

&.withVisibleFocus {
align-self: center;
height: calc(calc(var(--dropdown-height)) - 6px);

&:focus-visible {
@extend %dropdownFocusOutline;
}
}

&.arrowButton {
padding-right: var(--spacing-s);
}

.labels {
box-sizing: border-box;
display: flex;
Expand Down Expand Up @@ -80,6 +102,7 @@

&.selectedOptions {
flex-grow: 1;
overflow: hidden;

// button icon
> span:first-child {
Expand All @@ -89,24 +112,15 @@
}

&.icon {
align-self: stretch;
flex-shrink: 1;
padding-left: var(--spacing-2-xs);
}

&:last-child {
padding-right: var(--spacing-s);
}

&.placeholder {
background-color: inherit;
color: var(--placeholder-color);
}

&.placeholder:disabled {
color: var(--dropdown-color-disabled);
}

&.spaceForOneDigit .labels {
/* minus is option padding right, minus smth */
padding-right: calc(24px - var(--spacing-2-xs));
Expand Down Expand Up @@ -135,6 +149,10 @@
.count.count span {
background-color: var(--dropdown-background-disabled);
}

&.placeholder {
color: var(--dropdown-color-disabled);
}
}

&.hasHiddenItems {
Expand All @@ -144,10 +162,12 @@
}
}

.buttonOption {
.dropdownButtonOption {
max-width: 100%;
overflow: hidden;
padding: 0 var(--spacing-2-xs) 0 0;
position: relative;
text-overflow: ellipsis;
white-space: nowrap;

&:not(:last-child):after {
Expand Down Expand Up @@ -187,6 +207,7 @@
flex-direction: column;
margin: 0;
padding: 0;
position: relative;

.listItem {
@extend %dropdownMenuItem;
Expand Down Expand Up @@ -225,25 +246,29 @@

background-color: var(--color-black-5);
font-weight: bold;

&.disabledOption.disabledOption:hover {
background-color: var(--color-black-5);
}
}

.selectableListItem {
outline: none;

&:hover {
background-color: var(--menu-item-background-selected-hover);
background-color: var(--menu-item-background-hover);
}

&:focus,
&:focus-within {
box-shadow: 0 0 1px 1px var(--color-coat-of-arms) inset;
box-shadow: 0 0 0 2px var(--color-coat-of-arms) inset;
outline: none;
}
}

&.shiftOptions {
.selectableListItem:not(.groupLabel) {
padding-left: var(--spacing-layout-xs);
padding-left: var(--spacing-layout-s);
}
}

Expand All @@ -265,3 +290,12 @@
}
}
}

.errorText {
composes: hds-text-input__error-text from 'hds-core/lib/components/text-input/text-input.css';
margin-bottom: var(--spacing-2-xs);
}

.assistiveText {
margin-bottom: var(--spacing-2-xs);
}
127 changes: 122 additions & 5 deletions packages/react/src/components/select/Select.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import React, { useCallback, useState } from 'react';

import { SelectProps } from './types';
import { Group, SelectProps, Texts } from './types';
import { IconLocation } from '../../icons';
import { Select } from './Select';
import { Button } from '../button/Button';
import { clearAllSelectedOptions, propsToGroups } from './utils';

export default {
component: Select,
Expand Down Expand Up @@ -115,14 +117,22 @@ function generateOptionLabels(count = -1): string[] {
return Array.from(randomSet);
}

const dummyOnChange: SelectProps['onChange'] = () => ({});
const defaultTexts: Partial<Texts> = {
label: 'Label',
placeholder: 'Choose one',
selectedOptionsLabel: 'Selected options',
error: 'Wrong choice!',
};

export const Example = () => {
const options = generateOptionLabels(20);
return <Select options={options} label="Label" placeholder="Choose one" icon={<IconLocation />} />;
return <Select options={options} icon={<IconLocation />} onChange={dummyOnChange} texts={defaultTexts} />;
};

export const OptionsAsHtml = () => {
return (
<Select label="Label" placeholder="Choose one">
<Select onChange={dummyOnChange} texts={defaultTexts}>
<optgroup label="Group 1">
<option value="opt1">Option 1</option>
<option value="opt2">Option 2</option>
Expand Down Expand Up @@ -152,10 +162,117 @@ export const WithGroups = () => {
];

return (
<Select groups={groups} label="Label" placeholder="Choose one" icon={<IconLocation />}>
<Select groups={groups} icon={<IconLocation />} onChange={dummyOnChange} texts={defaultTexts}>
<optgroup label="Group label">
<option value="label">Text</option>
</optgroup>
</Select>
);
};

export const WithControls = () => {
const initialGroups = [
{
label: 'Healthy choices',
options: generateOptionLabels(4),
},
{
label: 'More healthy choices',
options: generateOptionLabels(4),
},
];

const onChange: SelectProps['onChange'] = useCallback(() => {
// not needed
}, []);

const [props, updateProps] = useState<SelectProps>({
groups: propsToGroups({ groups: initialGroups }),
onChange,
required: true,
disabled: false,
open: false,
invalid: false,
texts: { ...defaultTexts, label: 'Controlled select' },
});

const resetSelections = () => {
updateProps({ ...props, groups: clearAllSelectedOptions(props.groups as Group[]), open: false });
};

const toggleDisable = () => {
const { disabled } = props;
updateProps({ ...props, disabled: !disabled });
};

const toggleMenu = () => {
const { open } = props;
updateProps({ ...props, open: !open });
};

const toggleInvalid = () => {
const { invalid } = props;
updateProps({ ...props, invalid: !invalid });
};

const toggleRequired = () => {
const { required } = props;
updateProps({ ...props, required: !required });
};
return (
<>
<style>
{`
.buttons{
margin-top: 20px;
}
.buttons > *{
margin-right: 10px;
}
`}
</style>

<div>
<Select {...props} />
<div className="buttons">
<Button onClick={resetSelections}>Reset selections</Button>
<Button onClick={toggleDisable}>Disable/enable component</Button>
<Button onClick={toggleMenu}>Open/Close list</Button>
<Button onClick={toggleInvalid}>Set valid/invalid</Button>
<Button onClick={toggleRequired}>Toggle required</Button>
</div>
</div>
</>
);
};

export const WithValidation = () => {
const groups: SelectProps['groups'] = [
{
label: 'Healthy choices',
options: generateOptionLabels(3),
},
{
label: 'Bad choices',
options: [
{ value: 'invalid1', label: 'Candy cane' },
{ value: 'invalid2', label: 'Sugar bomb' },
{ value: 'invalid3', label: 'Dr. Pepper' },
],
},
];

const texts = { ...defaultTexts, label: 'Pick a healty choice' };

const onChange: SelectProps['onChange'] = (selectedOptions) => {
const hasErrorSelection = !!selectedOptions.find((option) => option.value.includes('invalid'));
return {
invalid: hasErrorSelection,
texts: {
assistive: hasErrorSelection || !selectedOptions.length ? '' : 'Excellent choice!',
},
};
};

return <Select groups={groups} onChange={onChange} texts={texts} />;
};
Loading

0 comments on commit bbd91ea

Please sign in to comment.