Experimental filters: add clear button and bumps UI to support ranges (#3975)
This commit is contained in:
parent
10d30ca9a4
commit
d2074f4824
9 changed files with 120 additions and 82 deletions
5
.changeset/tough-frogs-remember.md
Normal file
5
.changeset/tough-frogs-remember.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"saleor-dashboard": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Experimental filters: add clear function and bumps UI to support ranges
|
14
package-lock.json
generated
14
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
};
|
||||||
);
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)}>
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue