Experimental filters: add clear button and bumps UI to support ranges (#3975)

This commit is contained in:
Krzysztof Żuraw 2023-07-24 14:45:52 +02:00 committed by GitHub
parent 10d30ca9a4
commit d2074f4824
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 82 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---
Experimental filters: add clear function and bumps UI to support ranges

14
package-lock.json generated
View file

@ -27,7 +27,7 @@
"@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/lab": "^4.0.0-alpha.61",
"@material-ui/styles": "^4.11.4", "@material-ui/styles": "^4.11.4",
"@reach/auto-id": "^0.16.0", "@reach/auto-id": "^0.16.0",
"@saleor/macaw-ui": "0.8.0-pre.107", "@saleor/macaw-ui": "0.8.0-pre.109",
"@saleor/sdk": "0.6.0", "@saleor/sdk": "0.6.0",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6", "@types/faker": "^5.1.6",
@ -8260,9 +8260,9 @@
} }
}, },
"node_modules/@saleor/macaw-ui": { "node_modules/@saleor/macaw-ui": {
"version": "0.8.0-pre.107", "version": "0.8.0-pre.109",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.107.tgz", "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.109.tgz",
"integrity": "sha512-Z7xA1TM4KXjRJvz6gx6cKhw/pCB7q5BT+yYAAdGDKbfvMFRjh3mXilH8NOF3kkYwEe+3ykeLvijCyp1Bm7UH9w==", "integrity": "sha512-lTIe2ha18IBQk+Fy8boo1USi0Z6ISPT2jOqBd2dAP+PkPwM5quAR8hfEICuOUmac8qzjZBCQ/uv2FLbue9rquA==",
"dependencies": { "dependencies": {
"@dessert-box/react": "^0.4.0", "@dessert-box/react": "^0.4.0",
"@floating-ui/react-dom-interactions": "^0.5.0", "@floating-ui/react-dom-interactions": "^0.5.0",
@ -41546,9 +41546,9 @@
} }
}, },
"@saleor/macaw-ui": { "@saleor/macaw-ui": {
"version": "0.8.0-pre.107", "version": "0.8.0-pre.109",
"resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.107.tgz", "resolved": "https://registry.npmjs.org/@saleor/macaw-ui/-/macaw-ui-0.8.0-pre.109.tgz",
"integrity": "sha512-Z7xA1TM4KXjRJvz6gx6cKhw/pCB7q5BT+yYAAdGDKbfvMFRjh3mXilH8NOF3kkYwEe+3ykeLvijCyp1Bm7UH9w==", "integrity": "sha512-lTIe2ha18IBQk+Fy8boo1USi0Z6ISPT2jOqBd2dAP+PkPwM5quAR8hfEICuOUmac8qzjZBCQ/uv2FLbue9rquA==",
"requires": { "requires": {
"@dessert-box/react": "^0.4.0", "@dessert-box/react": "^0.4.0",
"@floating-ui/react-dom-interactions": "^0.5.0", "@floating-ui/react-dom-interactions": "^0.5.0",

View file

@ -34,7 +34,7 @@
"@material-ui/lab": "^4.0.0-alpha.61", "@material-ui/lab": "^4.0.0-alpha.61",
"@material-ui/styles": "^4.11.4", "@material-ui/styles": "^4.11.4",
"@reach/auto-id": "^0.16.0", "@reach/auto-id": "^0.16.0",
"@saleor/macaw-ui": "0.8.0-pre.107", "@saleor/macaw-ui": "0.8.0-pre.109",
"@saleor/sdk": "0.6.0", "@saleor/sdk": "0.6.0",
"@sentry/react": "^6.0.0", "@sentry/react": "^6.0.0",
"@types/faker": "^5.1.6", "@types/faker": "^5.1.6",

View file

@ -2,41 +2,45 @@ import { ConditionalFilters } from "@dashboard/components/ConditionalFilter";
import { Box, Button, CloseIcon, Popover, Text } from "@saleor/macaw-ui/next"; import { Box, Button, CloseIcon, Popover, Text } from "@saleor/macaw-ui/next";
import React from "react"; import React from "react";
export const ExpressionFilters = () => ( export const ExpressionFilters = () => {
<Popover> const [open, setOpen] = React.useState(false);
<Popover.Trigger>
<Button>Show filters</Button> return (
</Popover.Trigger> <Popover open={open} onOpenChange={open => setOpen(open)}>
<Popover.Content align="start"> <Popover.Trigger>
<Box <Button>Show filters</Button>
__minHeight="250px" </Popover.Trigger>
__minWidth="636px" <Popover.Content align="start">
display="grid"
__gridTemplateRows="auto 1fr"
>
<Box <Box
paddingTop={3} __minHeight="250px"
paddingX={3} __minWidth="636px"
paddingBottom={1.5} display="grid"
display="flex" __gridTemplateRows="auto 1fr"
gap={1}
alignItems="center"
justifyContent="space-between"
backgroundColor="surfaceNeutralPlain"
borderTopLeftRadius={2}
borderTopRightRadius={2}
> >
<Text variant="body" size="medium"> <Box
Conditions paddingTop={3}
</Text> paddingX={3}
<Box display="flex" alignItems="center" gap={2}> paddingBottom={1.5}
<Popover.Close> display="flex"
<Button variant="tertiary" icon={<CloseIcon />} /> gap={1}
</Popover.Close> alignItems="center"
justifyContent="space-between"
backgroundColor="surfaceNeutralPlain"
borderTopLeftRadius={2}
borderTopRightRadius={2}
>
<Text variant="body" size="medium">
Conditions
</Text>
<Box display="flex" alignItems="center" gap={2}>
<Popover.Close>
<Button variant="tertiary" icon={<CloseIcon />} />
</Popover.Close>
</Box>
</Box> </Box>
<ConditionalFilters onClose={() => setOpen(false)} />
</Box> </Box>
<ConditionalFilters /> </Popover.Content>
</Box> </Popover>
</Popover.Content> );
</Popover> };
);

View file

@ -6,11 +6,20 @@ import { FilterContainer } from "./FilterElement";
import { FiltersArea } from "./FiltersArea"; import { FiltersArea } from "./FiltersArea";
import { LoadingFiltersArea } from "./LoadingFiltersArea"; import { LoadingFiltersArea } from "./LoadingFiltersArea";
export const ConditionalFilters: FC = () => { export const ConditionalFilters: FC<{ onClose: () => void }> = ({
const { valueProvider } = useConditionalFilterContext(); onClose,
}) => {
const { valueProvider, containerState } = useConditionalFilterContext();
const handleConfirm = (value: FilterContainer) => { const handleConfirm = (value: FilterContainer) => {
valueProvider.persist(value); valueProvider.persist(value);
onClose();
};
const handleCancel = () => {
valueProvider.clear();
containerState.clear();
onClose();
}; };
return valueProvider.loading ? ( return valueProvider.loading ? (
@ -22,7 +31,7 @@ export const ConditionalFilters: FC = () => {
borderBottomLeftRadius={2} borderBottomLeftRadius={2}
borderBottomRightRadius={2} borderBottomRightRadius={2}
> >
<FiltersArea onConfirm={handleConfirm} /> <FiltersArea onConfirm={handleConfirm} onCancel={handleCancel} />
</Box> </Box>
); );
}; };

View file

@ -4,4 +4,5 @@ export interface FilterValueProvider {
value: FilterContainer; value: FilterContainer;
loading: boolean; loading: boolean;
persist: (newValue: FilterContainer) => void; persist: (newValue: FilterContainer) => void;
clear: () => void;
} }

View file

@ -1,5 +1,5 @@
import { _ExperimentalFilters, Box, FilterEvent } from "@saleor/macaw-ui/next"; import { _ExperimentalFilters, Box, FilterEvent } from "@saleor/macaw-ui/next";
import React from "react"; import React, { FC } from "react";
import { useConditionalFilterContext } from "./context"; import { useConditionalFilterContext } from "./context";
import { FilterContainer } from "./FilterElement"; import { FilterContainer } from "./FilterElement";
@ -8,9 +8,10 @@ import { useFilterContainer } from "./useFilterContainer";
interface FiltersAreaProps { interface FiltersAreaProps {
onConfirm: (value: FilterContainer) => void; onConfirm: (value: FilterContainer) => void;
onCancel?: () => void;
} }
export const FiltersArea = ({ onConfirm }: FiltersAreaProps) => { export const FiltersArea: FC<FiltersAreaProps> = ({ onConfirm, onCancel }) => {
const { apiProvider, leftOperandsProvider } = useConditionalFilterContext(); const { apiProvider, leftOperandsProvider } = useConditionalFilterContext();
const { const {
@ -72,7 +73,7 @@ export const FiltersArea = ({ onConfirm }: FiltersAreaProps) => {
+ Add row + Add row
</_ExperimentalFilters.AddRowButton> </_ExperimentalFilters.AddRowButton>
<Box display="flex" gap={3}> <Box display="flex" gap={3}>
<_ExperimentalFilters.ClearButton> <_ExperimentalFilters.ClearButton onClick={onCancel}>
Clear Clear
</_ExperimentalFilters.ClearButton> </_ExperimentalFilters.ClearButton>
<_ExperimentalFilters.ConfirmButton onClick={() => onConfirm(value)}> <_ExperimentalFilters.ConfirmButton onClick={() => onConfirm(value)}>

View file

@ -54,9 +54,17 @@ export const useUrlValueProvider = (
setValue(filterValue); setValue(filterValue);
}; };
const clear = () => {
router.history.replace({
pathname: router.location.pathname,
});
setValue([]);
};
return { return {
value, value,
loading, loading,
persist, persist,
clear,
}; };
}; };

View file

@ -6,48 +6,51 @@ import { FilterValueProvider } from "./FilterValueProvider";
type StateCallback = (el: FilterElement) => void; type StateCallback = (el: FilterElement) => void;
type Element = FilterContainer[number]; type Element = FilterContainer[number];
const isFilterElement = (el: unknown): el is FilterElement =>
const isFilterElement = (el: unknown): el is FilterElement => typeof el !== "string" && !Array.isArray(el) typeof el !== "string" && !Array.isArray(el);
const removeConstraint = (container: FilterContainer) => { const removeConstraint = (container: FilterContainer) => {
return container.map((el) => { return container.map(el => {
if (!isFilterElement(el)) return el if (!isFilterElement(el)) return el;
if (!el.constraint?.existIn(container)) { if (!el.constraint?.existIn(container)) {
el.clearConstraint() el.clearConstraint();
} }
return el return el;
}) });
} };
const calculateIndexesToRemove = (container: FilterContainer, position: number) => { const calculateIndexesToRemove = (
const next = position + 1 container: FilterContainer,
const previous = position - 1 position: number,
const indexTuple = [position] ) => {
const next = position + 1;
const previous = position - 1;
const indexTuple = [position];
if (typeof container[next] === "string") { if (typeof container[next] === "string") {
indexTuple.push(next) indexTuple.push(next);
return indexTuple return indexTuple;
} }
if (typeof container[previous] === "string") { if (typeof container[previous] === "string") {
indexTuple.push(previous) indexTuple.push(previous);
} }
return indexTuple return indexTuple;
} };
const removeElement = (container: FilterContainer, position: number) => { const removeElement = (container: FilterContainer, position: number) => {
const indexTuple = calculateIndexesToRemove(container, position) const indexTuple = calculateIndexesToRemove(container, position);
const newContainer = container const newContainer = container.filter(
.filter((_, elIndex) => !indexTuple.includes(elIndex)) (_, elIndex) => !indexTuple.includes(elIndex),
);
return removeConstraint(newContainer) return removeConstraint(newContainer);
} };
export const useContainerState = (valueProvider: FilterValueProvider) => { export const useContainerState = (valueProvider: FilterValueProvider) => {
const [value, setValue] = useState<FilterContainer>([]); const [value, setValue] = useState<FilterContainer>([]);
@ -81,13 +84,15 @@ export const useContainerState = (valueProvider: FilterValueProvider) => {
}; };
const updateBySlug = (slug: string, cb: StateCallback) => { const updateBySlug = (slug: string, cb: StateCallback) => {
setValue(v => v.map((el) => { setValue(v =>
if (isFilterElement(el) && el.value.value === slug) { v.map(el => {
cb(el) if (isFilterElement(el) && el.value.value === slug) {
} cb(el);
}
return el return el;
})) }),
);
}; };
const removeAt = (position: string) => { const removeAt = (position: string) => {
@ -109,13 +114,17 @@ export const useContainerState = (valueProvider: FilterValueProvider) => {
}; };
const exist = (slug: string) => { const exist = (slug: string) => {
return value.some((entry) => return value.some(
isFilterElement(entry) && entry.value.value === slug entry => isFilterElement(entry) && entry.value.value === slug,
) );
} };
const createEmpty = () => { const createEmpty = () => {
create(FilterElement.createEmpty()) create(FilterElement.createEmpty());
};
const clear = () => {
setValue([]);
}; };
return { return {
@ -126,5 +135,6 @@ export const useContainerState = (valueProvider: FilterValueProvider) => {
updateAt, updateAt,
removeAt, removeAt,
value, value,
clear,
}; };
}; };