@sonic-equipment/ui 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/algolia/algolia-provider.d.ts +1 -1
  2. package/dist/breadcrumbs/breadcrumb.stories.d.ts +2 -2
  3. package/dist/breadcrumbs/connected-breadcrumb.d.ts +1 -1
  4. package/dist/cards/category-card/category-card.d.ts +2 -5
  5. package/dist/cards/product-card/product-card.d.ts +2 -2
  6. package/dist/carousel/category-carousel/connected-category-carousel.d.ts +1 -0
  7. package/dist/index.d.ts +26 -51
  8. package/dist/index.js +266 -151
  9. package/dist/media/image/image.d.ts +2 -19
  10. package/dist/pages/product-listing-page/product-listing-page-data-types.d.ts +20 -0
  11. package/dist/pages/product-listing-page/product-listing-page-provider/product-listing-page-context.d.ts +9 -0
  12. package/dist/pages/product-listing-page/product-listing-page-provider/product-listing-page-provider.d.ts +8 -0
  13. package/dist/pages/product-listing-page/product-listing-page-provider/use-breadcrumb.d.ts +4 -0
  14. package/dist/pages/product-listing-page/product-listing-page-provider/use-subcategories.d.ts +4 -0
  15. package/dist/pages/product-listing-page/product-listing-page.d.ts +5 -3
  16. package/dist/pages/product-listing-page/product-listing-page.stories.d.ts +7 -10
  17. package/dist/pages/product-listing-page/types.d.ts +34 -0
  18. package/dist/pages/product-listing-page/use-fetch-product-listing-page/product-listing-page-data-response.d.ts +48 -0
  19. package/dist/pages/product-listing-page/use-fetch-product-listing-page/use-fetch-product-listing-page-data.d.ts +2 -0
  20. package/dist/pages/product-listing-page/use-fetch-product-listing-page/use-fetch-product-listing-page-data.stories.d.ts +20 -0
  21. package/dist/shared/fetch/ResponseError.d.ts +9 -0
  22. package/dist/shared/hooks/use-scroll-to.d.ts +1 -0
  23. package/dist/shared/types/category.d.ts +6 -0
  24. package/dist/shared/types/image.d.ts +20 -0
  25. package/dist/styles.css +13 -1
  26. package/package.json +26 -25
  27. package/dist/shared/providers/breadcrumb-provider.d.ts +0 -11
@@ -8,7 +8,7 @@ interface AlgoliaContextType {
8
8
  toggleOnline: VoidFunction;
9
9
  }
