Skip to content

Commit

Permalink
Merge branch 'main' into feature/1364
Browse files Browse the repository at this point in the history
  • Loading branch information
VadymBezpalko authored Nov 28, 2024
2 parents 41a83cb + 151a4d8 commit 9c6cee3
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';

import { Placement, Strategy } from '@floating-ui/react';

import { InputProps } from '../Input';
import { IInputProps } from '../Input/types';
import { IPickerListItem } from '../Picker';

// selectedItemBody is unnecessary for AutoCompleteListItem, key should be === name
Expand All @@ -13,7 +13,7 @@ export type IAutoCompleteListItem = Omit<
customElement?: React.ReactElement;
};

export interface AutoCompleteProps extends Omit<InputProps, 'type'> {
export interface AutoCompleteProps extends Omit<IInputProps, 'type'> {
/** Options that will be displayed in the picker. If they are strings, they will be converted to `IPickerListItem[]`*/
options?: string[] | IAutoCompleteListItem[];
/** If true, disables filtering of the options. Useful for getting options from an external source.*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { Info } from '@livechat/design-system-icons';
import { Meta, StoryFn } from '@storybook/react';

import { Icon } from '../Icon';
import { Input, InputProps } from '../Input';
import { Input } from '../Input';
import { IInputProps } from '../Input/types';

import { FormField as FormFieldComponent, FormFieldProps } from './FormField';

Expand All @@ -26,7 +27,7 @@ export default {
} as Meta<typeof FormFieldComponent>;

const ExampleIcon = () => <Icon source={Info} />;
const ExampleInput = ({ ...args }: InputProps) => (
const ExampleInput = ({ ...args }: IInputProps) => (
<Input placeholder="Placeholder text" {...args} />
);
const LabelText = 'Email';
Expand Down
22 changes: 21 additions & 1 deletion packages/react-components/src/components/Input/Input.module.scss
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
.input {
$base-class: 'input';

.#{$base-class} {
box-sizing: border-box;
display: flex;
align-items: center;
transition:
border-color var(--transition-duration-fast-2) ease,
background-color var(--transition-duration-fast-2) ease;
outline: none;
border: 1px solid var(--border-basic-primary);
border-radius: var(--radius-3);
Expand Down Expand Up @@ -29,6 +34,21 @@
color: var(--content-basic-disabled);
}

&--promo {
border: 2px solid var(--input-promo-border-default);
padding: 0 var(--spacing-4);
height: 52px;

&:hover {
border-color: var(--input-promo-border-hover);
}

&.#{$base-class}--focused,
&.#{$base-class}--focused:hover {
box-shadow: var(--state-active-field);
}
}

&--focused,
&--focused:hover {
border-color: var(--action-primary-default);
Expand Down
5 changes: 3 additions & 2 deletions packages/react-components/src/components/Input/Input.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { render, userEvent, vi } from 'test-utils';

import { Icon } from '../Icon';

import { Input, InputProps } from './Input';
import { Input } from './Input';
import { IInputProps } from './types';

const renderComponent = (props: InputProps) =>
const renderComponent = (props: IInputProps) =>
render(<Input {...props} data-testid="input" />);

describe('<Input> component', () => {
Expand Down
81 changes: 79 additions & 2 deletions packages/react-components/src/components/Input/Input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { Meta, StoryFn } from '@storybook/react';
import { StoryDescriptor } from '../../stories/components/StoryDescriptor';
import { Icon } from '../Icon';

import { Input, InputProps } from './Input';
import { Input, InputPromo as InputPromoComponent } from './Input';
import { IInputProps } from './types';

const placeholderText = 'Placeholder text';

Expand All @@ -27,7 +28,7 @@ export default {
},
} as Meta<typeof Input>;

export const Default: StoryFn<InputProps> = (args: InputProps) => (
export const Default: StoryFn<IInputProps> = (args: IInputProps) => (
<Input {...args} />
);

Expand Down Expand Up @@ -121,3 +122,79 @@ export const WithIcons = (): React.ReactElement => (
</StoryDescriptor>
</>
);

export const InputPromo = (): React.ReactElement => (
<InputPromoComponent placeholder={placeholderText} />
);

export const InputPromoStates = (): React.ReactElement => (
<>
<StoryDescriptor title="With error">
<InputPromoComponent error={true} placeholder={placeholderText} />
</StoryDescriptor>
<StoryDescriptor title="Disabled">
<InputPromoComponent disabled={true} placeholder={placeholderText} />
</StoryDescriptor>
</>
);

export const InputPromoTypes = (): React.ReactElement => (
<>
<StoryDescriptor title="Text">
<InputPromoComponent type="text" placeholder={placeholderText} />
</StoryDescriptor>
<StoryDescriptor title="Password">
<InputPromoComponent type="password" placeholder={placeholderText} />
</StoryDescriptor>
</>
);

export const InputPromoWithIcons = (): React.ReactElement => (
<>
<StoryDescriptor title="Left icon">
<InputPromoComponent
icon={{
source: <Icon source={AddCircleIcon} />,
place: 'left',
}}
placeholder={placeholderText}
/>
</StoryDescriptor>
<StoryDescriptor title="Right icon">
<InputPromoComponent
icon={{
source: <Icon source={AddCircleIcon} />,
place: 'right',
}}
placeholder={placeholderText}
/>
</StoryDescriptor>
<StoryDescriptor title="Left icon with password type">
<InputPromoComponent
icon={{
source: <Icon source={AddCircleIcon} />,
place: 'left',
}}
placeholder={placeholderText}
type="password"
/>
</StoryDescriptor>
<StoryDescriptor title="Disabled input with icon">
<InputPromoComponent
icon={{
source: <Icon source={AddCircleIcon} />,
place: 'left',
}}
placeholder={placeholderText}
disabled
/>
</StoryDescriptor>
<StoryDescriptor title="Disabled input with password type">
<InputPromoComponent
placeholder={placeholderText}
type="password"
disabled
/>
</StoryDescriptor>
</>
);
84 changes: 48 additions & 36 deletions packages/react-components/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,18 @@ import { Button } from '../Button';
import { Icon } from '../Icon';
import { Text } from '../Typography';

import styles from './Input.module.scss';

interface InputIcon {
source: React.ReactElement;
place: 'left' | 'right';
}
import {
IInputComponentProps,
IInputIcon,
IInputPromoProps,
IInputProps,
} from './types';

export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
/**
* Specify the input size
*/
inputSize?: 'xsmall' | 'compact' | 'medium' | 'large';
/**
* Specify whether the input should be in error state
*/
error?: boolean;
/**
* Specify whether the input should be disabled
*/
disabled?: boolean;
/**
* Set the icon and its position
*/
icon?: InputIcon;
/**
* Set to enable ellipsis
*/
cropOnBlur?: boolean;
}
import styles from './Input.module.scss';

const baseClass = 'input';

const renderIcon = (icon: InputIcon, disabled?: boolean) =>
const renderIcon = (icon: IInputIcon, disabled?: boolean) =>
React.cloneElement(icon.source, {
['data-testid']: `input-icon-${icon.place}`,
className: cx(
Expand All @@ -55,14 +33,18 @@ const renderIcon = (icon: InputIcon, disabled?: boolean) =>
),
});

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
export const InputComponent = React.forwardRef<
HTMLInputElement,
IInputComponentProps
>(
(
{
inputSize = 'medium',
error = false,
disabled,
icon = null,
className,
mainClassName,
isPromo = false,
cropOnBlur = true,
...inputProps
},
Expand All @@ -75,16 +57,15 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
const [isPasswordVisible, setIsPasswordVisible] = React.useState(false);
const { type, onFocus, onBlur } = inputProps;
const mergedClassNames = cx(
className,
styles[baseClass],
styles[`${baseClass}--${inputSize}`],
mainClassName,
{
[styles[`${baseClass}--disabled`]]: disabled,
[styles[`${baseClass}--focused`]]: isFocused,
[styles[`${baseClass}--error`]]: error,
[styles[`${baseClass}--crop`]]: cropOnBlur,
[styles[`${baseClass}--read-only`]]: inputProps.readOnly,
}
},
className
);
const iconCustomColor = !disabled
? 'var(--content-default)'
Expand All @@ -101,6 +82,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
return (
<Text
as="div"
size={isPromo ? 'lg' : 'md'}
className={mergedClassNames}
aria-disabled={disabled}
tab-index="0"
Expand Down Expand Up @@ -136,3 +118,33 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
);
}
);

