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 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({}); export const Default = Template.bind({});
Default.args = {}; Default.args = {};
@ -37,3 +49,11 @@ export const IconPrepend: Story = () => (
export const IconAppend: Story = () => ( export const IconAppend: Story = () => (
<Input value="I type words and even more words" appendIconName="search" /> <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 { forwardRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import type { IconName } from '../icon'; import type { IconName } from '../icon';
@ -8,13 +8,58 @@ import {
includesRightPadding, includesRightPadding,
} from '../../utils/class-names'; } from '../../utils/class-names';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> { type InputRootProps = InputHTMLAttributes<HTMLInputElement> & {
hasError?: boolean; hasError?: boolean;
disabled?: boolean; disabled?: boolean;
className?: string; 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 = ({ export const inputClassNames = ({
hasError, hasError,
className, className,
@ -61,41 +106,98 @@ export const inputStyle = ({
} }
: style; : 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>( export const Input = forwardRef<HTMLInputElement, InputProps>(
({ prependIconName, appendIconName, className, hasError, ...props }, ref) => { (
className = `h-28 ${className}`; {
if (prependIconName) { prependIconName,
className += ' pl-28'; prependIconDescription,
} appendIconName,
if (appendIconName) { appendIconDescription,
className += ' pr-28'; 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 = ( const input = (
<input <input
{...props} {...props}
ref={ref} ref={ref}
className={classNames(inputClassNames({ className, hasError }))} className={classNames(
inputClassNames({ className: inputClassName, hasError })
)}
/> />
); );
const iconName = prependIconName || appendIconName;
if (iconName !== undefined) { const element = getAffixElement({
const iconClassName = classNames( prependIconName,
['fill-black-60 dark:fill-white-60', 'absolute', 'z-10'], prependIconDescription,
{ appendIconName,
'left-8': prependIconName, appendIconDescription,
'right-8': appendIconName, prependElement,
} appendElement,
); });
const icon = <Icon name={iconName} className={iconClassName} size={16} />;
if (element) {
return ( return (
<div className="inline-flex items-center relative"> <div className="inline-flex items-center relative">
{prependIconName && icon} {hasPrepended && element}
{input} {input}
{appendIconName && icon} {hasAppended && element}
</div> </div>
); );
} }
return input; return input;
} }
); );