Merge pull request #30327 from backstage/canon-radiogroup

Canon - Add RadioGroup + Radio
This commit is contained in:
Charles de Dreuille
2025-06-23 16:34:09 +02:00
committed by GitHub
20 changed files with 837 additions and 20 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Add new `RadioGroup` + `Radio` component to Canon
@@ -0,0 +1,98 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { RadioGroupSnippet } from '@/snippets/stories-snippets';
import {
radioGroupPropDefs,
radioGroupUsageSnippet,
radioGroupDefaultSnippet,
radioGroupDescriptionSnippet,
radioGroupHorizontalSnippet,
radioGroupDisabledSnippet,
radioGroupDisabledSingleSnippet,
radioGroupValidationSnippet,
radioGroupReadOnlySnippet,
} from './radio-group.props';
import { ComponentInfos } from '@/components/ComponentInfos';
# RadioGroup
A radio group allows a user to select a single item from a list of mutually exclusive options.
<Snippet
align="center"
py={4}
preview={<RadioGroupSnippet story="Default" />}
code={radioGroupDefaultSnippet}
/>
<ComponentInfos
component="radio-group"
classNames={['canon-RadioGroup', 'canon-RadioGroupContent']}
usageCode={radioGroupUsageSnippet}
/>
## API reference
<PropsTable data={radioGroupPropDefs} />
## Examples
### Horizontal
Here's a simple TextField with a description.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Horizontal" />}
code={radioGroupHorizontalSnippet}
/>
### Disabled
You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Disabled" />}
code={radioGroupDisabledSnippet}
/>
### Disabled Single radio
You can disable a single radio by adding the `isDisabled` prop to the `Radio` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="DisabledSingle" />}
code={radioGroupDisabledSingleSnippet}
/>
### Validation
Here's an example of a radio group with errors.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="Validation" />}
code={radioGroupValidationSnippet}
/>
### Read only
You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component.
<Snippet
align="center"
py={4}
open
preview={<RadioGroupSnippet story="ReadOnly" />}
code={radioGroupReadOnlySnippet}
/>
@@ -0,0 +1,64 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const radioGroupPropDefs: Record<string, PropDef> = {
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'small',
responsive: true,
},
label: {
type: 'string',
},
icon: {
type: 'enum',
values: ['ReactNode'],
},
description: {
type: 'string',
},
name: {
type: 'string',
required: true,
},
...classNamePropDefs,
...stylePropDefs,
};
export const radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/canon';
<RadioGroup />`;
export const radioGroupDefaultSnippet = `<RadioGroup label="Label" />`;
export const radioGroupDescriptionSnippet = `<TextField label="Label" description="Description" placeholder="Enter a URL" />`;
export const radioGroupHorizontalSnippet = `<RadioGroup label="Label" orientation="horizontal" />`;
export const radioGroupDisabledSnippet = `<RadioGroup label="Label" isDisabled>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupDisabledSingleSnippet = `<RadioGroup label="Label">
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupValidationSnippet = `<RadioGroup validate: value => (value === \'charmander\' ? \'Nice try!\' : null)>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
export const radioGroupReadOnlySnippet = `<RadioGroup label="Label" isReadOnly>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>`;
@@ -18,6 +18,7 @@ import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.s
import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories';
import * as AvatarStories from '../../../packages/canon/src/components/Avatar/Avatar.stories';
import * as CollapsibleStories from '../../../packages/canon/src/components/Collapsible/Collapsible.stories';
import * as RadioGroupStories from '../../../packages/canon/src/components/RadioGroup/RadioGroup.stories';
import * as TabsStories from '../../../packages/canon/src/components/Tabs/Tabs.stories';
import * as SwitchStories from '../../../packages/canon/src/components/Switch/Switch.stories';
@@ -197,3 +198,14 @@ export const SwitchSnippet = ({
return StoryComponent ? <StoryComponent /> : null;
};
export const RadioGroupSnippet = ({
story,
}: {
story: keyof typeof RadioGroupStories;
}) => {
const stories = composeStories(RadioGroupStories);
const StoryComponent = stories[story as keyof typeof stories];
return StoryComponent ? <StoryComponent /> : null;
};
+5
View File
@@ -116,6 +116,11 @@ export const components: Page[] = [
slug: 'menu',
status: 'alpha',
},
{
title: 'RadioGroup',
slug: 'radio-group',
status: 'alpha',
},
{
title: 'Select',
slug: 'select',
+97 -7
View File
@@ -9496,6 +9496,13 @@
min-width: 10.5rem;
}
.canon-FieldError {
color: var(--canon-fg-danger);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-top: var(--canon-space-2);
}
.canon-FieldLabel {
margin-bottom: var(--canon-space-3);
gap: var(--canon-space-1);
@@ -9815,6 +9822,96 @@
overflow: hidden;
}
.canon-RadioGroup {
color: var(--canon-fg-primary);
flex-direction: column;
display: flex;
}
.canon-RadioGroup[data-orientation="horizontal"] .canon-RadioGroupContent {
gap: var(--canon-space-4);
flex-direction: row;
}
.canon-RadioGroupContent {
gap: var(--canon-space-2);
flex-direction: column;
display: flex;
}
.canon-Radio {
align-items: center;
gap: var(--canon-space-2);
font-size: var(--canon-font-size-2);
color: var(--canon-fg-primary);
forced-color-adjust: none;
display: flex;
position: relative;
&:before {
content: "";
box-sizing: border-box;
border: .125rem solid var(--canon-border);
background: var(--canon-gray-1);
border-radius: var(--canon-radius-full);
width: 1rem;
height: 1rem;
transition: all .2s;
display: block;
}
&[data-pressed]:before {
border-color: var(--canon-border);
}
&[data-selected] {
&:before {
border-color: var(--canon-bg-solid);
border-width: .25rem;
}
&[data-pressed]:before {
border-color: var(--canon-bg-solid);
}
}
&[data-focus-visible]:before {
outline: 2px solid var(--canon-ring);
outline-offset: 2px;
}
&[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
&:before {
border-color: var(--canon-border-disabled);
background: var(--canon-bg-disabled);
}
&[data-selected]:before {
border-color: var(--canon-border-disabled);
}
}
&[data-invalid]:before, &[data-invalid][data-selected]:before {
border-color: var(--canon-border-danger);
}
&[data-disabled][data-invalid] {
color: var(--canon-fg-disabled);
&:before {
border-color: var(--canon-border-disabled);
background: var(--canon-bg-disabled);
}
&[data-selected]:before {
border-color: var(--canon-border-disabled);
}
}
}
.canon-TableRoot {
caption-side: bottom;
border-collapse: collapse;
@@ -10119,13 +10216,6 @@
border: 1px solid var(--canon-border-disabled);
}
.canon-TextFieldError {
color: var(--canon-fg-danger);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-top: var(--canon-space-2);
}
.canon-TooltipPopup {
box-sizing: border-box;
transform-origin: var(--transform-origin);
+23
View File
@@ -20,6 +20,8 @@ import { HTMLAttributes } from 'react';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { LinkProps as LinkProps_2 } from 'react-aria-components';
import { Menu as Menu_2 } from '@base-ui-components/react/menu';
import type { RadioGroupProps as RadioGroupProps_2 } from 'react-aria-components';
import type { RadioProps as RadioProps_2 } from 'react-aria-components';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RefAttributes } from 'react';
@@ -1032,6 +1034,27 @@ export type PositionProps = GetPropDefTypes<typeof positionPropDefs>;
// @public (undocumented)
export type PropDef<T = any> = RegularPropDef<T> | ResponsivePropDef<T>;
// @public (undocumented)
export const Radio: ForwardRefExoticComponent<
RadioProps & RefAttributes<HTMLLabelElement>
>;
// @public (undocumented)
export const RadioGroup: ForwardRefExoticComponent<
RadioGroupProps & RefAttributes<HTMLDivElement>
>;
// @public (undocumented)
export interface RadioGroupProps
extends Omit<RadioGroupProps_2, 'children'>,
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
// (undocumented)
children?: ReactNode;
}
// @public (undocumented)
export interface RadioProps extends RadioProps_2 {}
// @public (undocumented)
export type ReactNodePropDef = {
type: 'ReactNode';
@@ -0,0 +1,87 @@
/*
* 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.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { TextField, Input, Form } from 'react-aria-components';
import { FieldError } from './FieldError';
const meta = {
title: 'Forms/FieldError',
component: FieldError,
} satisfies Meta<typeof FieldError>;
export default meta;
type Story = StoryObj<typeof meta>;
// Show error with server validation using Form component
export const WithServerValidation: Story = {
render: () => (
<Form validationErrors={{ demo: 'This is a server validation error.' }}>
<TextField
name="demo"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError />
</TextField>
</Form>
),
};
// Show error using children
export const WithCustomMessage: Story = {
render: () => (
<TextField
isInvalid
validationBehavior="aria"
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError>This is a custom error message.</FieldError>
</TextField>
),
};
// Show error with render prop function
export const WithRenderProp: Story = {
render: () => (
<TextField
isInvalid
validationBehavior="aria"
validate={() => 'This field is invalid'}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
}}
>
<Input />
<FieldError>
{({ validationErrors }) =>
validationErrors.length > 0 ? validationErrors[0] : 'Field is invalid'
}
</FieldError>
</TextField>
),
};
@@ -0,0 +1,6 @@
.canon-FieldError {
color: var(--canon-fg-danger);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-top: var(--canon-space-2);
}
@@ -0,0 +1,39 @@
/*
* Copyright 2025 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.
*/
import { forwardRef } from 'react';
import {
FieldError as AriaFieldError,
type FieldErrorProps,
} from 'react-aria-components';
import clsx from 'clsx';
/** @public */
export const FieldError = forwardRef<HTMLDivElement, FieldErrorProps>(
(props: FieldErrorProps, ref) => {
const { className, ...rest } = props;
return (
<AriaFieldError
className={clsx('canon-FieldError', className)}
ref={ref}
{...rest}
/>
);
},
);
FieldError.displayName = 'FieldError';
@@ -0,0 +1,17 @@
/*
* 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.
*/
export * from './FieldError';
@@ -0,0 +1,147 @@
/*
* 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.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { RadioGroup, Radio } from './RadioGroup';
const meta = {
title: 'Forms/RadioGroup',
component: RadioGroup,
} satisfies Meta<typeof RadioGroup>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
label: 'What is your favorite pokemon?',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Horizontal: Story = {
args: {
...Default.args,
orientation: 'horizontal',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Disabled: Story = {
args: {
...Default.args,
isDisabled: true,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const DisabledSingle: Story = {
args: {
...Default.args,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const DisabledAndSelected: Story = {
args: {
...Default.args,
value: 'charmander',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Invalid: Story = {
args: {
...Default.args,
name: 'pokemon',
isInvalid: true,
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander" isDisabled>
Charmander
</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const Validation: Story = {
args: {
...Default.args,
name: 'pokemon',
defaultValue: 'charmander',
validationBehavior: 'aria',
validate: value => (value === 'charmander' ? 'Nice try!' : null),
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
export const ReadOnly: Story = {
args: {
...Default.args,
isReadOnly: true,
defaultValue: 'charmander',
},
render: args => (
<RadioGroup {...args}>
<Radio value="bulbasaur">Bulbasaur</Radio>
<Radio value="charmander">Charmander</Radio>
<Radio value="squirtle">Squirtle</Radio>
</RadioGroup>
),
};
@@ -0,0 +1,95 @@
.canon-RadioGroup {
display: flex;
flex-direction: column;
color: var(--canon-fg-primary);
}
.canon-RadioGroup[data-orientation='horizontal'] .canon-RadioGroupContent {
flex-direction: row;
gap: var(--canon-space-4);
}
.canon-RadioGroupContent {
display: flex;
flex-direction: column;
gap: var(--canon-space-2);
}
.canon-Radio {
display: flex;
/* This is needed so the HiddenInput is positioned correctly */
position: relative;
align-items: center;
gap: var(--canon-space-2);
font-size: var(--canon-font-size-2);
color: var(--canon-fg-primary);
forced-color-adjust: none;
&:before {
content: '';
display: block;
width: 1rem;
height: 1rem;
box-sizing: border-box;
border: 0.125rem solid var(--canon-border);
background: var(--canon-gray-1);
border-radius: var(--canon-radius-full);
transition: all 200ms;
}
&[data-pressed]:before {
border-color: var(--canon-border);
}
&[data-selected] {
&:before {
border-color: var(--canon-bg-solid);
border-width: 0.25rem;
}
&[data-pressed]:before {
border-color: var(--canon-bg-solid);
}
}
&[data-focus-visible]:before {
outline: 2px solid var(--canon-ring);
outline-offset: 2px;
}
&[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
&:before {
border-color: var(--canon-border-disabled);
background: var(--canon-bg-disabled);
}
&[data-selected]:before {
border-color: var(--canon-border-disabled);
}
}
&[data-invalid]:before {
border-color: var(--canon-border-danger);
}
&[data-invalid][data-selected]:before {
border-color: var(--canon-border-danger);
}
/* Ensure disabled state prevails over invalid state */
&[data-disabled][data-invalid] {
color: var(--canon-fg-disabled);
&:before {
border-color: var(--canon-border-disabled);
background: var(--canon-bg-disabled);
}
&[data-selected]:before {
border-color: var(--canon-border-disabled);
}
}
}
@@ -0,0 +1,86 @@
/*
* 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.
*/
import { forwardRef, useEffect } from 'react';
import {
RadioGroup as AriaRadioGroup,
Radio as AriaRadio,
} from 'react-aria-components';
import clsx from 'clsx';
import { FieldLabel } from '../FieldLabel';
import { FieldError } from '../FieldError';
import type { RadioGroupProps, RadioProps } from './types';
/** @public */
export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
(props, ref) => {
const {
className,
label,
secondaryLabel,
description,
isRequired,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledBy,
children,
...rest
} = props;
useEffect(() => {
if (!label && !ariaLabel && !ariaLabelledBy) {
console.warn(
'RadioGroup requires either a visible label, aria-label, or aria-labelledby for accessibility',
);
}
}, [label, ariaLabel, ariaLabelledBy]);
// If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required.
const secondaryLabelText =
secondaryLabel || (isRequired ? 'Required' : null);
return (
<AriaRadioGroup
className={clsx('canon-RadioGroup', className)}
aria-label={ariaLabel}
aria-labelledby={ariaLabelledBy}
{...rest}
ref={ref}
>
<FieldLabel
label={label}
secondaryLabel={secondaryLabelText}
description={description}
/>
<div className="canon-RadioGroupContent">{children}</div>
<FieldError />
</AriaRadioGroup>
);
},
);
RadioGroup.displayName = 'RadioGroup';
/** @public */
export const Radio = forwardRef<HTMLLabelElement, RadioProps>((props, ref) => {
const { className, ...rest } = props;
return (
<AriaRadio className={clsx('canon-Radio', className)} {...rest} ref={ref} />
);
});
RadioGroup.displayName = 'RadioGroup';
@@ -0,0 +1,18 @@
/*
* 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.
*/
export * from './RadioGroup';
export * from './types';
@@ -0,0 +1,32 @@
/*
* Copyright 2025 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.
*/
import type {
RadioGroupProps as AriaRadioGroupProps,
RadioProps as AriaRadioProps,
} from 'react-aria-components';
import type { FieldLabelProps } from '../FieldLabel/types';
import { ReactNode } from 'react';
/** @public */
export interface RadioGroupProps
extends Omit<AriaRadioGroupProps, 'children'>,
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
children?: ReactNode;
}
/** @public */
export interface RadioProps extends AriaRadioProps {}
@@ -104,10 +104,3 @@
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextFieldError {
color: var(--canon-fg-danger);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-top: var(--canon-space-2);
}
@@ -15,14 +15,11 @@
*/
import { forwardRef, useEffect } from 'react';
import {
Input,
TextField as AriaTextField,
FieldError,
} from 'react-aria-components';
import { Input, TextField as AriaTextField } from 'react-aria-components';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import clsx from 'clsx';
import { FieldLabel } from '../FieldLabel';
import { FieldError } from '../FieldError';
import type { TextFieldProps } from './types';
@@ -88,7 +85,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
placeholder={placeholder}
/>
</div>
<FieldError className="canon-TextFieldError" />
<FieldError />
</AriaTextField>
);
},
+2
View File
@@ -23,6 +23,7 @@
@import '../components/Container/styles.css';
@import '../components/DataTable/Root/DataTableRoot.styles.css';
@import '../components/DataTable/Pagination/DataTablePagination.styles.css';
@import '../components/FieldError/FieldError.styles.css';
@import '../components/FieldLabel/FieldLabel.styles.css';
@import '../components/Flex/styles.css';
@import '../components/Grid/styles.css';
@@ -30,6 +31,7 @@
@import '../components/Icon/styles.css';
@import '../components/Link/styles.css';
@import '../components/Menu/Menu.styles.css';
@import '../components/RadioGroup/RadioGroup.styles.css';
@import '../components/Table/styles.css';
@import '../components/Table/TableCell/TableCell.styles.css';
@import '../components/Table/TableCellText/TableCellText.styles.css';
+1
View File
@@ -41,6 +41,7 @@ export * from './components/Icon';
export * from './components/ButtonIcon';
export * from './components/ButtonLink';
export * from './components/Checkbox';
export * from './components/RadioGroup';
export * from './components/Table';
export * from './components/Tabs';
export * from './components/TextField';