10
10
  interface AlgoliaProviderProps {
11
- category: string[];
11
+ category: string[] | undefined;
12
12
  children: ReactNode;
13
13
  offlineSearchClient?: SearchClient;
14
14
  online?: boolean;
@@ -1,5 +1,5 @@
1
1
  import type { StoryObj } from '@storybook/react';
2
- import { Link } from 'shared/providers/breadcrumb-provider';
2
+ import { ProductListingPageData } from 'pages/product-listing-page/product-listing-page-data-types';
3
3
  import { Breadcrumb } from './breadcrumb';
4
4
  declare const meta: {
5
5
  component: typeof Breadcrumb;
@@ -15,5 +15,5 @@ export declare const FourLevelsMobile: Story;
15
15
  export declare const TwoLevels: Story;
16
16
  export declare const TwoLevelsMobile: Story;
17
17
  export declare const Connected: StoryObj<{
18
- links: Link[];
18
+ data: ProductListingPageData;
19
19
  }>;
@@ -1 +1 @@
1
- export declare function ConnectedBreadcrumb(): import("react/jsx-runtime").JSX.Element;
1
+ export declare function ConnectedBreadcrumb(): import("react/jsx-runtime").JSX.Element | null;
@@ -1,8 +1,5 @@
1
- import { type ImageProps } from 'media/image/image';
2
- export interface CategoryCardProps {
3
- href: string;
4
- image: ImageProps;
1
+ import { Category } from 'shared/types/category';
2
+ export interface CategoryCardProps extends Category {
5
3
  isSelected?: boolean;
6
- title: string;
7
4
  }
8
5
  export declare function CategoryCard({ href, image, isSelected, title, }: CategoryCardProps): import("react/jsx-runtime").JSX.Element;
@@ -1,12 +1,12 @@
1
1
  import { MouseEvent, ReactElement } from 'react';
2
2
  import { PressEvent } from 'react-aria-components';
3
- import { ImageProps } from 'media/image/image';
3
+ import type { ImageSource } from 'shared/types/image';
4
4
  import type { ProductPrice as ProductPriceType } from 'shared/types/price';
5
5
  export interface ProductCardProps {
6
6
  addToCartButton: ReactElement;
7
7
  favoriteButton?: ReactElement;
8
8
  href: string;
9
- image: ImageProps;
9
+ image: ImageSource;
10
10
  /** @deprecated Use `onPress` instead */
11
11
  onClick?: (e: MouseEvent) => void;
12
12
  onPress?: (e: PressEvent) => void;
@@ -0,0 +1 @@
1
+ export declare function ConnectedCategoryCarousel(): import("react/jsx-runtime").JSX.Element | null;
package/dist/index.d.ts CHANGED
@@ -13,9 +13,9 @@ declare const breakpoints: {
13
13
  readonly xl: 1024;
14
14
  readonly xxl: 1440;
15
15
  };
16
- type Breakpoint$2 = keyof typeof breakpoints;
17
- interface UseBreakpointReturnType extends Record<Breakpoint$2, boolean> {
18
- current: Breakpoint$2;
16
+ type Breakpoint$1 = keyof typeof breakpoints;
17
+ interface UseBreakpointReturnType extends Record<Breakpoint$1, boolean> {
18
+ current: Breakpoint$1;
19
19
  }
20
20
  declare const useBreakpoint: () => UseBreakpointReturnType;
21
21
 
@@ -32,17 +32,6 @@ declare const useDisclosure: (initialState?: boolean) => UseDisclosureReturnType
32
32
 
33
33
  declare const useScrollLock: (lock: boolean) => void;
34
34
 
35
- interface Link$1 {
36
- href: string;
37
- label: string;
38
- }
39
- interface Props$3 {
40
- links: Link$1[];
41
- updateLinks: (links: Link$1[]) => void;
42
- }
43
- declare function BreadcrumbProvider(props: Props$3): null;
44
- declare function useBreadcrumb(): Props$3;
45
-
46
35
  interface CartLine$1 {
47
36
  id: string
48
37
  productId: string | null
@@ -173,7 +162,7 @@ interface BreadcrumbProps {
173
162
  }
174
163
  declare function Breadcrumb({ links }: BreadcrumbProps): react_jsx_runtime.JSX.Element | null;
175
164
 
176
- declare function ConnectedBreadcrumb(): react_jsx_runtime.JSX.Element;
165
+ declare function ConnectedBreadcrumb(): react_jsx_runtime.JSX.Element | null;
177
166
 
178
167
  interface ButtonProps {
179
168
  _pseudo?: 'none' | 'focus' | 'hover' | 'active';
@@ -232,25 +221,25 @@ interface Props {
232
221
  }
233
222
  declare const ConnectedAddToCartButton: ({ productId }: Props) => react_jsx_runtime.JSX.Element;
234
223
 
235
- interface Source$1 {
224
+ type Breakpoint = 'lg' | 'md' | 'sm'
225
+
226
+ type Sizes = Record<Breakpoint, number>
227
+
228
+ interface Source {
236
229
  url: string
237
230
  width: number
238
231
  }
239
232
 
240
- type Breakpoint$1 = 'lg' | 'md' | 'sm'
241
-
242
- type Sizes$1 = Record<Breakpoint$1, number>
243
-
244
- interface ImageProps$1 {
233
+ interface ImageSource {
245
234
  alt: string
246
235
  className?: string
247
236
  fallbackSrc?: string
248
237
  fit?: 'contain' | 'cover'
249
238
  height?: number
250
239
  loading?: 'lazy' | 'eager'
251
- sizes?: Sizes$1
240
+ sizes?: Sizes
252
241
  src: string
253
- srcSet?: Source$1[]
242
+ srcSet?: Source[]
254
243
  title: string
255
244
  width?: number
256
245
  }
@@ -265,7 +254,7 @@ interface ProductCardProps {
265
254
  addToCartButton: ReactElement;
266
255
  favoriteButton?: ReactElement;
267
256
  href: string;
268
- image: ImageProps$1;
257
+ image: ImageSource;
269
258
  /** @deprecated Use `onPress` instead */
270
259
  onClick?: (e: MouseEvent) => void;
271
260
  onPress?: (e: PressEvent) => void;
@@ -276,13 +265,16 @@ interface ProductCardProps {
276
265
  }
277
266
  declare function ProductCard({ addToCartButton: AddToCartButton, favoriteButton: FavoriteButton, href, image: { alt, fit, src, title: imageTitle }, onClick, onPress, price, sku, tag, title, }: ProductCardProps): react_jsx_runtime.JSX.Element;
278
267
 
279
- interface CategoryCardProps {
268
+ interface Category {
280
269
  href: string
281
- image: ImageProps$1
282
- isSelected?: boolean
270
+ image: ImageSource
283
271
  title: string
284
272
  }
285
273
 
274
+ interface CategoryCardProps extends Category {
275
+ isSelected?: boolean
276
+ }
277
+
286
278
  interface CategoryCarouselProps {
287
279
  categories: CategoryCardProps[];
288
280
  }
@@ -488,25 +480,7 @@ interface ProductOverviewGridProps {
488
480
  }
489
481
  declare function ProductOverviewGrid({ children }: ProductOverviewGridProps): react_jsx_runtime.JSX.Element;
490
482
 
491
- interface Source {
492
- url: string;
493
- width: number;
494
- }
495
- type Breakpoint = 'lg' | 'md' | 'sm';
496
- type Sizes = Record<Breakpoint, number>;
497
- interface ImageProps {
498
- alt: string;
499
- className?: string;
500
- fallbackSrc?: string;
501
- fit?: 'contain' | 'cover';
502
- height?: number;
503
- loading?: 'lazy' | 'eager';
504
- sizes?: Sizes;
505
- src: string;
506
- srcSet?: Source[];
507
- title: string;
508
- width?: number;
509
- }
483
+ type ImageProps = ImageSource;
510
484
  declare function Image({ alt, className, fallbackSrc, fit, height, loading, sizes: sizesProp, src, srcSet: srcSetProp, title, width, }: ImageProps): react_jsx_runtime.JSX.Element;
511
485
 
512
486
  declare function PageLayout({ children, className, }: {
@@ -531,10 +505,11 @@ interface Filters {
531
505
  };
532
506
  }
533
507
  type ProductListingPageProps = {
534
- category: string[];
535
- isLoading?: boolean;
508
+ bffUrl: string;
509
+ pageUrl: string;
510
+ searchClient?: SearchClient;
536
511
  };
537
- declare function ProductListingPage({ category, isLoading, }: ProductListingPageProps): react_jsx_runtime.JSX.Element;
512
+ declare function ProductListingPage({ bffUrl, pageUrl, searchClient, }: ProductListingPageProps): react_jsx_runtime.JSX.Element;
538
513
 
539
514
  interface SidebarProps {
540
515
  children: React.ReactNode;
@@ -588,7 +563,7 @@ interface AlgoliaContextType {
588
563
  toggleOnline: VoidFunction;
589
564
  }
590
565
  interface AlgoliaProviderProps {
591
- category: string[];
566
+ category: string[] | undefined;
592
567
  children: ReactNode;
593
568
  offlineSearchClient?: SearchClient;
594
569
  online?: boolean;
@@ -602,4 +577,4 @@ declare function AlgoliaResultsCount(): string | null;
602
577
 
603
578
  declare function AlgoliaSortBy(): react_jsx_runtime.JSX.Element | null;
604
579
 
605
- export { Accordion, AddToCartButton, AlgoliaCategories, AlgoliaFilterPanel, type AlgoliaFilterPanelProps, AlgoliaFilterSection, AlgoliaMultiSelectFilterSection, AlgoliaPagination, AlgoliaProvider, AlgoliaResultsCount, AlgoliaSortBy, Breadcrumb, type BreadcrumbProps, BreadcrumbProvider, Button, type ButtonProps, type Cart, CartFilledIcon, type CartLine, CartOutlinedIcon, CartProvider, CategoryCarousel, type CategoryCarouselProps, Checkbox, type CheckboxProps$1 as CheckboxProps, ColorCheckbox, type ColorCheckboxProps, ConnectedAddToCartButton, ConnectedBreadcrumb, DehashedOutlinedIcon, FavoriteButton, type FavoriteButtonProps, FavoriteFilledIcon, FavoriteOutlinedIcon, FavoriteProvider, type FilterOption, type Filters, FormattedMessage, type FormattedMessageFunction, type FormattedMessageProps, GlobalStateProvider, GlobalStateProviderContext, HashedOutlinedIcon, IconButton, type IconButtonProps, Image, type ImageProps, IntlProvider, LeftArrowFilledIcon, type Link$1 as Link, LinkButton, type LinkButtonProps, MultiSelect, type MultiSelectProps, NumberField, type NumberFieldSize, Page, PageLayout, type PageProps, type Product, ProductCard, type ProductCardProps, ProductListingPage, type ProductListingPageProps, ProductOverviewGrid, type ProductOverviewGridProps, ProductPrice, type ProductPriceProps, ProductSku, type ProductSkuProps, type RefinementListItem, RightArrowFilledIcon, Select, type SelectProps, ShowAll, type ShowAllProps, Sidebar, type SidebarProps, SidebarProvider, TextAlignedArrowIcon, TextField, createSonicSearchClient, useAlgolia, useBreadcrumb, useBreakpoint, useCart, useDebouncedCallback, useDisclosure, useFavorite, useFormattedMessage, useGlobalState, useProductCartLine, useScrollLock };
580
+ export { Accordion, AddToCartButton, AlgoliaCategories, AlgoliaFilterPanel, type AlgoliaFilterPanelProps, AlgoliaFilterSection, AlgoliaMultiSelectFilterSection, AlgoliaPagination, AlgoliaProvider, AlgoliaResultsCount, AlgoliaSortBy, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, type Cart, CartFilledIcon, type CartLine, CartOutlinedIcon, CartProvider, CategoryCarousel, type CategoryCarouselProps, Checkbox, type CheckboxProps$1 as CheckboxProps, ColorCheckbox, type ColorCheckboxProps, ConnectedAddToCartButton, ConnectedBreadcrumb, DehashedOutlinedIcon, FavoriteButton, type FavoriteButtonProps, FavoriteFilledIcon, FavoriteOutlinedIcon, FavoriteProvider, type FilterOption, type Filters, FormattedMessage, type FormattedMessageFunction, type FormattedMessageProps, GlobalStateProvider, GlobalStateProviderContext, HashedOutlinedIcon, IconButton, type IconButtonProps, Image, IntlProvider, LeftArrowFilledIcon, LinkButton, type LinkButtonProps, MultiSelect, type MultiSelectProps, NumberField, type NumberFieldSize, Page, PageLayout, type PageProps, type Product, ProductCard, type ProductCardProps, ProductListingPage, type ProductListingPageProps, ProductOverviewGrid, type ProductOverviewGridProps, ProductPrice, type ProductPriceProps, ProductSku, type ProductSkuProps, type RefinementListItem, RightArrowFilledIcon, Select, type SelectProps, ShowAll, type ShowAllProps, Sidebar, type SidebarProps, SidebarProvider, TextAlignedArrowIcon, TextField, createSonicSearchClient, useAlgolia, useBreakpoint, useCart, useDebouncedCallback, useDisclosure, useFavorite, useFormattedMessage, useGlobalState, useProductCartLine, useScrollLock };
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@ import React, { useState, useEffect, useRef, useCallback, createContext, useCont
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { Breadcrumbs, Breadcrumb as Breadcrumb$1, Link, Button as Button$1, FieldError as FieldError$1, useContextProps, InputContext, Input as Input$1, Label as Label$1, NumberField as NumberField$1, Checkbox as Checkbox$1, Select as Select$1, SelectValue, Popover, ListBox, Section, Header, ListBoxItem, TextAreaContext, TextArea as TextArea$1, TextField as TextField$1 } from 'react-aria-components';
4
4
  import clsx from 'clsx';
5
- import { useCurrentRefinements, useClearRefinements, useRefinementList, useHits, useDynamicWidgets, usePagination, useSortBy, InstantSearch, Configure } from 'react-instantsearch';
5
+ import { useCurrentRefinements, useClearRefinements, useRefinementList, useHits, useDynamicWidgets, usePagination, InstantSearch, Configure, useSortBy } from 'react-instantsearch';
6
6
  import { history } from 'instantsearch.js/es/lib/routers/index.js';
7
7
  import { simple } from 'instantsearch.js/es/lib/stateMappings/index.js';
8
+ import { useQuery } from '@tanstack/react-query';
8
9
 
9
10
  /* eslint-disable sort-keys-fix/sort-keys-fix */
10
11
  const breakpoints$1 = {
@@ -183,21 +184,6 @@ function useGlobalState(key, initialState) {
183
184
  return [rerenderState, (value) => (state.value = value)];
184
185
  }
185
186
 
186
- function BreadcrumbProvider(props) {
187
- const [, updateState] = useGlobalState('breadcrumb', props);
188
- useEffect(() => {
189
- updateState(props);
190
- }, [props, updateState]);
191
- return null;
192
- }
193
- function useBreadcrumb() {
194
- const [state] = useGlobalState('breadcrumb');
195
- if (!state) {
196
- throw new Error('useBreadcrumb must be used together with the BreadcrumbProvider');
197
- }
198
- return state;
199
- }
200
-
201
187
  function CartProvider(props) {
202
188
  const [, updateState] = useGlobalState('cart', props);
203
189
  useEffect(() => {
@@ -271,9 +257,27 @@ function BreadcrumbLong({ links }) {
271
257
  return (jsxs(Breadcrumbs, { className: styles$x.breadcrumbs, children: [jsx(Breadcrumb$1, { className: styles$x.breadcrumb, children: jsx(Link, { className: styles$x.link, href: homeLink.href, children: jsx(HomeFilledIcon, { className: clsx(styles$x['home-icon'], styles$x.icon) }) }) }), linksWithoutFirst.map((link, index) => (jsx(Breadcrumb$1, { className: styles$x.breadcrumb, children: jsxs(Link, { className: styles$x.link, href: link.href, isDisabled: index === linksWithoutFirst.length - 1, children: [jsx(ChevronLeftFilledIcon, { className: clsx(styles$x['previous-icon'], styles$x.icon) }), link.label] }) }, index)))] }));
272
258
  }
273
259
 
260
+ const ProductListingPageContext = createContext({
261
+ data: undefined,
262
+ });
263
+
264
+ function ProductListingPageProvider({ children, data, error, isError, isLoading, }) {
265
+ return (jsx(ProductListingPageContext.Provider, { value: { data, error, isError, isLoading }, children: children }));
266
+ }
267
+ function useProductListingPageProvider() {
268
+ return useContext(ProductListingPageContext);
269
+ }
270
+
271
+ function useBreadcrumb() {
272
+ const { data, isLoading } = useProductListingPageProvider();
273
+ return { breadCrumb: data?.breadCrumb, isLoading };
274
+ }
275
+
274
276
  function ConnectedBreadcrumb() {
275
- const { links } = useBreadcrumb();
276
- return jsx(Breadcrumb, { links: links });
277
+ const { breadCrumb } = useBreadcrumb();
278
+ if (!breadCrumb)
279
+ return null;
280
+ return jsx(Breadcrumb, { links: breadCrumb });
277
281
  }
278
282
 
279
283
  function TextAlignedArrowIcon(props) {
@@ -6000,7 +6004,7 @@ function ChevronRightFilledIcon(props) {
6000
6004
  var styles$3 = {"pagination":"pagination-module-k4OgY","page-number-container":"pagination-module-oq89A"};
6001
6005
 
6002
6006
  function Pagination({ currentPage, onChange, totalPages, }) {
6003
- return (jsxs("div", { className: styles$3.pagination, children: [jsx(IconButton, { isDisabled: currentPage === 1, onPress: () => onChange(currentPage - 1), children: jsx(ChevronLeftFilledIcon, {}) }), jsxs("div", { className: styles$3['page-number-container'], children: [jsx(NumberField, { autoGrow: true, label: "current-page", maxValue: totalPages, minValue: 1, onChange: onChange, value: currentPage }), jsx(FormattedMessage, { id: "of" }), jsx("div", { children: totalPages })] }), jsx(IconButton, { isDisabled: currentPage >= totalPages, onPress: () => onChange(currentPage + 1), children: jsx(ChevronRightFilledIcon, {}) })] }));
6007
+ return (jsxs("div", { className: styles$3.pagination, children: [jsx(IconButton, { color: "secondary", isDisabled: currentPage === 1, onPress: () => onChange(currentPage - 1), children: jsx(ChevronLeftFilledIcon, {}) }), jsxs("div", { className: styles$3['page-number-container'], children: [jsx(NumberField, { autoGrow: true, label: "current-page", maxValue: totalPages, minValue: 1, onChange: onChange, value: currentPage }), jsx(FormattedMessage, { id: "of" }), jsx("div", { children: totalPages })] }), jsx(IconButton, { color: "secondary", isDisabled: currentPage >= totalPages, onPress: () => onChange(currentPage + 1), children: jsx(ChevronRightFilledIcon, {}) })] }));
6004
6008
  }
6005
6009
 
6006
6010
  function AlgoliaPagination({ onChange }) {
@@ -6012,6 +6016,102 @@ function AlgoliaPagination({ onChange }) {
6012
6016
  return (jsx(Pagination, { currentPage: currentRefinement + 1, onChange: pageNumber => handlePageChange(pageNumber), totalPages: nbPages }));
6013
6017
  }
6014
6018
 
6019
+ const queryStringRouting = {
6020
+ router: history({
6021
+ cleanUrlOnDispose: true,
6022
+ createURL({ location, qsModule: qs, routeState }) {
6023
+ const indexNames = Object.keys(routeState);
6024
+ if (indexNames.length === 0)
6025
+ return location.href;
6026
+ if (indexNames.length !== 1)
6027
+ throw new Error('Only one index is supported');
6028
+ const queryString = qs.parse(location.search.slice(1), {
6029
+ allowDots: true,
6030
+ duplicates: 'combine',
6031
+ });
6032
+ const state = routeState[indexNames[0]];
6033
+ queryString.filters = state.refinementList;
6034
+ queryString.sortBy = state.sortBy;
6035
+ const newQueryString = qs.stringify(queryString, {
6036
+ addQueryPrefix: true,
6037
+ allowDots: true,
6038
+ arrayFormat: 'repeat',
6039
+ });
6040
+ return `${location.href.split('?')[0]}${newQueryString}`;
6041
+ },
6042
+ parseURL({ location, qsModule: qs }) {
6043
+ const queryString = qs.parse(location.search.slice(1), {
6044
+ allowDots: true,
6045
+ duplicates: 'combine',
6046
+ });
6047
+ const refinementList = Object.keys(queryString.filters || {}).reduce((refinementList, filter) => {
6048
+ refinementList[filter] = [].concat(queryString.filters?.[filter]);
6049
+ return refinementList;
6050
+ }, {});
6051
+ const uiState = {
6052
+ dev_sonic_products_en: {
6053
+ refinementList,
6054
+ sortBy: queryString.sortBy?.toString(),
6055
+ },
6056
+ };
6057
+ return uiState;
6058
+ },
6059
+ windowTitle({ category, query }) {
6060
+ const queryTitle = query ? `Results for "${query}"` : 'Search';
6061
+ if (category) {
6062
+ return `${category} – ${queryTitle}`;
6063
+ }
6064
+ return queryTitle;
6065
+ },
6066
+ }),
6067
+ stateMapping: simple(),
6068
+ };
6069
+
6070
+ const AlgoliaContext = createContext({
6071
+ online: false,
6072
+ setOnline: () => { },
6073
+ toggleOnline: () => { },
6074
+ });
6075
+ function AlgoliaProvider({ category, children, offlineSearchClient, online: _online = true, routing = queryStringRouting, searchClient, }) {
6076
+ const [online, setOnline] = useState(_online);
6077
+ if (!category) {
6078
+ // TODO: Implement loading page
6079
+ return jsx("h1", { children: "Loading..." });
6080
+ }
6081
+ return (jsx(AlgoliaContext.Provider, { value: {
6082
+ online,
6083
+ setOnline,
6084
+ toggleOnline: () => setOnline(online => !online),
6085
+ }, children: jsxs(InstantSearch, { future: {
6086
+ persistHierarchicalRootCount: true,
6087
+ preserveSharedStateOnUnmount: true,
6088
+ }, indexName: "dev_sonic_products_en", routing: routing, searchClient: online ? searchClient : offlineSearchClient || searchClient, children: [jsx(Configure, { analytics: false, filters: category.length
6089
+ ? `categoryPages: '${category.join(' > ')}'`
6090
+ : undefined, hitsPerPage: 9, maxValuesPerFacet: 100, ruleContexts: ['storefront'] }), children] }) }));
6091
+ }
6092
+ function useAlgolia() {
6093
+ return useContext(AlgoliaContext);
6094
+ }
6095
+
6096
+ const offlineSearchClient = {
6097
+ search(_queries, _requestOptions) {
6098
+ return fetch('data/algolia.json').then(response => response.json());
6099
+ },
6100
+ async searchForFacetValues(_queries, _requestOptions) {
6101
+ return [];
6102
+ },
6103
+ };
6104
+
6105
+ const createSonicSearchClient = (url) => ({
6106
+ async search(requests) {
6107
+ return fetch(url, {
6108
+ body: JSON.stringify({ requests }),
6109
+ headers: { 'Content-Type': 'application/json' },
6110
+ method: 'post',
6111
+ }).then(res => res.json());
6112
+ },
6113
+ });
6114
+
6015
6115
  function AlgoliaSortBy() {
6016
6116
  const { currentRefinement, options: algoliaOptions, refine, } = useSortBy({
6017
6117
  items: [
@@ -6032,15 +6132,68 @@ function ConnectedProductCart({ productId, ...props }) {
6032
6132
  return (jsx(ProductCard, { ...props, addToCartButton: jsx(ConnectedAddToCartButton, { productId: productId }) }));
6033
6133
  }
6034
6134
 
6035
- const useScrollTo = (ref, scrollOptions) => () => {
6036
- if (!ref.current)
6037
- return;
6038
- const rect = ref.current.getBoundingClientRect();
6039
- const scrollPadding = 12;
6040
- const scrollY = window.scrollY + rect.top - scrollPadding;
6135
+ function useSubcatagories() {
6136
+ const { data, isLoading } = useProductListingPageProvider();
6137
+ return { isLoading, subcategories: data?.subcategories };
6138
+ }
6139
+
6140
+ function ConnectedCategoryCarousel() {
6141
+ const { isLoading, subcategories } = useSubcatagories();
6142
+ if (isLoading || !subcategories)
6143
+ return null;
6144
+ return jsx(CategoryCarousel, { categories: subcategories });
6145
+ }
6146
+
6147
+ class ResponseError extends Error {
6148
+ constructor(response) {
6149
+ super(response.statusText);
6150
+ Object.defineProperty(this, "response", {
6151
+ enumerable: true,
6152
+ configurable: true,
6153
+ writable: true,
6154
+ value: void 0
6155
+ });
6156
+ Object.defineProperty(this, "status", {
6157
+ enumerable: true,
6158
+ configurable: true,
6159
+ writable: true,
6160
+ value: void 0
6161
+ });
6162
+ Object.defineProperty(this, "statusText", {
6163
+ enumerable: true,
6164
+ configurable: true,
6165
+ writable: true,
6166
+ value: void 0
6167
+ });
6168
+ Object.defineProperty(this, "type", {
6169
+ enumerable: true,
6170
+ configurable: true,
6171
+ writable: true,
6172
+ value: void 0
6173
+ });
6174
+ Object.defineProperty(this, "url", {
6175
+ enumerable: true,
6176
+ configurable: true,
6177
+ writable: true,
6178
+ value: void 0
6179
+ });
6180
+ this.message = response.statusText;
6181
+ this.name = 'ResponseError';
6182
+ this.response = response;
6183
+ this.status = response.status;
6184
+ this.statusText = response.statusText;
6185
+ this.type = response.type;
6186
+ this.url = response.url;
6187
+ }
6188
+ }
6189
+ function isResponseError(error) {
6190
+ return error instanceof ResponseError;
6191
+ }
6192
+
6193
+ const scrollToTop = (scrollOptions) => {
6041
6194
  window.scrollTo({
6042
6195
  behavior: 'smooth',
6043
- top: scrollY,
6196
+ top: 0,
6044
6197
  });
6045
6198
  };
6046
6199
 
@@ -6077,52 +6230,97 @@ const ToggleSidebarButton = () => {
6077
6230
  return (jsx(Button, { color: "secondary", icon: jsx(FilterOutlinedIcon, {}), onPress: toggle, size: "sm", variant: "outline", children: isOpen ? (jsx(FormattedMessage, { id: "Hide filters" })) : (jsx(FormattedMessage, { id: "Show filters" })) }));
6078
6231
  };
6079
6232
 
6233
+ function useFetchProductListingPageData(bffUrl, pageUrl) {
6234
+ return useQuery({
6235
+ queryFn: async () => {
6236
+ const response = await fetch(`${bffUrl}/api/v1/plp/?pageUrl=${pageUrl}`);
6237
+ if (!response.ok)
6238
+ throw new ResponseError(response);
6239
+ return (await response.json());
6240
+ },
6241
+ queryKey: [bffUrl, 'product-listing-page-data', pageUrl],
6242
+ retry: false,
6243
+ select: (data) => {
6244
+ return {
6245
+ banner: data.banner,
6246
+ breadCrumb: data.breadCrumb.map(breadCrumb => ({
6247
+ href: breadCrumb.url,
6248
+ label: breadCrumb.text,
6249
+ })),
6250
+ category: {
6251
+ href: data.categories.path,
6252
+ image: {
6253
+ alt: data.categories.imageAltText,
6254
+ src: data.categories.smallImagePath,
6255
+ title: data.categories.shortDescription,
6256
+ },
6257
+ title: data.categories.shortDescription,
6258
+ },
6259
+ subcategories: data?.categories?.subCategories?.map(subcategory => ({
6260
+ href: subcategory.path,
6261
+ image: {
6262
+ alt: subcategory.imageAltText,
6263
+ src: subcategory.smallImagePath,
6264
+ title: subcategory.shortDescription,
6265
+ },
6266
+ title: subcategory.shortDescription,
6267
+ })),
6268
+ };
6269
+ },
6270
+ });
6271
+ }
6272
+
6080
6273
  var styles$1 = {"product-listing":"product-listing-page-module-dmIHF","header":"product-listing-page-module-Oz76Z","action-bar":"product-listing-page-module-XxGrr","sort":"product-listing-page-module-aQzHr","count":"product-listing-page-module-zx79v","categories":"product-listing-page-module-R4aOl","product-grid-container":"product-listing-page-module-ICkKg","product-grid":"product-listing-page-module-LHE7z","pagination":"product-listing-page-module-xsRaj"};
6081
6274
 
6082
- const categories = Array.from({ length: 20 }, () => ({
6083
- href: '#',
6084
- image: {
6085
- alt: 'Category 1',
6086
- fit: 'contain',
6087
- height: 96,
6088
- src: '/images/pliers-md.png',
6089
- title: 'Category 1',
6090
- width: 96,
6091
- },
6092
- isSelected: false,
6093
- title: 'Schroevendraaier',
6094
- }));
6095
- function ProductListingPage({ category, isLoading = false, }) {
6275
+ function ProductListingPage({ bffUrl, pageUrl, searchClient, }) {
6096
6276
  const { toggle } = useSidebar();
6277
+ const { data, error, isError, isFetching } = useFetchProductListingPageData(bffUrl, pageUrl);
6278
+ if (isError) {
6279
+ if (!isResponseError(error))
6280
+ throw error;
6281
+ // TODO: Implement error page (404, 500, etc.)
6282
+ return (jsxs("h1", { children: [error.status, " - ", error.statusText] }));
6283
+ }
6284
+ if (!data || isFetching) {
6285
+ // TODO: Implement loading page
6286
+ return jsx("h1", { children: "Loading..." });
6287
+ }
6288
+ const category = data?.breadCrumb.slice(1).map(breadCrumb => breadCrumb.label);
6289
+ return (jsx(ProductListingPageProvider, { data: data, error: error, isError: isError, isLoading: isFetching, children: jsx(AlgoliaProvider, { category: category, offlineSearchClient: offlineSearchClient, searchClient: searchClient ||
6290
+ createSonicSearchClient(`${bffUrl}${bffUrl.endsWith('/') ? '' : '/'}search`), children: jsxs(Page, { className: styles$1['product-listing'], title: category.slice().pop(), children: [jsx("section", { className: styles$1.categories, children: jsx(ConnectedCategoryCarousel, {}) }), jsxs("section", { className: styles$1['action-bar'], children: [jsx("div", { children: jsx(ToggleSidebarButton, {}) }), jsx("span", { className: styles$1.count, children: jsx(AlgoliaResultsCount, {}) }), jsx("div", { className: styles$1.sort, children: jsx(AlgoliaSortBy, {}) })] }), jsx("section", { children: jsxs("div", { className: styles$1['product-grid-container'], children: [jsx(Sidebar, { children: jsx(AlgoliaFilterPanel, { onShowProducts: toggle }) }), jsxs("div", { className: styles$1['product-grid'], children: [jsx(ProductOverview, {}), jsx("div", { className: styles$1.pagination, children: jsx(AlgoliaPagination, { onChange: () => scrollToTop() }) })] })] }) })] }) }) }));
6291
+ }
6292
+ function ProductOverview() {
6097
6293
  const { hits: productHits } = useHits();
6098
6294
  const baseUrl = location.href.split('?')[0];
6099
- const productGrid = useRef(null);
6100
- const scrollToTopOfProductGrid = useScrollTo(productGrid);
6101
- return (jsxs(Page, { className: styles$1['product-listing'], title: category.slice().pop(), children: [categories?.length > 0 && (jsx("section", { className: styles$1.categories, children: jsx(CategoryCarousel, { categories: categories }) })), jsxs("div", { ref: productGrid, children: [jsxs("section", { className: styles$1['action-bar'], children: [jsx("div", { children: jsx(ToggleSidebarButton, {}) }), jsx("span", { className: styles$1.count, children: jsx(AlgoliaResultsCount, {}) }), jsx("div", { className: styles$1.sort, children: jsx(AlgoliaSortBy, {}) })] }), jsx("section", { children: isLoading ? (jsx("p", { children: "Loading..." })) : (jsxs("div", { className: styles$1['product-grid-container'], children: [jsx(Sidebar, { children: jsx(AlgoliaFilterPanel, { onShowProducts: toggle }) }), jsxs("div", { className: styles$1['product-grid'], children: [jsx(ProductOverviewGrid, { children: productHits.map(productHit => (jsx(ConnectedProductCart, { href: `${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${productHit.id}-${productHit.name.replace(/ /g, '-')}`, image: {
6102
- alt: productHit.name,
6103
- fit: 'contain',
6104
- sizes: { lg: 288, md: 204, sm: 122 },
6105
- src: productHit.images?.find(image => image.quality === 'large')?.url || '',
6106
- srcSet: [
6107
- {
6108
- url: productHit.images?.find(image => image.quality === 'small')?.url || '',
6109
- width: 122,
6110
- },
6111
- {
6112
- url: productHit.images?.find(image => image.quality === 'medium')?.url || '',
6113
- width: 204,
6114
- },
6115
- {
6116
- url: productHit.images?.find(image => image.quality === 'large')?.url || '',
6117
- width: 288,
6118
- },
6119
- ],
6120
- title: productHit.name,
6121
- }, price: {
6122
- current: productHit.discountedPrice,
6123
- includeVat: false,
6124
- original: productHit.price,
6125
- }, productId: productHit.id, sku: productHit.ean, tag: "new", title: productHit.name }, productHit.id))) }), jsx("div", { className: styles$1.pagination, children: jsx(AlgoliaPagination, { onChange: scrollToTopOfProductGrid }) })] })] })) })] })] }));
6295
+ return (jsx(ProductOverviewGrid, { children: productHits.map(productHit => (jsx(ConnectedProductCart, { href: `${baseUrl}${baseUrl.endsWith('/') ? '' : '/'}${productHit.id}-${productHit.name.replace(/ /g, '-')}`, image: {
6296
+ alt: productHit.name,
6297
+ fit: 'contain',
6298
+ sizes: { lg: 288, md: 204, sm: 122 },
6299
+ src: productHit.images?.find(image => image.quality === 'large')
6300
+ ?.url || '',
6301
+ srcSet: [
6302
+ {
6303
+ url: productHit.images?.find(image => image.quality === 'small')
6304
+ ?.url || '',
6305
+ width: 122,
6306
+ },
6307
+ {
6308
+ url: productHit.images?.find(image => image.quality === 'medium')
6309
+ ?.url || '',
6310
+ width: 204,
6311
+ },
6312
+ {
6313
+ url: productHit.images?.find(image => image.quality === 'large')
6314
+ ?.url || '',
6315
+ width: 288,
6316
+ },
6317
+ ],
6318
+ title: productHit.name,
6319
+ }, price: {
6320
+ current: productHit.discountedPrice,
6321
+ includeVat: false,
6322
+ original: productHit.price,
6323
+ }, productId: productHit.id, sku: productHit.ean, tag: "new", title: productHit.name }, productHit.objectID))) }));
6126
6324
  }
6127
6325
 
6128
6326
  var styles = {"sidebar-container":"sidebar-provider-module-rjeCL","transition":"sidebar-provider-module-C0cKR","sidebar-background":"sidebar-provider-module-VRgS9","is-open":"sidebar-provider-module-lxq2-"};
@@ -6164,87 +6362,4 @@ function SidebarProvider({ children }) {
6164
6362
  }), onClick: closeTransitions }), children] }) }));
6165
6363
  }
6166
6364
 
6167
- const createSonicSearchClient = (url) => ({
6168
- async search(requests) {
6169
- return fetch(url, {
6170
- body: JSON.stringify({ requests }),
6171
- headers: { 'Content-Type': 'application/json' },
6172
- method: 'post',
6173
- }).then(res => res.json());
6174
- },
6175
- });
6176
-
6177
- const queryStringRouting = {
6178
- router: history({
6179
- cleanUrlOnDispose: true,
6180
- createURL({ location, qsModule: qs, routeState }) {
6181
- const indexNames = Object.keys(routeState);
6182
- if (indexNames.length === 0)
6183
- return location.href;
6184
- if (indexNames.length !== 1)
6185
- throw new Error('Only one index is supported');
6186
- const queryString = qs.parse(location.search.slice(1), {
6187
- allowDots: true,
6188
- duplicates: 'combine',
6189
- });
6190
- const state = routeState[indexNames[0]];
6191
- queryString.filters = state.refinementList;
6192
- queryString.sortBy = state.sortBy;
6193
- const newQueryString = qs.stringify(queryString, {
6194
- addQueryPrefix: true,
6195
- allowDots: true,
6196
- arrayFormat: 'repeat',
6197
- });
6198
- return `${location.href.split('?')[0]}${newQueryString}`;
6199
- },
6200
- parseURL({ location, qsModule: qs }) {
6201
- const queryString = qs.parse(location.search.slice(1), {
6202
- allowDots: true,
6203
- duplicates: 'combine',
6204
- });
6205
- const refinementList = Object.keys(queryString.filters || {}).reduce((refinementList, filter) => {
6206
- refinementList[filter] = [].concat(queryString.filters?.[filter]);
6207
- return refinementList;
6208
- }, {});
6209
- const uiState = {
6210
- dev_sonic_products_en: {
6211
- refinementList,
6212
- sortBy: queryString.sortBy?.toString(),
6213
- },
6214
- };
6215
- return uiState;
6216
- },
6217
- windowTitle({ category, query }) {
6218
- const queryTitle = query ? `Results for "${query}"` : 'Search';
6219
- if (category) {
6220
- return `${category} – ${queryTitle}`;
6221
- }
6222
- return queryTitle;
6223
- },
6224
- }),
6225
- stateMapping: simple(),
6226
- };
6227
-
6228
- const AlgoliaContext = createContext({
6229
- online: false,
6230
- setOnline: () => { },
6231
- toggleOnline: () => { },
6232
- });
6233
- function AlgoliaProvider({ category, children, offlineSearchClient, online: _online = true, routing = queryStringRouting, searchClient, }) {
6234
- const [online, setOnline] = useState(_online);
6235
- return (jsx(AlgoliaContext.Provider, { value: {
6236
- online,
6237
- setOnline,
6238
- toggleOnline: () => setOnline(online => !online),
6239
- }, children: jsxs(InstantSearch, { future: {
6240
- persistHierarchicalRootCount: true,
6241
- preserveSharedStateOnUnmount: true,
6242
- }, indexName: "dev_sonic_products_en", routing: routing, searchClient: online ? searchClient : offlineSearchClient || searchClient, children: [jsx(Configure, { analytics: false, filters: category.length
6243
- ? `categoryPages: '${category.join(' > ')}'`
6244
- : undefined, hitsPerPage: 9, maxValuesPerFacet: 100, ruleContexts: ['storefront'] }), children] }) }));
6245
- }
6246
- function useAlgolia() {
6247
- return useContext(AlgoliaContext);
6248
- }
6249
-
6250
- export { Accordion, AddToCartButton, AlgoliaCategories, AlgoliaFilterPanel, AlgoliaFilterSection, AlgoliaMultiSelectFilterSection, AlgoliaPagination, AlgoliaProvider, AlgoliaResultsCount, AlgoliaSortBy, Breadcrumb, BreadcrumbProvider, Button, CartFilledIcon, CartOutlinedIcon, CartProvider, CategoryCarousel, Checkbox, ColorCheckbox, ConnectedAddToCartButton, ConnectedBreadcrumb, DehashedOutlinedIcon, FavoriteButton, FavoriteFilledIcon, FavoriteOutlinedIcon, FavoriteProvider, FormattedMessage, GlobalStateProvider, GlobalStateProviderContext, HashedOutlinedIcon, IconButton, Image, IntlProvider, LeftArrowFilledIcon, LinkButton, MultiSelect, NumberField, Page, PageLayout, ProductCard, ProductListingPage, ProductOverviewGrid, ProductPrice, ProductSku, RightArrowFilledIcon, Select, ShowAll, Sidebar, SidebarProvider, TextAlignedArrowIcon, TextField, createSonicSearchClient, useAlgolia, useBreadcrumb, useBreakpoint, useCart, useDebouncedCallback, useDisclosure, useFavorite, useFormattedMessage, useGlobalState, useProductCartLine, useScrollLock };
6365
+ export { Accordion, AddToCartButton, AlgoliaCategories, AlgoliaFilterPanel, AlgoliaFilterSection, AlgoliaMultiSelectFilterSection, AlgoliaPagination, AlgoliaProvider, AlgoliaResultsCount, AlgoliaSortBy, Breadcrumb, Button, CartFilledIcon, CartOutlinedIcon, CartProvider, CategoryCarousel, Checkbox, ColorCheckbox, ConnectedAddToCartButton, ConnectedBreadcrumb, DehashedOutlinedIcon, FavoriteButton, FavoriteFilledIcon, FavoriteOutlinedIcon, FavoriteProvider, FormattedMessage, GlobalStateProvider, GlobalStateProviderContext, HashedOutlinedIcon, IconButton, Image, IntlProvider, LeftArrowFilledIcon, LinkButton, MultiSelect, NumberField, Page, PageLayout, ProductCard, ProductListingPage, ProductOverviewGrid, ProductPrice, ProductSku, RightArrowFilledIcon, Select, ShowAll, Sidebar, SidebarProvider, TextAlignedArrowIcon, TextField, createSonicSearchClient, useAlgolia, useBreakpoint, useCart, useDebouncedCallback, useDisclosure, useFavorite, useFormattedMessage, useGlobalState, useProductCartLine, useScrollLock };
@@ -1,21 +1,4 @@
1
- interface Source {
2
- url: string;
3
- width: number;
4
- }
5
- type Breakpoint = 'lg' | 'md' | 'sm';
6
- type Sizes = Record<Breakpoint, number>;
7
- export interface ImageProps {
8
- alt: string;
9
- className?: string;
10
- fallbackSrc?: string;
11
- fit?: 'contain' | 'cover';
12
- height?: number;
13
- loading?: 'lazy' | 'eager';
14
- sizes?: Sizes;
15
- src: string;
16
- srcSet?: Source[];
17
- title: string;
18
- width?: number;
19
- }
1
+ import type { ImageSource } from 'shared/types/image';
2
+ type ImageProps = ImageSource;
20
3
  export declare function Image({ alt, className, fallbackSrc, fit, height, loading, sizes: sizesProp, src, srcSet: srcSetProp, title, width, }: ImageProps): import("react/jsx-runtime").JSX.Element;
21
4
  export {};
@@ -0,0 +1,20 @@
1
+ import { Category } from 'shared/types/category';
2
+ export interface BannerItem {
3
+ alt: string;
4
+ srcSet: {
5
+ src: string;
6
+ width: number;
7
+ }[];
8
+ }
9
+ export interface Link {
10
+ href: string;
11
+ label: string;
12
+ }
13
+ export interface ProductListingPageData {
14
+ banner: {
15
+ top: BannerItem[];
16
+ };
17
+ breadCrumb: Link[];
18
+ category: Category;
19
+ subcategories: Category[] | undefined;
20
+ }
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ import { ProductListingPageData } from 'pages/product-listing-page/product-listing-page-data-types';
3
+ export interface ProductListingPageContextType {
4
+ data: ProductListingPageData | undefined;
5
+ error?: Error | null;
6
+ isError?: boolean;
7
+ isLoading?: boolean;
8
+ }
9
+ export declare const ProductListingPageContext: React.Context<ProductListingPageContextType>;
@@ -0,0 +1,8 @@
1
+ /// <reference types="react" />
2
+ import { ProductListingPageContextType } from './product-listing-page-context';
3
+ interface ProductListingPageProviderProps extends ProductListingPageContextType {
4
+ children: React.ReactNode;
5
+ }
6
+ export declare function ProductListingPageProvider({ children, data, error, isError, isLoading, }: ProductListingPageProviderProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useProductListingPageProvider(): ProductListingPageContextType;
8
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare function useBreadcrumb(): {
2
+ breadCrumb: import("../product-listing-page-data-types").Link[] | undefined;
3
+ isLoading: boolean | undefined;
4
+ };
@@ -0,0 +1,4 @@
1
+ export declare function useSubcatagories(): {
2
+ isLoading: boolean | undefined;
3
+ subcategories: import("../../../shared/types/category").Category[] | undefined;
4
+ };
@@ -1,3 +1,4 @@
1
+ import { SearchClient } from 'algoliasearch/lite';
1
2
  export interface Filters {
2
3
  color: {
3
4
  options: {
@@ -8,7 +9,8 @@ export interface Filters {
8
9
  };
9
10
  }
10
11
  export type ProductListingPageProps = {
11
- category: string[];
12
- isLoading?: boolean;
12
+ bffUrl: string;
13
+ pageUrl: string;
14
+ searchClient?: SearchClient;
13
15
  };
14
- export declare function ProductListingPage({ category, isLoading, }: ProductListingPageProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function ProductListingPage({ bffUrl, pageUrl, searchClient, }: ProductListingPageProps): import("react/jsx-runtime").JSX.Element;
@@ -1,25 +1,22 @@
1
1
  import { ComponentProps } from 'react';
2
2
  import { StoryObj } from '@storybook/react';
3
3
  import { ProductListingPage } from './product-listing-page';
4
+ type StoryProps = Omit<ComponentProps<typeof ProductListingPage>, 'searchClient'> & {
5
+ bff: boolean;
6
+ online: boolean;
7
+ };
4
8
  declare const meta: {
5
9
  args: {
6
- BFF: true;
7
- category: string[];
8
- isLoading: false;
10
+ bff: true;
11
+ bffUrl: string;
9
12
  online: false;
13
+ pageUrl: string;
10
14
  };
11
- component: typeof ProductListingPage;
12
15
  parameters: {
13
16
  layout: string;
14
17
  };
15
18
  title: string;
16
19
  };
17
20
  export default meta;
18
- type StoryProps = ComponentProps<typeof ProductListingPage> & {
19
- BFF: boolean;
20
- online: boolean;
21
- };
22
21
  type Story = StoryObj<StoryProps>;
23
22
  export declare const Default: Story;
24
- export declare const Loading: Story;
25
- export declare const WithActiveFilters: Story;
@@ -0,0 +1,34 @@
1
+ export interface ProductHit {
2
+ [key: string]: any;
3
+ alternativeNumber: string;
4
+ categories: string[];
5
+ categoryPageId: string[];
6
+ consumerPrice: number;
7
+ discountedPrice: number;
8
+ drawerQuantity: string;
9
+ ean: string;
10
+ height: number;
11
+ hierarchicalCategories: {
12
+ lvl0: string;
13
+ lvl1: string;
14
+ };
15
+ id: string;
16
+ images: {
17
+ quality: string;
18
+ url: string;
19
+ }[];
20
+ labels: string[];
21
+ length: number;
22
+ listPrice: number;
23
+ moreInformation: string;
24
+ name: string;
25
+ price: number;
26
+ relatedProducts: string;
27
+ restrictionGroups: string[];
28
+ salePrice: number;
29
+ slug: string;
30
+ storefrontId: string;
31
+ storefrontSlug: string;
32
+ weight: number;
33
+ width: number;
34
+ }
@@ -0,0 +1,48 @@
1
+ export interface Category {
2
+ activateOn: string;
3
+ deactivateOn: string | null;
4
+ htmlContent: string;
5
+ id: string;
6
+ imageAltText: string;
7
+ isDynamic: boolean;
8
+ isFeatured: boolean;
9
+ largeImagePath: string;
10
+ metaDescription: string;
11
+ metaKeywords: string;
12
+ mobileBannerImageUrl: string;
13
+ mobilePrimaryText: string;
14
+ mobileSecondaryText: string;
15
+ mobileTextColor: string;
16
+ mobileTextJustification: string;
17
+ name: string;
18
+ path: string;
19
+ properties: {
20
+ pimId: string | null;
21
+ };
22
+ shortDescription: string;
23
+ smallImagePath: string;
24
+ sortOrder: number;
25
+ subCategories: Category[] | null;
26
+ uri: null;
27
+ urlSegment: string;
28
+ }
29
+ export interface BannerItem {
30
+ alt: string;
31
+ srcSet: {
32
+ src: string;
33
+ width: number;
34
+ }[];
35
+ }
36
+ export interface Banner {
37
+ top: BannerItem[];
38
+ }
39
+ export interface BreadCrumb {
40
+ categoryId: string;
41
+ text: string;
42
+ url: string;
43
+ }
44
+ export interface ProductListingPageDataResponse {
45
+ banner: Banner;
46
+ breadCrumb: BreadCrumb[];
47
+ categories: Category;
48
+ }
@@ -0,0 +1,2 @@
1
+ import { ProductListingPageData } from 'pages/product-listing-page/product-listing-page-data-types';
2
+ export declare function useFetchProductListingPageData(bffUrl: string, pageUrl: string): import("@tanstack/react-query").UseQueryResult<ProductListingPageData, Error>;
@@ -0,0 +1,20 @@
1
+ import { StoryObj } from '@storybook/react';
2
+ interface StoryProps {
3
+ bffUrl: string;
4
+ pageUrl: string;
5
+ }
6
+ declare function UseFetchProductListingPageDataStory(args: StoryProps): import("react/jsx-runtime").JSX.Element;
7
+ declare const meta: {
8
+ args: {
9
+ bffUrl: string;
10
+ pageUrl: string;
11
+ };
12
+ component: typeof UseFetchProductListingPageDataStory;
13
+ parameters: {
14
+ layout: string;
15
+ };
16
+ title: string;
17
+ };
18
+ export default meta;
19
+ type Story = StoryObj<typeof meta>;
20
+ export declare const Default: Story;
@@ -0,0 +1,9 @@
1
+ export declare class ResponseError extends Error {
2
+ response: Response;
3
+ status: number;
4
+ statusText: string;
5
+ type: ResponseType;
6
+ url: string;
7
+ constructor(response: Response);
8
+ }
9
+ export declare function isResponseError(error: unknown): error is ResponseError;
@@ -3,5 +3,6 @@ interface ScrollOptions {
3
3
  behavior?: ScrollBehavior;
4
4
  scrollPadding?: number;
5
5
  }
6
+ export declare const scrollToTop: (scrollOptions?: ScrollToOptions) => void;
6
7
  export declare const useScrollTo: (ref: RefObject<HTMLElement>, scrollOptions?: ScrollOptions) => () => void;
7
8
  export {};
@@ -0,0 +1,6 @@
1
+ import type { ImageSource } from './image';
2
+ export interface Category {
3
+ href: string;
4
+ image: ImageSource;
5
+ title: string;
6
+ }
@@ -0,0 +1,20 @@
1
+ type Breakpoint = 'lg' | 'md' | 'sm';
2
+ type Sizes = Record<Breakpoint, number>;
3
+ interface Source {
4
+ url: string;
5
+ width: number;
6
+ }
7
+ export interface ImageSource {
8
+ alt: string;
9
+ className?: string;
10
+ fallbackSrc?: string;
11
+ fit?: 'contain' | 'cover';
12
+ height?: number;
13
+ loading?: 'lazy' | 'eager';
14
+ sizes?: Sizes;
15
+ src: string;
16
+ srcSet?: Source[];
17
+ title: string;
18
+ width?: number;
19
+ }
20
+ export {};
package/dist/styles.css CHANGED
@@ -933,6 +933,7 @@
933
933
  display: grid;
934
934
  max-width: 136px;
935
935
  cursor: pointer;
936
+ padding-inline: 10px;
936
937
  place-items: center;
937
938
  }
938
939
 
@@ -990,6 +991,11 @@
990
991
  }
991
992
  }
992
993
 
994
+ @media (width >= 1024px) {.category-card-module-4NUjH {
995
+ padding-inline: 0
996
+ }
997
+ }
998
+
993
999
  @media (width >= 1440px) {.category-card-module-4NUjH {
994
1000
  max-width: 144px
995
1001
  }
@@ -1362,7 +1368,12 @@
1362
1368
  .carousel-module-ealh- .carousel-module-k7Z4S.carousel-module-5SGYn .carousel-module-Hi-0z {
1363
1369
  top: 50%;
1364
1370
  }
1365
- @media (width < 576px) {
1371
+ @media (width < 1024px) {
1372
+ .carousel-module-ealh- {
1373
+ width: calc(100% + 20px);
1374
+ margin-left: -10px
1375
+ }
1376
+
1366
1377
  .carousel-module-ealh- .carousel-module-Hi-0z {
1367
1378
  display: none;
1368
1379
  }
@@ -2197,6 +2208,7 @@
2197
2208
  align-items: center;
2198
2209
  justify-content: space-between;
2199
2210
  margin-bottom: var(--space-24);
2211
+ row-gap: var(--space-8);
2200
2212
  }
2201
2213
  .product-listing-page-module-dmIHF .product-listing-page-module-XxGrr .product-listing-page-module-aQzHr {
2202
2214
  display: flex;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -45,27 +45,27 @@
45
45
  "react-instantsearch-core": "^7"
46
46
  },
47
47
  "devDependencies": {
48
- "@chromatic-com/storybook": "^1.4.0",
48
+ "@chromatic-com/storybook": "^1.5.0",
49
49
  "@csstools/postcss-global-data": "^2.1.1",
50
50
  "@rollup/plugin-alias": "^5.1.0",
51
- "@rollup/plugin-commonjs": "^25.0.8",
51
+ "@rollup/plugin-commonjs": "^26.0.1",
52
52
  "@rollup/plugin-node-resolve": "^15.2.3",
53
53
  "@rollup/plugin-typescript": "^11.1.6",
54
- "@storybook/addon-docs": "^8.1.3",
55
- "@storybook/addon-essentials": "^8.1.3",
56
- "@storybook/addon-interactions": "^8.1.3",
57
- "@storybook/addon-links": "^8.1.3",
58
- "@storybook/addon-viewport": "^8.1.3",
59
- "@storybook/blocks": "^8.1.3",
60
- "@storybook/preview-api": "^8.1.3",
61
- "@storybook/react": "^8.1.3",
62
- "@storybook/react-vite": "^8.1.3",
63
- "@storybook/test": "^8.1.3",
64
- "@types/react": "^18.3.2",
54
+ "@storybook/addon-docs": "^8.1.9",
55
+ "@storybook/addon-essentials": "^8.1.9",
56
+ "@storybook/addon-interactions": "^8.1.9",
57
+ "@storybook/addon-links": "^8.1.9",
58
+ "@storybook/addon-viewport": "^8.1.9",
59
+ "@storybook/blocks": "^8.1.9",
60
+ "@storybook/preview-api": "^8.1.9",
61
+ "@storybook/react": "^8.1.9",
62
+ "@storybook/react-vite": "^8.1.9",
63
+ "@storybook/test": "^8.1.9",
64
+ "@types/react": "^18.3.3",
65
65
  "@types/react-dom": "^18.3.0",
66
- "@typescript-eslint/eslint-plugin": "^7.10.0",
67
- "@typescript-eslint/parser": "^7.10.0",
68
- "@vitejs/plugin-react": "^4.3.0",
66
+ "@typescript-eslint/eslint-plugin": "^7.13.0",
67
+ "@typescript-eslint/parser": "^7.13.0",
68
+ "@vitejs/plugin-react": "^4.3.1",
69
69
  "autoprefixer": "^10.4.19",
70
70
  "concurrently": "^8.2.2",
71
71
  "eslint": "^8.57.0",
@@ -75,7 +75,7 @@
75
75
  "eslint-plugin-mdx": "^3.1.5",
76
76
  "eslint-plugin-no-relative-import-paths": "^1.5.4",
77
77
  "eslint-plugin-prettier": "^5.1.3",
78
- "eslint-plugin-react": "^7.34.1",
78
+ "eslint-plugin-react": "^7.34.2",
79
79
  "eslint-plugin-react-hooks": "^4.6.2",
80
80
  "eslint-plugin-simple-import-sort": "^12.1.0",
81
81
  "eslint-plugin-sort-destructure-keys": "^2.0.0",
@@ -85,7 +85,7 @@
85
85
  "eslint-plugin-unused-imports": "^3.2.0",
86
86
  "http-server": "^14.1.1",
87
87
  "husky": "^9.0.11",
88
- "instantsearch.js": "^4.69.0",
88
+ "instantsearch.js": "^4.71.1",
89
89
  "postcss": "^8.4.38",
90
90
  "postcss-custom-media": "^10.0.6",
91
91
  "postcss-import": "^16.1.0",
@@ -98,13 +98,13 @@
98
98
  "rollup-plugin-peer-deps-external": "^2.2.4",
99
99
  "rollup-plugin-postcss": "^4.0.2",
100
100
  "rollup-plugin-ts": "^3.4.5",
101
- "storybook": "^8.1.3",
102
- "stylelint": "^16.5.0",
101
+ "storybook": "^8.1.9",
102
+ "stylelint": "^16.6.1",
103
103
  "stylelint-config-css-modules": "^4.4.0",
104
104
  "stylelint-config-idiomatic-order": "^10.0.0",
105
105
  "stylelint-config-standard": "^36.0.0",
106
106
  "typescript": "^5.4.5",
107
- "vite": "^5.2.11",
107
+ "vite": "^5.3.1",
108
108
  "vite-tsconfig-paths": "^4.3.2"
109
109
  },
110
110
  "dependencies": {
@@ -113,12 +113,13 @@
113
113
  "@algolia/autocomplete-plugin-recent-searches": "^1.17.2",
114
114
  "@algolia/autocomplete-preset-algolia": "^1.17.2",
115
115
  "@algolia/client-search": "^4.23.3",
116
+ "@tanstack/react-query": "^5.45.0",
116
117
  "algoliasearch": "^4.23.3",
117
118
  "clsx": "^2.1.1",
118
- "instantsearch.js": "^4.69.0",
119
+ "instantsearch.js": "^4.71.1",
119
120
  "react-aria-components": "^1.2.1",
120
- "react-instantsearch": "^7.9.0",
121
- "swiper": "^11.1.3"
121
+ "react-instantsearch": "^7.11.1",
122
+ "swiper": "^11.1.4"
122
123
  },
123
124
  "publishConfig": {
124
125
  "access": "public"
@@ -1,11 +0,0 @@
1
- export interface Link {
2
- href: string;
3
- label: string;
4
- }
5
- interface Props {
6
- links: Link[];
7
- updateLinks: (links: Link[]) => void;
8
- }
9
- export declare function BreadcrumbProvider(props: Props): null;
10
- export declare function useBreadcrumb(): Props;
11
- export {};