Merge pull request #31238 from birdhb/add-clear-and-visibility-actions-to-text-field
Add PasswordField to BUI
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Added a new `PasswordField` component. As part of this change, the `password` and `search` types have been removed from `TextField`.
|
||||
File diff suppressed because one or more lines are too long
@@ -85,6 +85,11 @@ building it incrementally with not conflict with the existing theming system.
|
||||
description="A search field component to help you search for items."
|
||||
href="/components/search-field"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="PasswordField"
|
||||
description="A password field component to help you input passwords."
|
||||
href="/components/password-field"
|
||||
/>
|
||||
<ComponentCard
|
||||
title="Select"
|
||||
description="A select component to help you select items."
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { PasswordFieldSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
inputPropDefs,
|
||||
passwordFieldUsageSnippet,
|
||||
passwordFieldDefaultSnippet,
|
||||
passwordFieldSizesSnippet,
|
||||
passwordFieldDescriptionSnippet,
|
||||
} from './password-field.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
|
||||
<PageTitle
|
||||
title="PasswordField"
|
||||
description="A password field component for your forms."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PasswordFieldSnippet story="WithLabel" />}
|
||||
code={passwordFieldDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={passwordFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={inputPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="Sizes" />}
|
||||
code={passwordFieldSizesSnippet}
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
Here's a simple PasswordField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="WithDescription" />}
|
||||
code={passwordFieldDescriptionSnippet}
|
||||
/>
|
||||
|
||||
<Theming component="PasswordField" />
|
||||
|
||||
<ChangelogComponent component="password-field" />
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const inputPropDefs: 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 passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui';
|
||||
|
||||
<PasswordField />`;
|
||||
|
||||
export const passwordFieldDefaultSnippet = `<PasswordField label="Label" placeholder="Enter a secret" />`;
|
||||
|
||||
export const passwordFieldSizesSnippet = `<Flex direction="row" gap="4">
|
||||
<PasswordField size="small" placeholder="Small" icon={<Icon name="sparkling" />} />
|
||||
<PasswordField size="medium" placeholder="Medium" icon={<Icon name="sparkling" />} />
|
||||
</Flex>`;
|
||||
|
||||
export const passwordFieldDescriptionSnippet = `<PasswordField label="Label" description="Description" placeholder="Enter a secret" />`;
|
||||
@@ -28,6 +28,7 @@ import * as HeaderStories from '../../../packages/ui/src/components/Header/Heade
|
||||
import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPage/HeaderPage.stories';
|
||||
import * as TableStories from '../../../packages/ui/src/components/Table/Table.stories';
|
||||
import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories';
|
||||
import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories';
|
||||
|
||||
// Helper function to create snippet components
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -53,6 +54,8 @@ export const ContainerSnippet = createSnippetComponent(ContainerStories);
|
||||
export const GridSnippet = createSnippetComponent(GridStories);
|
||||
export const IconSnippet = createSnippetComponent(IconStories);
|
||||
export const TextFieldSnippet = createSnippetComponent(TextFieldStories);
|
||||
export const PasswordFieldSnippet =
|
||||
createSnippetComponent(PasswordFieldStories);
|
||||
export const TextSnippet = createSnippetComponent(TextStories);
|
||||
export const FlexSnippet = createSnippetComponent(FlexStories);
|
||||
export const SelectSnippet = createSnippetComponent(SelectStories);
|
||||
|
||||
@@ -25,7 +25,8 @@ export type Component =
|
||||
| 'radio-group'
|
||||
| 'card'
|
||||
| 'skeleton'
|
||||
| 'header';
|
||||
| 'header'
|
||||
| 'passwordfield';
|
||||
|
||||
export type Version = `${number}.${number}.${number}`;
|
||||
|
||||
|
||||
@@ -126,6 +126,11 @@ export const components: Page[] = [
|
||||
slug: 'menu',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'PasswordField',
|
||||
slug: 'password-field',
|
||||
status: 'alpha',
|
||||
},
|
||||
{
|
||||
title: 'RadioGroup',
|
||||
slug: 'radio-group',
|
||||
|
||||
@@ -10522,6 +10522,59 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-PasswordField {
|
||||
font-family: var(--bui-font-regular);
|
||||
--bui-passwordmanager-icon-width: var(--bui-space-1);
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bui-InputVisibility {
|
||||
right: var(--bui-passwordmanager-icon-width);
|
||||
cursor: pointer;
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: #0000;
|
||||
border: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
&[data-size="small"] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
&[data-size="small"] svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
&[data-size="medium"] {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
&[data-size="medium"] svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-PasswordField .bui-InputWrapper[data-size="small"] .bui-Input {
|
||||
padding-right: calc(2rem + var(--bui-passwordmanager-icon-width));
|
||||
}
|
||||
|
||||
.bui-PasswordField .bui-InputWrapper[data-size="medium"] .bui-Input {
|
||||
padding-right: calc(2.5rem + var(--bui-passwordmanager-icon-width));
|
||||
}
|
||||
|
||||
.bui-SearchField {
|
||||
flex: 1 0;
|
||||
|
||||
|
||||
@@ -525,6 +525,12 @@ export const componentDefinitions: {
|
||||
readonly emptyState: 'bui-MenuEmptyState';
|
||||
};
|
||||
};
|
||||
readonly PasswordField: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-PasswordField';
|
||||
readonly inputVisibility: 'bui-InputVisibility';
|
||||
};
|
||||
};
|
||||
readonly Popover: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Popover';
|
||||
@@ -643,6 +649,7 @@ export const componentDefinitions: {
|
||||
readonly inputWrapper: 'bui-InputWrapper';
|
||||
readonly input: 'bui-Input';
|
||||
readonly inputIcon: 'bui-InputIcon';
|
||||
readonly inputAction: 'bui-InputAction';
|
||||
};
|
||||
readonly dataAttributes: {
|
||||
readonly invalid: readonly [true, false];
|
||||
@@ -1760,6 +1767,7 @@ export interface TextFieldProps
|
||||
icon?: ReactNode;
|
||||
placeholder?: string;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
type?: 'text' | 'email' | 'tel' | 'url';
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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-vite';
|
||||
import { PasswordField } from './PasswordField';
|
||||
import { Form } from 'react-aria-components';
|
||||
import { Icon } from '../Icon';
|
||||
import { Flex } from '../Flex';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/PasswordField',
|
||||
component: PasswordField,
|
||||
argTypes: {
|
||||
isRequired: {
|
||||
control: 'boolean',
|
||||
},
|
||||
icon: {
|
||||
control: 'object',
|
||||
},
|
||||
},
|
||||
} satisfies Meta<typeof PasswordField>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
name: 'secret',
|
||||
placeholder: 'Enter a secret',
|
||||
style: {
|
||||
maxWidth: '300px',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex direction="row" gap="4" style={{ width: '100%', maxWidth: '600px' }}>
|
||||
<PasswordField {...args} size="small" icon={<Icon name="sparkling" />} />
|
||||
<PasswordField {...args} size="medium" icon={<Icon name="sparkling" />} />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const DefaultValue: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
defaultValue: 'https://example.com',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
label: 'Label',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
description: 'Description',
|
||||
},
|
||||
};
|
||||
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
isRequired: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
isDisabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
},
|
||||
render: args => (
|
||||
<PasswordField {...args} size="small" icon={<Icon name="sparkling" />} />
|
||||
),
|
||||
};
|
||||
|
||||
export const DisabledWithIcon: Story = {
|
||||
args: {
|
||||
...WithIcon.args,
|
||||
isDisabled: true,
|
||||
},
|
||||
render: WithIcon.render,
|
||||
};
|
||||
|
||||
export const ShowError: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
},
|
||||
render: args => (
|
||||
<Form validationErrors={{ secret: 'Invalid secret' }}>
|
||||
<PasswordField {...args} />
|
||||
</Form>
|
||||
),
|
||||
};
|
||||
|
||||
export const Validation: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
validate: value => (value === 'admin' ? 'Nice try!' : null),
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomField: Story = {
|
||||
render: () => (
|
||||
<>
|
||||
<FieldLabel
|
||||
htmlFor="custom-field"
|
||||
id="custom-field-label"
|
||||
label="Custom Field"
|
||||
/>
|
||||
<PasswordField
|
||||
id="custom-field"
|
||||
aria-labelledby="custom-field-label"
|
||||
name="custom-field"
|
||||
defaultValue="Custom Field"
|
||||
/>
|
||||
</>
|
||||
),
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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-PasswordField {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--bui-font-regular);
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
/* Reserve space for browser/password manager icon (e.g. 1Password) */
|
||||
--bui-passwordmanager-icon-width: var(--bui-space-1);
|
||||
}
|
||||
|
||||
.bui-InputVisibility {
|
||||
position: absolute;
|
||||
right: var(--bui-passwordmanager-icon-width);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--bui-fg-primary);
|
||||
|
||||
/* Size: small */
|
||||
&[data-size='small'] {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
&[data-size='small'] svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/* Size: medium */
|
||||
&[data-size='medium'] {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
&[data-size='medium'] svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure input has enough right padding for our toggle + PM icon */
|
||||
.bui-PasswordField .bui-InputWrapper[data-size='small'] .bui-Input {
|
||||
padding-right: calc(2rem + var(--bui-passwordmanager-icon-width));
|
||||
}
|
||||
|
||||
.bui-PasswordField .bui-InputWrapper[data-size='medium'] .bui-Input {
|
||||
padding-right: calc(2.5rem + var(--bui-passwordmanager-icon-width));
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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, useState } from 'react';
|
||||
import {
|
||||
Input,
|
||||
TextField as AriaTextField,
|
||||
Button as RAButton,
|
||||
} from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
|
||||
import type { PasswordFieldProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { RiEyeLine, RiEyeOffLine } from '@remixicon/react';
|
||||
|
||||
/** @public */
|
||||
export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
|
||||
(props, ref) => {
|
||||
const {
|
||||
className,
|
||||
icon,
|
||||
size = 'small',
|
||||
label,
|
||||
secondaryLabel,
|
||||
description,
|
||||
isRequired,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
placeholder,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (!label && !ariaLabel && !ariaLabelledBy) {
|
||||
console.warn(
|
||||
'PasswordField requires either a visible label, aria-label, or aria-labelledby for accessibility',
|
||||
);
|
||||
}
|
||||
}, [label, ariaLabel, ariaLabelledBy]);
|
||||
|
||||
const { classNames: passwordFieldClassNames, dataAttributes } = useStyles(
|
||||
'PasswordField',
|
||||
{
|
||||
size,
|
||||
},
|
||||
);
|
||||
|
||||
const { classNames: textFieldClassNames } = useStyles('TextField', {});
|
||||
|
||||
// If a secondary label is provided, use it. Otherwise, use 'Required' if the field is required.
|
||||
const secondaryLabelText =
|
||||
secondaryLabel || (isRequired ? 'Required' : null);
|
||||
|
||||
// Manage secret visibility toggle
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<AriaTextField
|
||||
className={clsx(passwordFieldClassNames.root, className)}
|
||||
{...dataAttributes}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
type="password"
|
||||
{...rest}
|
||||
ref={ref}
|
||||
>
|
||||
<FieldLabel
|
||||
label={label}
|
||||
secondaryLabel={secondaryLabelText}
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={textFieldClassNames.inputWrapper}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon && (
|
||||
<div
|
||||
className={textFieldClassNames.inputIcon}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className={textFieldClassNames.inputAction}>
|
||||
<RAButton
|
||||
data-size={dataAttributes['data-size']}
|
||||
data-variant={'tertiary'}
|
||||
aria-label={isVisible ? 'Hide value' : 'Show value'}
|
||||
aria-controls={isVisible ? 'text' : 'password'}
|
||||
aria-expanded={isVisible}
|
||||
onPress={() => setIsVisible(v => !v)}
|
||||
className={passwordFieldClassNames.inputVisibility}
|
||||
>
|
||||
{isVisible ? <RiEyeLine /> : <RiEyeOffLine />}
|
||||
</RAButton>
|
||||
</div>
|
||||
<Input
|
||||
className={textFieldClassNames.input}
|
||||
{...(icon && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
/>
|
||||
</div>
|
||||
<FieldError />
|
||||
</AriaTextField>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
PasswordField.displayName = 'PasswordField';
|
||||
@@ -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 './PasswordField';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 { TextFieldProps as AriaTextFieldProps } from 'react-aria-components';
|
||||
import { ReactNode } from 'react';
|
||||
import type { Breakpoint } from '../../types';
|
||||
import type { FieldLabelProps } from '../FieldLabel/types';
|
||||
|
||||
/** @public */
|
||||
export interface PasswordFieldProps
|
||||
extends AriaTextFieldProps,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
|
||||
/**
|
||||
* The size of the password field
|
||||
* @defaultValue 'medium'
|
||||
*/
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
|
||||
/**
|
||||
* Text to display in the input when it has no value
|
||||
*/
|
||||
placeholder?: string;
|
||||
}
|
||||
@@ -23,6 +23,14 @@ import type { FieldLabelProps } from '../FieldLabel/types';
|
||||
export interface TextFieldProps
|
||||
extends AriaTextFieldProps,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id'> {
|
||||
/**
|
||||
* The HTML input type for the text field
|
||||
*
|
||||
* @remarks
|
||||
* Use `SearchField` for
|
||||
* search inputs and `PasswordField` for password inputs.
|
||||
*/
|
||||
type?: 'text' | 'email' | 'tel' | 'url';
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
@import '../components/TagGroup/TagGroup.styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/TextField/TextField.styles.css';
|
||||
@import '../components/PasswordField/PasswordField.styles.css';
|
||||
@import '../components/SearchField/SearchField.styles.css';
|
||||
@import '../components/Skeleton/Skeleton.styles.css';
|
||||
@import '../components/Tooltip/Tooltip.styles.css';
|
||||
|
||||
@@ -174,6 +174,12 @@ export const componentDefinitions = {
|
||||
emptyState: 'bui-MenuEmptyState',
|
||||
},
|
||||
},
|
||||
PasswordField: {
|
||||
classNames: {
|
||||
root: 'bui-PasswordField',
|
||||
inputVisibility: 'bui-InputVisibility',
|
||||
},
|
||||
},
|
||||
Popover: {
|
||||
classNames: {
|
||||
root: 'bui-Popover',
|
||||
@@ -286,6 +292,7 @@ export const componentDefinitions = {
|
||||
inputWrapper: 'bui-InputWrapper',
|
||||
input: 'bui-Input',
|
||||
inputIcon: 'bui-InputIcon',
|
||||
inputAction: 'bui-InputAction',
|
||||
},
|
||||
dataAttributes: {
|
||||
invalid: [true, false] as const,
|
||||
|
||||
Reference in New Issue
Block a user