Add support for custom icon
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
**Breaking change** We are moving the Select component to use React Aria under the hood. We updated most props and events according to their underlying API.
|
||||
File diff suppressed because one or more lines are too long
+108
-139
@@ -9552,6 +9552,7 @@
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
margin-top: var(--bui-space-2);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bui-FieldLabelWrapper {
|
||||
@@ -10049,6 +10050,16 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bui-Popover {
|
||||
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);
|
||||
margin-right: 12px;
|
||||
overflow: scroll;
|
||||
box-shadow: 0 4px 12px #0000001a;
|
||||
}
|
||||
|
||||
.bui-RadioGroup {
|
||||
color: var(--bui-fg-primary);
|
||||
flex-direction: column;
|
||||
@@ -10607,122 +10618,94 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bui-Select {
|
||||
font-family: var(--bui-font-regular);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-SelectLabel {
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
color: var(--bui-fg-primary);
|
||||
margin-bottom: var(--bui-space-1_5);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bui-SelectLabel[data-disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.bui-SelectDescription {
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
color: var(--bui-fg-secondary);
|
||||
padding-top: var(--bui-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bui-SelectError {
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
color: var(--bui-fg-danger);
|
||||
padding-top: var(--bui-space-1_5);
|
||||
margin: 0;
|
||||
.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);
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
display: flex;
|
||||
|
||||
& svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&[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, &[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;
|
||||
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);
|
||||
overflow: hidden;
|
||||
|
||||
.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, .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-SelectIcon {
|
||||
margin-left: var(--bui-space-5);
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.bui-SelectTrigger[data-popup-open] .bui-SelectIcon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.bui-SelectPopup {
|
||||
box-sizing: border-box;
|
||||
max-height: var(--available-height);
|
||||
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;
|
||||
transform-origin: var(--transform-origin);
|
||||
outline: 0;
|
||||
transition: transform .15s, opacity .15s;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 4px 12px #0003;
|
||||
}
|
||||
|
||||
.bui-SelectPopup[data-starting-style], .bui-SelectPopup[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(.9);
|
||||
& .bui-SelectItemIndicator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-SelectItem {
|
||||
@@ -10741,61 +10724,47 @@
|
||||
grid-template-areas: "icon text";
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bui-SelectItem[data-highlighted] {
|
||||
z-index: 0;
|
||||
color: var(--bui-fg-primary);
|
||||
position: relative;
|
||||
}
|
||||
&[data-focused] {
|
||||
z-index: 0;
|
||||
color: var(--bui-fg-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bui-SelectItem[data-highlighted]:before {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
background-color: var(--bui-bg-tint-hover);
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: .25rem;
|
||||
}
|
||||
&[data-focused]:before {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
background-color: var(--bui-bg-tint-hover);
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: .25rem;
|
||||
}
|
||||
|
||||
.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 {
|
||||
opacity: 0;
|
||||
grid-area: icon;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: opacity .2s ease-in-out;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-SelectItemText {
|
||||
.bui-SelectItemLabel {
|
||||
flex: 1;
|
||||
grid-area: text;
|
||||
}
|
||||
|
||||
.bui-SelectRequired {
|
||||
color: var(--bui-fg-secondary);
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
margin-left: var(--bui-space-1);
|
||||
}
|
||||
|
||||
.bui-SelectIcon {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-SelectValue {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bui-Switch {
|
||||
align-items: center;
|
||||
gap: var(--bui-space-3);
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
```ts
|
||||
import { Avatar as Avatar_2 } from '@base-ui-components/react/avatar';
|
||||
import { ButtonProps as ButtonProps_2 } from 'react-aria-components';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { Collapsible as Collapsible_2 } from '@base-ui-components/react/collapsible';
|
||||
import { ComponentProps } from 'react';
|
||||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { Context } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { ElementType } from 'react';
|
||||
import { FocusEvent as FocusEvent_2 } from 'react';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { HTMLAttributes } from 'react';
|
||||
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
||||
@@ -26,6 +24,7 @@ import { RefAttributes } from 'react';
|
||||
import type { RemixiconComponentType } from '@remixicon/react';
|
||||
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
|
||||
import type { SearchFieldProps as SearchFieldProps_2 } from 'react-aria-components';
|
||||
import type { SelectProps as SelectProps_2 } from 'react-aria-components';
|
||||
import type { SwitchProps as SwitchProps_2 } from 'react-aria-components';
|
||||
import { Table as Table_2 } from '@tanstack/react-table';
|
||||
import { Tabs as Tabs_2 } from '@base-ui-components/react/tabs';
|
||||
@@ -426,6 +425,13 @@ export const componentDefinitions: {
|
||||
readonly weight: readonly ['regular', 'bold'];
|
||||
};
|
||||
};
|
||||
readonly List: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-List';
|
||||
readonly row: 'bui-ListRow';
|
||||
readonly label: 'bui-ListLabel';
|
||||
};
|
||||
};
|
||||
readonly Menu: {
|
||||
readonly classNames: {
|
||||
readonly trigger: 'bui-MenuTrigger';
|
||||
@@ -445,6 +451,11 @@ export const componentDefinitions: {
|
||||
readonly separator: 'bui-MenuSeparator';
|
||||
};
|
||||
};
|
||||
readonly Popover: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Popover';
|
||||
};
|
||||
};
|
||||
readonly RadioGroup: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-RadioGroup';
|
||||
@@ -469,16 +480,13 @@ export const componentDefinitions: {
|
||||
readonly Select: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Select';
|
||||
readonly required: 'bui-SelectRequired';
|
||||
readonly trigger: 'bui-SelectTrigger';
|
||||
readonly value: 'bui-SelectValue';
|
||||
readonly icon: 'bui-SelectIcon';
|
||||
readonly popup: 'bui-SelectPopup';
|
||||
readonly list: 'bui-SelectList';
|
||||
readonly item: 'bui-SelectItem';
|
||||
readonly itemIndicator: 'bui-SelectItemIndicator';
|
||||
readonly itemText: 'bui-SelectItemText';
|
||||
readonly description: 'bui-SelectDescription';
|
||||
readonly error: 'bui-SelectError';
|
||||
readonly itemLabel: 'bui-SelectItemLabel';
|
||||
};
|
||||
readonly dataAttributes: {
|
||||
readonly size: readonly ['small', 'medium'];
|
||||
@@ -1521,28 +1529,19 @@ export const Select: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SelectProps {
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
label?: string;
|
||||
name: string;
|
||||
onBlur?: (event: FocusEvent_2<HTMLSelectElement>) => void;
|
||||
onChange?: (event: ChangeEvent<HTMLSelectElement>) => void;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onValueChange?: (value: string) => void;
|
||||
export interface SelectProps
|
||||
extends SelectProps_2<{
|
||||
name: string;
|
||||
value: string;
|
||||
}>,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
|
||||
icon?: ReactNode;
|
||||
options?: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
style?: React.CSSProperties;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -61,8 +61,8 @@ const DataTablePagination = forwardRef(
|
||||
{ label: 'Show 40 results', value: '40' },
|
||||
{ label: 'Show 50 results', value: '50' },
|
||||
]}
|
||||
value={pageSize?.toString()}
|
||||
onValueChange={value => {
|
||||
selectedKey={pageSize?.toString()}
|
||||
onSelectionChange={value => {
|
||||
table?.setPageSize(Number(value));
|
||||
}}
|
||||
className="bui-DataTablePagination--select"
|
||||
|
||||
@@ -17,6 +17,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Select } from './Select';
|
||||
import { Flex } from '../Flex';
|
||||
import { Form } from 'react-aria-components';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
const meta = {
|
||||
title: 'Forms/Select',
|
||||
@@ -64,6 +65,13 @@ export const WithLabelAndDescription: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
},
|
||||
render: args => <Select {...args} icon={<RiCloudLine />} />,
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
args: {
|
||||
...Preview.args,
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
gap: var(--bui-space-2);
|
||||
max-width: 100%;
|
||||
|
||||
& svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&[data-size='small'] {
|
||||
height: 2rem;
|
||||
padding-inline: var(--bui-space-3);
|
||||
@@ -103,11 +107,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-SelectIcon {
|
||||
margin-left: var(--bui-space-5);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.bui-SelectItem {
|
||||
position: relative;
|
||||
width: var(--anchor-width);
|
||||
@@ -164,16 +163,3 @@
|
||||
flex: 1;
|
||||
grid-area: text;
|
||||
}
|
||||
|
||||
.bui-SelectRequired {
|
||||
color: var(--bui-fg-secondary);
|
||||
font-size: var(--bui-font-size-2);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
margin-left: var(--bui-space-1);
|
||||
}
|
||||
|
||||
.bui-SelectIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
options,
|
||||
placeholder = 'Select an option',
|
||||
size = 'small',
|
||||
icon,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
isRequired,
|
||||
@@ -81,6 +82,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
className={classNames.trigger}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon}
|
||||
<SelectValue className={classNames.value} />
|
||||
<Icon aria-hidden="true" name="chevron-down" />
|
||||
</Button>
|
||||
|
||||
@@ -19,14 +19,12 @@ import { ReactNode } from 'react';
|
||||
import type { SelectProps as AriaSelectProps } from 'react-aria-components';
|
||||
import type { FieldLabelProps } from '../FieldLabel/types';
|
||||
|
||||
interface SelectOption {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SelectProps
|
||||
extends AriaSelectProps<SelectOption>,
|
||||
extends AriaSelectProps<{
|
||||
name: string;
|
||||
value: string;
|
||||
}>,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
|
||||
/**
|
||||
* An icon to render before the input
|
||||
|
||||
@@ -190,7 +190,6 @@ export const componentDefinitions = {
|
||||
item: 'bui-SelectItem',
|
||||
itemIndicator: 'bui-SelectItemIndicator',
|
||||
itemLabel: 'bui-SelectItemLabel',
|
||||
error: 'bui-SelectError',
|
||||
},
|
||||
dataAttributes: {
|
||||
size: ['small', 'medium'] as const,
|
||||
|
||||
Reference in New Issue
Block a user