Skip to content

Commit

Permalink
feat(panel): spread widgetRenderState in the options in panel (#4527)
Browse files Browse the repository at this point in the history
* update panel

* add a story

* provide an empty object to widgetRenderState by default

* add generic to panel

* update the story to infer the type instead of importing and using the type itself

* extract to PanelRenderOptions

* clean the code

* fix type error

* feat(panel): smoother accessing of widgetRenderState (#4558)

* WIP

* now it works

* better example

* revert for lower ts used here

* gotta be more generic

* WIP trying to replace any with unknown

* add UnknownWidgetFactory

* parenthesis, move back to any (unknown is stricter than any)

* reuse UnknownWidgetFactory in panel component

Co-authored-by: Haroen Viaene <[email protected]>
  • Loading branch information
Eunjae Lee and Haroenv committed Nov 30, 2020
1 parent 5ff5af2 commit 8f82eaa
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 23 deletions.
10 changes: 6 additions & 4 deletions src/components/Panel/Panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState, useEffect, useRef } from 'preact/hooks';
import cx from 'classnames';
import Template from '../Template/Template';
import { PanelCSSClasses, PanelTemplates } from '../../widgets/panel/panel';
import { RenderOptions } from '../../types';
import { RenderOptions, UnknownWidgetFactory } from '../../types';

type PanelComponentCSSClasses = Required<
Omit<
Expand All @@ -17,17 +17,19 @@ type PanelComponentCSSClasses = Required<
>
>;

type PanelProps = {
type PanelProps<TWidget extends UnknownWidgetFactory> = {
hidden: boolean;
collapsible: boolean;
isCollapsed: boolean;
data: RenderOptions;
cssClasses: PanelComponentCSSClasses;
templates: Required<PanelTemplates>;
templates: Required<PanelTemplates<TWidget>>;
bodyElement: HTMLElement;
};

function Panel(props: PanelProps) {
function Panel<TWidget extends UnknownWidgetFactory>(
props: PanelProps<TWidget>
) {
const [isCollapsed, setIsCollapsed] = useState<boolean>(props.isCollapsed);
const [isControlled, setIsControlled] = useState<boolean>(false);
const bodyRef = useRef<HTMLElement | null>(null);
Expand Down
5 changes: 3 additions & 2 deletions src/types/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,9 @@ export type IndexRenderState = Partial<{

export type WidgetRenderState<
TWidgetRenderState,
// @ts-ignore
TWidgetParams
> = TWidgetRenderState & {
widgetParams: any; // @TODO type as TWidgetParams
widgetParams: TWidgetParams;
};

/**
Expand Down Expand Up @@ -412,6 +411,8 @@ export type Template<TTemplateData = void> =
| string
| ((data: TTemplateData) => string);

export type UnknownWidgetFactory = WidgetFactory<any, any, any>;

export type TemplateWithBindEvent<TTemplateData = void> =
| string
| ((data: TTemplateData, bindEvent: BindEventForHits) => string);
55 changes: 38 additions & 17 deletions src/widgets/panel/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import {
} from '../../lib/utils';
import { component } from '../../lib/suit';
import Panel from '../../components/Panel/Panel';
import { Template, RenderOptions, Widget } from '../../types';
import {
Template,
RenderOptions,
UnknownWidgetFactory,
Widget,
} from '../../types';

export type PanelCSSClasses = {
/**
Expand Down Expand Up @@ -59,40 +64,49 @@ export type PanelCSSClasses = {
footer?: string | string[];
};

export type PanelTemplates = {
export type PanelTemplates<TWidget extends UnknownWidgetFactory> = {
/**
* Template to use for the header.
*/
header?: Template<RenderOptions>;
header?: Template<PanelRenderOptions<TWidget>>;

/**
* Template to use for the footer.
*/
footer?: Template<RenderOptions>;
footer?: Template<PanelRenderOptions<TWidget>>;

/**
* Template to use for collapse button.
*/
collapseButtonText?: Template<{ collapsed: boolean }>;
};

export type PanelWidgetOptions = {
export type PanelRenderOptions<
TWidget extends UnknownWidgetFactory
> = RenderOptions &
(ReturnType<TWidget>['getWidgetRenderState'] extends (
renderOptions: any
) => infer TRenderState
? TRenderState
: Record<string, any>);

export type PanelWidgetOptions<TWidget extends UnknownWidgetFactory> = {
/**
* A function that is called on each render to determine if the
* panel should be hidden based on the render options.
*/
hidden?(options: RenderOptions): boolean;
hidden?(options: PanelRenderOptions<TWidget>): boolean;

/**
* A function that is called on each render to determine if the
* panel should be collapsed based on the render options.
*/
collapsed?(options: RenderOptions): boolean;
collapsed?(options: PanelRenderOptions<TWidget>): boolean;

/**
* The templates to use for the widget.
*/
templates?: PanelTemplates;
templates?: PanelTemplates<TWidget>;

/**
* The CSS classes to override.
Expand All @@ -103,14 +117,14 @@ export type PanelWidgetOptions = {
const withUsage = createDocumentationMessageGenerator({ name: 'panel' });
const suit = component('Panel');

const renderer = ({
const renderer = <TWidget extends UnknownWidgetFactory>({
containerNode,
bodyContainerNode,
cssClasses,
templates,
}) => ({ options, hidden, collapsible, collapsed }) => {
render(
<Panel
<Panel<TWidget>
cssClasses={cssClasses}
hidden={hidden}
collapsible={collapsible}
Expand All @@ -123,13 +137,13 @@ const renderer = ({
);
};

export type PanelWidget = (
params?: PanelWidgetOptions
export type PanelWidget = <TWidget extends UnknownWidgetFactory>(
widgetParams?: PanelWidgetOptions<TWidget>
) => <
TWidgetOptions extends { container: HTMLElement | string; [key: string]: any }
TWidgetParams extends { container: HTMLElement | string; [key: string]: any }
>(
widgetFactory: (widgetOptions: TWidgetOptions) => Widget
) => (widgetOptions: TWidgetOptions) => Widget;
widgetFactory: TWidget
) => (widgetOptions: TWidgetParams) => Widget;

/**
* The panel widget wraps other widgets in a consistent panel design.
Expand Down Expand Up @@ -214,7 +228,7 @@ const panel: PanelWidget = widgetParams => {
</svg>`,
};

const renderPanel = renderer({
const renderPanel = renderer<typeof widgetFactory>({
containerNode: getContainerNode(container),
bodyContainerNode,
cssClasses,
Expand Down Expand Up @@ -248,7 +262,14 @@ const panel: PanelWidget = widgetParams => {
return undefined;
},
render(...args) {
const [options] = args;
const [renderOptions] = args;

const options = {
...(widget.getWidgetRenderState
? widget.getWidgetRenderState(renderOptions)
: {}),
...renderOptions,
};

renderPanel({
options,
Expand Down
58 changes: 58 additions & 0 deletions stories/panel.stories.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import { storiesOf } from '@storybook/html';
import { withHits } from '../.storybook/decorators';
import { connectHierarchicalMenu } from '../src/connectors';
import panel from '../src/widgets/panel/panel';
import refinementList from '../src/widgets/refinement-list/refinement-list';
import rangeInput from '../src/widgets/range-input/range-input';
import rangeSlider from '../src/widgets/range-slider/range-slider';
import breadcrumb from '../src/widgets/breadcrumb/breadcrumb';
import { noop } from '../src/lib/utils';

const virtualHierarchicalMenu = (args = {}) =>
connectHierarchicalMenu(
noop,
noop
)({
attributes: [
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
],
...args,
});

storiesOf('Basics/Panel', module)
.add(
Expand Down Expand Up @@ -129,4 +145,46 @@ storiesOf('Basics/Panel', module)
}),
]);
})
)
.add(
'collapsed unless canRefine',
withHits(
({ search, container }) => {
const breadcrumbInPanel = panel<typeof breadcrumb>({
collapsed({ canRefine }) {
return canRefine === false;
},
templates: {
header({ canRefine }) {
return `Breadcrumb that can${canRefine ? '' : "'t "} refine`;
},
footer:
'The panel collapses if it cannot refine. Click "Home". This panel will collapse and you will not see this footer anymore.',
},
})(breadcrumb);

search.addWidgets([
virtualHierarchicalMenu(),
breadcrumbInPanel({
container,
attributes: [
'hierarchicalCategories.lvl0',
'hierarchicalCategories.lvl1',
'hierarchicalCategories.lvl2',
],
}),
]);
},
{
initialUiState: {
instant_search: {
hierarchicalMenu: {
'hierarchicalCategories.lvl0': [
'Cameras & Camcorders > Digital Cameras',
],
},
},
},
}
)
);

0 comments on commit 8f82eaa

Please sign in to comment.