diff --git a/core/components/atoms/input/Input.tsx b/core/components/atoms/input/Input.tsx index 53b8d1a7ea..00618d1458 100644 --- a/core/components/atoms/input/Input.tsx +++ b/core/components/atoms/input/Input.tsx @@ -194,7 +194,7 @@ export const Input = React.forwardRef((props, ref) const trigger =
; return ( -
+
{inlineLabel && (
{inlineLabel} @@ -209,6 +209,7 @@ export const Input = React.forwardRef((props, ref)
)} ; + /** + * 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(); + 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) => { + 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 = ( + + ); + + const renderChildren = () => { + if (showComponent) { + return error && errorMessage && editing ? ( + + + + {errorMessage} + + + ) : inputComponent; + } + + return ( +
+ {value || placeholder} +
+ ); + }; + + return ( +
+ + {renderChildren()} + + {editing && ( +
+
+ )} +
+ ); +}; + +EditableInput.defaultProps = { + size: 'regular', + placeholder: '', + value: '', + inputOptions: {} +}; + +export default EditableInput; diff --git a/core/components/molecules/editableInput/__stories__/index.story.tsx b/core/components/molecules/editableInput/__stories__/index.story.tsx new file mode 100644 index 0000000000..d91f235b16 --- /dev/null +++ b/core/components/molecules/editableInput/__stories__/index.story.tsx @@ -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 ( +
+ +
+ ); +}; + +const customCode = `() => { + const [value, setValue] = React.useState(''); + + const onChange = (updatedValue) => { + setValue(updatedValue); + }; + + const options = { + placeholder: 'First Name', + onChange, + value, + }; + + return ( +
+ +
+ ); +}`; + +export default { + title: 'Molecules|EditableInput', + component: EditableInput, + parameters: { + docs: { + docPage: { + customCode, + } + } + } +}; diff --git a/core/components/molecules/editableInput/__stories__/variants/Error.story.tsx b/core/components/molecules/editableInput/__stories__/variants/Error.story.tsx new file mode 100644 index 0000000000..148486fc88 --- /dev/null +++ b/core/components/molecules/editableInput/__stories__/variants/Error.story.tsx @@ -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 ( +
+ +
+ ); +}; + +const customCode = `() => { + const [value, setValue] = React.useState(''); + + const onChange = (value) => { + setValue(value); + } + + return ( +
+ +
+ ); +}`; + +export default { + title: 'Molecules|EditableInput/Variants', + component: EditableInput, + parameters: { + docs: { + docPage: { + customCode, + } + } + } +}; diff --git a/core/components/molecules/editableInput/__stories__/variants/Size.story.tsx b/core/components/molecules/editableInput/__stories__/variants/Size.story.tsx new file mode 100644 index 0000000000..a28d6392a6 --- /dev/null +++ b/core/components/molecules/editableInput/__stories__/variants/Size.story.tsx @@ -0,0 +1,87 @@ +import * as React from 'react'; +import { EditableInput, Label } from '@/index'; + +// CSF format story +export const size = () => { + const [name, setName] = React.useState(''); + const [weight, setWeight] = React.useState(''); + + const onChangeName = (value: string) => { + setName(value); + }; + + const onChangeWeight = (value: string) => { + setWeight(value); + }; + + return ( +
+
+ + +
+
+ + +
+
+ ); +}; + +const customCode = `() => { + const [name, setName] = React.useState(''); + const [weight, setWeight] = React.useState(''); + + const onChangeName = (value) => { + setName(value); + }; + + const onChangeWeight = (value) => { + setWeight(value); + } + + return ( +
+
+ + +
+
+ + +
+
+ ); +}`; + +export default { + title: 'Molecules|EditableInput/Variants', + component: EditableInput, + parameters: { + docs: { + docPage: { + customCode, + } + } + } +}; diff --git a/core/components/molecules/editableInput/__tests__/EditableInput.test.tsx b/core/components/molecules/editableInput/__tests__/EditableInput.test.tsx new file mode 100644 index 0000000000..90d7f8a344 --- /dev/null +++ b/core/components/molecules/editableInput/__tests__/EditableInput.test.tsx @@ -0,0 +1,219 @@ +import * as React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { EditableInput } from '@/index'; +import { EditableInputProps as Props } from '@/index.type'; +import { testHelper, filterUndefined, valueHelper, testMessageHelper } from '@/utils/testHelper'; + +const StringValue = 'String Value'; +const size = ['tiny', 'regular']; +const onChange = jest.fn(); +const onInputChange = jest.fn(); + +const editableWrapperTestId = 'DesignSystem-EditableWrapper'; +const inputTestId = 'DesignSystem-InputWrapper'; +const defaultCompTestId = 'DesignSystem-EditableInput--Default'; +const inputCompTestId = 'DesignSystem-EditableInput--Input'; + +describe('EditableInput component', () => { + const mapper = { + placeholer: valueHelper(StringValue, { required: true }), + onChange: valueHelper(onChange, { iterate: true }), + disableSaveAction: valueHelper(true, { required: true }), + size: valueHelper(size, { required: true, iterate: true }), + error: valueHelper(true, { required: true }), + errorMessage: valueHelper(StringValue, { required: true }), + }; + + const testFunc = (props: Record): void => { + const attr = filterUndefined(props) as Props; + + it(testMessageHelper(attr), () => { + const { asFragment } = render( + + ); + expect(asFragment()).toMatchSnapshot(); + }); + }; + + testHelper(mapper, testFunc); +}); + +describe('EditableInput component', () => { + + it('renders children', () => { + const { getByTestId } = render( + + ); + + expect(getByTestId('DesignSystem-EditableInput').textContent).toMatch(StringValue); + }); + + it('renders default div initially', () => { + const { getByTestId, queryByTestId } = render( + + ); + + expect(getByTestId(defaultCompTestId)).toBeInTheDocument(); + expect(queryByTestId(inputCompTestId)).not.toBeInTheDocument(); + expect(queryByTestId('DesignSystem-EditableInput--Actions')).not.toBeInTheDocument(); + }); + + it('renders input on hover', () => { + const { getByTestId, queryByTestId } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.mouseEnter(editableWrapper); + + expect(queryByTestId(defaultCompTestId)).not.toBeInTheDocument(); + expect(getByTestId(inputCompTestId)).toBeInTheDocument(); + expect(queryByTestId('DesignSystem-EditableInput--Actions')).not.toBeInTheDocument(); + expect(queryByTestId(inputCompTestId)).toHaveAttribute('placeholder', StringValue); + + }); + + it('renders default div on mouseLeave', () => { + const { getByTestId, queryByTestId } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + + fireEvent.mouseEnter(editableWrapper); + expect(queryByTestId(defaultCompTestId)).not.toBeInTheDocument(); + expect(getByTestId(inputCompTestId)).toBeInTheDocument(); + + fireEvent.mouseLeave(editableWrapper); + expect(getByTestId(defaultCompTestId)).toBeInTheDocument(); + expect(queryByTestId(inputCompTestId)).not.toBeInTheDocument(); + }); + + it('renders input and button on click', () => { + + const { getByTestId, queryByTestId } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.click(editableWrapper); + + expect(queryByTestId(defaultCompTestId)).not.toBeInTheDocument(); + expect(getByTestId(inputCompTestId)).toBeInTheDocument(); + expect(getByTestId('DesignSystem-EditableInput--Actions')).toBeInTheDocument(); + + const inputComp = queryByTestId(inputCompTestId); + expect(document.activeElement).toEqual(inputComp); + + }); + +}); + +describe('EditableInput component with prop: size', () => { + + it('renders default size: regular', () => { + const { getByTestId } = render( + + ); + + expect(getByTestId(defaultCompTestId)).toHaveClass('EditableInput-default--regular'); + }); + + it('renders size: tiny', () => { + const { getByTestId } = render( + + ); + + expect(getByTestId(defaultCompTestId)).toHaveClass('EditableInput-default--tiny'); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.mouseEnter(editableWrapper); + expect(getByTestId(inputTestId)).toHaveClass('EditableInput-Input--tiny'); + }); + +}); + +describe('EditableInput component with action buttons and props: value and inputOptions', () => { + + it('discard changes', () => { + const { getByTestId, queryByTestId } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.click(editableWrapper); + + const inputTrigger = getByTestId(inputCompTestId); + fireEvent.change(inputTrigger, { target: 'Design System' }); + + const discardButton = getByTestId('DesignSystem-EditableInput--Discard'); + fireEvent.click(discardButton); + + expect(queryByTestId(inputCompTestId)).not.toBeInTheDocument(); + expect(getByTestId(defaultCompTestId).textContent).toMatch(StringValue); + + fireEvent.click(editableWrapper); + expect(getByTestId(inputCompTestId)).toHaveValue(''); + }); + + it('save changes', () => { + const updatedValue = 'Design System'; + + const { getByTestId, queryByTestId, rerender } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.click(editableWrapper); + + const inputTrigger = getByTestId(inputCompTestId); + fireEvent.change(inputTrigger, { target: updatedValue }); + + const saveButton = getByTestId('DesignSystem-EditableInput--Save'); + fireEvent.click(saveButton); + expect(onChange).toHaveBeenCalled(); + + rerender(); + expect(queryByTestId(inputCompTestId)).not.toBeInTheDocument(); + expect(getByTestId(defaultCompTestId).textContent).toMatch(updatedValue); + + fireEvent.click(editableWrapper); + expect(getByTestId(inputCompTestId)).toHaveValue(updatedValue); + }); +}); + +describe('EditableInput component with prop: error and errorMessage', () => { + + it('renders error popover on click', () => { + const { getByTestId, queryByTestId } = render( + + ); + + const editableWrapper = getByTestId(editableWrapperTestId); + fireEvent.click(editableWrapper); + expect(queryByTestId('DesignSystem-InputWrapper')).toHaveClass('Input--error'); + + const inputWrapper = getByTestId(inputTestId); + fireEvent.mouseEnter(inputWrapper); + expect(getByTestId('DesignSystem-EditableInput--ErrorPopper')).toBeInTheDocument(); + }); + +}); + +describe('EditableInput Component with overwrite class', () => { + const className = 'DS-EditableInput'; + + it('overwrite EditableInput class', () => { + const { getByTestId } = render( + + ); + expect(getByTestId('DesignSystem-EditableInput')).toHaveClass(className); + }); + +}); diff --git a/core/components/molecules/editableInput/__tests__/__snapshots__/EditableInput.test.tsx.snap b/core/components/molecules/editableInput/__tests__/__snapshots__/EditableInput.test.tsx.snap new file mode 100644 index 0000000000..7e1619c0b2 --- /dev/null +++ b/core/components/molecules/editableInput/__tests__/__snapshots__/EditableInput.test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EditableInput component + placeholer: "String Value", disableSaveAction: true, size: "regular", error: true, errorMessage: "String Value" + 1`] = ` + +
+
+
+
+
+
+
+ +`; + +exports[`EditableInput component + placeholer: "String Value", disableSaveAction: true, size: "tiny", error: true, errorMessage: "String Value" + 1`] = ` + +
+
+
+
+
+
+
+ +`; diff --git a/core/components/molecules/editableInput/index.tsx b/core/components/molecules/editableInput/index.tsx new file mode 100644 index 0000000000..fa1eb64968 --- /dev/null +++ b/core/components/molecules/editableInput/index.tsx @@ -0,0 +1,2 @@ +export { default } from './EditableInput'; +export * from './EditableInput'; diff --git a/core/components/molecules/popover/Popover.tsx b/core/components/molecules/popover/Popover.tsx index 6692e8f3d8..4bbbdb739e 100644 --- a/core/components/molecules/popover/Popover.tsx +++ b/core/components/molecules/popover/Popover.tsx @@ -3,7 +3,15 @@ import classNames from 'classnames'; import { PopperWrapper } from '@/utils'; import { BaseProps } from '@/utils/types'; -export type Position = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end'; +export type Position = 'top' | +'top-start' | +'top-end' | +'bottom' | +'bottom-start' | +'bottom-end'| +'left' | +'right'; + export type ActionType = 'click' | 'hover'; export interface CustomStyle { diff --git a/core/components/molecules/popover/__stories__/index.story.tsx b/core/components/molecules/popover/__stories__/index.story.tsx index e1968f8d9f..77658de240 100644 --- a/core/components/molecules/popover/__stories__/index.story.tsx +++ b/core/components/molecules/popover/__stories__/index.story.tsx @@ -14,7 +14,7 @@ export const all = () => { const position = select( 'position', - ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end'], + ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'right'], 'bottom-start' ); const on = select( diff --git a/core/components/molecules/popover/__stories__/variants/Position.story.tsx b/core/components/molecules/popover/__stories__/variants/Position.story.tsx index f0e2254891..cafade6f74 100644 --- a/core/components/molecules/popover/__stories__/variants/Position.story.tsx +++ b/core/components/molecules/popover/__stories__/variants/Position.story.tsx @@ -5,7 +5,16 @@ import { action } from '@storybook/addon-actions'; // CSF format story export const position = () => { - const positions: Position[] = ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end']; + const positions: Position[] = [ + 'top', + 'top-start', + 'top-end', + 'bottom', + 'bottom-start', + 'bottom-end', + 'left', + 'right' + ]; const appendToBody = false; const hoverable = false; const closeOnBackdropClick = true; diff --git a/core/components/organisms/grid/__stories__/_common_/editableSchema.tsx b/core/components/organisms/grid/__stories__/_common_/editableSchema.tsx new file mode 100644 index 0000000000..e02b8b5236 --- /dev/null +++ b/core/components/organisms/grid/__stories__/_common_/editableSchema.tsx @@ -0,0 +1,129 @@ +import * as React from 'react'; +import { Schema } from '../../Grid'; +// @ts-ignore +import iconImg from './image.png'; +import { GridCellProps } from '../../GridCell'; +import { PlaceholderParagraph, EditableInput } from '@/index'; + +const schema: Schema = [ + { + name: 'name', + displayName: 'Name', + width: '40%', + resizable: true, + tooltip: true, + // pinned: 'left', + separator: true, + translate: a => ({ + title: `${a.firstName} ${a.lastName}`, + firstName: a.firstName, + lastName: a.lastName + }), + filters: [ + { label: 'A-G', value: 'a-g' }, + { label: 'H-R', value: 'h-r' }, + { label: 'S-Z', value: 's-z' }, + ], + // comparator: (a, b) => ( + // a.lastName.localeCompare(b.lastName) && a.firstName.localeCompare(b.firstName) + // ), + onFilterChange: (a, filters) => { + for (const filter of filters) { + switch (filter) { + case 'a-g': + if (a.firstName[0].toLowerCase() >= 'a' && a.firstName[0].toLowerCase() <= 'g') return true; + break; + case 'h-r': + if (a.firstName[0].toLowerCase() >= 'h' && a.firstName[0].toLowerCase() <= 'r') return true; + break; + case 's-z': + if (a.firstName[0].toLowerCase() >= 's' && a.firstName[0].toLowerCase() <= 'z') return true; + break; + } + } + return false; + }, + // cellType: 'AVATAR' + cellType: 'AVATAR_WITH_TEXT', + }, + { + name: 'email', + displayName: 'Email', + width: 250, + resizable: true, + sorting: false, + // separator: true, + // pinned: 'left', + // align: 'center', + // comparator: (a, b) => a.email.title.localeCompare(b.email.title), + cellType: 'WITH_META_LIST' + // image: iconImg, + }, + { + name: 'gender', + displayName: 'Gender', + width: 180, + resizable: true, + // separator: true, + comparator: (a, b) => a.gender.localeCompare(b.gender), + cellType: 'STATUS_HINT', + translate: a => ({ + title: a.gender, + statusAppearance: (a.gender === 'Female') ? 'alert' : 'success' + }), + filters: [ + { label: 'Male', value: 'male' }, + { label: 'Female', value: 'female' }, + ], + onFilterChange: (a, filters) => { + for (const filter of filters) { + if (a.gender.toLowerCase() === filter) return true; + } + return false; + }, + }, + { + name: 'icon', + displayName: 'Icon', + width: 100, + resizable: true, + align: 'center', + cellType: 'ICON', + translate: _ => ({ + icon: 'events' + }) + // separator: true, + // status: "success" + }, + { + name: 'customCell', + displayName: 'Custom Cell', + width: 200, + resizable: true, + // pinned: 'right', + // separator: true, + cellRenderer: (props: GridCellProps) => { + const { loading } = props; + + if (loading) return ; + + const [weight, setWeight] = React.useState(''); + + const onChangeWeight = (value: string) => { + setWeight(value); + }; + + return ( + + ); + } + // status: "success" + }, +]; + +export default schema; diff --git a/core/components/organisms/table/__stories__/variants/withEditableCell.story.tsx b/core/components/organisms/table/__stories__/variants/withEditableCell.story.tsx new file mode 100644 index 0000000000..c9e1f4d248 --- /dev/null +++ b/core/components/organisms/table/__stories__/variants/withEditableCell.story.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import loaderSchema from '@/components/organisms/grid/__stories__/_common_/loaderSchema'; +import data from '@/components/organisms/grid/__stories__/_common_/data'; +import editableSchema from '@/components/organisms/grid/__stories__/_common_/editableSchema'; +import { Card, Table } from '@/index'; +import { AsyncTable, SyncTable } from '../_common_/types'; +import { action } from '@storybook/addon-actions'; + +export const withEditableCell = () => { + return ( +
+ + action(`on-select:- rowIndex: ${rowIndex} selected: ${selected} selectedList: ${JSON.stringify(selectedList)} selectAll: ${selectAll}`)()} + headerOptions={{ + withSearch: true + }} + onSearch={(currData, searchTerm) => { + return currData.filter(d => + d.firstName.toLowerCase().match(searchTerm.toLowerCase()) + || d.lastName.toLowerCase().match(searchTerm.toLowerCase()) + ); + }} + withPagination={true} + pageSize={5} + onPageChange={newPage => action(`on-page-change:- ${newPage}`)()} + /> + + + ); +}; + +const customCode = ` +() => { + const data = ${JSON.stringify(data.slice(0, 10), null, 4)}; + + const schema = [ + { + name: 'name', + displayName: 'Name', + width: '40%', + resizable: true, + separator: true, + tooltip: true, + translate: a => ({ + title: \`\${a.firstName} \${a.lastName}\`, + firstName: a.firstName, + lastName: a.lastName + }), + filters: [ + { label: 'A-G', value: 'a-g' }, + { label: 'H-R', value: 'h-r' }, + { label: 'S-Z', value: 's-z' }, + ], + onFilterChange: (a, filters) => { + for (const filter of filters) { + switch (filter) { + case 'a-g': + if (a.firstName[0].toLowerCase() >= 'a' && a.firstName[0].toLowerCase() <= 'g') return true; + break; + case 'h-r': + if (a.firstName[0].toLowerCase() >= 'h' && a.firstName[0].toLowerCase() <= 'r') return true; + break; + case 's-z': + if (a.firstName[0].toLowerCase() >= 's' && a.firstName[0].toLowerCase() <= 'z') return true; + break; + } + } + return false; + }, + cellType: 'AVATAR_WITH_TEXT', + }, + { + name: 'email', + displayName: 'Email', + width: 350, + resizable: true, + sorting: false, + cellType: 'WITH_META_LIST' + }, + { + name: 'gender', + displayName: 'Gender', + width: 200, + resizable: true, + comparator: (a, b) => a.gender.localeCompare(b.gender), + cellType: 'STATUS_HINT', + translate: a => ({ + title: a.gender, + statusAppearance: (a.gender === 'Female') ? 'alert' : 'success' + }), + filters: [ + { label: 'Male', value: 'male' }, + { label: 'Female', value: 'female' }, + ], + onFilterChange: (a, filters) => { + for (const filter of filters) { + if (a.gender.toLowerCase() === filter) return true; + } + return false; + }, + }, + { + name: 'icon', + displayName: 'Icon', + width: 100, + resizable: true, + align: 'center', + cellType: 'ICON', + translate: _ => ({ + icon: 'events' + }) + }, + { + name: 'customCell', + displayName: 'Custom Cell', + width: 200, + resizable: true, + separator: true, + cellRenderer: (props) => { + const { loading } = props; + + if (loading) return ( + + ); + + const [weight, setWeight] = React.useState(''); + + const onChangeWeight = (value) => { + setWeight(value); + }; + + return ( + + ); + } + }, + ]; + + const loaderSchema = ${JSON.stringify(loaderSchema, null, 4)}; + + return ( +
+ +
{ + return currData.filter(d => + d.firstName.toLowerCase().match(searchTerm.toLowerCase()) + || d.lastName.toLowerCase().match(searchTerm.toLowerCase()) + ); + }} + withCheckbox={true} + onSelect={(rowIndex, selected, selectedList, selectAll) => console.log(\`on-select:- rowIndex: \${rowIndex} selected: \${selected} selectedList: \${JSON.stringify(selectedList)} selectAll: \${selectAll}\`)} + withPagination={true} + pageSize={5} + onPageChange={newPage => console.log(\`on-page-change:- \${newPage}\`)} + /> + + + ); +}; +`; + +export default { + title: 'Organisms|Table/Variants', + component: Table, + parameters: { + docs: { + docPage: { + customCode, + props: { + components: { AsyncTable, SyncTable }, + exclude: ['showHead'] + } + } + } + } +}; diff --git a/core/index.tsx b/core/index.tsx index 559b84e681..d19ac01064 100644 --- a/core/index.tsx +++ b/core/index.tsx @@ -47,6 +47,7 @@ export { ModalBody } from './components/molecules/modalBody'; export { ModalDescription } from './components/molecules/modalDescription'; export { Pagination } from './components/molecules/pagination'; export { Placeholder } from './components/molecules/placeholder'; +export { EditableInput } from './components/molecules/editableInput'; export { PlaceholderParagraph } from './components/atoms/placeholderParagraph'; export { ProgressRing } from './components/atoms/progressRing'; export { Popover } from './components/molecules/popover'; diff --git a/core/index.type.tsx b/core/index.type.tsx index 6503798160..b5110efe15 100644 --- a/core/index.type.tsx +++ b/core/index.type.tsx @@ -49,6 +49,7 @@ export { PaginationProps } from './components/molecules/pagination'; export { PlaceholderProps } from './components/molecules/placeholder'; export { PlaceholderParagraphProps } from './components/atoms/placeholderParagraph'; export { ProgressBarProps } from './components/atoms/progressBar'; +export { EditableInputProps } from './components/molecules/editableInput'; export { PopoverProps } from './components/molecules/popover'; export { ProgressRingProps } from './components/atoms/progressRing'; export { StepperProps } from './components/molecules/stepper'; diff --git a/css/src/components/editableInput.css b/css/src/components/editableInput.css new file mode 100644 index 0000000000..357cbda4e0 --- /dev/null +++ b/css/src/components/editableInput.css @@ -0,0 +1,47 @@ +.EditableInput { + position: relative; + display: inline-flex; + flex-direction: column; + width: 100%; +} + +.EditableInput-actions { + position: absolute; + display: flex; + justify-content: flex-end; + margin-top: var(--spacing-m); + width: 100%; +} + +.EditableInput-actions--regular { + top: var(--spacing-3); +} + +.EditableInput-actions--tiny { + top: var(--spacing-xl); +} + +.EditableInput-default { + border: var(--spacing-xs) solid transparent; + box-sizing: border-box; + white-space: nowrap; + padding-left: var(--spacing-l); + padding-right: var(--spacing-l); + display: flex; + align-items: center; +} + +.EditableInput-default--regular { + min-width: var(--spacing-9); + height: var(--spacing-3); +} + +.EditableInput-default--tiny { + min-width: var(--spacing-6); + height: var(--spacing-xl); +} + +.EditableInput-Input--tiny { + min-width: var(--spacing-6) !important; + width: 100%; +} \ No newline at end of file diff --git a/css/src/components/input.css b/css/src/components/input.css index bb40dc8127..6a2891f732 100644 --- a/css/src/components/input.css +++ b/css/src/components/input.css @@ -42,8 +42,15 @@ font-size: var(--font-size-m); } +.Input:hover { + background: var(--secondary-lightest); + border-color: var(--secondary-lightest); + cursor: pointer; +} + .Input:focus-within { - border: var(--spacing-xs) solid var(--primary); + background: var(--white); + border-color: var(--primary); } .Input:focus-within .Input-icon--left .Icon { @@ -51,7 +58,7 @@ } .Input--error:focus-within { - border: var(--spacing-xs) solid var(--alert); + border-color: var(--alert); box-shadow: var(--shadow-spread) var(--alert-shadow); } @@ -62,14 +69,17 @@ .Input--disabled { background: var(--secondary-lightest); border-color: var(--secondary-light); + pointer-events: none; } .Input--disabled .Input-icon--left .Icon{ color: var(--inverse-lightest); } -.Input--error { - border: var(--spacing-xs) solid var(--alert); +.Input--error, +.Input--error:hover { + background: var(--white); + border-color: var(--alert); } .Input-input {