Skip to content

Commit

Permalink
Merge pull request #8 from searchspring/recommendation-variants
Browse files Browse the repository at this point in the history
 bundle variant components
  • Loading branch information
korgon authored Oct 1, 2024
2 parents c463584 + 85cb7f0 commit 98e96f3
Show file tree
Hide file tree
Showing 16 changed files with 1,766 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ export interface BundleSelectorSubProps {
icon: Partial<IconProps>;
}

interface BundledCTAProps extends ComponentProps {
ctaSlot?: JSX.Element;
export interface BundledCTAProps extends ComponentProps {
ctaSlot?: JSX.Element | React.FunctionComponent<BundledCTAProps>;
cartStore: CartStore;
onAddToCart: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
ctaIcon?: IconType | Partial<IconProps> | false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { RecommendationControllerConfig } from '@searchspring/snap-controll
export default {
title: 'Templates/RecommendationBundle',
component: RecommendationBundle,
tags: ['autodocs'],
parameters: {
docs: {
page: () => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IconProps, IconType } from '../../Atoms/Icon';
import type { RecommendationController } from '@searchspring/snap-controller';
import type { Product } from '@searchspring/snap-store-mobx';
import { BundleSelector } from './BundleSelector';
import { BundledCTA } from './BundleCTA';
import { BundledCTA, BundledCTAProps } from './BundleCTA';
import { Lang } from '../../../hooks';
import { useIntersection } from '../../../hooks';

Expand Down Expand Up @@ -664,7 +664,12 @@ export interface RecommendationBundleProps extends ComponentProps {
onAddToCart: (e: MouseEvent, items: Product[]) => void;
title?: JSX.Element | string;
breakpoints?: BreakpointsProps;
resultComponent?: ResultComponent<{ seed?: boolean; selected?: boolean; onProductSelect?: (product: Product) => void }>;
resultComponent?: ResultComponent<{
controller: RecommendationController;
seed?: boolean;
selected?: boolean;
onProductSelect?: (product: Product) => void;
}>;
preselectedCount?: number;
hideCheckboxes?: boolean;
hideSeed?: boolean;
Expand All @@ -676,7 +681,7 @@ export interface RecommendationBundleProps extends ComponentProps {
ctaButtonText?: string;
ctaButtonSuccessText?: string;
ctaButtonSuccessTimeout?: number;
ctaSlot?: JSX.Element;
ctaSlot?: JSX.Element | React.FunctionComponent<BundledCTAProps>;
vertical?: boolean;
carousel?: BundleCarouselProps;
slidesPerView?: number; // TODO: remove this prop?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
import { h } from 'preact';

import { ArgsTable, PRIMARY_STORY, Markdown } from '@storybook/blocks';

import { RecommendationBundleEasyAdd, RecommendationBundleEasyAddProps } from './RecommendationBundleEasyAdd';
import { componentArgs, highlightedCode } from '../../../utilities';
import { Snapify } from '../../../utilities/snapify';

import Readme from './readme.md';
import type { RecommendationController } from '@searchspring/snap-controller';
import type { Product } from '@searchspring/snap-store-mobx';
import type { Next } from '@searchspring/snap-event-manager';
import { iconPaths } from '../../Atoms/Icon';
import type { RecommendationControllerConfig } from '@searchspring/snap-controller';

export default {
title: 'Templates/RecommendationBundleEasyAdd',
component: RecommendationBundleEasyAdd,
tags: ['autodocs'],
parameters: {
docs: {
page: () => (
<div>
<Markdown
options={{
overrides: {
code: highlightedCode,
},
}}
>
{Readme}
</Markdown>
<ArgsTable story={PRIMARY_STORY} />
</div>
),
},
},
decorators: [
(Story: any) => (
<div
style={{
maxWidth: '300px',
height: '500px',
}}
>
<Story />
</div>
),
],
argTypes: {
controller: {
description: 'Controller reference',
type: { required: true },
table: {
type: {
summary: 'Controller',
},
},
control: { type: 'none' },
},
results: {
description: 'Results store reference, overrides controller.store.results',
type: { required: false },
table: {
type: {
summary: 'Results store object',
},
},
control: { type: 'none' },
},
resultComponent: {
description: 'Slot for custom result component',
table: {
type: {
summary: 'component',
},
},
},
title: {
description: 'recommendation title',
table: {
type: {
summary: 'string | JSX Element',
},
defaultValue: { summary: '' },
},
control: { type: 'text' },
},
onAddToCart: {
description: 'onClick event handler for add bundle to cart button in CTA',
type: { required: true },
table: {
type: {
summary: 'function',
},
},
action: 'onAddToCart',
},
seedText: {
description: 'Text to render in seed product badge',
table: {
type: {
summary: 'string',
},
defaultValue: { summary: 'Seed Product' },
},
control: { type: 'text' },
},
vertical: {
description: 'set the recommendation to render vertically',
table: {
type: {
summary: 'boolean',
},
defaultValue: { summary: true },
},
control: { type: 'boolean' },
},
ctaButtonText: {
description: 'text to render in add to cart button',
table: {
type: {
summary: 'string',
},
defaultValue: { summary: 'Add All To Cart' },
},
control: { type: 'text' },
},
ctaIcon: {
desciption: 'The `ctaIcon` prop specifies the icon to render in the CTA. Takes an object with `Icon` component props or a string.',
table: {
type: {
summary: 'string',
},
defaultValue: { summary: 'bag' },
},
control: {
type: 'select',
options: [...Object.keys(iconPaths)],
},
},
ctaButtonSuccessText: {
description: 'text to temporarily render in the add to cart button after it is clicked',
table: {
type: {
summary: 'string',
},
defaultValue: { summary: 'Bundle Added!' },
},
control: { type: 'text' },
},
ctaButtonSuccessTimeout: {
description: 'Number of ms to show success text in add to cart button before reverting back to normal text',
defaultValue: 2000,
table: {
type: {
summary: 'number',
},
},
control: { type: 'number' },
},
ctaInline: {
description: 'boolean to enable the stacked add to cart button display',
table: {
type: {
summary: 'boolean',
},
defaultValue: { summary: true },
},
control: { type: 'boolean' },
},
ctaSlot: {
description: 'Slot for custom add to cart component',
table: {
type: {
summary: 'component',
},
},
},
lazyRender: {
description: 'Lazy render settings object',
defaultValue: {
enabled: true,
offset: '10%',
},
table: {
type: {
summary: 'object',
},
defaultValue: { summary: 'Lazy render settings object' },
},
control: { type: 'object' },
},
breakpoints: {
defaultValue: undefined,
description: 'Recommendation title',
table: {
type: {
summary: 'object',
},
defaultValue: { summary: 'Breakpoint object' },
},
control: { type: 'object' },
},
...componentArgs,
},
};

const config: RecommendationControllerConfig = {
id: 'RecommendationBundle',
tag: 'bundle',
globals: {
siteId: '8uyt2m',
products: ['C-AD-W1-1869P'],
},
};

const snapInstance = Snapify.recommendation(config);

export const Default = (
props: RecommendationBundleEasyAddProps,
{ loaded: { controller } }: { loaded: { controller: RecommendationController } }
) => {
return <RecommendationBundleEasyAdd {...props} controller={controller} results={controller.store.results} />;
};

Default.loaders = [
async () => {
snapInstance.on('afterStore', async ({ controller }: { controller: RecommendationController }, next: Next) => {
controller.store.results.forEach((result: Product) => (result.mappings.core!.url = 'javascript:void(0);'));
await next();
});
await snapInstance.search();
return {
controller: snapInstance,
};
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { h } from 'preact';
import { css } from '@emotion/react';
import { observer } from 'mobx-react';

import { defined, mergeProps } from '../../../utilities';
import { Theme, useTheme } from '../../../providers';
import { ComponentProps, StylingCSS } from '../../../types';
import { RecommendationBundle, RecommendationBundleProps } from '../RecommendationBundle';

const CSS = {
RecommendationBundleEasyAdd: ({}: Partial<RecommendationBundleEasyAddProps>) =>
css({
'.ss__recommendation-bundle__wrapper__cta': {
textAlign: 'center',
},
}),
};

export const RecommendationBundleEasyAdd = observer((properties: RecommendationBundleEasyAddProps): JSX.Element => {
const globalTheme: Theme = useTheme();
const defaultProps: Partial<RecommendationBundleEasyAddProps> = {};

const props = mergeProps('recommendationBundleEasyAdd', globalTheme, defaultProps, properties);

const { treePath, styleScript, theme, style, disableStyles, ...additionalProps } = props;

const subProps: RecommendationBundleEasyAddSubProps = {
recommendationBundle: {
// default props
className: 'ss__recommendation-bundle-easy-add',
hideCheckboxes: true,
seedText: '',
ctaButtonText: 'Add Both',
ctaInline: false,
hideSeed: true,
vertical: true,
limit: 1,
carousel: {
enabled: false,
},
separatorIcon: false,
// inherited props
...defined({
disableStyles,
}),
// component theme overrides
theme: props?.theme,
treePath,
},
};

const styling: { css?: StylingCSS } = {};
const stylingProps = { ...props, theme };

if (styleScript && !disableStyles) {
styling.css = [styleScript(stylingProps), style];
} else if (!disableStyles) {
styling.css = [CSS.RecommendationBundleEasyAdd(stylingProps), style];
} else if (style) {
styling.css = [style];
}

return <RecommendationBundle {...styling} {...subProps.recommendationBundle} {...additionalProps} />;
});

export type RecommendationBundleEasyAddProps = Omit<
RecommendationBundleProps,
'hideSeed' | 'limit' | 'hideCheckboxes' | 'carousel' | 'separatorIcon' | 'separatorIconSeedOnly' | 'preselectedCount'
> &
ComponentProps;

interface RecommendationBundleEasyAddSubProps {
recommendationBundle: Partial<RecommendationBundleProps>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './RecommendationBundleEasyAdd';
Loading

0 comments on commit 98e96f3

Please sign in to comment.