@thoughtindustries/catalog
Advanced tools
Comparing version 1.2.3 to 1.2.4
{ | ||
"name": "@thoughtindustries/catalog", | ||
"version": "1.2.3", | ||
"version": "1.2.4", | ||
"description": "A base component for catalog", | ||
@@ -25,13 +25,13 @@ "author": "Lu Jiang <lu.jiang@thoughtindustries.com>", | ||
"@apollo/client": "^3.7.0", | ||
"@thoughtindustries/content": "^1.2.3", | ||
"@thoughtindustries/content": "^1.2.4", | ||
"@thoughtindustries/header": "^1.1.2", | ||
"@thoughtindustries/hooks": "^1.2.2", | ||
"@thoughtindustries/pagination": "^1.2.2", | ||
"clsx": "^1.1.1", | ||
"@thoughtindustries/pagination": "^1.2.3", | ||
"dayjs": "^1.10.8", | ||
"graphql": "^16.6.0", | ||
"i18next": "^21.10.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"react": "^17 || ^18", | ||
"react-dom": "^17 || ^18", | ||
"react-i18next": "^11.18.6", | ||
"tailwind-merge": "^1.14.0", | ||
"use-debounce": "^7.0.1" | ||
@@ -43,3 +43,3 @@ }, | ||
}, | ||
"gitHead": "55420e262db95eb8b0d6a7d223dcf271c5035773" | ||
"gitHead": "a55fe18b2076468826ae5c4cbea0ca8802e31e10" | ||
} |
240
README.md
@@ -5,51 +5,217 @@ # `@thoughtindustries/catalog` | ||
## Import component | ||
**Table of contents:** | ||
- [`Catalog`](#catalog) | ||
- [Core catalog components](#core-catalog-components) | ||
- [`CatalogLinkButton`](#cataloglinkbutton) | ||
- [`CatalogProvider`](#catalogprovider) | ||
- [`HeightEqualizer`](#heightequalizer) | ||
- [Core catalog hooks](#core-catalog-hooks) | ||
- [`useCatalog`](#usecatalog) | ||
## Catalog | ||
The `Catalog` component takes a callback function to handle catalog item's add-to-queue event, and optional properties to customize the catalog like price formatting, currency formating, etc. It must be a descendent of the `CatalogProvider` component. | ||
### Example code | ||
```tsx | ||
import { CatalogProvider, Catalog } from '@thoughtindustries/catalog'; | ||
export function MyComponent() { | ||
// ... | ||
const addToQueueHandler = (item) => Promise.resolve(); | ||
return ( | ||
<CatalogProvider ssr pathName="/catalog"> | ||
<Catalog onAddedToQueue={addToQueueHandler} /> | ||
</CatalogProvider> | ||
); | ||
} | ||
``` | ||
import { Catalog, CatalogProvider } from '@thoughtindustries/catalog'; | ||
## Props | ||
| Name | Required | Type | Description | | ||
| -------- | -------- | ----------------------------- | ------------------------- | | ||
| title | No | <code>string</code> | The title that appears on top of the catalog. | | ||
| alternateTitleDisplay | No | <code>boolean</code> | Option to display the alternative title. | | ||
| pagination | No | <code>(args: PaginationFnArgs) => ReactElement</code> | An alternative view for the pagination display. | | ||
| companyHasSessionLevelCustomFieldsFeature | No | <code>boolean</code> | Company feature flag for content hydration. | | ||
| companyTimeZone | No | <code>string</code> | Company property to override item's timezone. | | ||
| onAddedToQueue | Yes | <code>(item: CatalogResultItem) => Promise<boolean \| void></code> | Event handler for add to queue button for each item. | | ||
| onClick | No | <code>(evt: SyntheticEvent, item: CatalogResultItem) => void</code> | Optional click event handler for each item. | | ||
| priceFormat | No | <code>(priceInCents: number) => string</code> | A callback that is invoked to format monetary value with currency. It takes a number value for the price in cent unit and return the formatted value. Default value uses `Intl.NumberFormat` with props `companyDefaultLocale` and/or `currencyCode` to enable locale-specific currency formatting. | | ||
| companyDefaultLocale | No | <code>string</code> | A locale value to format price when prop `priceFormat` is not specified. Used to speficy the locale in `Intl.NumberFormat`. Default to `en-US`. | | ||
| currencyCode | No | <code>string</code> | A currency code value to format price when prop `priceFormat` is not specified. Used to speficy the currency code in `Intl.NumberFormat`. Default to `USD`. | | ||
| numberOfContentItems | No | <code>number</code> | Specify the number of items to display in the `grid` view. | | ||
## Core catalog components | ||
Core catalog components are objects that contain all of business logic for the catalog concept that they represent. They're used to parse and process data. | ||
### CatalogLinkButton | ||
The `CatalogLinkButton` component renders a link button that conditionally overrides the link behavior to handle client side navigation. It must be a descendent of a `CatalogProvider` component. This is used internally by `Catalog` component to handle both server and client side rendering. If you are composing own version of `Catalog` component for different layout and styles, this component can be useful. | ||
#### Example code | ||
```tsx | ||
import { CatalogProvider, CatalogLinkButton } from '@thoughtindustries/catalog'; | ||
function CustomCatalog() { | ||
// ... | ||
return ( | ||
<> | ||
{/* // ... */} | ||
<CatalogLinkButton href="/catalog?page=2" /> | ||
</> | ||
); | ||
} | ||
export function MyComponent() { | ||
// ... | ||
return ( | ||
<CatalogProvider ssr={false} pathName="/catalog"> | ||
<CustomCatalog /> | ||
</CatalogProvider> | ||
); | ||
} | ||
``` | ||
## Usage | ||
#### Props | ||
This component is inherently an `HTMLAnchorElement` element with only the `onClick` prop omitted. The component will use own `onClick` handler. | ||
#### Related components | ||
- [`CatalogProvider`](#catalogprovider) | ||
#### Related hooks | ||
- [`useCatalog`](#usecatalog) | ||
### CatalogProvider | ||
The `CatalogProvider` component creates a context for using a catalog. It requires an url path name and a `ssr` flag to handle the data fetching as well as user interactions with catalog filters. It creates relevant context values that can be accessed by any descendent component using the `useCatalog` hook. | ||
You must use this component if you want to use the `useCatalog` hook, or if you would like to use the `CatalogLinkButton` component. | ||
#### Example code | ||
```tsx | ||
import { CatalogProvider } from '@thoughtindustries/catalog'; | ||
export function App() { | ||
return <CatalogProvider ssr pathName="/catalog">{/* Your JSX */}</CatalogProvider>; | ||
} | ||
``` | ||
# CatalogProvider takes a config object to parse URL search params and/or handle custom catalog configurations | ||
# Sample config object | ||
const config = { | ||
parsedUrl: { | ||
pathname: '/catalog', | ||
searchString: '?query=test' | ||
} | ||
#### Props | ||
| Name | Required | Type | Description | | ||
| ---------------------- | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ||
| children | Yes | <code>React.ReactNode</code> | Any `ReactNode` elements. | | ||
| pathName | Yes | <code>string</code> | The URL path for the catalog. | | ||
| searchString | No | <code>string</code> | The URL search string used to initialize the catalog. | | ||
| layoutId | No | <code>string</code> | The id of the custom catalog widget layout. Use this along with `widgetId` to specify the custom catalog widget created in the core platform. | | ||
| widgetId | No | <code>string</code> | The id of the custom catalog widget. Use this along with `layoutId` to specify the custom catalog widget created in the core platform. | | ||
| ssr | Yes | <code>boolean</code> | Option to render the catalog on the server side or client side. When set to `true`, catalog will be rendered on the server side and user interactions with the filters will cause a full page load. When set to `false`, catalog will be rendered on the client side and user interactions with the filters will only affect the catalog portion of the page. | | ||
#### Related hooks | ||
- [`useCatalog`](#usecatalog) | ||
### HeightEqualizer | ||
The `HeightEqualizer` component is a wrapper component that calculate the maximum height from its children `HeightEqualizerElement` components. With the max height value, it updates all children `HeightEqualizerElement` components to the same value. This is used internally by `Catalog` component to handle catalog item height in `grid` view. If you are composing own version of `Catalog` component for different layout and styles, this component can be useful. | ||
#### Example code | ||
```tsx | ||
import { HeightEqualizer, HeightEqualizerElement } from '@thoughtindustries/catalog'; | ||
export function MyComponent() { | ||
// ... | ||
return ( | ||
<HeightEqualizer> | ||
<> | ||
<HeightEqualizerElement key="item-1">{/* Your JSX */}</HeightEqualizerElement> | ||
<HeightEqualizerElement key="item-2">{/* Your JSX */}</HeightEqualizerElement> | ||
</> | ||
</HeightEqualizer> | ||
); | ||
} | ||
# Sample config object with custom catalog configurations | ||
const config = { | ||
parsedUrl: { | ||
pathname: '/catalog', | ||
searchString: '?query=test' | ||
}, | ||
``` | ||
#### Props | ||
##### Props for HeightEqualizer | ||
| Name | Required | Type | Description | | ||
| ----------- | -------- | ---------------------- | ---------------------- | | ||
| children | Yes | <code>ReactNode</code> | A `ReactNode` element. | | ||
| timeout | No | <code>number</code> | Time to recalculate heights. | | ||
| animationSpeed | No | <code>number</code> | Time of animation for height change (in milliseconds). | | ||
##### Props for HeightEqualizerElement | ||
| Name | Required | Type | Description | | ||
| ----------- | -------- | ---------------------- | ---------------------- | | ||
| children | No | <code>ReactNode</code> | A `ReactNode` element. | | ||
| name | Yes | <code>string</code> | All heights of elements with the same name will be compared. | | ||
| as | No | <code>string</code> | An HTML tag to be rendered as the base element wrapper. The default is `div`. | | ||
| className | No | <code>string</code> | A string of styling class names to apply to the underlying element. | | ||
## Core catalog hooks | ||
Core catalog hooks are functions that allow you to use state and other methods inside catalog components. | ||
### useCatalog | ||
The `useCatalog` hook provides access to the catalog context. It must be a descendent of a `CatalogProvider` component. | ||
#### Example code | ||
```tsx | ||
import { CatalogProvider, useCatalog } from '@thoughtindustries/catalog'; | ||
export function MyComponent() { | ||
return ( | ||
<CatalogProvider ssr={false} pathName="/catalog"> | ||
<CatalogLoader /> | ||
</CatalogProvider> | ||
); | ||
} | ||
<CatalogProvider config={config}> | ||
<Catalog onAddedToQueue={(item) => Promise.resolve()} /> | ||
</CatalogProvider> | ||
export function CatalogLoader() { | ||
const { isLoading } = useCatalog(); | ||
# Or use custom pagination | ||
<CatalogProvider config={config}> | ||
<Catalog | ||
onAddedToQueue={(item) => Promise.resolve()} | ||
pagination={({page, pageSize, total, getPageLink}) => <>...</>} /> | ||
</CatalogProvider> | ||
if (isLoading) { | ||
return <>Loading data</>; | ||
} | ||
# Or use custom price formatter | ||
const priceFormat = (priceInCents) => { | ||
const formatter = new Intl.NumberFormat('en-US', { | ||
style: 'currency', | ||
currency: 'USD' | ||
}); | ||
return formatter.format(priceInCents / 100); | ||
return ({/* Your JSX */}); | ||
} | ||
<CatalogProvider config={config}> | ||
<Catalog | ||
onAddedToQueue={(item) => Promise.resolve()} | ||
priceFormat={priceFormat} /> | ||
</CatalogProvider> | ||
``` | ||
#### Return value | ||
The `useCatalog` hook returns an object with the following keys: | ||
| Name | Required | Description | | ||
| ------------------------------- | -------- | ----------- | | ||
| `params` | Yes | The catalog content and metadata. | | ||
| `urlManager` | Yes | The URL manager instance to manage URL state. | | ||
| `ssr` | Yes | This is pass-through value for the prop `ssr` of `CatalogProvider` component. | | ||
| `navigateClientSideAsync` | No | An async function for user interaction with the catalog filters. This value is provided for client side render. | | ||
| `isLoading` | Yes | The loading state of data fetching. During server side render, this value is always `false`. During client side render, this value will reflect the data fetching loading state. | | ||
| `scrollToRef` | No | A reference to `HTMLDivElement`. During client side render, browser will scroll to the top of the assigned element when fetching data. | | ||
| `contentWrapperRef` | No | A reference to `HTMLDivElement`. During client side render, browser will mutate the height of the assigned element when fetching data. This will prevent flashing effect when user interface switches between catalog content and a loading indicator. | | ||
#### Related components | ||
- [`CatalogProvider`](#catalogprovider) |
import { CatalogParams } from '../../utilities/parse-catalog-data'; | ||
import { CatalogParsedURL, CatalogURLManager } from '../../utilities/manage-catalog-url'; | ||
import { ReactNode } from 'react'; | ||
import { ReactNode, RefObject } from 'react'; | ||
export type navigateClientSideAsyncFnParams = { | ||
url: string; | ||
pushToUrl?: boolean; | ||
}; | ||
export type CatalogContextType = { | ||
params: CatalogParams; | ||
urlManager: CatalogURLManager; | ||
ssr: boolean; | ||
navigateClientSideAsync?: (params: navigateClientSideAsyncFnParams) => Promise<void>; | ||
isLoading: boolean; | ||
scrollToRef?: RefObject<HTMLDivElement>; | ||
contentWrapperRef?: RefObject<HTMLDivElement>; | ||
}; | ||
export type CatalogProviderConfig = { | ||
parsedUrl: CatalogParsedURL; | ||
}; | ||
export type CatalogProviderProps = { | ||
config: CatalogProviderConfig; | ||
export type CatalogProviderProps = CatalogParsedURL & { | ||
children: ReactNode; | ||
layoutId?: string; | ||
widgetId?: string; | ||
ssr: boolean; | ||
}; |
@@ -35,4 +35,4 @@ import { Dispatch, ReactNode, SetStateAction } from 'react'; | ||
as?: string; | ||
/** A string of classes to apply to the `div` that wraps the Shop Pay button. */ | ||
/** A string of styling class names to apply to the underlying element. */ | ||
className?: string; | ||
} |
@@ -0,2 +1,3 @@ | ||
export * from './catalog-link-button'; | ||
export * from './catalog-provider'; | ||
export * from './height-equalizer'; |
@@ -7,3 +7,4 @@ import { GlobalTypes } from '@thoughtindustries/content'; | ||
Sort, | ||
serializeSort | ||
serializeSort, | ||
CatalogRequestParams | ||
} from '../parse-catalog-data'; | ||
@@ -30,5 +31,5 @@ import { | ||
export default class CatalogURLManager { | ||
private readonly _pathname; | ||
private readonly _searchParams; | ||
private readonly _parsedRequestParams; | ||
private _pathname = ''; | ||
private _searchParams: URLSearchParams | undefined; | ||
private _parsedRequestParams: Partial<CatalogRequestParams> = {}; | ||
private _isCurated: boolean | undefined; | ||
@@ -38,4 +39,17 @@ private _selectedDisplayType: GlobalTypes.ContentItemDisplayType | undefined; | ||
constructor(parsedUrl: CatalogParsedURL) { | ||
const { pathname, searchString } = parsedUrl; | ||
this._pathname = pathname; | ||
this._parseUrl(parsedUrl); | ||
} | ||
updateUrl(url: string) { | ||
const { pathname, search } = new URL(url, 'http://dummy.com'); | ||
const parsedUrl = { | ||
pathName: pathname, | ||
searchString: search | ||
}; | ||
this._parseUrl(parsedUrl); | ||
} | ||
private _parseUrl(parsedUrl: CatalogParsedURL) { | ||
const { pathName, searchString } = parsedUrl; | ||
this._pathname = pathName; | ||
this._searchParams = new URLSearchParams(searchString || undefined); | ||
@@ -192,6 +206,12 @@ this._parsedRequestParams = toRequestParams(this._searchParams); | ||
composeURLForSetSearchTermForm(): string { | ||
composeActionURLForSetSearchTermForm(): string { | ||
return this._composeURL(''); | ||
} | ||
composeURLForSetSearchTermForm(searchTerm: string): string { | ||
const clonedParams = this._resetOrDefaultClonedParams(); | ||
clonedParams.set(CatalogURLSearchParams.SearchTerm, searchTerm); | ||
return this._composeURL(clonedParams.toString()); | ||
} | ||
composeSearchTermFormHiddenFields(): SearchTermFormHiddenField[] { | ||
@@ -198,0 +218,0 @@ const clonedParams = this._resetOrDefaultClonedParams(); |
import { AggregationFilter } from '../parse-catalog-data'; | ||
export type CatalogParsedURL = { | ||
pathname: string; | ||
pathName: string; | ||
searchString?: string; | ||
@@ -6,0 +6,0 @@ }; |
@@ -25,4 +25,3 @@ import { CatalogParams } from './types'; | ||
// default general params | ||
isLoading: false, | ||
pageSize: DEFAULT_PAGE_SIZE | ||
}; |
@@ -56,3 +56,2 @@ import { GlobalTypes } from '@thoughtindustries/content'; | ||
error?: string; | ||
isLoading: boolean; | ||
pageSize: number; | ||
@@ -59,0 +58,0 @@ }; |
@@ -1,2 +0,2 @@ | ||
import { SyntheticEvent, ReactElement } from 'react'; | ||
import { SyntheticEvent, ReactElement, ElementType } from 'react'; | ||
import { HydratedContentItem, GlobalTypes } from '@thoughtindustries/content'; | ||
@@ -13,7 +13,7 @@ | ||
companyHasSessionLevelCustomFieldsFeature?: boolean; | ||
/** company property to override item timezone */ | ||
/** company property to override item's timezone */ | ||
companyTimeZone?: string; | ||
/** event handler for add to queue button */ | ||
/** event handler for add to queue button for each item */ | ||
onAddedToQueue: (item: CatalogResultItem) => Promise<boolean | void>; | ||
/** optional event handler for result item */ | ||
/** optional event handler for each item */ | ||
onClick?: (evt: SyntheticEvent, item: CatalogResultItem) => void; | ||
@@ -35,2 +35,3 @@ /** optional function for prioritized price formatting */ | ||
getPageLink: (page: number) => string; | ||
linkComponent?: ElementType; | ||
}; | ||
@@ -40,3 +41,3 @@ export type PaginationFn = (args: PaginationFnArgs) => ReactElement; | ||
export interface CatalogProps extends CatalogResultsProps { | ||
/** title that appears on top of the link lists */ | ||
/** title that appears on top of the catalog */ | ||
title?: string; | ||
@@ -43,0 +44,0 @@ /** display alternate title */ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
126982
78
3292
220
+ Addedtailwind-merge@^1.14.0
- Removedclsx@^1.1.1
- Removedclsx@1.2.1(transitive)
Updatedreact@^17 || ^18
Updatedreact-dom@^17 || ^18