Skip to content

Commit

Permalink
Add dropdown on Sort button on table
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesBochet committed Apr 19, 2023
1 parent bb40c1b commit 1e635b9
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ cd infra/dev

```
make build
make up
```

Once this is completed you should have:
Expand Down
6 changes: 1 addition & 5 deletions front/src/components/form/__stories__/Checkbox.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { MemoryRouter } from 'react-router-dom';

import Checkbox from '../Checkbox';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../layout/styles/themes';
Expand All @@ -12,9 +10,7 @@ export default {
export const RegularCheckbox = () => {
return (
<ThemeProvider theme={lightTheme}>
<MemoryRouter initialEntries={['/companies']}>
<Checkbox name="selected-company-1" id="selected-company--1" />
</MemoryRouter>
<Checkbox name="selected-company-1" id="selected-company--1" />
</ThemeProvider>
);
};
2 changes: 1 addition & 1 deletion front/src/components/form/__tests__/Checkbox.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render } from '@testing-library/react';

import { RegularCheckbox } from '../__stories__/Checkbox.stories';

it('Checks the NavItem renders', () => {
it('Checks the Checkbox renders', () => {
const { getByTestId } = render(<RegularCheckbox />);

expect(getByTestId('input-checkbox')).toHaveAttribute(
Expand Down
2 changes: 1 addition & 1 deletion front/src/components/table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
getCoreRowModel,
useReactTable,
} from '@tanstack/react-table';
import TableHeader from './TableHeader';
import TableHeader from './table-header/TableHeader';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import styled from '@emotion/styled';

Expand Down
106 changes: 106 additions & 0 deletions front/src/components/table/table-header/DropdownButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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';

type OwnProps = {
label: string;
options: Array<{ label: string; icon: IconProp }>;
};

const StyledDropdownButtonContainer = styled.div`
display: flex;
flex-direction: column;
position: relative;
`;

type StyledDropdownButtonProps = {
isUnfolded: boolean;
};

const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
display: flex;
margin-left: ${(props) => props.theme.spacing(3)};
cursor: pointer;
background: ${(props) => props.theme.primaryBackground};
padding: ${(props) => props.theme.spacing(1)};
border-radius: 4px;
filter: ${(props) => (props.isUnfolded ? 'brightness(0.95)' : 'none')};
&:hover {
filter: brightness(0.95);
}
`;

const StyledDropdown = styled.ul`
display: flex;
position: absolute;
top: 14px;
right: 0;
border: 1px solid ${(props) => props.theme.primaryBorder};
box-shadow: 0px 3px 12px rgba(0, 0, 0, 0.09);
border-radius: 8px;
padding: 0px;
min-width: 160px;
${modalBackground}
`;

const StyledDropdownItem = styled.li`
display: flex;
padding: ${(props) => props.theme.spacing(2)}
calc(${(props) => props.theme.spacing(2)} - 2px);
margin: 2px;
background: ${(props) => props.theme.primaryBackground};
cursor: pointer;
width: 100%;
border-radius: 4px;
color: ${(props) => props.theme.text60};
&:hover {
filter: brightness(0.95);
}
`;

const StyledIcon = styled.div`
display: flex;
margin-right: ${(props) => props.theme.spacing(1)};
`;

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

const onButtonClick = () => {
setIsUnfolded(!isUnfolded);
};

const onOutsideClick = () => {
setIsUnfolded(false);
};

const dropdownRef = useRef(null);
useOutsideAlerter(dropdownRef, onOutsideClick);

return (
<StyledDropdownButtonContainer>
<StyledDropdownButton isUnfolded={isUnfolded} onClick={onButtonClick}>
{label}
</StyledDropdownButton>
{isUnfolded && options.length > 0 && (
<StyledDropdown ref={dropdownRef}>
{options.map((option, index) => (
<StyledDropdownItem key={index}>
<StyledIcon>
<FontAwesomeIcon icon={option.icon} />
</StyledIcon>
{option.label}
</StyledDropdownItem>
))}
</StyledDropdown>
)}
</StyledDropdownButtonContainer>
);
}

export default DropdownButton;
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import styled from '@emotion/styled';
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';

type OwnProps = {
viewName: string;
Expand Down Expand Up @@ -30,12 +32,7 @@ const StyledViewSection = styled.div`
const StyledFilters = styled.div`
display: flex;
font-weight: 400;
margin-right: ${(props) => props.theme.spacing(1)};
`;

const StyledFilterButton = styled.div`
display: flex;
margin-left: ${(props) => props.theme.spacing(4)};
margin-right: ${(props) => props.theme.spacing(2)};
`;

function TableHeader({ viewName, viewIcon }: OwnProps) {
Expand All @@ -48,9 +45,12 @@ function TableHeader({ viewName, viewIcon }: OwnProps) {
{viewName}
</StyledViewSection>
<StyledFilters>
<StyledFilterButton>Filter</StyledFilterButton>
<StyledFilterButton>Sort</StyledFilterButton>
<StyledFilterButton>Settings</StyledFilterButton>
<DropdownButton label="Filter" options={[]} />
<DropdownButton
label="Sort"
options={[{ label: 'Created at', icon: faCalendar }]}
/>
<DropdownButton label="Settings" options={[]} />
</StyledFilters>
</StyledTitle>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import TableHeader from '../TableHeader';
import { ThemeProvider } from '@emotion/react';
import { lightTheme } from '../../../../layout/styles/themes';
import { faBuilding } from '@fortawesome/pro-regular-svg-icons';

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

export const RegularTableHeader = () => {
return (
<ThemeProvider theme={lightTheme}>
<TableHeader viewName="Test" viewIcon={faBuilding} />
</ThemeProvider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render } from '@testing-library/react';

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

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

expect(getByText('Test')).toBeDefined();
});
33 changes: 33 additions & 0 deletions front/src/hooks/__tests__/useOutsideAlerter.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const onOutsideClick = jest.fn();
import { useRef } from 'react';
import TableHeader from '../../components/table/table-header/TableHeader';
import { render, fireEvent } from '@testing-library/react';
import { useOutsideAlerter } from '../useOutsideAlerter';
import { act } from 'react-dom/test-utils';

function TestComponent() {
const buttonRef = useRef(null);
useOutsideAlerter(buttonRef, onOutsideClick);

return (
<div>
<span>Outside</span>
<button ref={buttonRef}>Inside</button>
</div>
);
}

export default TableHeader;

test('clicking the button toggles an answer on/off', async () => {
const { getByText } = render(<TestComponent />);
const inside = getByText('Inside');
const outside = getByText('Outside');
await act(() => Promise.resolve());

fireEvent.mouseDown(inside);
expect(onOutsideClick).toHaveBeenCalledTimes(0);

fireEvent.mouseDown(outside);
expect(onOutsideClick).toHaveBeenCalledTimes(1);
});
23 changes: 23 additions & 0 deletions front/src/hooks/useOutsideAlerter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useEffect } from 'react';

declare type CallbackType = () => void;

export function useOutsideAlerter(
ref: React.RefObject<HTMLInputElement>,
callback: CallbackType,
) {
useEffect(() => {
function handleClickOutside(event: Event) {
console.log('test3');

const target = event.target as HTMLButtonElement;
if (ref.current && !ref.current.contains(target)) {
callback();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref]);
}
14 changes: 14 additions & 0 deletions front/src/layout/styles/themes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { css } from '@emotion/react';

const commonTheme = {
fontSizeSmall: '0.92rem',
fontSizeMedium: '1rem',
Expand All @@ -21,6 +23,8 @@ const lightThemeSpecific = {
purpleBackground: '#e0e0ff',
yellowBackground: '#fff2e7',

secondaryBackgroundSmallTransparency: 'rgba(252, 252, 252, 0.8)',

primaryBorder: 'rgba(0, 0, 0, 0.08)',

text100: '#000',
Expand Down Expand Up @@ -49,6 +53,10 @@ const darkThemeSpecific = {
purpleBackground: '#1111b7',
yellowBackground: '#cc660a',

secondaryBackgroundSmallTransparency: 'rgba(23, 23, 23, 0.8)',

primaryBorder: 'rgba(255, 255, 255, 0.08)',

text100: '#ffffff',
text80: '#ccc',
text60: '#999',
Expand All @@ -64,6 +72,12 @@ const darkThemeSpecific = {
yellow: '#fff2e7',
};

export const modalBackground = (props: any) =>
css`
backdrop-filter: blur(20px);
background: ${props.theme.secondaryBackgroundSmallTransparency};
`;

export const lightTheme = { ...commonTheme, ...lightThemeSpecific };
export const darkTheme = { ...commonTheme, ...darkThemeSpecific };

Expand Down

0 comments on commit 1e635b9

Please sign in to comment.