Skip to content

Commit

Permalink
feat: goals list keyboard navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
asabotovich committed Feb 1, 2024
1 parent c2d6a81 commit b517417
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 154 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"@sentry/nextjs": "7.92.0",
"@tanstack/react-query": "4.29.5",
"@tanstack/react-query-devtools": "4.29.6",
"@taskany/bricks": "5.5.0",
"@taskany/bricks": "5.5.1",
"@taskany/colors": "1.9.0",
"@taskany/icons": "2.0.1",
"@tippyjs/react": "4.2.6",
Expand Down
56 changes: 33 additions & 23 deletions src/components/DashboardPage/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { MouseEventHandler, useCallback, useEffect, useMemo } from 'react';
import { nullable, TreeViewElement } from '@taskany/bricks';
import { ListView, nullable, TreeViewElement } from '@taskany/bricks';

import { refreshInterval } from '../../utils/config';
import { ExternalPageProps } from '../../utils/declareSsrProps';
import { useUrlFilterParams } from '../../hooks/useUrlFilterParams';
import { useFiltersPreset } from '../../hooks/useFiltersPreset';
import { GoalByIdReturnType } from '../../../trpc/inferredTypes';
import { Page } from '../Page';
import { CommonHeader } from '../CommonHeader';
import { trpc } from '../../utils/trpcClient';
Expand Down Expand Up @@ -119,6 +120,13 @@ export const DashboardPage = ({ user, ssrTime, defaultPresetFallback }: External
? currentPreset.description
: tr('This is your personal goals bundle');

const handleItemEnter = useCallback(
(goal: NonNullable<GoalByIdReturnType>) => {
setPreview(goal._shortId, goal);
},
[setPreview],
);

return (
<Page user={user} ssrTime={ssrTime} title={tr('title')}>
<CommonHeader title={title} description={description} />
Expand All @@ -130,28 +138,30 @@ export const DashboardPage = ({ user, ssrTime, defaultPresetFallback }: External
onFilterStar={onFilterStar}
isLoading={isLoading}
>
{groupsOnScreen?.map(({ project, goals }) => (
<ProjectListItemCollapsable
key={project.id}
interactive={false}
visible
project={project}
href={routes.project(project.id)}
goals={nullable(goals, (g) => (
<TreeViewElement>
<GoalTableList
goals={g}
selectedGoalResolver={selectedGoalResolver}
onGoalPreviewShow={onGoalPreviewShow}
/>
</TreeViewElement>
))}
>
{nullable(!goals.length, () => (
<InlineCreateGoalControl project={project} />
))}
</ProjectListItemCollapsable>
))}
<ListView onKeyboardClick={handleItemEnter}>
{groupsOnScreen?.map(({ project, goals }) => (
<ProjectListItemCollapsable
key={project.id}
interactive={false}
visible
project={project}
href={routes.project(project.id)}
goals={nullable(goals, (g) => (
<TreeViewElement>
<GoalTableList
goals={g}
selectedGoalResolver={selectedGoalResolver}
onGoalPreviewShow={onGoalPreviewShow}
/>
</TreeViewElement>
))}
>
{nullable(!goals.length, () => (
<InlineCreateGoalControl project={project} />
))}
</ProjectListItemCollapsable>
))}
</ListView>

{nullable(hasNextPage, () => (
<LoadMoreButton onClick={fetchNextPage as () => void} />
Expand Down
58 changes: 30 additions & 28 deletions src/components/FilterPopup/FilterPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,35 +104,37 @@ export const FilterPopup: React.FC<React.PropsWithChildren<FilterPopupProps>> =
return (
<>
{nullable(visible, () => (
<StyledPopupOverlay />
<>
<StyledPopupOverlay />
<StyledPopup
reference={filterRef}
interactive
placement="bottom-start"
visible={visible}
{...onESC}
offset={[-5, 0]}
>
<StyledPopupWrapper key={String(visible)} ref={popupWrapperRef}>
<StyledTabs active={activeTab} layout="vertical">
{children}
</StyledTabs>
<StyledFilterPanelPopupFooter>
<StyledTip
title={tr('Pro tip!')}
size="xs"
icon={<IconBulbOnOutline size="xs" color={textColor} />}
>
{tr('You can apply and save filters as preset.')}
</StyledTip>
<StyledActionWrapper>
<Button outline text={tr('Cancel')} onClick={() => switchVisible(false)} />
<Button view="primary" outline text={tr('Apply')} onClick={onApplyClick} />
</StyledActionWrapper>
</StyledFilterPanelPopupFooter>
</StyledPopupWrapper>
</StyledPopup>
</>
))}
<StyledPopup
reference={filterRef}
interactive
placement="bottom-start"
visible={visible}
{...onESC}
offset={[-5, 0]}
>
<StyledPopupWrapper key={String(visible)} ref={popupWrapperRef}>
<StyledTabs active={activeTab} layout="vertical">
{children}
</StyledTabs>
<StyledFilterPanelPopupFooter>
<StyledTip
title={tr('Pro tip!')}
size="xs"
icon={<IconBulbOnOutline size="xs" color={textColor} />}
>
{tr('You can apply and save filters as preset.')}
</StyledTip>
<StyledActionWrapper>
<Button outline text={tr('Cancel')} onClick={() => switchVisible(false)} />
<Button view="primary" outline text={tr('Apply')} onClick={onApplyClick} />
</StyledActionWrapper>
</StyledFilterPanelPopupFooter>
</StyledPopupWrapper>
</StyledPopup>
</>
);
};
14 changes: 11 additions & 3 deletions src/components/FlatGoalList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ComponentProps, MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
import { nullable } from '@taskany/bricks';
import { ListView, nullable } from '@taskany/bricks';

import { QueryState } from '../hooks/useUrlFilterParams';
import { trpc } from '../utils/trpcClient';
import { refreshInterval } from '../utils/config';
import { useFMPMetric } from '../utils/telemetry';
import { GoalByIdReturnType } from '../../trpc/inferredTypes';

import { GoalTableList } from './GoalTableList/GoalTableList';
import { useGoalPreview } from './GoalPreview/GoalPreviewProvider';
Expand Down Expand Up @@ -77,8 +78,15 @@ export const FlatGoalList: React.FC<GoalListProps> = ({ queryState, onTagClick }
[setPreview],
);

const handleItemEnter = useCallback(
(goal: NonNullable<GoalByIdReturnType>) => {
setPreview(goal._shortId, goal);
},
[setPreview],
);

return (
<>
<ListView onKeyboardClick={handleItemEnter}>
{nullable(goalsOnScreen, (goals) => (
<GoalTableList
goals={goals}
Expand All @@ -91,6 +99,6 @@ export const FlatGoalList: React.FC<GoalListProps> = ({ queryState, onTagClick }
{nullable(hasNextPage, () => (
<LoadMoreButton onClick={onFetchNextPage} />
))}
</>
</ListView>
);
};
134 changes: 68 additions & 66 deletions src/components/GlobalSearch/GlobalSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,84 +133,86 @@ export const GlobalSearch = () => {
[router],
);
const resultsExists = Boolean(suggestions.data?.goals?.length || suggestions.data?.projects.length);

return (
<TaskanyGlobalSearch
query={query}
setQuery={setQuery}
searchResultExists={resultsExists}
placeholder={tr('Search or jump to...')}
>
<ListView onKeyboardClick={onKeyboardNavigate}>
{nullable(suggestions.data?.goals?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
{tr('Goals')} <IconTargetOutline size="s" />
</StyledGroupHeader>
<Table width={tableWidth}>
{suggestions.data?.goals.map((item) => {
const value: ListViewItemValue = ['goal', item._shortId];

return (
{resultsExists && (
<ListView onKeyboardClick={onKeyboardNavigate}>
{nullable(suggestions.data?.goals?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
{tr('Goals')} <IconTargetOutline size="s" />
</StyledGroupHeader>
<Table width={tableWidth}>
{suggestions.data?.goals.map((item) => {
const value: ListViewItemValue = ['goal', item._shortId];

return (
<ListViewItem
key={item.id}
value={value}
renderItem={({ active, ...props }) => (
<NextLink passHref href={routes.goal(item._shortId)} legacyBehavior>
<StyledGoalListItemCompact
focused={active}
align="center"
gap={10}
item={item}
columns={[
{ name: 'title', columnProps: { col: 3 } },
{
name: 'state',
columnProps: { col: 1, justify: 'end' },
},
{
name: 'priority',
columnProps: { width: '12ch' },
},
{ name: 'projectId', columnProps: { col: 3 } },
{ name: 'issuers' },
{ name: 'estimate', columnProps: { width: '8ch' } },
]}
{...props}
/>
</NextLink>
)}
/>
);
})}
</Table>
</>
))}
{nullable(suggestions.data?.projects?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
{tr('Projects')} <IconUsersOutline size="s" />
</StyledGroupHeader>
<Table width={tableWidth}>
{suggestions.data?.projects?.map((item) => (
<ListViewItem
key={item.id}
value={value}
value={['project', item.id]}
renderItem={({ active, ...props }) => (
<NextLink passHref href={routes.goal(item._shortId)} legacyBehavior>
<StyledGoalListItemCompact
focused={active}
align="center"
gap={10}
item={item}
columns={[
{ name: 'title', columnProps: { col: 3 } },
{
name: 'state',
columnProps: { col: 1, justify: 'end' },
},
{
name: 'priority',
columnProps: { width: '12ch' },
},
{ name: 'projectId', columnProps: { col: 3 } },
{ name: 'issuers' },
{ name: 'estimate', columnProps: { width: '8ch' } },
]}
{...props}
/>
</NextLink>
<StyledProjectListItemCompact
id={item.id}
title={item.title}
owner={item.activity}
focused={active}
{...props}
/>
)}
/>
);
})}
</Table>
</>
))}
{nullable(suggestions.data?.projects?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
{tr('Projects')} <IconUsersOutline size="s" />
</StyledGroupHeader>
<Table width={tableWidth}>
{suggestions.data?.projects?.map((item) => (
<ListViewItem
key={item.id}
value={['project', item.id]}
renderItem={({ active, ...props }) => (
<StyledProjectListItemCompact
key={item.id}
id={item.id}
title={item.title}
owner={item.activity}
focused={active}
{...props}
/>
)}
/>
))}
</Table>
</>
))}
</ListView>
))}
</Table>
</>
))}
</ListView>
)}
</TaskanyGlobalSearch>
);
};
Loading

0 comments on commit b517417

Please sign in to comment.