Skip to content

Commit

Permalink
fix: bring inputs to one view
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisVorop committed Feb 20, 2024
1 parent c105193 commit c95940f
Show file tree
Hide file tree
Showing 11 changed files with 73 additions and 172 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import React, { ChangeEvent, ComponentProps, ReactNode, useCallback } from 'react';
import styled from 'styled-components';
import { nullable } from '@taskany/bricks';
import { FormControl, FormControlError, FormControlInput } from '@taskany/bricks/harmony';
import { IconSearchOutline } from '@taskany/icons';
import { gapS } from '@taskany/colors';

import { tr } from './FilterAutoCompleteInput.i18n';

const StyledFormControl = styled(FormControl)`
margin-bottom: ${gapS};
`;

interface FilterAutoCompleteInputProps extends Omit<React.ComponentProps<typeof FormControlInput>, 'onChange'> {
onChange: (value: string) => void;
error?: ComponentProps<typeof FormControlError>['error'];
Expand All @@ -32,7 +26,7 @@ export const FilterAutoCompleteInput: React.FC<FilterAutoCompleteInputProps> = (
);

return (
<StyledFormControl>
<FormControl>
<FormControlInput
outline
autoFocus
Expand All @@ -44,6 +38,6 @@ export const FilterAutoCompleteInput: React.FC<FilterAutoCompleteInputProps> = (
{nullable(error?.message, (message) => (
<FormControlError error={{ message }} />
))}
</StyledFormControl>
</FormControl>
);
};
1 change: 0 additions & 1 deletion src/components/FiltersPanel/FiltersPanel.i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"Search": "Search",
"Priority": "Priority",
"State": "State",
"Owner": "Owner",
Expand Down
1 change: 0 additions & 1 deletion src/components/FiltersPanel/FiltersPanel.i18n/ru.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"Search": "Поиск",
"Priority": "Приоритет",
"State": "Статус",
"Owner": "Ответственный",
Expand Down
6 changes: 1 addition & 5 deletions src/components/FiltersPanel/FiltersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,7 @@ export const FiltersPanel: FC<{
<FiltersPanelContainer className={s.FiltersPanel} loading={loading} {...filtersPanel.attr}>
<FiltersPanelContent>
<FiltersSearchContainer>
<SearchFilter
placeholder={tr('Search')}
defaultValue={queryState?.query}
onChange={onSearchChange}
/>
<SearchFilter defaultValue={queryState?.query} onChange={onSearchChange} />
</FiltersSearchContainer>
<FiltersCounterContainer>
<FiltersCounter total={total} counter={counter} />
Expand Down
2 changes: 1 addition & 1 deletion src/components/GlobalSearch/GlobalSearch.i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Search or jump to...": "Search or jump to...",
"Search...": "",
"Goals": "",
"Projects": ""
}
2 changes: 1 addition & 1 deletion src/components/GlobalSearch/GlobalSearch.i18n/ru.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"Search or jump to...": "Найти и/или перейти...",
"Search...": "Поиск...",
"Goals": "Цели",
"Projects": "Проекты"
}
14 changes: 14 additions & 0 deletions src/components/GlobalSearch/GlobalSearch.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.GroupHeader {
display: flex;
align-items: center;
justify-content: space-between;

box-sizing: border-box;

padding-bottom: var(--gap-xs);
margin: 0 var(--gap-s);

max-width: 392px;

border-bottom: 1px solid var(--gray-900);
}
179 changes: 41 additions & 138 deletions src/components/GlobalSearch/GlobalSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,119 +1,19 @@
/* eslint-disable no-nested-ternary */
import React, { useState, useCallback } from 'react';
import styled from 'styled-components';
import NextLink from 'next/link';
import { Text, Table, nullable, ListView, ListViewItem, GlobalSearch as TaskanyGlobalSearch } from '@taskany/bricks';
import { Table, nullable, ListView, ListViewItem } from '@taskany/bricks';
import { GlobalSearch as TaskanyGlobalSearch, MenuItem, Link, Text } from '@taskany/bricks/harmony';
import { IconTargetOutline, IconUsersOutline } from '@taskany/icons';
import { gapS, gapXs, gray4, radiusM } from '@taskany/colors';

import { trpc } from '../../utils/trpcClient';
import { routes, useRouter } from '../../hooks/router';
import { GoalListItemCompact } from '../GoalListItemCompact';
import { ProjectListItemCompact } from '../ProjectListItemCompact';

import { tr } from './GlobalSearch.i18n';
import s from './GlobalSearch.module.css';

type ListViewItemValue = ['goal' | 'project', string];

// TODO: https://github.com/taskany-inc/issues/issues/1568
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledGoalListItemCompact = styled(({ hovered, ...props }) => <GoalListItemCompact {...props} />)<{
hovered?: boolean;
}>`
padding: ${gapXs} ${gapS};
border-radius: ${radiusM};
cursor: pointer;
&:hover {
background-color: transparent;
}
${({ hovered }) =>
hovered &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
${({ hovered, focused }) =>
hovered &&
focused &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
${({ focused }) =>
focused &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
`;

