Uniformize folder structure (#693)

* Uniformize folder structure

* Fix icons

* Fix icons

* Fix tests

* Fix tests
This commit is contained in:
Charles Bochet
2023-07-16 14:29:28 -07:00
committed by GitHub
parent 900ec5572f
commit 6ced8434bd
462 changed files with 931 additions and 960 deletions

View File

@ -0,0 +1,39 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import { Checkbox } from '@/ui/input/components/Checkbox';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { contextMenuPositionState } from '../states/contextMenuPositionState';
const StyledContainer = styled.div`
align-items: center;
display: flex;
height: 32px;
justify-content: center;
`;
export function CheckboxCell() {
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const { currentRowSelected, setCurrentRowSelected } = useCurrentRowSelected();
function onChange(checked: boolean) {
handleCheckboxChange(checked);
}
function handleCheckboxChange(newCheckedValue: boolean) {
setCurrentRowSelected(newCheckedValue);
setContextMenuPosition({ x: null, y: null });
}
return (
<StyledContainer>
<Checkbox checked={currentRowSelected} onChange={onChange} />
</StyledContainer>
);
}

View File

@ -0,0 +1,30 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
type OwnProps = {
viewName: string;
viewIcon?: ReactNode;
};
const StyledTitle = styled.div`
align-items: center;
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: ${({ theme }) => theme.spacing(8)};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
const StyledIcon = styled.div`
display: flex;
margin-right: ${({ theme }) => theme.spacing(1)};
`;
export function ColumnHead({ viewName, viewIcon }: OwnProps) {
return (
<StyledTitle>
<StyledIcon>{viewIcon}</StyledIcon>
{viewName}
</StyledTitle>
);
}

View File

@ -0,0 +1,113 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { TableColumn } from '@/people/table/components/peopleColumns';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef';
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
import { TableHeader } from '../table-header/components/TableHeader';
import { EntityTableBody } from './EntityTableBody';
import { EntityTableHeader } from './EntityTableHeader';
const StyledTable = styled.table`
border-collapse: collapse;
border-radius: ${({ theme }) => theme.border.radius.sm};
border-spacing: 0;
margin-left: ${({ theme }) => theme.table.horizontalCellMargin};
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
table-layout: fixed;
width: calc(100% - ${({ theme }) => theme.table.horizontalCellMargin} * 2);
th {
border: 1px solid ${({ theme }) => theme.background.tertiary};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.tertiary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
}
}
td {
border: 1px solid ${({ theme }) => theme.background.tertiary};
border-collapse: collapse;
color: ${({ theme }) => theme.font.color.primary};
padding: 0;
text-align: left;
:last-child {
border-right-color: transparent;
}
:first-of-type {
border-left-color: transparent;
border-right-color: transparent;
}
:last-of-type {
min-width: 0;
width: 100%;
}
}
`;
const StyledTableWithHeader = styled.div`
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
`;
type OwnProps<SortField> = {
columns: Array<TableColumn>;
viewName: string;
viewIcon?: React.ReactNode;
availableSorts?: Array<SortType<SortField>>;
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
onRowSelectionChange?: (rowSelection: string[]) => void;
};
export function EntityTable<SortField>({
columns,
viewName,
viewIcon,
availableSorts,
onSortsUpdate,
}: OwnProps<SortField>) {
const tableBodyRef = React.useRef<HTMLDivElement>(null);
const leaveTableFocus = useLeaveTableFocus();
useListenClickOutsideArrayOfRef([tableBodyRef], () => {
leaveTableFocus();
});
return (
<StyledTableWithHeader>
<TableHeader
viewName={viewName}
viewIcon={viewIcon}
availableSorts={availableSorts}
onSortsUpdate={onSortsUpdate}
/>
<div ref={tableBodyRef}>
<StyledTable>
<EntityTableHeader columns={columns} />
<EntityTableBody columns={columns} />
</StyledTable>
</div>
</StyledTableWithHeader>
);
}

View File

@ -0,0 +1,33 @@
import { useRecoilValue } from 'recoil';
import { TableColumn } from '@/people/table/components/peopleColumns';
import { isNavbarSwitchingSizeState } from '@/ui/layout/states/isNavbarSwitchingSizeState';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { isFetchingEntityTableDataState } from '../states/isFetchingEntityTableDataState';
import { RowContext } from '../states/RowContext';
import { tableRowIdsState } from '../states/tableRowIdsState';
import { EntityTableRow } from './EntityTableRow';
export function EntityTableBody({ columns }: { columns: Array<TableColumn> }) {
const rowIds = useRecoilValue(tableRowIdsState);
const isNavbarSwitchingSize = useRecoilValue(isNavbarSwitchingSizeState);
const isFetchingEntityTableData = useRecoilValue(
isFetchingEntityTableDataState,
);
return (
<tbody>
{!isFetchingEntityTableData && !isNavbarSwitchingSize
? rowIds.map((rowId, index) => (
<RecoilScope SpecificContext={RowContext} key={rowId}>
<EntityTableRow columns={columns} rowId={rowId} index={index} />
</RecoilScope>
))
: null}
</tbody>
);
}

View File

@ -0,0 +1,58 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { useCurrentRowSelected } from '../hooks/useCurrentRowSelected';
import { CellContext } from '../states/CellContext';
import { contextMenuPositionState } from '../states/contextMenuPositionState';
import { currentColumnNumberScopedState } from '../states/currentColumnNumberScopedState';
export function EntityTableCell({
rowId,
cellIndex,
children,
size,
}: {
size: number;
rowId: string;
cellIndex: number;
children: React.ReactNode;
}) {
const [, setCurrentColumnNumber] = useRecoilScopedState(
currentColumnNumberScopedState,
CellContext,
);
useEffect(() => {
setCurrentColumnNumber(cellIndex);
}, [cellIndex, setCurrentColumnNumber]);
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
const { setCurrentRowSelected } = useCurrentRowSelected();
function handleContextMenu(event: React.MouseEvent) {
event.preventDefault();
setCurrentRowSelected(true);
setContextMenuPosition({
x: event.clientX,
y: event.clientY,
});
}
return (
<td
onContextMenu={(event) => handleContextMenu(event)}
style={{
width: size,
minWidth: size,
maxWidth: size,
}}
>
{children}
</td>
);
}

View File

@ -0,0 +1,39 @@
import { TableColumn } from '@/people/table/components/peopleColumns';
import { ColumnHead } from './ColumnHead';
import { SelectAllCheckbox } from './SelectAllCheckbox';
export function EntityTableHeader({
columns,
}: {
columns: Array<TableColumn>;
}) {
return (
<thead>
<tr>
<th
style={{
width: 30,
minWidth: 30,
maxWidth: 30,
}}
>
<SelectAllCheckbox />
</th>
{columns.map((column) => (
<th
key={column.id.toString()}
style={{
width: column.size,
minWidth: column.size,
maxWidth: column.size,
}}
>
<ColumnHead viewName={column.title} viewIcon={column.icon} />
</th>
))}
<th></th>
</tr>
</thead>
);
}

View File

@ -0,0 +1,81 @@
import { useEffect } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { TableColumn } from '@/people/table/components/peopleColumns';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { useRecoilScopedState } from '@/ui/recoil-scope/hooks/useRecoilScopedState';
import { CellContext } from '../states/CellContext';
import { currentRowEntityIdScopedState } from '../states/currentRowEntityIdScopedState';
import { currentRowNumberScopedState } from '../states/currentRowNumberScopedState';
import { isRowSelectedFamilyState } from '../states/isRowSelectedFamilyState';
import { RowContext } from '../states/RowContext';
import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCell';
const StyledRow = styled.tr<{ selected: boolean }>`
background: ${(props) =>
props.selected ? props.theme.background.secondary : 'none'};
`;
export function EntityTableRow({
columns,
rowId,
index,
}: {
columns: TableColumn[];
rowId: string;
index: number;
}) {
const [currentRowEntityId, setCurrentRowEntityId] = useRecoilScopedState(
currentRowEntityIdScopedState,
RowContext,
);
const isCurrentRowSelected = useRecoilValue(isRowSelectedFamilyState(rowId));
const [, setCurrentRowNumber] = useRecoilScopedState(
currentRowNumberScopedState,
RowContext,
);
useEffect(() => {
if (currentRowEntityId !== rowId) {
setCurrentRowEntityId(rowId);
}
}, [rowId, setCurrentRowEntityId, currentRowEntityId]);
useEffect(() => {
setCurrentRowNumber(index);
}, [index, setCurrentRowNumber]);
return (
<StyledRow
key={rowId}
data-testid={`row-id-${rowId}`}
selected={isCurrentRowSelected}
>
<td>
<CheckboxCell />
</td>
{columns.map((column, columnIndex) => {
return (
<RecoilScope SpecificContext={CellContext} key={column.id.toString()}>
<RecoilScope>
<EntityTableCell
rowId={rowId}
size={column.size}
cellIndex={columnIndex}
>
{column.cellComponent}
</EntityTableCell>
</RecoilScope>
</RecoilScope>
);
})}
<td></td>
</StyledRow>
);
}

View File

@ -0,0 +1,25 @@
import { FilterDefinition } from '@/ui/filter-n-sort/types/FilterDefinition';
import { useInitializeEntityTable } from '../hooks/useInitializeEntityTable';
import { useInitializeEntityTableFilters } from '../hooks/useInitializeEntityTableFilters';
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
export function HooksEntityTable({
numberOfColumns,
availableFilters,
}: {
numberOfColumns: number;
availableFilters: FilterDefinition[];
}) {
useMapKeyboardToSoftFocus();
useInitializeEntityTable({
numberOfColumns,
});
useInitializeEntityTableFilters({
availableFilters,
});
return <></>;
}

View File

@ -0,0 +1,35 @@
import React from 'react';
import styled from '@emotion/styled';
import { Checkbox } from '@/ui/input/components/Checkbox';
import { useSelectAllRows } from '../hooks/useSelectAllRows';
const StyledContainer = styled.div`
align-items: center;
display: flex;
height: 32px;
justify-content: center;
`;
export const SelectAllCheckbox = () => {
const { selectAllRows, allRowsSelectedStatus } = useSelectAllRows();
const checked = allRowsSelectedStatus === 'all';
const indeterminate = allRowsSelectedStatus === 'some';
function onChange(value: boolean) {
selectAllRows();
}
return (
<StyledContainer>
<Checkbox
checked={checked}
onChange={onChange}
indeterminate={indeterminate}
/>
</StyledContainer>
);
};