Match style to React Aria
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
+5
-2
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user