// TODO: https://github.com/taskany-inc/issues/issues/1568
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledProjectListItemCompact = styled(({ hovered, ...props }) => <ProjectListItemCompact {...props} />)<{
hovered?: boolean;
}>`
${({ hovered }) =>
hovered &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
${({ hovered, focused }) =>
hovered &&
focused &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
${({ focused }) =>
focused &&
`
&:hover {
background-color: ${gray4};
}
background-color: ${gray4};
`}
`;

const StyledGroupHeader = styled(Text)`
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding-top: ${gapS};
padding-bottom: ${gapXs};
margin: 0 ${gapS};
max-width: 392px;
border-bottom: 1px solid ${gray4};
`;

const tableWidth = 700;

export const GlobalSearch = () => {
Expand All @@ -136,18 +36,19 @@ export const GlobalSearch = () => {

return (
<TaskanyGlobalSearch
query={query}
setQuery={setQuery}
value={query}
onChange={setQuery}
searchResultExists={resultsExists}
placeholder={tr('Search or jump to...')}
placeholder={tr('Search...')}
outline
>
{resultsExists && (
<ListView onKeyboardClick={onKeyboardNavigate}>
{nullable(suggestions.data?.goals?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
<Text size="m" weight="bolder" className={s.GroupHeader}>
{tr('Goals')} <IconTargetOutline size="s" />
</StyledGroupHeader>
</Text>
<Table width={tableWidth}>
{suggestions.data?.goals.map((item) => {
const value: ListViewItemValue = ['goal', item._shortId];
Expand All @@ -158,27 +59,29 @@ export const GlobalSearch = () => {
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}
/>
<Link>
<MenuItem {...props} hovered={active}>
<GoalListItemCompact
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' } },
]}
/>
</MenuItem>
</Link>
</NextLink>
)}
/>
Expand All @@ -189,22 +92,22 @@ export const GlobalSearch = () => {
))}
{nullable(suggestions.data?.projects?.length, () => (
<>
<StyledGroupHeader size="m" weight="bolder">
<Text size="m" weight="bolder" className={s.GroupHeader}>
{tr('Projects')} <IconUsersOutline size="s" />
</StyledGroupHeader>
</Text>
<Table width={tableWidth}>
{suggestions.data?.projects?.map((item) => (
<ListViewItem
key={item.id}
value={['project', item.id]}
renderItem={({ active, ...props }) => (
<StyledProjectListItemCompact
id={item.id}
title={item.title}
owner={item.activity}
focused={active}
{...props}
/>
<MenuItem {...props} hovered={active}>
<ProjectListItemCompact
id={item.id}
title={item.title}
owner={item.activity}
/>
</MenuItem>
)}
/>
))}
Expand Down
4 changes: 4 additions & 0 deletions src/components/PageHeader/PageHeader.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
margin-left: var(--gap-m);
}

.PageHeaderSearch {
width: 400px;
}

.PageHeader .PageHeaderNav {
display: flex;
align-items: center;
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProjectListItemCompact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface ProjectListItemCompactProps {
focused?: boolean;
icon?: boolean;
className?: string;
interactive?: boolean;
onClick?: MouseEventHandler<HTMLDivElement>;
}

Expand All @@ -28,10 +29,10 @@ const StyledRow = styled(TableRow)`
`;

export const ProjectListItemCompact: React.FC<ProjectListItemCompactProps> = React.memo(
({ id, owner, title, icon, ...attrs }) => {
({ id, owner, title, icon, interactive, ...attrs }) => {
return (
<NextLink href={routes.project(id)} passHref legacyBehavior>
<StyledRow interactive align="center" gap={10} {...attrs}>
<StyledRow interactive={interactive} align="center" gap={10} {...attrs}>
{nullable(icon, () => (
<TableCell min>
<IconUsersOutline size="s" />
Expand Down
21 changes: 6 additions & 15 deletions src/components/SearchFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import { FC, useCallback } from 'react';
import { FC } from 'react';
import { debounce } from 'throttle-debounce';
import { FormControl, FormControlInput } from '@taskany/bricks/harmony';
import { FormControl } from '@taskany/bricks/harmony';

import { FilterAutoCompleteInput } from './FilterAutoCompleteInput/FilterAutoCompleteInput';

export const SearchFilter: FC<{
placeholder?: string;
defaultValue?: string;
onChange: (search: string) => void;
}> = ({ placeholder, defaultValue, onChange }) => {
}> = ({ defaultValue, onChange }) => {
const debouncedSearchHandler = debounce(200, onChange);

const onSearchInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => debouncedSearchHandler(e.currentTarget.value),
[debouncedSearchHandler],
);
return (
<FormControl>
<FormControlInput
outline
size="xs"
placeholder={placeholder}
defaultValue={defaultValue}
onChange={onSearchInputChange}
/>
<FilterAutoCompleteInput defaultValue={defaultValue} onChange={debouncedSearchHandler} />
</FormControl>
);
};

0 comments on commit c95940f

Please sign in to comment.