diff --git a/packages/examples-site/src/App.tsx b/packages/examples-site/src/App.tsx
index 15fb501..3afbfb9 100644
--- a/packages/examples-site/src/App.tsx
+++ b/packages/examples-site/src/App.tsx
@@ -16,7 +16,8 @@ import InstallScreenApp from "./pages/InstallScreenPage/AppExample";
import InstallScreenChannel from "./pages/InstallScreenPage/ChannelExample";
import PageFiltersSearch from "./pages/FiltersSearchPage/FiltersSearchPage";
import PageFiltersDropdowns from "./pages/FiltersDropdownsPage/FiltersDropdownsPage";
-import PageFiltersAdvanced from "./pages/FiltersAdvancedPAge/FiltersAdvancedPage";
+import PageFiltersAdvanced from "./pages/FiltersAdvancedPage/FiltersAdvancedPage";
+import PageFiltersAdvancedAdditive from "./pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage";
export const alertsManager = createAlertsManager();
@@ -39,6 +40,10 @@ const RouteFC = () => {
{ path: "/filters-search", element: },
{ path: "/filters-dropdowns", element: },
{ path: "/filters-advanced", element: },
+ {
+ path: "/filters-advanced-additive",
+ element: ,
+ },
]);
return routes;
};
diff --git a/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.styled.tsx b/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.styled.tsx
new file mode 100644
index 0000000..efdc569
--- /dev/null
+++ b/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.styled.tsx
@@ -0,0 +1,64 @@
+import { theme as defaultTheme } from "@bigcommerce/big-design-theme";
+import styled from "styled-components";
+import { BoxProps } from "@bigcommerce/big-design";
+
+import { GridItem, Link } from "@bigcommerce/big-design";
+
+export const StyledGridItem = styled(GridItem)`
+ align-content: center;
+`;
+
+export const StyledFiltersLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ flex-gap: 0.25rem;
+`;
+
+export const StyledPanelContents = styled.div`
+ display: block;
+ box-sizing: border-box;
+ margin-inline: -${({ theme }) => theme.spacing.medium};
+ max-width: calc(
+ 100% + ${({ theme }) => theme.spacing.medium}px +
+ ${({ theme }) => theme.spacing.medium}px
+ );
+ overflow-x: auto;
+ @media (min-width: ${({ theme }) => theme.breakpointValues.tablet}) {
+ margin-inline: -${({ theme }) => theme.spacing.xLarge};
+ max-width: calc(
+ 100% + ${({ theme }) => theme.spacing.xLarge}px +
+ ${({ theme }) => theme.spacing.xLarge}px
+ );
+ }
+`;
+
+// Provides default theme props to ensure consistent styling if not provided externally
+StyledPanelContents.defaultProps = { theme: defaultTheme };
+
+export const StyledProductImage = styled.div`
+ display: block;
+ box-sizing: border-box;
+ width: 47px;
+ height: 47px;
+ border: ${({ theme }) => theme.border.box};
+ border-radius: ${({ theme }) => theme.borderRadius.normal};
+ overflow: hidden;
+
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ }
+`;
+
+StyledProductImage.defaultProps = { theme: defaultTheme };
+
+
+export const StyledBulkActions = styled.div`
+display: block;
+@media (min-width: ${({ theme }) => theme.breakpointValues.tablet}) {
+ min-width: 300px;
+}
+`;
+
+StyledBulkActions.defaultProps = { theme: defaultTheme };
\ No newline at end of file
diff --git a/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.tsx b/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.tsx
new file mode 100644
index 0000000..029f6f2
--- /dev/null
+++ b/packages/examples-site/src/pages/FiltersAdvancedAdditivePage/FiltersAdvancedAdditivePage.tsx
@@ -0,0 +1,593 @@
+import React, { FunctionComponent, useState, useEffect } from "react";
+import {
+ Flex,
+ FlexItem,
+ Box,
+ Button,
+ Panel,
+ Chip,
+ Table,
+ TableItem,
+ Text,
+ Dropdown,
+ ProgressCircle,
+ Input,
+ Form,
+ FormGroup,
+ Grid,
+ Modal,
+ Fieldset,
+ MultiSelect,
+ Select,
+} from "@bigcommerce/big-design";
+import { InfoIllustration } from "bigcommerce-design-patterns";
+import {
+ ArrowDropDownIcon,
+ CloseIcon,
+ FilterListIcon,
+ MoreHorizIcon,
+ SearchIcon,
+} from "@bigcommerce/big-design-icons";
+import { Header, Page } from "@bigcommerce/big-design-patterns";
+import { useNavigate } from "react-router";
+import { useLocation } from "react-router-dom";
+import { theme } from "@bigcommerce/big-design-theme";
+import {
+ StyledGridItem,
+ StyledFiltersLink,
+ StyledPanelContents,
+ StyledProductImage,
+} from "./FiltersAdvancedAdditivePage.styled";
+
+import { DummyItem } from "../../data/dummyProducts";
+import { getCategories, getProducts } from "../../data/services";
+import { Category } from "../../data/dummyCategories";
+import { findCategoryById } from "../../helpers/categories";
+import { formatPrice } from "../../helpers/price";
+
+/**
+ * Mock data for the items to be displayed in the table.
+ */
+interface Item extends DummyItem, TableItem {}
+
+/**
+ * Column definitions for the table.
+ */
+
+/**
+ * Function to sort the items based on a column and direction.
+ * @param {Item[]} items - The items to sort.
+ * @param {string} columnHash - The column to sort by.
+ * @param {string} direction - The direction to sort (ASC or DESC).
+ * @returns {Item[]} - The sorted items.
+ */
+const sort = (items: Item[], columnHash: string, direction: string) => {
+ return items
+ .concat()
+ .sort((a, b) =>
+ direction === "ASC"
+ ? a[columnHash] >= b[columnHash]
+ ? 1
+ : -1
+ : a[columnHash] <= b[columnHash]
+ ? 1
+ : -1
+ );
+};
+
+/**
+ * PageList component - Displays a page with a list of items in a table.
+ */
+const PageFiltersAdvancedAdditive: FunctionComponent = () => {
+ // NAVIGATION
+ const location = useLocation();
+
+ const navigate = useNavigate();
+
+ // TABLE HEADERS
+ const columns = [
+ {
+ header: "Name",
+ hash: "name",
+ render: ({ name, image }: { name: string; image: string }) => {
+ const imgSrc = `./assets/images/product-images/${image}`;
+ return (
+
+
+
+
+ {name}
+
+ );
+ },
+ isSortable: true,
+ },
+ {
+ header: "Sku",
+ hash: "sku",
+ render: ({ sku }: { sku: string }) => sku,
+ isSortable: true,
+ },
+ {
+ header: "Categories",
+ hash: "categories",
+ render: ({ categories }: { categories: number[] }) => {
+ // get category labels from a deep object and join them
+ return categories
+ .map((categoryId) => {
+ const category = findCategoryById(productCats, categoryId);
+ return category ? category.label : "";
+ })
+ .join(", ");
+ },
+ },
+ {
+ header: "Stock",
+ hash: "stock",
+ render: ({ stock }: { stock: number }) => stock,
+ isSortable: true,
+ },
+ {
+ header: "Price",
+ hash: "price",
+ render: ({ price }: { price: number }) => formatPrice(price),
+ isSortable: true,
+ },
+ {
+ header: null,
+ hash: "actions",
+ render: ({ productId, url }: { productId: number; url: string }) => {
+ return (
+ {
+ return null;
+ },
+ hash: "edsome-action",
+ },
+ {
+ content: "Some other action",
+ onItemClick: () => {
+ return null;
+ },
+ hash: "some-other-action",
+ },
+ ]}
+ maxHeight={250}
+ placement="bottom-end"
+ toggle={
+ }>
+ }
+ />
+ );
+ },
+ isSortable: true,
+ },
+ ];
+
+ // DATA HANDLING
+ const [currentItems, setCurrentItems] = useState- ([]);
+ const [itemsLoaded, setItemsLoaded] = useState(false);
+
+ const setTableItems = (
+ themItems: any,
+ thePage = currentPage,
+ itemCount = itemsPerPage
+ ) => {
+ const maxItems = thePage * itemCount;
+ const lastItem = Math.min(maxItems, themItems.length);
+ const firstItem = Math.max(0, maxItems - itemsPerPage);
+
+ setCurrentItems(themItems.slice(firstItem, lastItem));
+ };
+
+ // PAGINATION
+ const [currentPage, setCurrentPage] = useState(1);
+ const [itemsPerPageOptions] = useState([10, 20, 30]);
+ const [itemsPerPage, setItemsPerPage] = useState(10);
+
+ const onItemsPerPageChange = (newRange: number) => {
+ setCurrentPage(1);
+ setItemsPerPage(newRange);
+ };
+
+ const onPageChange = (newPage: number) => {
+ setCurrentPage(newPage);
+ setTableItems(items, newPage);
+ };
+
+ // SORTING
+ const [columnHash, setColumnHash] = useState("");
+ const [direction, setDirection] = useState<"ASC" | "DESC">("ASC");
+ const onSort = (newColumnHash: string, newDirection: "ASC" | "DESC") => {
+ setColumnHash(newColumnHash);
+ setDirection(newDirection);
+ const orderedItems = sort(items, newColumnHash, newDirection);
+ setTableItems(orderedItems);
+ };
+
+ // SEARCH
+ const [searchValue, setSearchValue] = useState("");
+ const onSearchChange = (event: React.ChangeEvent) => {
+ setSearchValue(event.target.value);
+ // let's reset the items to the original data if the search value is empty
+ if (!event.target.value) {
+ setItems(allItems);
+ setTableItems(allItems);
+ }
+ };
+ // search submission handler
+ const onSearchSubmit = (e) => {
+ e.preventDefault();
+ if (searchValue) {
+ // let's find the items
+ const foundItems = items.filter((item) =>
+ item.name.toLowerCase().includes(searchValue.toLowerCase())
+ );
+ // set the items
+ setItems(foundItems);
+ setTableItems(foundItems);
+ setFiltersApplied(true);
+ }
+ };
+
+ // EFFECTS
+
+ // fetch categories and product all at once
+
+ const [productCats, setProductCats] = useState([]);
+ const [items, setItems] = useState
- ([]);
+ const [allItems, setAllItems] = useState
- ([]);
+ useEffect(() => {
+ Promise.all([getCategories(), getProducts()]).then(
+ ([categories, products]) => {
+ setProductCats(categories as Category[]);
+ setAllItems(products as Item[]);
+ setItems(products as Item[]);
+ setTableItems(products as Item[]);
+ setItemsLoaded(true);
+ }
+ );
+ }, []);
+
+ // PAGE ELEMENTS
+
+ // FILTERING MODAL
+ const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
+ const [filtersApplied, setFiltersApplied] = useState(false);
+ const [filterCategory, setFilterCategory] = useState([]);
+ const [filterPriceMin, setFilterPriceMin] = useState(
+ undefined
+ );
+ const [filterPriceMax, setFilterPriceMax] = useState(
+ undefined
+ );
+ const [filterStockMin, setFilterStockMin] = useState(
+ undefined
+ );
+ const [filterStockMax, setFilterStockMax] = useState(
+ undefined
+ );
+
+ const filteringCriteria = {
+ name: {
+ operators: ["=", "!="],
+ },
+ category: {
+ operators: ["=", "!="],
+ },
+ price: {
+ operators: ["=", "!=", ">", ">=", "<", "=<"],
+ },
+ stock: {
+ operators: ["=", "!=", ">", ">=", "<", "=<"],
+ },
+ };
+
+ const applyFilters = () => {
+ console.log("Applying filters");
+ console.log("Name", searchValue);
+ console.log("Category", filterCategory);
+ console.log("Price range", filterPriceMin, filterPriceMax);
+ console.log("Stock range", filterStockMin, filterStockMax);
+
+ // let's find out if filters are applied
+ setFiltersApplied(
+ searchValue !== "" ||
+ filterCategory.length > 0 ||
+ filterPriceMin !== undefined ||
+ filterPriceMax !== undefined ||
+ filterStockMin !== undefined ||
+ filterStockMax !== undefined
+ );
+
+ // filter the items
+ handleFiltering();
+
+ // close the modal
+ closeFilterModal();
+ };
+
+ const clearAllFilters = (e) => {
+ e && e.preventDefault();
+ setSearchValue("");
+ setFilterCategory([]);
+ setFilterPriceMin(undefined);
+ setFilterPriceMax(undefined);
+ setFilterStockMin(undefined);
+ setFilterStockMax(undefined);
+
+ handleFiltering(true);
+ setFiltersApplied(false);
+ };
+
+ const openFilterModal = () => {
+ setIsFilterModalOpen(true);
+ };
+ const closeFilterModal = () => {
+ setIsFilterModalOpen(false);
+ };
+
+ const handleFiltering = (clear = false) => {
+ let filteredItems = [...allItems];
+
+ if (!clear) {
+ if (searchValue != "" && searchValue !== undefined) {
+ filteredItems = filteredItems.filter((item) =>
+ item.name.toLowerCase().includes(searchValue.toLowerCase())
+ );
+ }
+
+ if (filterCategory.length > 0) {
+ filteredItems = filteredItems.filter((item) => {
+ let found = false;
+ item.categories.forEach((cat) => {
+ if (filterCategory.includes(cat)) {
+ found = true;
+ }
+ });
+ return found;
+ });
+ }
+
+ if (filterPriceMin !== undefined) {
+ filteredItems = filteredItems.filter(
+ (item) => item.price >= filterPriceMin
+ );
+ }
+
+ if (filterPriceMax !== undefined) {
+ filteredItems = filteredItems.filter(
+ (item) => item.price <= filterPriceMax
+ );
+ }
+
+ if (filterStockMin !== undefined) {
+ filteredItems = filteredItems.filter(
+ (item) => item.stock >= filterStockMin
+ );
+ }
+
+ if (filterStockMax !== undefined) {
+ filteredItems = filteredItems.filter(
+ (item) => item.stock <= filterStockMax
+ );
+ }
+ }
+
+ setItems(filteredItems as Item[]);
+ setTableItems(filteredItems);
+ };
+
+ // Empty state
+ const EmptyState = (
+
+ {items.length < 1 && !itemsLoaded ? (
+ // if products havent been loaded, let's show a loader
+
+ ) : (
+ // if products have been loaded, let's show the empty
+
+
+ {
+ // differentiate from empty search or empty products and show a loader element if the data is being fetched
+ filtersApplied
+ ? `No products were found for the criteria`
+ : "You have no products yet."
+ }
+
+
+ )}
+
+ );
+
+ return (
+ <>
+ navigate("/"),
+ href: "#",
+ }}
+ />
+ }
+ >
+
+
+ {
+ //The most common way of organizing information within the BigDesign patterns is with the use of panels.
+ //In this case we only have one panel, but you can have multiple panels within a page.
+ }
+
+ {
+ //search and filtering
+ }
+
+
+
+ }
+ iconRight={}
+ >
+ Filter
+
+
+
+ {filtersApplied && (
+
+ {/* let's showcase teh filters applied with chips here*/}
+ {searchValue && (
+ setSearchValue("")}
+ label={`Name: ${searchValue}`}
+ />
+ )}
+ {filterCategory.map((catId) => {
+ const cat = findCategoryById(productCats, catId);
+ return (
+
+ setFilterCategory(
+ filterCategory.filter((c) => c !== catId)
+ )
+ }
+ label={`Category: ${cat?.label}`}
+ />
+ );
+ })}
+ {filterPriceMin !== undefined && (
+ setFilterPriceMin(undefined)}
+ label={`Min price: ${filterPriceMin}`}
+ />
+ )}
+ {filterPriceMax !== undefined && (
+ setFilterPriceMax(undefined)}
+ label={`Max price: ${filterPriceMax}`}
+ />
+ )}
+ {filterStockMin !== undefined && (
+ setFilterStockMin(undefined)}
+ label={`Min stock: ${filterStockMin}`}
+ />
+ )}
+ {filterStockMax !== undefined && (
+ setFilterStockMax(undefined)}
+ label={`Max stock: ${filterStockMax}`}
+ />
+ )}
+
+
+ Clear all filters
+
+
+ )}
+
+ {
+ //The Table component is used to display tabular data.
+ //It allows you to display a list of items in a table format.
+ //The table can be customized with different columns and actions.
+ //The table also allows you to select items and perform actions on them.
+ //In this case, the table is displaying a list of products.
+ }
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default PageFiltersAdvancedAdditive;
diff --git a/packages/examples-site/src/pages/FiltersAdvancedPAge/FiltersAdvancedPage.tsx b/packages/examples-site/src/pages/FiltersAdvancedPAge/FiltersAdvancedPage.tsx
index 9e87a26..4465f8c 100644
--- a/packages/examples-site/src/pages/FiltersAdvancedPAge/FiltersAdvancedPage.tsx
+++ b/packages/examples-site/src/pages/FiltersAdvancedPAge/FiltersAdvancedPage.tsx
@@ -49,17 +49,6 @@ import { formatPrice } from "../../helpers/price";
*/
interface Item extends DummyItem, TableItem {}
-/**
- * Column definitions for the table.
- */
-
-/**
- * Function to sort the items based on a column and direction.
- * @param {Item[]} items - The items to sort.
- * @param {string} columnHash - The column to sort by.
- * @param {string} direction - The direction to sort (ASC or DESC).
- * @returns {Item[]} - The sorted items.
- */
const sort = (items: Item[], columnHash: string, direction: string) => {
return items
.concat()
@@ -228,14 +217,12 @@ const PageFiltersAdvanced: FunctionComponent = () => {
// set the items
setItems(foundItems);
setTableItems(foundItems);
- setFiltersApplied(true);
}
};
// EFFECTS
// fetch categories and product all at once
-
const [productCats, setProductCats] = useState([]);
const [items, setItems] = useState
- ([]);
const [allItems, setAllItems] = useState
- ([]);
@@ -251,8 +238,6 @@ const PageFiltersAdvanced: FunctionComponent = () => {
);
}, []);
- // PAGE ELEMENTS
-
// FILTERING MODAL
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
const [filtersApplied, setFiltersApplied] = useState(false);
@@ -271,16 +256,9 @@ const PageFiltersAdvanced: FunctionComponent = () => {
);
const applyFilters = () => {
- console.log("Applying filters");
- console.log("Name", searchValue);
- console.log("Category", filterCategory);
- console.log("Price range", filterPriceMin, filterPriceMax);
- console.log("Stock range", filterStockMin, filterStockMax);
-
// let's find out if filters are applied
setFiltersApplied(
- searchValue !== "" ||
- filterCategory.length > 0 ||
+ filterCategory.length > 0 ||
filterPriceMin !== undefined ||
filterPriceMax !== undefined ||
filterStockMin !== undefined ||
@@ -296,7 +274,6 @@ const PageFiltersAdvanced: FunctionComponent = () => {
const clearAllFilters = (e) => {
e && e.preventDefault();
- setSearchValue("");
setFilterCategory([]);
setFilterPriceMin(undefined);
setFilterPriceMax(undefined);
@@ -336,25 +313,25 @@ const PageFiltersAdvanced: FunctionComponent = () => {
});
}
- if (filterPriceMin !== undefined) {
+ if (filterPriceMin !== undefined && !isNaN(filterPriceMin)) {
filteredItems = filteredItems.filter(
(item) => item.price >= filterPriceMin
);
}
- if (filterPriceMax !== undefined) {
+ if (filterPriceMax !== undefined && !isNaN(filterPriceMax)) {
filteredItems = filteredItems.filter(
(item) => item.price <= filterPriceMax
);
}
- if (filterStockMin !== undefined) {
+ if (filterStockMin !== undefined && !isNaN(filterStockMin)) {
filteredItems = filteredItems.filter(
(item) => item.stock >= filterStockMin
);
}
- if (filterStockMax !== undefined) {
+ if (filterStockMax !== undefined && !isNaN(filterStockMax)) {
filteredItems = filteredItems.filter(
(item) => item.stock <= filterStockMax
);
@@ -365,6 +342,47 @@ const PageFiltersAdvanced: FunctionComponent = () => {
setTableItems(filteredItems);
};
+ const deleteFilter = (type: string, value?: number) => {
+ let checkCategory = [...filterCategory];
+ let checkPriceMin = filterPriceMin;
+ let checkPriceMax = filterPriceMax;
+ let checkStockMin = filterStockMin;
+ let checkStockMax = filterStockMax;
+
+ switch (type) {
+ case "category":
+ checkCategory = checkCategory.filter((cat) => cat !== value);
+ setFilterCategory(checkCategory);
+ break;
+ case "priceMin":
+ setFilterPriceMin(undefined);
+ checkPriceMin = undefined;
+ break;
+ case "priceMax":
+ setFilterPriceMax(undefined);
+ checkPriceMax = undefined;
+ break;
+ case "stockMin":
+ setFilterStockMin(undefined);
+ checkStockMin = undefined;
+ break;
+ case "stockMax":
+ setFilterStockMax(undefined);
+ checkStockMax = undefined;
+ break;
+ default:
+ break;
+ }
+
+ setFiltersApplied(
+ checkCategory.length > 0 ||
+ checkPriceMin !== undefined ||
+ checkPriceMax !== undefined ||
+ checkStockMin !== undefined ||
+ checkStockMax !== undefined
+ );
+ };
+
// Empty state
const EmptyState = (
{
value={searchValue}
onChange={onSearchChange}
iconLeft={}
+ type="search"
/>
@@ -440,54 +459,46 @@ const PageFiltersAdvanced: FunctionComponent = () => {
{filtersApplied && (
- {/* let's showcase teh filters applied with chips here*/}
- {searchValue && (
- setSearchValue("")}
- label={`Name: ${searchValue}`}
- />
- )}
+ {/* let's showcase the filters applied with chips here*/}
{filterCategory.map((catId) => {
const cat = findCategoryById(productCats, catId);
return (
- setFilterCategory(
- filterCategory.filter((c) => c !== catId)
- )
- }
+ onDelete={() => {
+ deleteFilter("category", catId);
+ }}
label={`Category: ${cat?.label}`}
/>
);
})}
- {filterPriceMin !== undefined && (
+ {filterPriceMin !== undefined && !isNaN(filterPriceMin) && (
setFilterPriceMin(undefined)}
+ onDelete={() => deleteFilter("priceMin")}
label={`Min price: ${filterPriceMin}`}
/>
)}
- {filterPriceMax !== undefined && (
+ {filterPriceMax !== undefined && !isNaN(filterPriceMax) && (
setFilterPriceMax(undefined)}
+ onDelete={() => deleteFilter("priceMax")}
label={`Max price: ${filterPriceMax}`}
/>
)}
- {filterStockMin !== undefined && (
+ {filterStockMin !== undefined && !isNaN(filterStockMin) && (
setFilterStockMin(undefined)}
+ onDelete={() => deleteFilter("stockMin")}
label={`Min stock: ${filterStockMin}`}
/>
)}
- {filterStockMax !== undefined && (
+ {filterStockMax !== undefined && !isNaN(filterStockMax) && (
setFilterStockMax(undefined)}
+ onDelete={() => deleteFilter("stockMax")}
label={`Max stock: ${filterStockMax}`}
/>
)}
@@ -498,13 +509,6 @@ const PageFiltersAdvanced: FunctionComponent = () => {
)}
- {
- //The Table component is used to display tabular data.
- //It allows you to display a list of items in a table format.
- //The table can be customized with different columns and actions.
- //The table also allows you to select items and perform actions on them.
- //In this case, the table is displaying a list of products.
- }
{
onClose={closeFilterModal}
isOpen={isFilterModalOpen}
>
-