export const Input = React.forwardRef<HTMLInputElement, IInputProps>(
({ inputSize = 'medium', ...props }, ref) => {
const mainClassName = cx(
styles[baseClass],
styles[`${baseClass}--${inputSize}`]
);

return (
<InputComponent mainClassName={mainClassName} {...props} ref={ref} />
);
}
);

const promoBaseClass = `${baseClass}--promo`;

export const InputPromo = React.forwardRef<HTMLInputElement, IInputPromoProps>(
(props, ref) => {
const mainClassName = cx(styles[baseClass], styles[promoBaseClass]);

return (
<InputComponent
mainClassName={mainClassName}
isPromo
{...props}
ref={ref}
/>
);
}
);
46 changes: 46 additions & 0 deletions packages/react-components/src/components/Input/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';

export interface IInputIcon {
source: React.ReactElement;
place: 'left' | 'right';
}

export interface IInputGlobalProps
extends React.InputHTMLAttributes<HTMLInputElement> {
/**
* Specify whether the input should be in error state
*/
error?: boolean;
/**
* Specify whether the input should be disabled
*/
disabled?: boolean;
/**
* Set the icon and its position
*/
icon?: IInputIcon;
/**
* Set to enable ellipsis
*/
cropOnBlur?: boolean;
}

export interface IInputProps extends IInputGlobalProps {
/**
* Specify the input size
*/
inputSize?: 'xsmall' | 'compact' | 'medium' | 'large';
}

export interface IInputPromoProps extends IInputGlobalProps {}

export interface IInputComponentProps extends IInputGlobalProps {
/**
* CSS class name for the main input wrapper
*/
mainClassName: string;
/**
* Set to display promo input
*/
isPromo?: boolean;
}
4 changes: 0 additions & 4 deletions packages/react-components/src/foundations/color-scheme.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
:root,
.lc-legacy-theme {
--color-scheme: normal;
}

.lc-light-theme {
--color-scheme: normal;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/src/foundations/design-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ export const DesignToken = {
'--surface-check-list-item-open-background',
SurfaceCheckListBackground: '--surface-check-list-background',
ContentBasicPlaceholder: '--content-basic-placeholder',
InputPromoBorderDefault: '--input-promo-border-default',
InputPromoBorderHover: '--input-promo-border-hover',
};

export type DesignTokenKey = keyof typeof DesignToken;
Loading

0 comments on commit 9c6cee3

Please sign in to comment.