Add docs
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': minor
|
||||
---
|
||||
|
||||
Add new Select component for Canon
|
||||
@@ -0,0 +1,129 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SelectSnippet } from '@/snippets/stories-snippets';
|
||||
import { buttonVariants } from '@/snippets/code-snippets';
|
||||
import { selectPropDefs } from './props';
|
||||
|
||||
# Select
|
||||
|
||||
A common form component for choosing a predefined value in a dropdown menu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SelectSnippet story="Preview" />}
|
||||
code={`<Select name="font" label="Font Family" options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]} />
|
||||
`}
|
||||
/>
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
<Tabs.Tab>Theming</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock
|
||||
code={`import { Select } from '@backstage/canon';
|
||||
|
||||
<Select
|
||||
name="font"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>
|
||||
`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel>
|
||||
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
|
||||
your brand. For additional flexibility, you can use the provided class names for each element listed below.
|
||||
<CodeBlock
|
||||
code={`<Select className="canon-SelectFieldRoot" />`}
|
||||
/>
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={selectPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Label and description
|
||||
|
||||
Select component with label and description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="WithDescription" />}
|
||||
code={`<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={[ ... ]}
|
||||
/>`}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Sizes" />}
|
||||
code={`<Flex>
|
||||
<Select
|
||||
size="small"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
<Select
|
||||
size="medium"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
</Flex>`}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Disabled" />}
|
||||
code={`<Select
|
||||
disabled
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock
|
||||
code={`<Select
|
||||
size={{ initial: 'small', lg: 'medium' }}
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`}
|
||||
/>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '../../../../utils/propDefs';
|
||||
import type { PropDef } from '../../../../utils/propDefs';
|
||||
|
||||
export const selectPropDefs: Record<string, PropDef> = {
|
||||
label: {
|
||||
type: 'string',
|
||||
default: 'Select an option',
|
||||
responsive: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['Array<{ value: string, label: string }>'],
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
onValueChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: string) => void'],
|
||||
responsive: false,
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(open: boolean) => void'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -25,8 +25,8 @@ export const PropsTable = <T extends Record<string, PropData>>({
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell style={{ width: '16%' }}>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '56%' }}>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '14%' }}>Default</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '50%' }}>Type</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '20%' }}>Default</Table.HeaderCell>
|
||||
<Table.HeaderCell style={{ width: '14%' }}>
|
||||
Responsive
|
||||
</Table.HeaderCell>
|
||||
@@ -45,7 +45,7 @@ export const PropsTable = <T extends Record<string, PropData>>({
|
||||
<Table.Cell style={{ width: '16%' }}>
|
||||
<Chip head>{n}</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '56%' }}>
|
||||
<Table.Cell style={{ width: '50%' }}>
|
||||
<div
|
||||
style={{ display: 'flex', flexWrap: 'wrap', gap: '0.375rem' }}
|
||||
>
|
||||
@@ -61,7 +61,7 @@ export const PropsTable = <T extends Record<string, PropData>>({
|
||||
)}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '14%' }}>
|
||||
<Table.Cell style={{ width: '20%' }}>
|
||||
<Chip>{data[n].default ? data[n].default : '-'}</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell style={{ width: '14%' }}>
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as IconStories from '../../../packages/canon/src/components/Icon/Icon.s
|
||||
import * as InputStories from '../../../packages/canon/src/components/Input/Input.stories';
|
||||
import * as TextStories from '../../../packages/canon/src/components/Text/Text.stories';
|
||||
import * as FlexStories from '../../../packages/canon/src/components/Flex/Flex.stories';
|
||||
import * as SelectStories from '../../../packages/canon/src/components/Select/Select.stories';
|
||||
|
||||
export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => {
|
||||
const stories = composeStories(BoxStories);
|
||||
@@ -125,3 +126,14 @@ export const TextSnippet = ({ story }: { story: keyof typeof TextStories }) => {
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
export const SelectSnippet = ({
|
||||
story,
|
||||
}: {
|
||||
story: keyof typeof SelectStories;
|
||||
}) => {
|
||||
const stories = composeStories(SelectStories);
|
||||
const StoryComponent = stories[story as keyof typeof stories];
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
|
||||
@@ -97,6 +97,11 @@ export const components: Page[] = [
|
||||
slug: 'input',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Select',
|
||||
slug: 'select',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Table',
|
||||
slug: 'table',
|
||||
|
||||
@@ -691,3 +691,166 @@
|
||||
background-color: var(--canon-scrollbar-thumb);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.canon-SelectFieldRoot {
|
||||
font-family: var(--canon-font-regular);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectFieldLabel {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
margin-bottom: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
.canon-SelectFieldDescription {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-secondary);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectFieldError {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-danger);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger {
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--canon-radius-3);
|
||||
border: 1px solid var(--canon-border);
|
||||
padding: 0 var(--canon-space-4);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:focus-visible {
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid] {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
border-color: var(--canon-border-disabled);
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-small, .canon-SelectItem--size-small {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-medium, .canon-SelectItem--size-medium {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.canon-SelectIcon {
|
||||
margin-left: var(--canon-space-5);
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.canon-SelectPopup {
|
||||
box-sizing: border-box;
|
||||
max-height: var(--available-height);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
border: 1px solid var(--canon-border);
|
||||
border-radius: var(--canon-radius-3);
|
||||
padding-block: var(--canon-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;
|
||||
}
|
||||
|
||||
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(.9);
|
||||
}
|
||||
|
||||
.canon-SelectItem {
|
||||
width: var(--anchor-width);
|
||||
padding-block: var(--canon-space-2);
|
||||
padding-inline: var(--canon-space-4);
|
||||
color: var(--canon-fg-primary);
|
||||
border-radius: var(--canon-radius-3);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: var(--canon-font-size-3);
|
||||
align-items: center;
|
||||
gap: var(--canon-space-2);
|
||||
outline: none;
|
||||
grid-template-columns: 1rem 1fr;
|
||||
grid-template-areas: "icon text";
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted] {
|
||||
z-index: 0;
|
||||
color: var(--canon-fg-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted]:before {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
background-color: var(--canon-bg-tint-hover);
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: .25rem;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectItemIndicator {
|
||||
grid-area: icon;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectItemText {
|
||||
flex: 1;
|
||||
grid-area: text;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
.canon-SelectFieldRoot {
|
||||
font-family: var(--canon-font-regular);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectFieldLabel {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
margin-bottom: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
.canon-SelectFieldDescription {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-secondary);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectFieldError {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-danger);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger {
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--canon-radius-3);
|
||||
border: 1px solid var(--canon-border);
|
||||
padding: 0 var(--canon-space-4);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:focus-visible {
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid] {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
border-color: var(--canon-border-disabled);
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-small, .canon-SelectItem--size-small {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-medium, .canon-SelectItem--size-medium {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.canon-SelectIcon {
|
||||
margin-left: var(--canon-space-5);
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.canon-SelectPopup {
|
||||
box-sizing: border-box;
|
||||
max-height: var(--available-height);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
border: 1px solid var(--canon-border);
|
||||
border-radius: var(--canon-radius-3);
|
||||
padding-block: var(--canon-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;
|
||||
}
|
||||
|
||||
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(.9);
|
||||
}
|
||||
|
||||
.canon-SelectItem {
|
||||
width: var(--anchor-width);
|
||||
padding-block: var(--canon-space-2);
|
||||
padding-inline: var(--canon-space-4);
|
||||
color: var(--canon-fg-primary);
|
||||
border-radius: var(--canon-radius-3);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: var(--canon-font-size-3);
|
||||
align-items: center;
|
||||
gap: var(--canon-space-2);
|
||||
outline: none;
|
||||
grid-template-columns: 1rem 1fr;
|
||||
grid-template-areas: "icon text";
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted] {
|
||||
z-index: 0;
|
||||
color: var(--canon-fg-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted]:before {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
background-color: var(--canon-bg-tint-hover);
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: .25rem;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectItemIndicator {
|
||||
grid-area: icon;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectItemText {
|
||||
flex: 1;
|
||||
grid-area: text;
|
||||
}
|
||||
@@ -9897,3 +9897,166 @@
|
||||
background-color: var(--canon-scrollbar-thumb);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.canon-SelectFieldRoot {
|
||||
font-family: var(--canon-font-regular);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectFieldLabel {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
margin-bottom: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
.canon-SelectFieldDescription {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-secondary);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectFieldError {
|
||||
font-size: var(--canon-font-size-2);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-danger);
|
||||
padding-top: var(--canon-space-1_5);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger {
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--canon-radius-3);
|
||||
border: 1px solid var(--canon-border);
|
||||
padding: 0 var(--canon-space-4);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
font-size: var(--canon-font-size-3);
|
||||
font-family: var(--canon-font-regular);
|
||||
font-weight: var(--canon-font-weight-regular);
|
||||
color: var(--canon-fg-primary);
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger:focus-visible {
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid] {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
border-color: var(--canon-border-disabled);
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-small, .canon-SelectItem--size-small {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger--size-medium, .canon-SelectItem--size-medium {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.canon-SelectIcon {
|
||||
margin-left: var(--canon-space-5);
|
||||
transition: transform .2s;
|
||||
}
|
||||
|
||||
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.canon-SelectPopup {
|
||||
box-sizing: border-box;
|
||||
max-height: var(--available-height);
|
||||
background-color: var(--canon-bg-surface-1);
|
||||
border: 1px solid var(--canon-border);
|
||||
border-radius: var(--canon-radius-3);
|
||||
padding-block: var(--canon-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;
|
||||
}
|
||||
|
||||
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(.9);
|
||||
}
|
||||
|
||||
.canon-SelectItem {
|
||||
width: var(--anchor-width);
|
||||
padding-block: var(--canon-space-2);
|
||||
padding-inline: var(--canon-space-4);
|
||||
color: var(--canon-fg-primary);
|
||||
border-radius: var(--canon-radius-3);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: var(--canon-font-size-3);
|
||||
align-items: center;
|
||||
gap: var(--canon-space-2);
|
||||
outline: none;
|
||||
grid-template-columns: 1rem 1fr;
|
||||
grid-template-areas: "icon text";
|
||||
display: grid;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted] {
|
||||
z-index: 0;
|
||||
color: var(--canon-fg-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-highlighted]:before {
|
||||
content: "";
|
||||
z-index: -1;
|
||||
background-color: var(--canon-bg-tint-hover);
|
||||
border-radius: .25rem;
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline: .25rem;
|
||||
}
|
||||
|
||||
.canon-SelectItem[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
color: var(--canon-fg-disabled);
|
||||
}
|
||||
|
||||
.canon-SelectItemIndicator {
|
||||
grid-area: icon;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-SelectItemText {
|
||||
flex: 1;
|
||||
grid-area: text;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Breakpoint as Breakpoint_2 } from '@backstage/canon';
|
||||
import { Context } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Field as Field_2 } from '@base-ui-components/react/field';
|
||||
@@ -955,6 +956,32 @@ export const ScrollArea: {
|
||||
>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const Select: React_2.ForwardRefExoticComponent<
|
||||
SelectProps & React_2.RefAttributes<HTMLSelectElement>
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SelectProps {
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
name: string;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
onValueChange?: (value: string) => void;
|
||||
options?: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
placeholder?: string;
|
||||
required?: boolean;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint_2, 'small' | 'medium'>>;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Space =
|
||||
| '0.5'
|
||||
|
||||
@@ -18,11 +18,11 @@ import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Select } from './Select';
|
||||
import { Form } from '@base-ui-components/react/form';
|
||||
import { Button } from '../Button';
|
||||
import { Flex } from '../Flex';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Select',
|
||||
component: Select,
|
||||
decorators: [story => <div style={{ maxWidth: 400 }}>{story()}</div>],
|
||||
} satisfies Meta<typeof Select>;
|
||||
|
||||
export default meta;
|
||||
@@ -36,38 +36,58 @@ const fontOptions = [
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
options: fontOptions,
|
||||
name: 'font',
|
||||
},
|
||||
};
|
||||
|
||||
export const Preview: Story = {
|
||||
args: {
|
||||
label: 'Font Family',
|
||||
options: fontOptions,
|
||||
placeholder: 'Select a font',
|
||||
name: 'font',
|
||||
style: { maxWidth: 260 },
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
description: 'Choose a font family for your document',
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
args: {
|
||||
...Preview.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex direction="row" gap="2" style={{ width: '100%', maxWidth: 540 }}>
|
||||
<Select {...args} size="small" />
|
||||
<Select {...args} size="medium" />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledOption: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
options: [
|
||||
...fontOptions,
|
||||
{ value: 'comic-sans', label: 'Comic sans', disabled: true },
|
||||
@@ -77,28 +97,28 @@ export const DisabledOption: Story = {
|
||||
|
||||
export const NoLabel: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
label: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const NoOptions: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
options: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
size: 'small',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithValue: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
value: 'mono',
|
||||
defaultValue: 'serif',
|
||||
},
|
||||
@@ -106,7 +126,7 @@ export const WithValue: Story = {
|
||||
|
||||
export const WithDefaultValue: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
defaultValue: 'serif',
|
||||
options: fontOptions,
|
||||
name: 'font',
|
||||
@@ -259,7 +279,7 @@ async function validateFont(value: string) {
|
||||
|
||||
export const ShowErrorOnSubmit: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
...Preview.args,
|
||||
label: 'Font Family (select Comic sans to see error)',
|
||||
options: [...fontOptions, { value: 'comic-sans', label: 'Comic sans' }],
|
||||
required: true,
|
||||
|
||||
@@ -21,11 +21,7 @@ import clsx from 'clsx';
|
||||
import './Select.styles.css';
|
||||
import { SelectProps } from './types';
|
||||
|
||||
/**
|
||||
* Select component
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
/** @public */
|
||||
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
@@ -42,12 +38,14 @@ export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
size = 'medium',
|
||||
disabled = false,
|
||||
required = false,
|
||||
style,
|
||||
} = props;
|
||||
return (
|
||||
<Field.Root
|
||||
className={clsx('canon-SelectFieldRoot', className)}
|
||||
disabled={disabled}
|
||||
name={name}
|
||||
style={style}
|
||||
>
|
||||
{label && (
|
||||
<Field.Label className="canon-SelectFieldLabel">{label}</Field.Label>
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export * from './Select';
|
||||
export * from './types';
|
||||
|
||||
@@ -85,4 +85,9 @@ export interface SelectProps {
|
||||
* Callbak that is called when the select field is opened or closed
|
||||
*/
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
|
||||
/**
|
||||
* The style of the select field
|
||||
*/
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user