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

Add sorting bar on table views #47

Merged
merged 1 commit into from
Apr 19, 2023
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
4 changes: 3 additions & 1 deletion front/src/components/table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ type OwnProps = {
};

const StyledTable = styled.table`
min-width: 100%;
min-width: calc(100% - ${(props) => props.theme.spacing(4)});
border-radius: 4px;
border-spacing: 0;
border-collapse: collapse;
margin-left: ${(props) => props.theme.spacing(2)};
margin-right: ${(props) => props.theme.spacing(2)};

th {
border-collapse: collapse;
Expand Down
19 changes: 14 additions & 5 deletions front/src/components/table/table-header/DropdownButton.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import styled from '@emotion/styled';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useState, useRef } from 'react';
import { useOutsideAlerter } from '../../../hooks/useOutsideAlerter';
import { modalBackground } from '../../../layout/styles/themes';
import { SortType } from './SortAndFilterBar';

type OwnProps = {
label: string;
options: Array<{ label: string; icon: IconProp }>;
options: Array<SortType>;
onSortSelect?: (id: string) => void;
};

const StyledDropdownButtonContainer = styled.div`
Expand Down Expand Up @@ -68,7 +69,7 @@ const StyledIcon = styled.div`
margin-right: ${(props) => props.theme.spacing(1)};
`;

