mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-01 08:06:49 +03:00
First working version of new dropdown UI (#360)
* First working version of new dropdown UI * Removed consolelog * Fixed test storybook * Cleaned optional args
This commit is contained in:
parent
703f31632d
commit
ceaf482f62
@ -2,6 +2,6 @@ import { DocumentNode } from 'graphql';
|
||||
|
||||
export type SearchConfigType = {
|
||||
query: DocumentNode;
|
||||
template: (searchInput: string) => any;
|
||||
template: (searchInput: string, currentSelectedId?: string) => any;
|
||||
resultMapper: (data: any) => any;
|
||||
};
|
||||
|
@ -79,7 +79,13 @@ export type SearchResultsType<T> = {
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export const useSearch = <T>(): [
|
||||
type SearchArgs = {
|
||||
currentSelectedId?: string | null;
|
||||
};
|
||||
|
||||
export const useSearch = <T>(
|
||||
searchArgs?: SearchArgs,
|
||||
): [
|
||||
SearchResultsType<T>,
|
||||
React.Dispatch<React.SetStateAction<string>>,
|
||||
React.Dispatch<React.SetStateAction<SearchConfigType | null>>,
|
||||
@ -99,9 +105,12 @@ export const useSearch = <T>(): [
|
||||
return (
|
||||
searchConfig &&
|
||||
searchConfig.template &&
|
||||
searchConfig.template(searchInput)
|
||||
searchConfig.template(
|
||||
searchInput,
|
||||
searchArgs?.currentSelectedId ?? undefined,
|
||||
)
|
||||
);
|
||||
}, [searchConfig, searchInput]);
|
||||
}, [searchConfig, searchInput, searchArgs]);
|
||||
|
||||
const searchQueryResults = useQuery(searchConfig?.query || EMPTY_QUERY, {
|
||||
variables: {
|
||||
|
@ -57,6 +57,7 @@ export function DropdownMenuSelectableItem({
|
||||
onClick={onClick}
|
||||
selected={selected}
|
||||
hovered={hovered}
|
||||
data-testid="dropdown-menu-item"
|
||||
>
|
||||
<StyledLeftContainer>{children}</StyledLeftContainer>
|
||||
<StyledRightIcon>
|
||||
|
@ -11,6 +11,9 @@ import { SearchResultsType, useSearch } from '@/search/services/search';
|
||||
import { humanReadableDate } from '@/utils/utils';
|
||||
|
||||
import DatePicker from '../../form/DatePicker';
|
||||
import { DropdownMenuItemContainer } from '../../menu/DropdownMenuItemContainer';
|
||||
import { DropdownMenuSelectableItem } from '../../menu/DropdownMenuSelectableItem';
|
||||
import { DropdownMenuSeparator } from '../../menu/DropdownMenuSeparator';
|
||||
|
||||
import DropdownButton from './DropdownButton';
|
||||
|
||||
@ -29,6 +32,8 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
}: OwnProps<TData>) => {
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const [selectedEntityId, setSelectedEntityId] = useState<string | null>(null);
|
||||
|
||||
const [isOperandSelectionUnfolded, setIsOperandSelectionUnfolded] =
|
||||
useState(false);
|
||||
|
||||
@ -41,7 +46,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
>(undefined);
|
||||
|
||||
const [filterSearchResults, setSearchInput, setFilterSearch] =
|
||||
useSearch<TData>();
|
||||
useSearch<TData>({ currentSelectedId: selectedEntityId });
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setIsOperandSelectionUnfolded(false);
|
||||
@ -92,29 +97,50 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
);
|
||||
}
|
||||
|
||||
return filterSearchResults.results.map((result, index) => {
|
||||
return (
|
||||
<DropdownButton.StyledDropdownItem
|
||||
key={`fields-value-${index}`}
|
||||
onClick={() => {
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: result.value,
|
||||
displayValue: result.render(result.value),
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
setIsUnfolded(false);
|
||||
setSelectedFilter(undefined);
|
||||
}}
|
||||
>
|
||||
<DropdownButton.StyledDropdownItemClipped>
|
||||
{result.render(result.value)}
|
||||
</DropdownButton.StyledDropdownItemClipped>
|
||||
</DropdownButton.StyledDropdownItem>
|
||||
);
|
||||
});
|
||||
function resultIsEntity(result: any): result is { id: string } {
|
||||
return Object.keys(result ?? {}).includes('id');
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItemContainer>
|
||||
{filterSearchResults.results.map((result, index) => {
|
||||
return (
|
||||
<DropdownMenuSelectableItem
|
||||
key={`fields-value-${index}`}
|
||||
selected={
|
||||
resultIsEntity(result.value) &&
|
||||
result.value.id === selectedEntityId
|
||||
}
|
||||
onClick={() => {
|
||||
console.log({ result });
|
||||
|
||||
if (resultIsEntity(result.value)) {
|
||||
setSelectedEntityId(result.value.id);
|
||||
}
|
||||
|
||||
onFilterSelect({
|
||||
key: selectedFilter.key,
|
||||
label: selectedFilter.label,
|
||||
value: result.value,
|
||||
displayValue: result.render(result.value),
|
||||
icon: selectedFilter.icon,
|
||||
operand: selectedFilterOperand,
|
||||
});
|
||||
setIsUnfolded(false);
|
||||
setSelectedFilter(undefined);
|
||||
}}
|
||||
>
|
||||
<DropdownButton.StyledDropdownItemClipped>
|
||||
{result.render(result.value)}
|
||||
</DropdownButton.StyledDropdownItemClipped>
|
||||
</DropdownMenuSelectableItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function renderValueSelection(
|
||||
@ -131,7 +157,7 @@ export const FilterDropdownButton = <TData extends FilterableFieldsType>({
|
||||
|
||||
<DropdownButton.StyledDropdownTopOptionAngleDown />
|
||||
</DropdownButton.StyledDropdownTopOption>
|
||||
<DropdownButton.StyledSearchField key={'search-filter'}>
|
||||
<DropdownButton.StyledSearchField autoFocus key={'search-filter'}>
|
||||
{['text', 'relation'].includes(selectedFilter.type) && (
|
||||
<input
|
||||
type="text"
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
import assert from 'assert';
|
||||
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
@ -62,9 +63,16 @@ export const FilterByAccountOwner: Story = {
|
||||
delay: 200,
|
||||
});
|
||||
|
||||
const charlesChip = canvas.getByText('Charles Test', {
|
||||
selector: 'li > span',
|
||||
});
|
||||
const charlesChip = canvas
|
||||
.getAllByTestId('dropdown-menu-item')
|
||||
.find((item) => {
|
||||
return item.textContent === 'Charles Test';
|
||||
});
|
||||
|
||||
expect(charlesChip).toBeInTheDocument();
|
||||
|
||||
assert(charlesChip);
|
||||
|
||||
await userEvent.click(charlesChip);
|
||||
|
||||
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
|
||||
|
@ -164,11 +164,18 @@ export const accountOwnerFilter = {
|
||||
type: 'relation',
|
||||
searchConfig: {
|
||||
query: SEARCH_USER_QUERY,
|
||||
template: (searchString: string) => ({
|
||||
displayName: {
|
||||
contains: `%${searchString}%`,
|
||||
mode: QueryMode.Insensitive,
|
||||
},
|
||||
template: (searchString: string, currentSelectedId?: string) => ({
|
||||
OR: [
|
||||
{
|
||||
displayName: {
|
||||
contains: `%${searchString}%`,
|
||||
mode: QueryMode.Insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: currentSelectedId ? { equals: currentSelectedId } : undefined,
|
||||
},
|
||||
],
|
||||
}),
|
||||
resultMapper: (data: any) => ({
|
||||
value: data,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
import assert from 'assert';
|
||||
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
@ -59,7 +60,16 @@ export const CompanyName: Story = {
|
||||
delay: 200,
|
||||
});
|
||||
|
||||
const qontoChip = canvas.getByText('Qonto', { selector: 'li > span' });
|
||||
const qontoChip = canvas
|
||||
.getAllByTestId('dropdown-menu-item')
|
||||
.find((item) => {
|
||||
return item.textContent === 'Qonto';
|
||||
});
|
||||
|
||||
expect(qontoChip).toBeInTheDocument();
|
||||
|
||||
assert(qontoChip);
|
||||
|
||||
await userEvent.click(qontoChip);
|
||||
|
||||
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
|
||||
|
@ -100,8 +100,18 @@ export const companyFilter = {
|
||||
type: 'relation',
|
||||
searchConfig: {
|
||||
query: SEARCH_COMPANY_QUERY,
|
||||
template: (searchString: string) => ({
|
||||
name: { contains: `%${searchString}%`, mode: QueryMode.Insensitive },
|
||||
template: (searchString: string, currentSelectedId?: string) => ({
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
contains: `%${searchString}%`,
|
||||
mode: QueryMode.Insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: currentSelectedId ? { equals: currentSelectedId } : undefined,
|
||||
},
|
||||
],
|
||||
}),
|
||||
resultMapper: (data) => ({
|
||||
value: data,
|
||||
|
Loading…
Reference in New Issue
Block a user