Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(EditableInput): adds component #365

Merged
merged 4 commits into from
Sep 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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