Improve input
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -281,6 +281,7 @@
|
||||
.canon-input-root {
|
||||
font-family: var(--canon-font-regular);
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Tabs } from '@/components/Tabs';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
InputPreview,
|
||||
InputLabelAndDescription,
|
||||
InputError,
|
||||
} from '@/snippets/input';
|
||||
import { BaseUI } from '@/components/HeadlessBanners/BaseUI';
|
||||
|
||||
# Input
|
||||
|
||||
A input component tfor your forms.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<InputPreview />}
|
||||
code={`<Input label="Name" placeholder="Enter your name" />`}
|
||||
/>
|
||||
|
||||
<Tabs.Root>
|
||||
<Tabs.List>
|
||||
<Tabs.Tab>Usage</Tabs.Tab>
|
||||
<Tabs.Tab>Theming</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel>
|
||||
<CodeBlock
|
||||
code={`import { Input } from '@backstage/canon';
|
||||
|
||||
<Input />
|
||||
`}
|
||||
/>
|
||||
</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={`<Input className="canon-input-root" />`}
|
||||
/>
|
||||
</Tabs.Panel>
|
||||
</Tabs.Root>
|
||||
|
||||
## API reference
|
||||
|
||||
<BaseUI href="https://base-ui.com/react/components/field" />
|
||||
|
||||
<PropsTable
|
||||
data={{
|
||||
label: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
size: {
|
||||
type: ['sm', 'md'],
|
||||
responsive: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
match: {
|
||||
type: [
|
||||
'badInput',
|
||||
'customError',
|
||||
'patternMismatch',
|
||||
'rangeOverflow',
|
||||
'rangeUnderflow',
|
||||
'stepMismatch',
|
||||
'tooLong',
|
||||
'tooShort',
|
||||
'typeMismatch',
|
||||
'valid',
|
||||
'valueMissing',
|
||||
],
|
||||
responsive: false,
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
required: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
className: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
style: {
|
||||
type: 'CSSProperties',
|
||||
responsive: false,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
## Examples
|
||||
|
||||
### With label and description
|
||||
|
||||
Here's a simple input with a label and description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<InputLabelAndDescription />}
|
||||
code={`<Input label="Name" description="Visible on your profile" />`}
|
||||
/>
|
||||
|
||||
### Error
|
||||
|
||||
Here's a simple input with an error message.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<InputError />}
|
||||
code={`<Input label="Name" required invalid />`}
|
||||
/>
|
||||
@@ -9,6 +9,7 @@ import { ButtonPlayground } from '@/snippets/button';
|
||||
import { CheckboxPlayground } from '@/snippets/checkbox';
|
||||
import { HeadingPlayground } from '@/snippets/heading';
|
||||
import { TextPlayground } from '@/snippets/text';
|
||||
import { InputPlayground } from '@/snippets/input';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
@@ -66,6 +67,9 @@ const Content = () => {
|
||||
{selectedComponents.find(c => c === 'text') && (
|
||||
<Line content={<TextPlayground />} title="Text" />
|
||||
)}
|
||||
{selectedComponents.find(c => c === 'input') && (
|
||||
<Line content={<InputPlayground />} title="Input" />
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Input } from '@backstage/canon';
|
||||
|
||||
export const InputPreview = () => {
|
||||
return (
|
||||
<div style={{ width: '300px' }}>
|
||||
<Input label="Name" placeholder="Enter your name" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const InputLabelAndDescription = () => {
|
||||
return (
|
||||
<div style={{ width: '300px' }}>
|
||||
<Input label="Name" description="Visible on your profile" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const InputError = () => {
|
||||
return (
|
||||
<div style={{ width: '300px' }}>
|
||||
<Input
|
||||
label="Name"
|
||||
errorMessage="Please enter your name"
|
||||
required
|
||||
invalid
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const InputPlayground = () => {
|
||||
return (
|
||||
<div style={{ maxWidth: '300px' }}>
|
||||
<Input label="Name" description="Visible on your profile" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -70,12 +70,12 @@ export const components: Page[] = [
|
||||
{
|
||||
title: 'Button',
|
||||
slug: 'button',
|
||||
status: 'inProgress',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Checkbox',
|
||||
slug: 'checkbox',
|
||||
status: 'inProgress',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Heading',
|
||||
@@ -87,6 +87,11 @@ export const components: Page[] = [
|
||||
slug: 'icon',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Input',
|
||||
slug: 'input',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'Table',
|
||||
slug: 'table',
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
/// <reference types="react" />
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { Field } from '@base-ui-components/react/field';
|
||||
import { ForwardRefExoticComponent } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import * as React_3 from 'react';
|
||||
@@ -323,6 +324,13 @@ export interface InlineProps extends SpaceProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "InputProps" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const Input: React_2.ForwardRefExoticComponent<
|
||||
InputProps & React_2.RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export type JustifyContent =
|
||||
| 'stretch'
|
||||
|
||||
@@ -89,13 +89,26 @@ export const Sizes: Story = {
|
||||
),
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
export const WithErrorValidation: Story = {
|
||||
args: {
|
||||
invalid: true,
|
||||
validate: value => (value ? null : 'Please enter your name'),
|
||||
validate: value =>
|
||||
value !== 'Charles' ? 'Please enter a different name' : null,
|
||||
required: true,
|
||||
label: 'Name',
|
||||
description: 'Visible on your profile',
|
||||
error: 'Please enter your name',
|
||||
validationMode: 'onChange',
|
||||
errorMatch: 'customError',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithInvalidProp: Story = {
|
||||
args: {
|
||||
invalid: true,
|
||||
required: true,
|
||||
label: 'Name',
|
||||
description: 'Visible on your profile',
|
||||
validationMode: 'onChange',
|
||||
errorForceShow: true,
|
||||
validate: () => 'Stuff',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use client';
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Field } from '@base-ui-components/react/field';
|
||||
@@ -25,12 +27,14 @@ const Input = forwardRef<HTMLDivElement, InputProps>((props, ref) => {
|
||||
className,
|
||||
label,
|
||||
description,
|
||||
match = 'valueMissing',
|
||||
errorMatch = 'customError',
|
||||
errorForceShow = false,
|
||||
errorMessage = 'An error occurred',
|
||||
validationMode = 'onChange',
|
||||
size = 'md',
|
||||
placeholder,
|
||||
error = 'Please enter your a value',
|
||||
required = false,
|
||||
style,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -38,6 +42,7 @@ const Input = forwardRef<HTMLDivElement, InputProps>((props, ref) => {
|
||||
<Field.Root
|
||||
ref={ref}
|
||||
className={clsx('canon-input-root', className)}
|
||||
style={style}
|
||||
validationMode={validationMode}
|
||||
{...rest}
|
||||
>
|
||||
@@ -54,9 +59,12 @@ const Input = forwardRef<HTMLDivElement, InputProps>((props, ref) => {
|
||||
})}
|
||||
/>
|
||||
|
||||
<Field.Error className="canon-input-error" match={match}>
|
||||
{error}
|
||||
</Field.Error>
|
||||
<Field.Error
|
||||
className="canon-input-error"
|
||||
match={errorMatch}
|
||||
forceShow={errorForceShow}
|
||||
// children={errorForceShow ? 'An error occurred' : null}
|
||||
/>
|
||||
|
||||
{description && (
|
||||
<Field.Description className="canon-input-description">
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--canon-font-regular);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.canon-input-label {
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
import { Field } from '@base-ui-components/react/field';
|
||||
|
||||
/** @public */
|
||||
export interface InputProps
|
||||
extends React.ComponentPropsWithoutRef<typeof Field.Root> {
|
||||
label?: string;
|
||||
description?: string;
|
||||
size?: 'sm' | 'md';
|
||||
placeholder?: string;
|
||||
match?: Field.Error.Props['match'];
|
||||
error?: string;
|
||||
errorMatch?: Field.Error.Props['match'];
|
||||
errorForceShow?: boolean;
|
||||
errorMessage?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ export * from './components/Button';
|
||||
export * from './components/Icon';
|
||||
export * from './components/Checkbox';
|
||||
export * from './components/Table';
|
||||
export * from './components/Input';
|
||||
|
||||
// Types
|
||||
export * from './types';
|
||||
|
||||
Reference in New Issue
Block a user