function DropdownButton({ label, options }: OwnProps) {
function DropdownButton({ label, options, onSortSelect }: OwnProps) {
const [isUnfolded, setIsUnfolded] = useState(false);

const onButtonClick = () => {
Expand All @@ -90,9 +91,17 @@ function DropdownButton({ label, options }: OwnProps) {
{isUnfolded && options.length > 0 && (
<StyledDropdown ref={dropdownRef}>
{options.map((option, index) => (
<StyledDropdownItem key={index}>
<StyledDropdownItem
key={index}
onClick={() => {
setIsUnfolded(false);
if (onSortSelect) {
onSortSelect(option.id);
}
}}
>
<StyledIcon>
<FontAwesomeIcon icon={option.icon} />
{option.icon && <FontAwesomeIcon icon={option.icon} />}
</StyledIcon>
{option.label}
</StyledDropdownItem>
Expand Down
45 changes: 45 additions & 0 deletions front/src/components/table/table-header/SortAndFilterBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import styled from '@emotion/styled';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import SortOrFilterChip from './SortOrFilterChip';
import { faArrowDown, faArrowUp } from '@fortawesome/pro-regular-svg-icons';

type OwnProps = {
sorts: Array<SortType>;
onRemoveSort: (sortId: string) => void;
};

export type SortType = {
label: string;
order: string;
id: string;
icon?: IconProp;
};

const StyledBar = styled.div`
display: flex;
flex-direction: row;
border-top: 1px solid ${(props) => props.theme.primaryBorder};
align-items: center;
justify-content: space-between;
height: 40px;
`;

function SortAndFilterBar({ sorts, onRemoveSort }: OwnProps) {
return (
<StyledBar>
{sorts.map((sort) => {
return (
<SortOrFilterChip
key={sort.id}
label={sort.label}
id={sort.id}
icon={sort.order === 'asc' ? faArrowDown : faArrowUp}
onRemove={() => onRemoveSort(sort.id)}
/>
);
})}
</StyledBar>
);
}

export default SortAndFilterBar;
48 changes: 48 additions & 0 deletions front/src/components/table/table-header/SortOrFilterChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import styled from '@emotion/styled';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { faTimes } from '@fortawesome/pro-regular-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

type OwnProps = {
id: string;
label: string;
icon: IconProp;
onRemove: () => void;
};

const StyledChip = styled.div`
border-radius: 50px;
display: flex;
flex-direction: row;
background-color: ${(props) => props.theme.blueHighTransparency};
border: 1px solid ${(props) => props.theme.blueLowTransparency};
color: ${(props) => props.theme.blue};
padding: ${(props) => props.theme.spacing(1)}
${(props) => props.theme.spacing(2)};
margin-left: ${(props) => props.theme.spacing(2)};
fontsize: ${(props) => props.theme.fontSizeSmall};
`;
const StyledIcon = styled.div`
margin-right: ${(props) => props.theme.spacing(1)};
`;

const StyledDelete = styled.div`
margin-left: ${(props) => props.theme.spacing(2)};
cursor: pointer;
`;

function SortOrFilterChip({ id, label, icon, onRemove }: OwnProps) {
return (
<StyledChip>
<StyledIcon>
<FontAwesomeIcon icon={icon} />
</StyledIcon>
{label}
<StyledDelete onClick={onRemove} data-testid={'remove-icon-' + id}>
<FontAwesomeIcon icon={faTimes} />
</StyledDelete>
</StyledChip>
);
}

export default SortOrFilterChip;
74 changes: 56 additions & 18 deletions front/src/components/table/table-header/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import DropdownButton from './DropdownButton';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { faCalendar } from '@fortawesome/pro-regular-svg-icons';
import SortAndFilterBar, { SortType } from './SortAndFilterBar';
import { useState } from 'react';

type OwnProps = {
viewName: string;
viewIcon?: IconProp;
};

const StyledTitle = styled.div`
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
`;

const StyledTableHeader = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 40px;
color: ${(props) => props.theme.text60};
font-weight: 500;
padding-left: ${(props) => props.theme.spacing(2)};
padding-left: ${(props) => props.theme.spacing(3)};
padding-right: ${(props) => props.theme.spacing(1)};
`;

const StyledIcon = styled.div`
Expand All @@ -36,23 +44,53 @@ const StyledFilters = styled.div`
`;

function TableHeader({ viewName, viewIcon }: OwnProps) {
const [sorts, setSorts] = useState([] as Array<SortType>);
const onSortItemSelect = (sortId: string) => {
setSorts([
{
label: 'Created at',
order: 'asc',
id: sortId,
},
]);
};

const onSortItemUnSelect = (sortId: string) => {
setSorts([]);
};

const sortsAvailable: Array<SortType> = [
{
id: 'created_at',
label: 'Created at',
order: 'asc',
icon: faCalendar,
},
];

return (
<StyledTitle>
<StyledViewSection>
<StyledIcon>
{viewIcon && <FontAwesomeIcon icon={viewIcon} size="lg" />}
</StyledIcon>
{viewName}
</StyledViewSection>
<StyledFilters>
<DropdownButton label="Filter" options={[]} />
<DropdownButton
label="Sort"
options={[{ label: 'Created at', icon: faCalendar }]}
/>
<DropdownButton label="Settings" options={[]} />
</StyledFilters>
</StyledTitle>
<StyledContainer>
<StyledTableHeader>
<StyledViewSection>
<StyledIcon>
{viewIcon && <FontAwesomeIcon icon={viewIcon} size="lg" />}
</StyledIcon>
{viewName}
</StyledViewSection>
<StyledFilters>
<DropdownButton label="Filter" options={[]} />
<DropdownButton
label="Sort"
options={sortsAvailable}
onSortSelect={onSortItemSelect}
/>
<DropdownButton label="Settings" options={[]} />
</StyledFilters>
</StyledTableHeader>
{sorts.length > 0 && (
<SortAndFilterBar sorts={sorts} onRemoveSort={onSortItemUnSelect} />
)}
</StyledContainer>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import SortAndFilterBar from '../SortAndFilterBar';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { faArrowDown } from '@fortawesome/pro-regular-svg-icons';

export default {
title: 'SortAndFilterBar',
component: SortAndFilterBar,
};

type OwnProps = {
removeFunction: () => void;
};

export const RegularSortAndFilterBar = ({ removeFunction }: OwnProps) => {
return (
<ThemeProvider theme={lightTheme}>
<SortAndFilterBar
sorts={[
{
label: 'Test sort',
order: 'asc',
id: 'test_sort',
icon: faArrowDown,
},
{
label: 'Test sort 2',
order: 'desc',
id: 'test_sort_2',
icon: faArrowDown,
},
]}
onRemoveSort={removeFunction}
/>
</ThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import SortOrFilterChip from '../SortOrFilterChip';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { faArrowDown } from '@fortawesome/pro-regular-svg-icons';

export default {
title: 'SortOrFilterChip',
component: SortOrFilterChip,
};

type OwnProps = {
removeFunction: () => void;
};

export const RegularSortOrFilterChip = ({ removeFunction }: OwnProps) => {
return (
<ThemeProvider theme={lightTheme}>
<SortOrFilterChip
id="test_sort"
icon={faArrowDown}
label="Test sort"
onRemove={removeFunction}
/>
</ThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fireEvent, render } from '@testing-library/react';

import { RegularSortAndFilterBar } from '../__stories__/SortAndFilterBar.stories';

const removeFunction = jest.fn();

it('Checks the SortAndFilterBar renders', async () => {
const { getByText, getByTestId } = render(
<RegularSortAndFilterBar removeFunction={removeFunction} />,
);
expect(getByText('Test sort')).toBeDefined();

const removeIcon = getByTestId('remove-icon-test_sort');
fireEvent.click(removeIcon);

expect(removeFunction).toHaveBeenCalled();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { fireEvent, render } from '@testing-library/react';

import { RegularSortOrFilterChip } from '../__stories__/SortOrFilterChip.stories';

const removeFunction = jest.fn();

it('Checks the RegularSortOrFilterChip renders', async () => {
const { getByText, getByTestId } = render(
<RegularSortOrFilterChip removeFunction={removeFunction} />,
);
expect(getByText('Test sort')).toBeDefined();

const removeIcon = getByTestId('remove-icon-test_sort');
fireEvent.click(removeIcon);

expect(removeFunction).toHaveBeenCalled();
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';

import { RegularTableHeader } from '../__stories__/TableHeader.stories';

it('Checks the TableHeader renders', () => {
it('Checks the TableHeader renders', async () => {
const { getByText } = render(<RegularTableHeader />);

expect(getByText('Test')).toBeDefined();
const sortDropdownButton = getByText('Sort');
fireEvent.click(sortDropdownButton);

const sortByCreatedAt = getByText('Created at');
fireEvent.click(sortByCreatedAt);

expect(getByText('Created at')).toBeDefined();
});
2 changes: 0 additions & 2 deletions front/src/hooks/useOutsideAlerter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ export function useOutsideAlerter(
) {
useEffect(() => {
function handleClickOutside(event: Event) {
console.log('test3');

const target = event.target as HTMLButtonElement;
if (ref.current && !ref.current.contains(target)) {
callback();
Expand Down
Loading