Merge pull request #29820 from jabrks/text-field
Canon - Support icon in TextField
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/canon': patch
|
||||
---
|
||||
|
||||
Added new icon and onClear props to the TextField to make it easier to accessorize inputs.
|
||||
@@ -567,48 +567,92 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
.canon-TextFieldInputWrapper {
|
||||
padding: 0 var(--canon-space-3);
|
||||
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);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputIcon {
|
||||
padding-right: var(--canon-space-1);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--canon-fg-primary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
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);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButton {
|
||||
padding: 0 0 0 var(--canon-space-1);
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
border: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButtonIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:focus-visible {
|
||||
.canon-TextFieldInput[data-focused] {
|
||||
outline-color: var(--canon-border-pressed);
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-invalid] {
|
||||
.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextField[data-focused] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-border-pressed);
|
||||
}
|
||||
|
||||
.canon-TextField[data-invalid] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-disabled] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldInputWrapper {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid var(--canon-border-disabled);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="small"] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldClearButton {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputWrapper[data-size="small"] {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="medium"] {
|
||||
.canon-TextFieldInputWrapper[data-size="medium"] {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -9791,48 +9791,92 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
.canon-TextFieldInputWrapper {
|
||||
padding: 0 var(--canon-space-3);
|
||||
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);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputIcon {
|
||||
padding-right: var(--canon-space-1);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--canon-fg-primary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
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);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButton {
|
||||
padding: 0 0 0 var(--canon-space-1);
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
border: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButtonIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:focus-visible {
|
||||
.canon-TextFieldInput[data-focused] {
|
||||
outline-color: var(--canon-border-pressed);
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-invalid] {
|
||||
.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextField[data-focused] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-border-pressed);
|
||||
}
|
||||
|
||||
.canon-TextField[data-invalid] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-disabled] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldInputWrapper {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid var(--canon-border-disabled);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="small"] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldClearButton {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputWrapper[data-size="small"] {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="medium"] {
|
||||
.canon-TextFieldInputWrapper[data-size="medium"] {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,48 +34,92 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
.canon-TextFieldInputWrapper {
|
||||
padding: 0 var(--canon-space-3);
|
||||
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);
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputIcon {
|
||||
padding-right: var(--canon-space-1);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--canon-fg-primary);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
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);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: inherit;
|
||||
background: none;
|
||||
border: none;
|
||||
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButton {
|
||||
padding: 0 0 0 var(--canon-space-1);
|
||||
vertical-align: middle;
|
||||
background: none;
|
||||
border: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButtonIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:hover {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:focus-visible {
|
||||
.canon-TextFieldInput[data-focused] {
|
||||
outline-color: var(--canon-border-pressed);
|
||||
border-color: var(--canon-border-pressed);
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-invalid] {
|
||||
.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextField[data-focused] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-border-pressed);
|
||||
}
|
||||
|
||||
.canon-TextField[data-invalid] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-disabled] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldInputWrapper {
|
||||
opacity: .5;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid var(--canon-border-disabled);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="small"] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldClearButton {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputWrapper[data-size="small"] {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size="medium"] {
|
||||
.canon-TextFieldInputWrapper[data-size="medium"] {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -744,6 +744,7 @@ export type IconNames =
|
||||
| 'chevron-left'
|
||||
| 'chevron-right'
|
||||
| 'chevron-up'
|
||||
| 'close'
|
||||
| 'cloud'
|
||||
| 'code'
|
||||
| 'discord'
|
||||
@@ -759,6 +760,7 @@ export type IconNames =
|
||||
| 'heart'
|
||||
| 'moon'
|
||||
| 'plus'
|
||||
| 'search'
|
||||
| 'sidebar-fold'
|
||||
| 'sidebar-unfold'
|
||||
| 'sparkling'
|
||||
@@ -1218,8 +1220,10 @@ export interface TextFieldProps
|
||||
className?: string;
|
||||
description?: string;
|
||||
error?: string | null;
|
||||
icon?: IconNames;
|
||||
label?: string;
|
||||
name: string;
|
||||
onClear?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ import {
|
||||
RiGithubLine,
|
||||
RiDiscordLine,
|
||||
RiYoutubeLine,
|
||||
RiCloseLine,
|
||||
RiSearchLine,
|
||||
} from '@remixicon/react';
|
||||
|
||||
/** @public */
|
||||
@@ -103,6 +105,7 @@ export const icons: IconMap = {
|
||||
'chevron-left': RiArrowLeftSLine,
|
||||
'chevron-right': RiArrowRightSLine,
|
||||
'chevron-up': RiArrowUpSLine,
|
||||
close: RiCloseLine,
|
||||
cloud: RiCloudLine,
|
||||
code: RiCodeLine,
|
||||
discord: RiDiscordLine,
|
||||
@@ -118,6 +121,7 @@ export const icons: IconMap = {
|
||||
heart: RiHeartLine,
|
||||
moon: RiMoonLine,
|
||||
plus: RiAddLine,
|
||||
search: RiSearchLine,
|
||||
'sidebar-fold': RiSidebarFoldLine,
|
||||
'sidebar-unfold': RiSidebarUnfoldLine,
|
||||
sparkling: RiSparklingLine,
|
||||
|
||||
@@ -46,6 +46,7 @@ export type IconNames =
|
||||
| 'chevron-left'
|
||||
| 'chevron-right'
|
||||
| 'chevron-up'
|
||||
| 'close'
|
||||
| 'cloud'
|
||||
| 'code'
|
||||
| 'discord'
|
||||
@@ -61,6 +62,7 @@ export type IconNames =
|
||||
| 'heart'
|
||||
| 'moon'
|
||||
| 'plus'
|
||||
| 'search'
|
||||
| 'sidebar-fold'
|
||||
| 'sidebar-unfold'
|
||||
| 'sparkling'
|
||||
|
||||
@@ -109,3 +109,35 @@ export const WithErrorAndDescription: Story = {
|
||||
description: 'Description',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
placeholder: 'Search...',
|
||||
icon: 'search',
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledWithIcon: Story = {
|
||||
args: {
|
||||
...WithIcon.args,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithOnClear: Story = {
|
||||
args: {
|
||||
...WithLabel.args,
|
||||
placeholder: 'Search...',
|
||||
type: 'search',
|
||||
onClear: () => null,
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledWithOnClear: Story = {
|
||||
args: {
|
||||
...WithOnClear.args,
|
||||
defaultValue: 'Testing',
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
margin-right: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.canon-TextFieldLabel[data-disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -49,48 +50,93 @@
|
||||
padding-top: var(--canon-space-1_5);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
.canon-TextFieldInputWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--canon-space-3);
|
||||
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);
|
||||
}
|
||||
|
||||
.canon-TextFieldInputIcon {
|
||||
display: block;
|
||||
padding-right: var(--canon-space-1);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
color: var(--canon-fg-primary);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput {
|
||||
border: none;
|
||||
background: none;
|
||||
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);
|
||||
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[type='search']::-webkit-search-cancel-button,
|
||||
.canon-TextFieldInput[type='search']::-webkit-search-decoration {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButton {
|
||||
display: none;
|
||||
padding: 0 0 0 var(--canon-space-1);
|
||||
background: none;
|
||||
border: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.canon-TextFieldClearButtonIcon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput::placeholder {
|
||||
color: var(--canon-fg-secondary);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:hover {
|
||||
.canon-TextFieldInput[data-focused] {
|
||||
outline-color: var(--canon-border-pressed);
|
||||
outline-width: 0px;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:hover) {
|
||||
border-color: var(--canon-border-hover);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput:focus-visible {
|
||||
outline-color: var(--canon-border-pressed);
|
||||
outline-width: 0px;
|
||||
.canon-TextField[data-focused] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-border-pressed);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-invalid] {
|
||||
.canon-TextField[data-invalid] .canon-TextFieldInputWrapper {
|
||||
border-color: var(--canon-fg-danger);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-disabled] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldInputWrapper {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
border: 1px solid var(--canon-border-disabled);
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size='small'] {
|
||||
.canon-TextField[data-disabled] .canon-TextFieldClearButton {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.canon-TextFieldInputWrapper[data-size='small'] {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.canon-TextFieldInput[data-size='medium'] {
|
||||
.canon-TextFieldInputWrapper[data-size='medium'] {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useResponsiveValue } from '../../hooks/useResponsiveValue';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import type { TextFieldProps } from './types';
|
||||
import { Icon } from '../Icon';
|
||||
|
||||
/** @public */
|
||||
export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
@@ -33,6 +34,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
required,
|
||||
style,
|
||||
disabled,
|
||||
icon,
|
||||
onClear,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
@@ -57,12 +60,23 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
)}
|
||||
</Field.Label>
|
||||
)}
|
||||
<Field.Control
|
||||
className="canon-TextFieldInput"
|
||||
data-size={responsiveSize}
|
||||
required={required}
|
||||
{...rest}
|
||||
/>
|
||||
<div className="canon-TextFieldInputWrapper" data-size={responsiveSize}>
|
||||
{icon && <Icon className="canon-TextFieldInputIcon" name={icon} />}
|
||||
<Field.Control
|
||||
className="canon-TextFieldInput"
|
||||
required={required}
|
||||
{...rest}
|
||||
/>
|
||||
{onClear && (
|
||||
<button
|
||||
className="canon-TextFieldClearButton"
|
||||
disabled={disabled}
|
||||
onClick={onClear}
|
||||
>
|
||||
<Icon className="canon-TextFieldClearButtonIcon" name="close" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{description && (
|
||||
<Field.Description className="canon-TextFieldDescription">
|
||||
{description}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { Breakpoint } from '../../types';
|
||||
import type { IconNames } from '../Icon';
|
||||
|
||||
/** @public */
|
||||
export interface TextFieldProps
|
||||
@@ -49,4 +50,14 @@ export interface TextFieldProps
|
||||
* The error message of the text field
|
||||
*/
|
||||
error?: string | null;
|
||||
|
||||
/**
|
||||
* An icon to render before the input
|
||||
*/
|
||||
icon?: IconNames;
|
||||
|
||||
/**
|
||||
* Handler to call when the clear button is pressed
|
||||
*/
|
||||
onClear?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user