Skip to content

Commit

Permalink
Merge pull request #365 from riyalohia/feat-EditableInput
Browse files Browse the repository at this point in the history
feat(EditableInput): adds component
  • Loading branch information
satyamyadav authored Sep 18, 2020
2 parents 3ece7d3 + fe0f604 commit e3e103a
Show file tree
Hide file tree
Showing 17 changed files with 1,108 additions and 8 deletions.
3 changes: 2 additions & 1 deletion core/components/atoms/input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
const trigger = <div className={rightIconClass}><Icon name={'info'} size={sizeMapping[size]} /></div>;

return (
<div className={classes}>
<div data-test="DesignSystem-InputWrapper" className={classes}>
{inlineLabel && (
<div className="Input-inlineLabel">
<Text appearance="subtle">{inlineLabel}</Text>
Expand All @@ -209,6 +209,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref)
</div>
)}
<input
data-test="DesignSystem-Input"
{...baseProps}
{...rest}
ref={ref}
Expand Down
204 changes: 204 additions & 0 deletions core/components/molecules/editableInput/EditableInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@

import * as React from 'react';
import classNames from 'classnames';
import Editable from '@/components/atoms/editable';
import { Input, Button, Popover, Icon, Text } from '@/index';
import { InputProps } from '@/index.type';
import { BaseProps, extractBaseProps } from '@/utils/types';

export interface EditableInputProps extends BaseProps {
/**
* Value of the `Editable Input`
*/
value: string;
/**
* String to show inside `Editable Input` when value is not defined
*/
placeholder: string;
/**
* Size of `Editable Input`
*/
size: 'tiny' | 'regular';
/**
* Determines if save action button is disabled
*/
disableSaveAction?: boolean;
/**
* Shows error state in case of failed validation
*/
error?: boolean;
/**
* Error message to be shown in case of failed validation
*/
errorMessage?: string;
/**
* Props to be used for `Input`
*/
inputOptions: Omit<InputProps, 'error' | 'value' | 'defaultValue' | 'size' | 'placeholder'>;
/**
* Callback function called on save action click
*/
onChange?: (value: string) => void;
}

export const EditableInput = (props: EditableInputProps) => {
const {
value,
error,
size,
errorMessage,
placeholder,
inputOptions,
disableSaveAction,
onChange,
className,
} = props;

const { onChange: onInputChange, ...rest } = inputOptions;

const [inputValue, setInputValue] = React.useState(value);
const [editing, setEditing] = React.useState(false);
const [showComponent, setShowComponent] = React.useState(false);

const inputRef = React.createRef<HTMLInputElement>();
const baseProps = extractBaseProps(props);

const EditableInputClass = classNames({
['EditableInput']: true,
}, className);

const EditableDefaultClass = classNames({
['EditableInput-default']: true,
[`EditableInput-default--${size}`]: size,
});

const InputClass = classNames({
['EditableInput-Input--tiny']: size === 'tiny'
});

const ActionClass = classNames({
['EditableInput-actions']: true,
[`EditableInput-actions--${size}`]: size
});

React.useEffect(() => {
setDefaultComponent();
}, [value]);

const setDefaultComponent = () => {
setInputValue(value);
setEditing(false);
setShowComponent(false);
};

const onSaveChanges = () => {
if (onChange) onChange(inputValue);
};

const onInputChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
if (onInputChange) onInputChange(e);
};

const onChangeHandler = (eventType: string) => {
switch (eventType) {
case 'edit':
inputRef.current?.focus();
setEditing(true);
case 'hover':
setShowComponent(true);
return;
case 'default':
setShowComponent(false);
}
};

const inputComponent = (
<Input
defaultValue={inputValue}
placeholder={placeholder}
className={InputClass}
autoFocus={editing}
size={size}
onChange={onInputChangeHandler}
error={error && editing}
ref={inputRef}
data-test="DesignSystem-EditableInput--Input"
{...rest}
/>
);

const renderChildren = () => {
if (showComponent) {
return error && errorMessage && editing ? (
<Popover
trigger={inputComponent}
position="right"
className="px-6 py-6 d-flex align-items-center"
on="hover"
>
<Icon name="error" appearance={'alert'} className="mr-4" />
<Text
data-test="DesignSystem-EditableInput--ErrorPopper"
appearance="destructive"
weight="medium"
>
{errorMessage}
</Text>
</Popover>
) : inputComponent;
}

return (
<div
className={EditableDefaultClass}
data-test="DesignSystem-EditableInput--Default"
>
{value || placeholder}
</div>
);
};

return (
<div
data-test="DesignSystem-EditableInput"
{...baseProps}
className={EditableInputClass}
>
<Editable
onChange={onChangeHandler}
editing={editing}
>
{renderChildren()}
</Editable>
{editing && (
<div className={ActionClass} data-test="DesignSystem-EditableInput--Actions">
<Button
icon="clear"
className="mr-3"
size="tiny"
onClick={setDefaultComponent}
data-test="DesignSystem-EditableInput--Discard"
/>
<Button
icon="check"
appearance="primary"
size="tiny"
disabled={disableSaveAction}
onClick={onSaveChanges}
data-test="DesignSystem-EditableInput--Save"
/>
</div>
)}
</div>
);
};

EditableInput.defaultProps = {
size: 'regular',
placeholder: '',
value: '',
inputOptions: {}
};

export default EditableInput;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from 'react';
import { select, text, boolean } from '@storybook/addon-knobs';
import { EditableInput } from '@/index';

// CSF format story
export const all = () => {
const placeholder = text('Placeholder', 'First Name');
const error = boolean('error', false);
const errorMessage = text('Error Message', 'Error Message Description');

const [value, setValue] = React.useState('');

const size = select(
'size',
['regular', 'tiny'],
'regular'
);

const onChange = (updatedValue: string) => {
setValue(updatedValue);
};

const options = {
placeholder,
errorMessage,
onChange,
error,
size,
value,
};

return (
<div style={{ width: 'var(--spacing-9)' }}>
<EditableInput
{...options}
/>
</div>
);
};

const customCode = `() => {
const [value, setValue] = React.useState('');
const onChange = (updatedValue) => {
setValue(updatedValue);
};
const options = {
placeholder: 'First Name',
onChange,
value,
};
return (
<div style={{ width: 'var(--spacing-9)', height: 'var(--spacing-3)' }}>
<EditableInput
{...options}
/>
</div>
);
}`;

export default {
title: 'Molecules|EditableInput',
component: EditableInput,
parameters: {
docs: {
docPage: {
customCode,
}
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import { EditableInput } from '@/index';

// CSF format story
export const error = () => {
const [value, setValue] = React.useState('');

const onChange = (updatedValue: string) => {
setValue(updatedValue);
};

return (
<div style={{ width: 'var(--spacing-9)' }}>
<EditableInput
placeholder="First Name"
value={value}
onChange={onChange}
error={true}
errorMessage={'Error Message'}
/>
</div>
);
};

const customCode = `() => {
const [value, setValue] = React.useState('');
const onChange = (value) => {
setValue(value);
}
return (
<div style={{ width: 'var(--spacing-9)' }}>
<EditableInput
placeholder="First Name"
value={value}
onChange={onChange}
error={true}
errorMessage={'Error Message'}
/>
</div>
);
}`;

export default {
title: 'Molecules|EditableInput/Variants',
component: EditableInput,
parameters: {
docs: {
docPage: {
customCode,
}
}
}
};
Loading

0 comments on commit e3e103a

Please sign in to comment.