Skip to content

Commit

Permalink
Enumeration component: Add, edit, remove and import
Browse files Browse the repository at this point in the history
  • Loading branch information
dlcaldeira committed Nov 17, 2023
1 parent 48748ec commit 018d449
Show file tree
Hide file tree
Showing 14 changed files with 609 additions and 211 deletions.
7 changes: 4 additions & 3 deletions packages/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@
"classnames": "^2.3.2",
"keycode": "^2.2.1",
"modern-css-reset": "^1.4.0",
"polished": "^4.2.2",
"react-transition-group": "^2.2.9",
"react-transition-group": "^2.9.0",
"react-use": "^17.4.0",
"react-virtualized": "^9.22.5",
"typeface-inconsolata": "^1.1.13",
Expand Down Expand Up @@ -76,12 +75,14 @@
"@talend/scripts-config-typescript": "^11.2.0",
"@talend/storybook-docs": "^2.2.0",
"@testing-library/cypress": "^9.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/classnames": "^2.3.1",
"@types/jest-axe": "^3.5.8",
"@types/react-is": "^18.2.4",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@types/react-is": "^18.2.4",
"@types/react-transition-group": "^2.9.2",
"@types/react-virtualized": "^9.21.26",
"browser-sync": "^2.29.3",
"browser-sync-webpack-plugin": "^2.3.0",
"concurrently": "^7.6.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,136 +1,103 @@
import { EnumerationItem, EnumerationProps, EnumerationMode } from './Enumeration.types';

import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { I18N_DOMAIN_DESIGN_SYSTEM } from '../constants';
import { ChangeEvent, useState } from 'react';
import { Form } from '../Form';
import { AutoSizer, InfiniteLoader, List } from 'react-virtualized';
import { ButtonIcon, ButtonIconToggle } from '../ButtonIcon';

import { Divider } from '../Divider';
import { StackHorizontal } from '../Stack';
import { EmptyState } from '../EmptyState';
import { StackHorizontal } from '../Stack';
import { I18N_DOMAIN_DESIGN_SYSTEM } from '../constants';
import { EnumerationMode, EnumerationProps } from './Enumeration.types';
import { EnumerationHeader } from './EnumerationHeader/EnumerationHeader.component';
import { EnumerationItem } from './EnumerationItem/EnumerationItem.component';

import style from './Enumeration.module.scss';
import styles from './Enumeration.module.scss';

