Match style to React Aria

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-07-14 10:38:44 +02:00
parent 70da598ff9
commit 2a9ece59e2
8 changed files with 160 additions and 149 deletions
@@ -1,4 +1,5 @@
.bui-FieldError {
display: inline-block;
color: var(--bui-fg-danger);
font-size: var(--bui-font-size-2);
font-weight: var(--bui-font-weight-regular);
@@ -15,8 +15,11 @@
*/
.bui-Popover {
margin-right: 12px;
overflow: scroll;
background-color: var(--bui-bg-surface-1);
padding: var(--bui-space-2);
border-radius: var(--bui-radius-2);
border: 1px solid var(--bui-border);
border-radius: var(--bui-radius-3);
padding-block: var(--bui-space-1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
@@ -16,9 +16,10 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Select } from './Select';
import { Flex } from '../Flex';
import { Form } from 'react-aria-components';
const meta = {
title: 'Components/Select',
title: 'Forms/Select',
component: Select,
} satisfies Meta<typeof Select>;
@@ -56,7 +57,7 @@ export const WithLabel: Story = {
},
};
export const WithDescription: Story = {
export const WithLabelAndDescription: Story = {
args: {
...WithLabel.args,
description: 'Choose a font family for your document',
@@ -92,10 +93,7 @@ export const Disabled: Story = {
export const DisabledOption: Story = {
args: {
...Preview.args,
options: [
...fontOptions,
{ value: 'comic-sans', label: 'Comic sans', disabled: true },
],
disabledKeys: ['cursive', 'serif'],
},
};
@@ -253,14 +251,19 @@ export const WithManyOptions: Story = {
},
};
// export const WithErrorAndDescription: Story = {
// args: {
// ...Preview.args,
// error: 'Invalid font family',
// },
// };
export const WithError: Story = {
args: {
...WithLabel.args,
name: 'font',
},
render: args => (
<Form validationErrors={{ font: 'Invalid font family' }}>
<Select {...args} />
</Form>
),
};
export const WithLongOptionNames: Story = {
export const WithLongNames: Story = {
args: {
label: 'Document Template',
options: [
@@ -293,6 +296,19 @@ export const WithLongOptionNames: Story = {
placeholder: 'Select a document template',
name: 'template',
style: { maxWidth: 400 },
selectedKey: 'annual-report-2024',
defaultSelectedKey: 'annual-report-2024',
},
};
export const WithLongNamesAndPadding: Story = {
args: {
...WithLongNames.args,
},
decorators: [
(Story, { args }) => (
<div style={{ padding: 128 }}>
<Story {...args} />
</div>
),
],
};
@@ -14,64 +14,93 @@
* limitations under the License.
*/
/* .bui-Select {
} */
.bui-Select[data-invalid] {
& .bui-SelectTrigger {
border-color: var(--bui-fg-danger);
}
}
.bui-SelectTrigger {
box-sizing: border-box;
border-radius: var(--bui-radius-3);
border: 1px solid var(--bui-border);
padding: 0 var(--bui-space-4);
background-color: var(--bui-bg-surface-1);
font-size: var(--bui-font-size-3);
font-family: var(--bui-font-regular);
font-weight: var(--bui-font-weight-regular);
color: var(--bui-fg-primary);
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
cursor: pointer;
gap: var(--bui-space-2);
max-width: 100%;
&[data-size='small'] {
height: 2rem;
padding-inline: var(--bui-space-3);
}
&[data-size='medium'] {
height: 3rem;
padding-inline: var(--bui-space-4);
}
&[data-size='small'] svg {
width: 1rem;
height: 1rem;
}
&[data-size='medium'] svg {
width: 1.25rem;
height: 1.25rem;
}
&::placeholder {
color: var(--bui-fg-secondary);
}
&:hover {
border-color: var(--bui-border-hover);
}
&:focus-visible {
border-color: var(--bui-border-pressed);
outline: 0;
}
&[data-invalid] {
border-color: var(--bui-fg-danger);
}
&[data-invalid]:hover {
border-width: 2px;
}
&[data-invalid]:focus-visible {
border-width: 2px;
}
&[data-disabled] {
cursor: not-allowed;
border-color: var(--bui-border-disabled);
color: var(--bui-fg-disabled);
}
&[data-popup-open] .bui-SelectIcon {
transform: rotate(180deg);
}
}
.bui-SelectTrigger::placeholder {
color: var(--bui-fg-secondary);
}
.bui-SelectValue {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
font-size: var(--bui-font-size-3);
font-family: var(--bui-font-regular);
font-weight: var(--bui-font-weight-regular);
color: var(--bui-fg-primary);
.bui-SelectTrigger:hover {
border-color: var(--bui-border-hover);
}
.bui-SelectTrigger:focus-visible {
border-color: var(--bui-border-pressed);
outline: 0;
}
.bui-SelectTrigger[data-invalid] {
border-color: var(--bui-fg-danger);
}
.bui-SelectTrigger[data-invalid]:hover {
border-width: 2px;
}
.bui-SelectTrigger[data-invalid]:focus-visible {
border-width: 2px;
}
.bui-SelectTrigger[data-disabled] {
cursor: not-allowed;
border-color: var(--bui-border-disabled);
color: var(--bui-fg-disabled);
}
.bui-SelectTrigger[data-size='small'] {
height: 2rem;
}
.bui-SelectTrigger[data-size='medium'] {
height: 3rem;
& .bui-SelectItemIndicator {
display: none;
}
}
.bui-SelectIcon {
@@ -79,31 +108,6 @@
transition: transform 0.2s ease;
}
.bui-SelectTrigger[data-popup-open] .bui-SelectIcon {
transform: rotate(180deg);
}
.bui-SelectPopup {
box-sizing: border-box;
max-height: var(--available-height);
overflow-y: auto;
background-color: var(--bui-bg-surface-1);
border: 1px solid var(--bui-border);
border-radius: var(--bui-radius-3);
padding-block: var(--bui-space-1);
z-index: 1;
outline: 0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transform-origin: var(--transform-origin);
transition: transform 150ms, opacity 150ms;
}
.bui-SelectPopup[data-starting-style],
.bui-SelectPopup[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
.bui-SelectItem {
position: relative;
width: var(--anchor-width);
@@ -120,27 +124,31 @@
font-size: var(--bui-font-size-3);
gap: var(--bui-space-2);
outline: none;
}
.bui-SelectItem[data-highlighted] {
z-index: 0;
position: relative;
color: var(--bui-fg-primary);
}
&[data-focused] {
z-index: 0;
position: relative;
color: var(--bui-fg-primary);
}
.bui-SelectItem[data-highlighted]::before {
content: '';
z-index: -1;
position: absolute;
inset-block: 0;
inset-inline: 0.25rem;
border-radius: 0.25rem;
background-color: var(--bui-bg-tint-hover);
}
&[data-focused]::before {
content: '';
z-index: -1;
position: absolute;
inset-block: 0;
inset-inline: 0.25rem;
border-radius: 0.25rem;
background-color: var(--bui-bg-tint-hover);
}
.bui-SelectItem[data-disabled] {
cursor: not-allowed;
color: var(--bui-fg-disabled);
&[data-disabled] {
cursor: not-allowed;
color: var(--bui-fg-disabled);
}
&[data-selected] .bui-SelectItemIndicator {
opacity: 1;
}
}
.bui-SelectItemIndicator {
@@ -148,9 +156,11 @@
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.bui-SelectItemText {
.bui-SelectItemLabel {
flex: 1;
grid-area: text;
}
@@ -167,10 +177,3 @@
align-items: center;
justify-content: center;
}
.bui-SelectValue {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
}
@@ -22,12 +22,15 @@ import {
Popover,
ListBox,
ListBoxItem,
Text,
} from 'react-aria-components';
import clsx from 'clsx';
import './Select.styles.css';
import { SelectProps } from './types';
import { useStyles } from '../../hooks/useStyles';
import { FieldLabel } from '../FieldLabel';
import { Icon } from '../Icon';
import { FieldError } from '../FieldError';
/** @public */
export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
@@ -37,7 +40,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
description,
options,
placeholder = 'Select an option',
size = 'medium',
size = 'small',
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
isRequired,
@@ -47,7 +50,6 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
} = props;
const { classNames: popoverClassNames } = useStyles('Popover');
const { classNames: listClassNames } = useStyles('List');
const { classNames, dataAttributes } = useStyles('Select', {
size,
});
@@ -75,15 +77,28 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
secondaryLabel={secondaryLabelText}
description={description}
/>
<Button data-size={dataAttributes['data-size']}>
<SelectValue />
<span aria-hidden="true"></span>
<Button
className={classNames.trigger}
data-size={dataAttributes['data-size']}
>
<SelectValue className={classNames.value} />
<Icon aria-hidden="true" name="chevron-down" />
</Button>
<FieldError />
<Popover className={popoverClassNames.root}>
<ListBox className={listClassNames.root}>
<ListBox className={classNames.list}>
{options?.map(option => (
<ListBoxItem key={option.value} className={listClassNames.row}>
{option.label}
<ListBoxItem
key={option.value}
id={option.value}
className={classNames.item}
>
<div className={classNames.itemIndicator}>
<Icon name="check" />
</div>
<Text slot="label" className={classNames.itemLabel}>
{option.label}
</Text>
</ListBoxItem>
))}
</ListBox>
+1 -5
View File
@@ -14,11 +14,6 @@
* limitations under the License.
*/
/* Generic component styles */
@import './generic/popover.css';
@import './generic/list.css';
/* Component styles */
@import '../components/Avatar/Avatar.styles.css';
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@@ -39,6 +34,7 @@
@import '../components/Icon/styles.css';
@import '../components/Link/styles.css';
@import '../components/Menu/Menu.styles.css';
@import '../components/Popover/Popover.styles.css';
@import '../components/RadioGroup/RadioGroup.styles.css';
@import '../components/Table/styles.css';
@import '../components/Table/TableCell/TableCell.styles.css';
-23
View File
@@ -1,23 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.bui-List {
background-color: green;
}
.bui-ListRow {
padding: var(--bui-space-2);
}
@@ -132,6 +132,7 @@ export const componentDefinitions = {
classNames: {
root: 'bui-List',
row: 'bui-ListRow',
label: 'bui-ListLabel',
},
},
Menu: {
@@ -185,11 +186,10 @@ export const componentDefinitions = {
trigger: 'bui-SelectTrigger',
value: 'bui-SelectValue',
icon: 'bui-SelectIcon',
popup: 'bui-SelectPopup',
list: 'bui-SelectList',
item: 'bui-SelectItem',
itemIndicator: 'bui-SelectItemIndicator',
itemText: 'bui-SelectItemText',
description: 'bui-SelectDescription',
itemLabel: 'bui-SelectItemLabel',
error: 'bui-SelectError',
},
dataAttributes: {