Feat/Input prepend and append elements (#402)

* add custom element support to inputs

* use classnames instead of template literals
This commit is contained in:
botond 2022-05-20 09:42:11 +01:00 committed by GitHub
parent 348badca2a
commit a352702bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 26 deletions

View File

@ -7,6 +7,18 @@ export default {
const Template: Story = (args) => <Input {...args} value="I type words" />;
const customElementPlaceholder = (
<span
style={{
fontFamily: 'monospace',
backgroundColor: 'grey',
padding: '4px',
}}
>
Ω
</span>
);
export const Default = Template.bind({});
Default.args = {};
@ -37,3 +49,11 @@ export const IconPrepend: Story = () => (
export const IconAppend: Story = () => (
<Input value="I type words and even more words" appendIconName="search" />
);
export const ElementPrepend: Story = () => (
<Input value="<- custom element" prependElement={customElementPlaceholder} />
);
export const ElementAppend: Story = () => (
<Input value="custom element ->" appendElement={customElementPlaceholder} />
);

View File

@ -1,4 +1,4 @@
import type { InputHTMLAttributes } from 'react';
import type { InputHTMLAttributes, ReactNode } from 'react';
import { forwardRef } from 'react';
import classNames from 'classnames';
import type { IconName } from '../icon';
@ -8,13 +8,58 @@ import {
includesRightPadding,
} from '../../utils/class-names';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
type InputRootProps = InputHTMLAttributes<HTMLInputElement> & {
hasError?: boolean;
disabled?: boolean;
className?: string;
prependIconName?: IconName;
appendIconName?: IconName;
};
type NoPrepend = {
prependIconName?: never;
prependIconDescription?: string;
prependElement?: never;
};
type NoAppend = {
appendIconName?: never;
appendIconDescription?: string;
appendElement?: never;
};
type InputPrepend = NoAppend &
(
| NoPrepend
| {
prependIconName: IconName;
prependIconDescription?: string;
prependElement?: never;
}
| {
prependIconName?: never;
prependIconDescription?: never;
prependElement: ReactNode;
}
);
type InputAppend = NoPrepend &
(
| NoAppend
| {
appendIconName: IconName;
appendIconDescription?: string;
appendElement?: never;
}
| {
appendIconName?: never;
appendIconDescription?: never;
appendElement: ReactNode;
}
);
type AffixProps = InputPrepend | InputAppend;
type InputProps = InputRootProps & AffixProps;
export const inputClassNames = ({
hasError,
className,
@ -61,41 +106,98 @@ export const inputStyle = ({
}
: style;
const getAffixElement = ({
prependElement,
prependIconName,
prependIconDescription,
appendElement,
appendIconName,
appendIconDescription,
}: Pick<InputProps, keyof AffixProps>) => {
const position = prependIconName || prependElement ? 'pre' : 'post';
const className = classNames(
['fill-black-60 dark:fill-white-60', 'absolute', 'z-10'],
{
'left-8': position === 'pre',
'right-8': position === 'post',
}
);
const element = prependElement || appendElement;
const iconName = prependIconName || appendIconName;
const iconDescription = prependIconDescription || appendIconDescription;
if (element) {
return <div className={className}>{element}</div>;
}
if (iconName) {
return (
<Icon
name={iconName}
className={className}
size={16}
aria-label={iconDescription}
aria-hidden={!iconDescription}
/>
);
}
return null;
};
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ prependIconName, appendIconName, className, hasError, ...props }, ref) => {
className = `h-28 ${className}`;
if (prependIconName) {
className += ' pl-28';
}
if (appendIconName) {
className += ' pr-28';
}
(
{
prependIconName,
prependIconDescription,
appendIconName,
appendIconDescription,
prependElement,
appendElement,
className,
hasError,
...props
},
ref
) => {
const hasPrepended = !!(prependIconName || prependElement);
const hasAppended = !!(appendIconName || appendElement);
const inputClassName = classNames('h-28', className, {
'pl-28': hasPrepended ?? hasAppended,
});
const input = (
<input
{...props}
ref={ref}
className={classNames(inputClassNames({ className, hasError }))}
className={classNames(
inputClassNames({ className: inputClassName, hasError })
)}
/>
);
const iconName = prependIconName || appendIconName;
if (iconName !== undefined) {
const iconClassName = classNames(
['fill-black-60 dark:fill-white-60', 'absolute', 'z-10'],
{
'left-8': prependIconName,
'right-8': appendIconName,
}
);
const icon = <Icon name={iconName} className={iconClassName} size={16} />;
const element = getAffixElement({
prependIconName,
prependIconDescription,
appendIconName,
appendIconDescription,
prependElement,
appendElement,
});
if (element) {
return (
<div className="inline-flex items-center relative">
{prependIconName && icon}
{hasPrepended && element}
{input}
{appendIconName && icon}
{hasAppended && element}
</div>
);
}
return input;
}
);