export const Enumeration = ({ items, loadMoreRows, onImport, title }: EnumerationProps) => {
export const Enumeration = ({
id,
items,
loadMoreRows,
onCreate,
onChange,
onImport,
onRemove,
title,
}: EnumerationProps) => {
const { t } = useTranslation(I18N_DOMAIN_DESIGN_SYSTEM);
const [mode, setMode] = useState<string>();
const [filteredItems, setFilteredItems] = useState<EnumerationItem[]>(items);

const rowRenderer = ({ index }: { index: number }) => (
<div className={style['enumeration__body--item']}>
<p>{filteredItems[index].label}</p>
<ButtonIcon icon="dots-vertical" onClick={() => {}} size="S">
{t('ENUMERATION_IMPORT', 'Import items')}
</ButtonIcon>
</div>
);
const [mode, setMode] = useState(EnumerationMode.VIEW);
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const [filteredItems, setFilteredItems] = useState(items);

const ListEmptyState = () => (
<StackHorizontal gap={0} padding={{ x: 0, y: 'M' }}>
<EmptyState
description={t('ENUMERATION_EMPTY_LIST_DESCRIPTION', 'Any additional details here.')}
title={t('ENUMERATION_EMPTY_LIST_TITLE', 'The list is empty.')}
variant={'M'}
return (
<div className={styles.enumeration}>
<EnumerationHeader
filteredItems={filteredItems}
id={id}
items={items}
mode={mode}
onChange={onChange}
onCreate={onCreate}
onImport={onImport}
onRemove={onRemove}
selectedItems={selectedItems}
setFilteredItems={setFilteredItems}
setMode={setMode}
setSelectedItems={setSelectedItems}
title={title}
/>
</StackHorizontal>
);

const filterList = (event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
if (!value) {
setFilteredItems(items);
} else {
setFilteredItems(items.filter(item => item.id.includes(value)));
}
};
{filteredItems.length ? (
<div className={styles.enumeration__body}>
<AutoSizer disableHeight={true}>
{({ width }) => {
const itemHeight = 38;

return (
<div className={style.enumeration}>
<div className={style.enumeration__header}>
<div className={style.enumeration__title}>
<h4>{title}</h4>
return (
<InfiniteLoader
isRowLoaded={({ index }) => !!items[index]}
loadMoreRows={loadMoreRows}
rowCount={filteredItems.length}
>
{({ onRowsRendered, registerChild }) => (
<List
height={filteredItems.length * itemHeight}
onRowsRendered={onRowsRendered}
ref={registerChild}
rowCount={filteredItems.length}
rowHeight={itemHeight}
rowRenderer={({ index }) => (
<EnumerationItem
mode={mode}
onChange={value => {
const indexToReplace = items.indexOf(filteredItems[index]);

<StackHorizontal as="ul" gap={'XS'} justify="end">
{onImport && (
<li>
<ButtonIcon icon="import" onClick={() => {}} size="S">
{t('ENUMERATION_IMPORT', 'Import items')}
</ButtonIcon>
<Divider orientation="vertical" />
</li>
)}
<li>
<ButtonIconToggle
disabled={!!mode && mode !== EnumerationMode.CREATE}
icon="plus"
isActive={mode === EnumerationMode.CREATE}
onClick={() => {
setMode(
mode === EnumerationMode.CREATE ? EnumerationMode.VIEW : EnumerationMode.CREATE,
);
}}
size="S"
>
{t('ENUMERATION_ADD', 'Add item')}
</ButtonIconToggle>
</li>
<li>
<ButtonIconToggle
disabled={!!mode && mode === EnumerationMode.EDIT}
icon="pencil"
isActive={mode === EnumerationMode.EDIT}
onClick={() => {
setMode(
mode === EnumerationMode.EDIT ? EnumerationMode.VIEW : EnumerationMode.EDIT,
);
}}
size="S"
>
{t('ENUMERATION_EDIT', 'Edit item')}
</ButtonIconToggle>
</li>
</StackHorizontal>
if (indexToReplace !== -1) {
const newItems = [...items];
newItems[indexToReplace] = value;
onChange(newItems);
}
}}
onRemove={onRemove}
selectedItems={selectedItems}
setSelectedItems={setSelectedItems}
value={filteredItems[index]}
/>
)}
width={width}
/>
)}
</InfiniteLoader>
);
}}
</AutoSizer>
</div>

<Form.Search
placeholder="Search"
name="search"
label="Search"
onChange={filterList}
hideLabel
/>
</div>

{filteredItems.length ? (
<AutoSizer disableHeight={true}>
{({ width }) => {
const itemHeight = 36;

return (
<InfiniteLoader
isRowLoaded={({ index }) => !!items[index]}
loadMoreRows={loadMoreRows}
rowCount={filteredItems.length}
>
{({ onRowsRendered, registerChild }) => (
<List
height={filteredItems.length * itemHeight}
onRowsRendered={onRowsRendered}
ref={registerChild}
rowCount={filteredItems.length}
rowHeight={itemHeight}
rowRenderer={rowRenderer}
width={width}
/>
)}
</InfiniteLoader>
);
}}
</AutoSizer>
) : (
<ListEmptyState />
<StackHorizontal gap={0} padding={{ x: 0, y: 'M' }}>
<EmptyState
description={t('ENUMERATION_EMPTY_LIST_DESCRIPTION', 'Any additional details here.')}
title={t('ENUMERATION_EMPTY_LIST_TITLE', 'The list is empty.')}
variant={'M'}
/>
</StackHorizontal>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,17 @@
padding: 0;
}

&__header {
border-bottom: tokens.$coral-border-s-solid tokens.$coral-color-neutral-border;
padding: tokens.$coral-spacing-s;

h4 {
font: tokens.$coral-heading-m;
}
}

&__title {
align-items: center;
display: flex;
margin-bottom: tokens.$coral-spacing-s;

hr {
margin-left: tokens.$coral-spacing-xs;
}
}

&__title h4,
p {
h4,
p,
label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: tokens.$coral-spacing-xxs;
}

&__body {
&--item {
align-items: center;
background-color: tokens.$coral-color-neutral-background;
display: flex;
justify-content: space-between;
padding: tokens.$coral-spacing-xxs tokens.$coral-spacing-s;
transition: background-color tokens.$coral-transition-fast;

&:hover {
background-color: tokens.$coral-color-neutral-background-medium;
}
}
max-height: 400px;
overflow: hidden auto;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export interface EnumerationItem {
id: string;
label: string;
}

export interface EnumerationProps {
items: EnumerationItem[];
error?: string;
id: string;
items: string[];
loadMoreRows: (params: { startIndex: number; stopIndex: number }) => Promise<void>;
onChange: (items: string[]) => void;
onCreate?: (value: string) => Promise<unknown>;
onImport?: (...params: unknown[]) => void;
onRemove?: (entries: string[]) => Promise<unknown>;
title: string;
}

Expand Down
Loading

0 comments on commit 018d449

Please sign in to comment.