Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select/dropdown - improve error handling and focus state #390

Merged
merged 13 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions packages/react-components/src/components/Select/Select.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.bcds-react-aria-Select {
display: inline-flex;
display: flex;
flex-direction: column;
gap: var(--layout-margin-xsmall);
gap: var(--layout-margin-small);
align-items: flex-start;
/* Hacks for `stretch`: https://caniuse.com/mdn-css_properties_max-width_stretch */
max-width: -moz-available;
max-width: -webkit-fill-available;
Expand All @@ -16,9 +17,10 @@
color: var(--typography-color-disabled);
}

/* "(optional)" text after Label*/
.bcds-react-aria-Select--Label > .optional {
color: var(--typography-color-secondary);
/* Error message */
.bcds-react-aria-Select--Error {
font: var(--typography-regular-small-body);
color: var(--typography-color-danger);
}

/* Select input equivalent */
Expand Down Expand Up @@ -48,7 +50,9 @@
}
.bcds-react-aria-Select--Button[data-focused] {
border-color: var(--surface-color-border-active);
outline: none;
outline: solid var(--layout-border-width-medium)
var(--surface-color-border-active);
outline-offset: var(--layout-margin-hair);
}
.bcds-react-aria-Select--Button[data-pressed] {
border-color: var(--surface-color-border-active);
Expand Down Expand Up @@ -78,7 +82,7 @@
box-shadow: var(--surface-shadow-medium);
box-sizing: border-box;
overflow-y: auto;
padding: var(--layout-margin-hair) var(--layout-margin-xsmall);
padding: var(--layout-margin-hair) var(--layout-padding-xsmall);
width: var(
--trigger-width
); /* Variable provided by Select component https://react-spectrum.adobe.com/react-aria/Select.html#popover-1 */
Expand All @@ -96,7 +100,7 @@
.bcds-react-aria-Select--Header {
color: var(--typography-color-secondary);
font: var(--typography-regular-small-body);
padding: var(--layout-margin-hair) var(--layout-margin-small);
padding: var(--layout-margin-hair) var(--layout-padding-small);
}

/* ListBox option item */
Expand Down
37 changes: 29 additions & 8 deletions packages/react-components/src/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import {
Button,
Collection,
FieldError,
Header,
Key,
Label,
Expand All @@ -14,6 +15,7 @@ import {
SelectProps as ReactAriaSelectProps,
SelectValue,
Text,
ValidationResult,
} from "react-aria-components";

import "./Select.css";
Expand Down Expand Up @@ -50,6 +52,8 @@ export interface SelectProps<T extends object> extends ReactAriaSelectProps<T> {
placeholder?: string;
/** Defaults to `medium` */
size?: "small" | "medium";
/* Used for data validation and error handling */
errorMessage?: string | ((validation: ValidationResult) => string);
}

function ChevronDown() {
Expand Down Expand Up @@ -86,28 +90,41 @@ function ChevronUp() {
);
}

/* Icon displayed when input is in invalid state */
const iconError = (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<g id="20px/Error icon">
<path
id="Icon"
d="M17.835 15.0312C18.335 15.9062 17.71 17 16.6787 17H3.33499C2.30374 17 1.67874 15.9062 2.14749 15.0312L8.83499 3.65625C9.11624 3.21875 9.55373 3 10.0225 3C10.46 3 10.8975 3.21875 11.1787 3.65625L17.835 15.0312ZM3.64749 15.5H16.3663L9.99123 4.65625L3.64749 15.5ZM10.0225 12.5625C10.5537 12.5625 10.9912 13 10.9912 13.5312C10.9912 14.0625 10.5537 14.5 10.0225 14.5C9.45998 14.5 9.02249 14.0625 9.02249 13.5312C9.02249 13 9.45998 12.5625 10.0225 12.5625ZM9.27249 7.75C9.27249 7.34375 9.58498 7 10.0225 7C10.4287 7 10.7725 7.34375 10.7725 7.75V10.75C10.7725 11.1875 10.4287 11.5 10.0225 11.5C9.58498 11.5 9.27249 11.1875 9.27249 10.75V7.75Z"
fill="var(--icons-color-danger)"
/>
</g>
</svg>
);

/** Select displays a collapsible list of options and allows a user to select one of them. */
export default function Select<T extends object>({
items,
sections,
label,
placeholder,
size = "medium",
errorMessage,
...props
}: SelectProps<T>) {
return (
<ReactAriaSelect {...props}>
<ReactAriaSelect {...props} className="bcds-react-aria-Select">
{({ isOpen, isRequired, isInvalid }) => (
<>
{label && (
<Label className="bcds-react-aria-Select--Label">
{label}
{!isRequired && (
<>
{" "}
<span className="optional">(optional)</span>
</>
)}
{isRequired ? label : `${label} (optional)`}
</Label>
)}
<Button
Expand All @@ -123,8 +140,12 @@ export default function Select<T extends object>({
return "Select an item";
}}
/>
{isInvalid && iconError}
{isOpen ? <ChevronUp /> : <ChevronDown />}
</Button>
<FieldError className="bcds-react-aria-Select--Error">
{errorMessage}
</FieldError>
<Popover className="bcds-react-aria-Select--Popover" offset={4}>
<ListBox
className="bcds-react-aria-Select--ListBox"
Expand Down
7 changes: 4 additions & 3 deletions packages/react-components/src/stories/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ const meta = {
config: {
// overrides automated accessibility testing for aria-hidden-focus to suppress a known false positive
// see https://react-spectrum.adobe.com/react-aria/Select.html#false-positives for more information
rules: [{ id: 'aria-hidden-focus', enabled: false }],
}
}
rules: [{ id: "aria-hidden-focus", enabled: false }],
},
},
},
argTypes: {
size: {
Expand Down Expand Up @@ -168,6 +168,7 @@ export const Invalid: Story = {
...SelectTemplate.args,
label: "Invalid example",
isInvalid: true,
errorMessage: "Error messages can be customised or passed programmatically",
},
};

Expand Down