Add tasks page (#1015)

* Refactor top bar component

* Add task page with tabs

* Add tasks

* Add logic for task status

* Fix isoweek definition

* Enable click on task

* Deduplicate component

* Lint

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
This commit is contained in:
Emilien Chauvet
2023-07-31 16:14:35 -07:00
committed by GitHub
parent 700b567320
commit 22ca00bb67
22 changed files with 625 additions and 143 deletions

View File

@ -6,6 +6,7 @@ import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { TopBar } from '@/ui/top-bar/TopBar';
type OwnProps<SortField> = {
viewName: string;
@ -15,24 +16,6 @@ type OwnProps<SortField> = {
context: Context<string | null>;
};
const StyledContainer = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
flex-direction: column;
`;
const StyledBoardHeader = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: 40px;
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledIcon = styled.div`
display: flex;
margin-left: ${({ theme }) => theme.spacing(1)};
@ -43,16 +26,6 @@ const StyledIcon = styled.div`
}
`;
const StyledViewSection = styled.div`
display: flex;
`;
const StyledFilters = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: 2px;
`;
export function BoardHeader<SortField>({
viewName,
viewIcon,
@ -83,35 +56,37 @@ export function BoardHeader<SortField>({
);
return (
<StyledContainer>
<StyledBoardHeader>
<StyledViewSection>
<TopBar
leftComponent={
<>
<StyledIcon>{viewIcon}</StyledIcon>
{viewName}
</StyledViewSection>
<StyledFilters>
<FilterDropdownButton
context={context}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
</StyledFilters>
</StyledBoardHeader>
<SortAndFilterBar
context={context}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
innerSetSorts([]);
onSortsUpdate && onSortsUpdate([]);
}}
/>
</StyledContainer>
</>
}
rightComponents={[
<FilterDropdownButton
context={context}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>,
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>,
]}
bottomComponent={
<SortAndFilterBar
context={context}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
innerSetSorts([]);
onSortsUpdate && onSortsUpdate([]);
}}
/>
}
/>
);
}

View File

@ -9,6 +9,7 @@ export { IconUser } from '@tabler/icons-react';
export { IconList } from '@tabler/icons-react';
export { IconInbox } from '@tabler/icons-react';
export { IconSearch } from '@tabler/icons-react';
export { IconArchive } from '@tabler/icons-react';
export { IconSettings } from '@tabler/icons-react';
export { IconLogout } from '@tabler/icons-react';
export { IconColorSwatch } from '@tabler/icons-react';

View File

@ -3,6 +3,7 @@ import styled from '@emotion/styled';
type OwnProps = {
title: string;
icon?: React.ReactNode;
active?: boolean;
className?: string;
onClick?: () => void;
@ -28,10 +29,16 @@ const StyledTab = styled.div<{ active?: boolean }>`
}
`;
export function Tab({ title, active = false, onClick, className }: OwnProps) {
export function Tab({
title,
icon,
active = false,
onClick,
className,
}: OwnProps) {
return (
<StyledTab onClick={onClick} active={active} className={className}>
{title}
{icon} {title}
</StyledTab>
);
}

View File

@ -0,0 +1,47 @@
import * as React from 'react';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { activeTabIdScopedState } from '../states/activeTabIdScopedState';
import { Tab } from './Tab';
type SingleTabProps = {
title: string;
icon?: React.ReactNode;
id: string;
};
type OwnProps = {
tabs: SingleTabProps[];
context: React.Context<string | null>;
};
export function TabList({ tabs, context }: OwnProps) {
const initialActiveTabId = tabs[0].id;
const [activeTabId, setActiveTabId] = useRecoilScopedState(
activeTabIdScopedState,
context,
);
React.useEffect(() => {
setActiveTabId(initialActiveTabId);
}, [initialActiveTabId, setActiveTabId]);
return (
<>
{tabs.map((tab) => (
<Tab
key={tab.id}
title={tab.title}
icon={tab.icon}
active={tab.id === activeTabId}
onClick={() => {
setActiveTabId(tab.id);
}}
/>
))}
</>
);
}

View File

@ -1,4 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
import { IconCheckbox } from '@tabler/icons-react';
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
@ -22,7 +23,7 @@ export const Default: Story = {
};
export const Catalog: Story = {
args: { title: 'Tab title' },
args: { title: 'Tab title', icon: <IconCheckbox /> },
argTypes: {
active: { control: false },
onClick: { control: false },

View File

@ -0,0 +1,6 @@
import { atomFamily } from 'recoil';
export const activeTabIdScopedState = atomFamily<string | null, string>({
key: 'activeTabIdScopedState',
default: null,
});

View File

@ -6,6 +6,7 @@ import SortAndFilterBar from '@/ui/filter-n-sort/components/SortAndFilterBar';
import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownButton';
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { TopBar } from '@/ui/top-bar/TopBar';
import { TableContext } from '../../states/TableContext';
@ -16,23 +17,6 @@ type OwnProps<SortField> = {
onSortsUpdate?: (sorts: Array<SelectedSortType<SortField>>) => void;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
`;
const StyledTableHeader = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: 40px;
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledIcon = styled.div`
display: flex;
margin-left: ${({ theme }) => theme.spacing(1)};
@ -43,16 +27,6 @@ const StyledIcon = styled.div`
}
`;
const StyledViewSection = styled.div`
display: flex;
`;
const StyledFilters = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: 2px;
`;
export function TableHeader<SortField>({
viewName,
viewIcon,
@ -82,35 +56,37 @@ export function TableHeader<SortField>({
);
return (
<StyledContainer>
<StyledTableHeader>
<StyledViewSection>
<TopBar
leftComponent={
<>
<StyledIcon>{viewIcon}</StyledIcon>
{viewName}
</StyledViewSection>
<StyledFilters>
<FilterDropdownButton
context={TableContext}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
</StyledFilters>
</StyledTableHeader>
<SortAndFilterBar
context={TableContext}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
innerSetSorts([]);
onSortsUpdate && onSortsUpdate([]);
}}
/>
</StyledContainer>
</>
}
rightComponents={[
<FilterDropdownButton
context={TableContext}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>,
<SortDropdownButton<SortField>
isSortSelected={sorts.length > 0}
availableSorts={availableSorts || []}
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>,
]}
bottomComponent={
<SortAndFilterBar
context={TableContext}
sorts={sorts}
onRemoveSort={sortUnselect}
onCancelClick={() => {
innerSetSorts([]);
onSortsUpdate && onSortsUpdate([]);
}}
/>
}
/>
);
}

View File

@ -0,0 +1,51 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
type OwnProps = {
leftComponent?: ReactNode;
rightComponents?: ReactNode[];
bottomComponent?: ReactNode;
};
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
`;
const StyledTableHeader = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary};
display: flex;
flex-direction: row;
font-weight: ${({ theme }) => theme.font.weight.medium};
height: 40px;
justify-content: space-between;
padding-left: ${({ theme }) => theme.spacing(3)};
padding-right: ${({ theme }) => theme.spacing(2)};
`;
const StyledLeftSection = styled.div`
display: flex;
`;
const StyledRightSection = styled.div`
display: flex;
font-weight: ${({ theme }) => theme.font.weight.regular};
gap: 2px;
`;
export function TopBar({
leftComponent,
rightComponents,
bottomComponent,
}: OwnProps) {
return (
<StyledContainer>
<StyledTableHeader>
<StyledLeftSection>{leftComponent}</StyledLeftSection>
<StyledRightSection>{rightComponents}</StyledRightSection>
</StyledTableHeader>
{bottomComponent}
</StyledContainer>
);
}