Add icon to input

This commit is contained in:
Bartłomiej Głownia 2022-03-02 10:32:22 +01:00 committed by Matthew Russell
parent e5f96448fc
commit 3f490f03ca
21 changed files with 81 additions and 36 deletions

View File

@ -82,8 +82,11 @@ module.exports = {
4: '0.25rem', 4: '0.25rem',
8: '0.5rem', 8: '0.5rem',
12: '0.75rem', 12: '0.75rem',
16: '1rem',
20: '1.25rem', 20: '1.25rem',
24: '1.5rem',
28: '1.75rem', 28: '1.75rem',
32: '2rem',
44: '2.75rem', 44: '2.75rem',
}, },
/* /*

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Button from './button'; import { Button } from './button';
describe('Button', () => { describe('Button', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -85,5 +85,3 @@ export function Button({
</ButtonTag> </ButtonTag>
); );
} }
export default Button;

View File

@ -0,0 +1 @@
export * from './button';

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Icon from './icon'; import { Icon } from './icon';
describe('Icon', () => { describe('Icon', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -1,6 +1,8 @@
import { IconSvgPaths20, IconSvgPaths16, IconName } from '@blueprintjs/icons'; import { IconSvgPaths20, IconSvgPaths16, IconName } from '@blueprintjs/icons';
import classNames from 'classnames'; import classNames from 'classnames';
export type { IconName } from '@blueprintjs/icons';
interface IconProps { interface IconProps {
hasError?: boolean; hasError?: boolean;
disabled?: boolean; disabled?: boolean;
@ -9,11 +11,13 @@ interface IconProps {
size?: 16 | 20 | 24 | 32 | 48 | 64; size?: 16 | 20 | 24 | 32 | 48 | 64;
} }
export const Icon = ({ size = 20, name, className }: IconProps) => { export const Icon = ({ size = 16, name, className }: IconProps) => {
const effectiveClassName = classNames( const effectiveClassName = classNames(
{ {
'w-20': size === 20, 'w-20': size === 20,
'h-20': size === 20, 'h-20': size === 20,
'w-16': size === 16,
'h-16': size === 16,
}, },
className className
); );
@ -21,12 +25,10 @@ export const Icon = ({ size = 20, name, className }: IconProps) => {
return ( return (
<svg className={effectiveClassName} viewBox={viewbox}> <svg className={effectiveClassName} viewBox={viewbox}>
<g> <g>
{(size <= 16 ? IconSvgPaths16 : IconSvgPaths20)[name].map((d) => ( {(size <= 16 ? IconSvgPaths16 : IconSvgPaths20)[name].map((d, key) => (
<path fill-rule="evenodd" clip-rule="evenodd" d={d} /> <path fillRule="evenodd" clipRule="evenodd" d={d} key={key} />
))} ))}
</g> </g>
</svg> </svg>
); );
}; };
export default Icon;

View File

@ -0,0 +1 @@
export * from './icon';

View File

@ -0,0 +1 @@
export * from './input';

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Input from './input'; import { Input } from './input';
describe('Input', () => { describe('Input', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -1,6 +1,5 @@
import { Story, Meta } from '@storybook/react'; import { Story, Meta } from '@storybook/react';
import { Input } from './input'; import { Input } from './input';
export default { export default {
component: Input, component: Input,
title: 'Input', title: 'Input',
@ -20,3 +19,11 @@ export const Disabled = Template.bind({});
Disabled.args = { Disabled.args = {
disabled: true, disabled: true,
}; };
export const IconPrepend: Story = () => (
<Input value="I type words" prependIconName="search" />
);
export const IconAppend: Story = () => (
<Input value="I type words and even more words" appendIconName="search" />
);

View File

@ -1,10 +1,13 @@
import { InputHTMLAttributes, forwardRef } from 'react'; import { InputHTMLAttributes, forwardRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Icon, IconName } from '../icon';
interface InputProps extends InputHTMLAttributes<HTMLInputElement> { interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
hasError?: boolean; hasError?: boolean;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
prependIconName?: IconName;
appendIconName?: IconName;
} }
export const inputClassNames = ({ export const inputClassNames = ({
hasError, hasError,
@ -21,8 +24,6 @@ export const inputClassNames = ({
'items-center', 'items-center',
'box-border', 'box-border',
'h-28', 'h-28',
'pl-8',
'pr-8',
'border', 'border',
'border-light-gray-50', 'border-light-gray-50',
'bg-neutral-753', 'bg-neutral-753',
@ -32,6 +33,8 @@ export const inputClassNames = ({
'focus-visible:outline-0', 'focus-visible:outline-0',
], ],
{ {
'pl-8': !className?.match(/(^| )p(l|x)-\d+( |$)/),
'pr-8': !className?.match(/(^| )p(r|x)-\d+( |$)/),
'border-vega-pink': hasError, 'border-vega-pink': hasError,
'text-disabled': disabled, 'text-disabled': disabled,
'bg-transparent': disabled, 'bg-transparent': disabled,
@ -54,13 +57,41 @@ export const inputStyle = ({
} }
: style; : style;
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => ( export const Input = forwardRef<HTMLInputElement, InputProps>(
<input ({ prependIconName, appendIconName, className, ...props }, ref) => {
{...props} className = `${className} h-28`;
ref={ref} if (prependIconName) {
className={classNames(inputClassNames(props), 'h-28')} className += ' pl-28';
style={inputStyle(props)} }
/> if (appendIconName) {
)); className += ' pr-28';
}
export default Input; const input = (
<input
{...props}
ref={ref}
className={classNames(inputClassNames({ className, ...props }))}
style={inputStyle(props)}
/>
);
const iconName = prependIconName || appendIconName;
if (iconName !== undefined) {
const iconClassName = classNames(
['fill-light-gray-50', 'absolute', 'z-10'],
{
'left-8': prependIconName,
'right-8': appendIconName,
}
);
const icon = <Icon name={iconName} className={iconClassName} size={16} />;
return (
<div className="inline-flex items-center relative">
{prependIconName && icon}
{input}
{appendIconName && icon}
</div>
);
}
return input;
}
);

View File

@ -0,0 +1 @@
export * from './inputError';

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import InputError from './inputError'; import { InputError } from './inputError';
describe('InputError', () => { describe('InputError', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from '../icon/icon'; import { Icon } from '../icon';
interface InputErrorProps { interface InputErrorProps {
children?: React.ReactNode; children?: React.ReactNode;
@ -39,5 +39,3 @@ export const InputError = ({
</div> </div>
); );
}; };
export default InputError;

View File

@ -0,0 +1 @@
export * from './select';

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import Select from './select'; import { Select } from './select';
describe('Select', () => { describe('Select', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -32,5 +32,3 @@ export function Select({
</select> </select>
); );
} }
export default Select;

View File

@ -0,0 +1 @@
export * from './textArea';

View File

@ -1,6 +1,6 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import TextArea from './textArea'; import { TextArea } from './textArea';
describe('TextArea', () => { describe('TextArea', () => {
it('should render successfully', () => { it('should render successfully', () => {

View File

@ -28,5 +28,3 @@ export function TextArea({
</textarea> </textarea>
); );
} }
export default TextArea;

View File

@ -1,7 +1,11 @@
import * as EthereumUtils from './utils/web3'; import * as EthereumUtils from './utils/web3';
export { Button } from './components/button';
export { Callout } from './components/callout'; export { Callout } from './components/callout';
export { Button } from './components/button/button';
export { Input } from './components/input/input';
export { EtherscanLink } from './components/etherscan-link';
export { EthereumUtils }; export { EthereumUtils };
export { EtherscanLink } from './components/etherscan-link';
export { Icon } from './components/icon';
export { Input } from './components/input';
export { InputError } from './components/inputError';
export { Select } from './components/select';
export { TextArea } from './components/textArea';