From a0a7fc4a8be1316be2a03f4b9591ad17b5c5887c Mon Sep 17 00:00:00 2001 From: Roy Schut Date: Wed, 4 Sep 2024 17:18:39 +0200 Subject: [PATCH] feat(project): add injectable wrapper to common components (#598) --- .../ui-react/src/components/Button/Button.tsx | 11 ++++++---- .../ui-react/src/components/Card/Card.tsx | 7 +++++-- .../src/components/CardGrid/CardGrid.tsx | 7 +++++-- .../ui-react/src/components/Header/Header.tsx | 13 ++++++++---- .../src/components/MenuButton/MenuButton.tsx | 21 ++++++++++++++++--- .../ui-react/src/components/Shelf/Shelf.tsx | 5 ++++- .../src/components/Sidebar/Sidebar.tsx | 7 +++++-- .../src/modules/createInjectableComponent.tsx | 2 ++ platforms/web/src/modules/register.ts | 15 ++++++------- 9 files changed, 63 insertions(+), 25 deletions(-) diff --git a/packages/ui-react/src/components/Button/Button.tsx b/packages/ui-react/src/components/Button/Button.tsx index aa4844198..592415604 100644 --- a/packages/ui-react/src/components/Button/Button.tsx +++ b/packages/ui-react/src/components/Button/Button.tsx @@ -3,14 +3,17 @@ import classNames from 'classnames'; import { NavLink } from 'react-router-dom'; import Spinner from '../Spinner/Spinner'; +import createInjectableComponent from '../../modules/createInjectableComponent'; import styles from './Button.module.scss'; +export const ButtonIdentifier = Symbol(`BUTTON`); + type Color = 'default' | 'primary' | 'delete'; type Variant = 'contained' | 'outlined' | 'text' | 'danger' | 'delete'; -type Props = { +export type ButtonProps = { children?: React.ReactNode; label: string; active?: boolean; @@ -31,7 +34,7 @@ type Props = { activeClassname?: string; } & React.AriaAttributes; -const Button: React.FC = ({ +const Button: React.FC = ({ label, children, color = 'default', @@ -48,7 +51,7 @@ const Button: React.FC = ({ className, activeClassname = '', ...rest -}: Props) => { +}: ButtonProps) => { const buttonClassName = (isActive: boolean) => classNames(styles.button, className, styles[color], styles[variant], { [styles.active]: isActive, @@ -82,4 +85,4 @@ const Button: React.FC = ({ ); }; -export default Button; +export default createInjectableComponent(ButtonIdentifier, Button); diff --git a/packages/ui-react/src/components/Card/Card.tsx b/packages/ui-react/src/components/Card/Card.tsx index 1deb3f7db..c3def2a83 100644 --- a/packages/ui-react/src/components/Card/Card.tsx +++ b/packages/ui-react/src/components/Card/Card.tsx @@ -13,13 +13,16 @@ import type { PosterAspectRatio } from '@jwp/ott-common/src/utils/collection'; import Image from '../Image/Image'; import Icon from '../Icon/Icon'; +import createInjectableComponent from '../../modules/createInjectableComponent'; import styles from './Card.module.scss'; +export const CardIdentifier = Symbol(`CARD`); + type ReplaceColon = T extends `${infer Left}:${infer Right}` ? `${Left}${Right}` : T; type PosterAspectRatioClass = ReplaceColon; -type CardProps = { +export type CardProps = { item: PlaylistItem; onHover?: () => void; progress?: number; @@ -137,4 +140,4 @@ function Card({ ); } -export default memo(Card); +export default memo(createInjectableComponent(CardIdentifier, Card)); diff --git a/packages/ui-react/src/components/CardGrid/CardGrid.tsx b/packages/ui-react/src/components/CardGrid/CardGrid.tsx index ed6616dd8..868628b92 100644 --- a/packages/ui-react/src/components/CardGrid/CardGrid.tsx +++ b/packages/ui-react/src/components/CardGrid/CardGrid.tsx @@ -10,6 +10,7 @@ import useBreakpoint, { Breakpoint, type Breakpoints } from '@jwp/ott-ui-react/s import Card from '../Card/Card'; import InfiniteScrollLoader from '../InfiniteScrollLoader/InfiniteScrollLoader'; import LayoutGrid from '../LayoutGrid/LayoutGrid'; +import createInjectableComponent from '../../modules/createInjectableComponent'; import styles from './CardGrid.module.scss'; @@ -26,7 +27,9 @@ const defaultCols: Breakpoints = { [Breakpoint.xl]: 5, }; -type CardGridProps = { +export const CardGridIdentifier = Symbol(`CARD_GRID`); + +export type CardGridProps = { playlist: Playlist; watchHistory?: { [key: string]: number }; isLoading: boolean; @@ -104,4 +107,4 @@ function CardGrid({ ); } -export default CardGrid; +export default createInjectableComponent(CardGridIdentifier, CardGrid); diff --git a/packages/ui-react/src/components/Header/Header.tsx b/packages/ui-react/src/components/Header/Header.tsx index c5b6142a5..28c4a6d9a 100644 --- a/packages/ui-react/src/components/Header/Header.tsx +++ b/packages/ui-react/src/components/Header/Header.tsx @@ -1,17 +1,21 @@ -import React, { type PropsWithChildren } from 'react'; +import { type PropsWithChildren } from 'react'; import classNames from 'classnames'; +import createInjectableComponent from '../../modules/createInjectableComponent'; + import styles from './Header.module.scss'; +export const HeaderIdentifier = Symbol(`HEADER`); + type TypeHeader = 'static' | 'fixed'; -type Props = { +export type HeaderProps = { headerType?: TypeHeader; className?: string; searchActive: boolean; }; -const Header = ({ children, className, headerType = 'static', searchActive }: PropsWithChildren) => { +const Header = ({ children, className, headerType = 'static', searchActive }: PropsWithChildren) => { const headerClassName = classNames(styles.header, styles[headerType], className, { [styles.searchActive]: searchActive, }); @@ -22,4 +26,5 @@ const Header = ({ children, className, headerType = 'static', searchActive }: Pr ); }; -export default Header; + +export default createInjectableComponent(HeaderIdentifier, Header); diff --git a/packages/ui-react/src/components/MenuButton/MenuButton.tsx b/packages/ui-react/src/components/MenuButton/MenuButton.tsx index 1a69d0072..68f024a91 100644 --- a/packages/ui-react/src/components/MenuButton/MenuButton.tsx +++ b/packages/ui-react/src/components/MenuButton/MenuButton.tsx @@ -2,9 +2,13 @@ import React from 'react'; import { NavLink } from 'react-router-dom'; import classNames from 'classnames'; +import createInjectableComponent from '../../modules/createInjectableComponent'; + import styles from './MenuButton.module.scss'; -type Props = { +export const MenuButtonIdentifier = Symbol(`MENU_BUTTON`); + +export type MenuButtonProps = { label?: string; to?: string; onClick?: () => void; @@ -16,7 +20,18 @@ type Props = { small?: boolean; } & React.AriaAttributes; -const MenuButton: React.FC = ({ label, to, onClick, onBlur, onFocus, tabIndex = 0, active = false, startIcon, small = false, ...rest }: Props) => { +const MenuButton: React.FC = ({ + label, + to, + onClick, + onBlur, + onFocus, + tabIndex = 0, + active = false, + startIcon, + small = false, + ...rest +}: MenuButtonProps) => { const icon = startIcon ?
{startIcon}
: null; const getClassName = (isActive: boolean) => classNames(styles.menuButton, { [styles.small]: small }, { [styles.active]: isActive }); @@ -45,4 +60,4 @@ const MenuButton: React.FC = ({ label, to, onClick, onBlur, onFocus, tabI ); }; -export default MenuButton; +export default createInjectableComponent(MenuButtonIdentifier, MenuButton); diff --git a/packages/ui-react/src/components/Shelf/Shelf.tsx b/packages/ui-react/src/components/Shelf/Shelf.tsx index 5a3ae2d63..cec054ba7 100644 --- a/packages/ui-react/src/components/Shelf/Shelf.tsx +++ b/packages/ui-react/src/components/Shelf/Shelf.tsx @@ -15,6 +15,7 @@ import '@videodock/tile-slider/lib/style.css'; import Card from '../Card/Card'; import Icon from '../Icon/Icon'; +import createInjectableComponent from '../../modules/createInjectableComponent'; import styles from './Shelf.module.scss'; @@ -34,6 +35,8 @@ export const featuredTileBreakpoints: Breakpoints = { [Breakpoint.xl]: 1, }; +export const ShelfIdentifier = Symbol(`SHELF`); + export type ShelfProps = { playlist: Playlist; type: PlaylistType; @@ -151,4 +154,4 @@ const Shelf = ({ ); }; -export default Shelf; +export default createInjectableComponent(ShelfIdentifier, Shelf); diff --git a/packages/ui-react/src/components/Sidebar/Sidebar.tsx b/packages/ui-react/src/components/Sidebar/Sidebar.tsx index 44d1d8058..54f0fbdfc 100644 --- a/packages/ui-react/src/components/Sidebar/Sidebar.tsx +++ b/packages/ui-react/src/components/Sidebar/Sidebar.tsx @@ -6,10 +6,13 @@ import IconButton from '../IconButton/IconButton'; import Icon from '../Icon/Icon'; import Modal, { type AnimationProps } from '../Modal/Modal'; import Slide from '../Animation/Slide/Slide'; +import createInjectableComponent from '../../modules/createInjectableComponent'; import styles from './Sidebar.module.scss'; -type SidebarProps = { +export const SidebarIdentifier = Symbol(`SIDEBAR`); + +export type SidebarProps = { isOpen: boolean; onClose: () => void; children?: ReactNode; @@ -40,4 +43,4 @@ const Sidebar: React.FC = ({ isOpen, onClose, children }) => { ); }; -export default Sidebar; +export default createInjectableComponent(SidebarIdentifier, Sidebar); diff --git a/packages/ui-react/src/modules/createInjectableComponent.tsx b/packages/ui-react/src/modules/createInjectableComponent.tsx index f8ff1a1b6..e62ffbfa8 100644 --- a/packages/ui-react/src/modules/createInjectableComponent.tsx +++ b/packages/ui-react/src/modules/createInjectableComponent.tsx @@ -2,6 +2,8 @@ import React, { type FC } from 'react'; import { container } from './container'; +// This HOC is used to override a component top-level, using Inversify +// More info: platforms/web/src/modules/register.ts const createInjectableComponent = (identifier: symbol, DefaultComponent: FC) => { return (props: Props) => { const isOverridden = container.isBound(identifier); diff --git a/platforms/web/src/modules/register.ts b/platforms/web/src/modules/register.ts index 64d2ea427..3a592a26f 100644 --- a/platforms/web/src/modules/register.ts +++ b/platforms/web/src/modules/register.ts @@ -52,17 +52,18 @@ container.bind(LogTransporter).toDynamicValue(() => new ConsoleT * * @example * ```ts - * // Define in types.ts (from `ui-react` for example) - * const TAG_IDENTIFIER = 'TAG_IDENTIFIER'; + * // Define an identifier from the component (from `ui-react` for example): + * export const CardIdentifier = Symbol('CARD'); * - * // Bind custom component - * container.bind(TAG_IDENTIFIER).toConstantValue(CustomTag); + * // Make sure the component is wrapped in the HOC: + * export default createInjectableComponent(CardIdentifier, Card); * - * // Then wrap the component in a HOC, from the component file itself - * export default createInjectableComponent(TAG_IDENTIFIER, DefaultTag); + * // Override the component into the associated container, from register.ts: + * import { container as uiComponentContainer } from '@jwp/ott-ui-react/src/modules/container'; * + * uiComponentContainer.bind>(CardIdentifier).toConstantValue(CustomCard); * ``` */ // Override ui-react component -// uiComponentContainer.bind>(TAG_IDENTIFIER).toConstantValue(Tag); +// uiComponentContainer.bind>(CardIdentifier).toConstantValue(CustomCard);