From 0ba662f0e75c8741dcf0f59386bb1a6b551b2d9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kry=C5=A1p=C3=ADn?= Date: Tue, 4 Jun 2024 13:30:27 +0200 Subject: [PATCH] BREAKING CHANGE(web-react): Drop `HeaderDesktopActions` `color` prop in favor of `isAtEnd` #DS-1059 See the Header: HeaderDesktopActions `isAtEnd` prop section in the web-react package Migration Guide to version 2. --- docs/migrations/web-react/MIGRATION-v2.md | 26 +++++++++ .../src/transforms/v2/web-react/README.md | 22 +++++++ ...der-headerdesktopactions-isatend.input.tsx | 11 ++++ ...er-headerdesktopactions-isatend.output.tsx | 11 ++++ ...eader-headerdesktopactions-isatend.test.ts | 3 + .../header-headerdesktopactions-isatend.ts | 58 +++++++++++++++++++ .../Header/HeaderDesktopActions.tsx | 5 +- .../web-react/src/components/Header/README.md | 23 ++++---- .../__tests__/HeaderDesktopActions.test.tsx | 13 +++-- .../__tests__/useHeaderStyleProps.test.ts | 4 +- .../Header/demo/HeaderInvertedWithActions.tsx | 4 +- .../HeaderInvertedWithActionsAndDialog.tsx | 4 +- .../Header/stories/Header.stories.tsx | 4 +- .../stories/HeaderDesktopActions.stories.tsx | 11 ++-- .../Header/stories/HeaderDialog.stories.tsx | 4 +- .../stories/HeaderDialogActions.stories.tsx | 4 +- .../stories/HeaderDialogButton.stories.tsx | 4 +- .../HeaderDialogCloseButton.stories.tsx | 4 +- .../stories/HeaderDialogLink.stories.tsx | 4 +- .../stories/HeaderDialogNav.stories.tsx | 4 +- .../stories/HeaderDialogNavItem.stories.tsx | 4 +- .../stories/HeaderDialogText.stories.tsx | 4 +- .../Header/stories/HeaderLink.stories.tsx | 4 +- .../stories/HeaderMobileActions.stories.tsx | 4 +- .../Header/stories/HeaderNav.stories.tsx | 4 +- .../Header/stories/HeaderNavItem.stories.tsx | 4 +- .../components/Header/useHeaderStyleProps.ts | 15 +++-- packages/web-react/src/types/header.ts | 2 +- 28 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.input.tsx create mode 100644 packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.output.tsx create mode 100644 packages/codemods/src/transforms/v2/web-react/__tests__/header-headerdesktopactions-isatend.test.ts create mode 100644 packages/codemods/src/transforms/v2/web-react/header-headerdesktopactions-isatend.ts diff --git a/docs/migrations/web-react/MIGRATION-v2.md b/docs/migrations/web-react/MIGRATION-v2.md index 17abb58abd..df18da417b 100644 --- a/docs/migrations/web-react/MIGRATION-v2.md +++ b/docs/migrations/web-react/MIGRATION-v2.md @@ -18,6 +18,7 @@ Introducing version 2 of the _spirit-web-react_ package - [Dropdown: Refactored](#dropdown-refactored) - [Grid: Breakpoint Props](#grid-breakpoint-props) - [Grid: GridSpan Component](#grid-gridspan-component) + - [Header: HeaderDesktopActions `isAtEnd` prop](#header-headerdesktopactions-isatend-prop) - [Modal: ModalDialog `isExpandedOnMobile` Prop](#modal-modaldialog-isexpandedonmobile-prop) - [Modal: ModalDialog `isScrollable` Prop](#modal-modaldialog-isscrollable-prop) - [Modal: ModalDialog Custom Height](#modal-modaldialog-custom-height) @@ -237,6 +238,31 @@ Examples: - `columnStart` = 1 + (12 - 6) / 2 = 4 - `columnStart` = 1 + (12 - 4) / 2 = 5 +### Header: HeaderDesktopActions `isAtEnd` prop + +The `HeaderDesktopActions` component slots were simplified and the second slot alignment is the now +available by using the `isAtEnd` prop. + +The `HeaderDesktopActions` prop `color` was removed. + +#### Migration Guide + +Use our codemod to automatically migrate your code. + +```sh +npx @lmc-eu/spirit-codemods -p -t v2/web-react/header-headerdesktopactions-isatend +``` + +See [Codemods documentation][readme-codemods] for more details. + +Or follow these steps: + +Use the `HeaderDesktopActions` with `isAtEnd` prop instead of the `color="secondary"` prop. +Remove the `color` prop from the `HeaderDesktopActions` component. + +- `` → `` +- `` → `` + ### Modal: ModalDialog `isExpandedOnMobile` Prop The `isExpandedOnMobile` prop is set to `true` by default and the ModalDialog is expanded on mobile diff --git a/packages/codemods/src/transforms/v2/web-react/README.md b/packages/codemods/src/transforms/v2/web-react/README.md index 54cbf37e37..e37bb60e5e 100644 --- a/packages/codemods/src/transforms/v2/web-react/README.md +++ b/packages/codemods/src/transforms/v2/web-react/README.md @@ -102,6 +102,28 @@ npx @lmc-eu/spirit-codemods -p -t v2/web-react/grid-gridspan + ``` +### `v2/web-react/header-headerdesktopactions-isatend` — HeaderDesktopActions isAtEnd Prop + +This codemod sets the `isAtEnd` prop instead of the removed `color="secondary"` prop. +Also it removes the `color="primary"` prop from the `HeaderDesktopActions` component +because it is not needed anymore. + +#### Usage + +```sh +npx @lmc-eu/spirit-codemods -p -t v2/web-react/header-headerdesktopactions-isatend +``` + +#### Example + +```diff +- ++ + +- ++ +``` + ### `v2/web-react/modal-custom-height` — Modal Custom Height This codemod updates the `ModalDialog` component to use the `height` and diff --git a/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.input.tsx b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.input.tsx new file mode 100644 index 0000000000..bcaeed4e41 --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.input.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures. +import { HeaderDesktopActions } from '@lmc-eu/spirit-web-react'; + +export const MyComponent = () => ( + <> + + + + +); diff --git a/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.output.tsx b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.output.tsx new file mode 100644 index 0000000000..73e6b61628 --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/header-headerdesktopactions-isatend.output.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures. +import { HeaderDesktopActions } from '@lmc-eu/spirit-web-react'; + +export const MyComponent = () => ( + <> + + + + +) diff --git a/packages/codemods/src/transforms/v2/web-react/__tests__/header-headerdesktopactions-isatend.test.ts b/packages/codemods/src/transforms/v2/web-react/__tests__/header-headerdesktopactions-isatend.test.ts new file mode 100644 index 0000000000..ac49953f28 --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__tests__/header-headerdesktopactions-isatend.test.ts @@ -0,0 +1,3 @@ +import { testTransform } from '../../../../../tests/testUtils'; + +testTransform(__dirname, 'header-headerdesktopactions-isatend'); diff --git a/packages/codemods/src/transforms/v2/web-react/header-headerdesktopactions-isatend.ts b/packages/codemods/src/transforms/v2/web-react/header-headerdesktopactions-isatend.ts new file mode 100644 index 0000000000..c03dd99609 --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/header-headerdesktopactions-isatend.ts @@ -0,0 +1,58 @@ +import { API, FileInfo } from 'jscodeshift'; + +const transform = (fileInfo: FileInfo, api: API) => { + const j = api.jscodeshift; + const root = j(fileInfo.source); + + // Find import statements for the specific module and HeaderDesktopActions specifier + const importStatements = root.find(j.ImportDeclaration, { + source: { + value: (value: string) => /^@lmc-eu\/spirit-web-react(\/.*)?$/.test(value), + }, + }); + + // Check if the module is imported + if (importStatements.length > 0) { + const componentSpecifier = importStatements.find(j.ImportSpecifier, { + imported: { + type: 'Identifier', + name: 'HeaderDesktopActions', + }, + }); + + // Check if HeaderDesktopActions specifier is present + if (componentSpecifier.length > 0) { + // Find HeaderDesktopActions components in the module + const components = root.find(j.JSXOpeningElement, { + name: { + type: 'JSXIdentifier', + name: 'HeaderDesktopActions', + }, + }); + + // Replace color prop + components + .find(j.JSXAttribute, { + name: { + type: 'JSXIdentifier', + name: 'color', + }, + }) + .forEach((attributePath) => { + if (attributePath.value.value?.type === 'StringLiteral') { + if (attributePath.value.value.value === 'primary') { + attributePath.prune(); + } else if (attributePath.value.value.value === 'secondary') { + // Replace color prop with isAtEnd prop without value + attributePath.node.name.name = 'isAtEnd'; + attributePath.node.value = null; + } + } + }); + } + } + + return root.toSource(); +}; + +export default transform; diff --git a/packages/web-react/src/components/Header/HeaderDesktopActions.tsx b/packages/web-react/src/components/Header/HeaderDesktopActions.tsx index 723cb51528..641da1a520 100644 --- a/packages/web-react/src/components/Header/HeaderDesktopActions.tsx +++ b/packages/web-react/src/components/Header/HeaderDesktopActions.tsx @@ -3,12 +3,11 @@ import classNames from 'classnames'; import { useStyleProps } from '../../hooks'; import { HeaderDesktopActionsProps } from '../../types'; import { useHeaderStyleProps } from './useHeaderStyleProps'; -import { HEADER_ACTIONS_COLOR_DEFAULT } from './constants'; const HeaderDesktopActions = (props: HeaderDesktopActionsProps) => { - const { color = HEADER_ACTIONS_COLOR_DEFAULT, ...restProps } = props; + const { isAtEnd = false, ...restProps } = props; - const { classProps } = useHeaderStyleProps({ actionsColor: color }); + const { classProps } = useHeaderStyleProps({ isActionAtEnd: isAtEnd }); const { styleProps, props: otherProps } = useStyleProps(restProps); return ( diff --git a/packages/web-react/src/components/Header/README.md b/packages/web-react/src/components/Header/README.md index 82dc84ffff..de5e916e47 100644 --- a/packages/web-react/src/components/Header/README.md +++ b/packages/web-react/src/components/Header/README.md @@ -140,8 +140,7 @@ and [escape hatches][readme-escape-hatches]. As the name suggests, desktop-only actions are intended to display on desktop screens only. They generally work as flex containers that define vertical alignment and spacing. -There are two slots you can use: primary actions (aligned to left in LTR documents), and secondary actions (aligned to -right). +If you need to align actions to the end of the Header, use the `isAtEnd` prop. 👉 It is critical to **make sure all your actions fit the Header on the desktop breakpoint**. Spirit intentionally does not provide any overflow @@ -149,19 +148,19 @@ control here. ```jsx - {/* Desktop-only primary actions */} + {/* Desktop-only actions */} - - {/* Desktop-only secondary actions */} + + {/* Desktop-only actions aligned to the end */} ``` #### API -| Name | Type | Default | Required | Description | -| ---------- | -------------------------- | --------- | -------- | --------------------------- | -| `children` | `ReactNode` | — | ✕ | Children node | -| `color` | [`primary` \| `secondary`] | `primary` | ✕ | Color and alignment variant | +| Name | Type | Default | Required | Description | +| ---------- | ----------- | ------- | -------- | ------------------------------------------- | +| `children` | `ReactNode` | — | ✕ | Children node | +| `isAtEnd` | `bool` | `false` | ✕ | If true, the actions are aligned to the end | The component implements the [`HTMLElement`][mdn-api-html-element] interface. @@ -218,7 +217,7 @@ You can avoid using the [HeaderNav](#navigation) for standalone links. That way, the same container: ```jsx - + Marian @@ -476,7 +475,7 @@ composition: {/* … */} - {/* Desktop-only secondary actions */} + {/* Desktop-only secondary actions */} ``` @@ -531,7 +530,7 @@ const handleClose = () => setOpen(false); - + Sign in Enterprise diff --git a/packages/web-react/src/components/Header/__tests__/HeaderDesktopActions.test.tsx b/packages/web-react/src/components/Header/__tests__/HeaderDesktopActions.test.tsx index e8816c5860..b97f9b52fb 100644 --- a/packages/web-react/src/components/Header/__tests__/HeaderDesktopActions.test.tsx +++ b/packages/web-react/src/components/Header/__tests__/HeaderDesktopActions.test.tsx @@ -1,6 +1,6 @@ import '@testing-library/jest-dom'; import React from 'react'; -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest'; import HeaderDesktopActions from '../HeaderDesktopActions'; import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest'; @@ -17,9 +17,14 @@ describe('HeaderDesktopActions', () => { restPropsTest((props) => , 'nav'); it('should render text children', () => { - const dom = render(Hello World); + render(Hello World); - const element = dom.container.querySelector('nav') as HTMLElement; - expect(element.textContent).toBe('Hello World'); + expect(screen.getByRole('navigation')).toHaveTextContent('Hello World'); + }); + + it('should have isAtEnd class', () => { + render(); + + expect(screen.getByRole('navigation')).toHaveClass('HeaderDesktopActions--isAtEnd'); }); }); diff --git a/packages/web-react/src/components/Header/__tests__/useHeaderStyleProps.test.ts b/packages/web-react/src/components/Header/__tests__/useHeaderStyleProps.test.ts index 5d10871f37..b1886f0942 100644 --- a/packages/web-react/src/components/Header/__tests__/useHeaderStyleProps.test.ts +++ b/packages/web-react/src/components/Header/__tests__/useHeaderStyleProps.test.ts @@ -9,9 +9,7 @@ describe('useHeaderStyleProps', () => { expect(result.current.classProps).toBeDefined(); expect(result.current.classProps.root).toBe(`Header Header--${HEADER_COLOR_DEFAULT}`); expect(result.current.classProps.headerButton).toBe('HeaderLink'); - expect(result.current.classProps.headerDesktopActions).toBe( - `HeaderDesktopActions HeaderDesktopActions--${HEADER_ACTIONS_COLOR_DEFAULT}`, - ); + expect(result.current.classProps.headerDesktopActions).toBe('HeaderDesktopActions'); expect(result.current.classProps.headerDialog).toBeDefined(); expect(result.current.classProps.headerDialog.root).toBe('HeaderDialog'); expect(result.current.classProps.headerDialog.panel).toBe('HeaderDialog__panel'); diff --git a/packages/web-react/src/components/Header/demo/HeaderInvertedWithActions.tsx b/packages/web-react/src/components/Header/demo/HeaderInvertedWithActions.tsx index 5ec4aa8773..40b399c21a 100644 --- a/packages/web-react/src/components/Header/demo/HeaderInvertedWithActions.tsx +++ b/packages/web-react/src/components/Header/demo/HeaderInvertedWithActions.tsx @@ -30,7 +30,7 @@ const HeaderInvertedWithActions = () => { - + Job offers @@ -49,7 +49,7 @@ const HeaderInvertedWithActions = () => { - + diff --git a/packages/web-react/src/components/Header/demo/HeaderInvertedWithActionsAndDialog.tsx b/packages/web-react/src/components/Header/demo/HeaderInvertedWithActionsAndDialog.tsx index fabe455e9e..e706f9269f 100644 --- a/packages/web-react/src/components/Header/demo/HeaderInvertedWithActionsAndDialog.tsx +++ b/packages/web-react/src/components/Header/demo/HeaderInvertedWithActionsAndDialog.tsx @@ -38,7 +38,7 @@ const HeaderInvertedWithActionsAndDialog = () => { - + @@ -59,7 +59,7 @@ const HeaderInvertedWithActionsAndDialog = () => { - + { - + @@ -92,7 +92,7 @@ const HeaderWithHooks = (args: HeaderProps) => { - + = { children: { control: 'object', }, - color: { - control: 'radio', - options: ['primary', 'secondary'], + isAtEnd: { + control: 'boolean', table: { - defaultValue: { summary: 'primary' }, + defaultValue: { summary: false }, }, }, }, args: { - color: 'primary', + isAtEnd: false, }, }; @@ -81,7 +80,7 @@ const HeaderWithHooks = (args: HeaderDesktopActionsProps) => { - + { - + @@ -86,7 +86,7 @@ const HeaderWithHooks = (args: HeaderDialogProps) => { - + { - + @@ -81,7 +81,7 @@ const HeaderWithHooks = (args: HeaderDialogActionsProps) => { - + { - + @@ -71,7 +71,7 @@ const HeaderWithHooks = (args: HeaderDialogButtonProps) => { - + { - + @@ -77,7 +77,7 @@ const HeaderWithHooks = (args: HeaderDialogCloseButtonProps) => { - + { - + @@ -81,7 +81,7 @@ const HeaderWithHooks = (args: HeaderDialogLinkProps) => { - + { - + @@ -71,7 +71,7 @@ const HeaderWithHooks = (args: HeaderDialogNavProps) => { - + { - + @@ -71,7 +71,7 @@ const HeaderWithHooks = (args: HeaderDialogNavItemProps) => { - + { - + @@ -75,7 +75,7 @@ const HeaderWithHooks = (args: HeaderDialogTextProps) => { - + { - + @@ -79,7 +79,7 @@ const HeaderWithHooks = (args: HeaderLinkProps) => { - + { - + @@ -93,7 +93,7 @@ const HeaderWithHooks = (args: HeaderMobileActionsProps) => { - + { - + @@ -71,7 +71,7 @@ const HeaderWithHooks = (args: HeaderNavProps) => { - + { - + @@ -71,7 +71,7 @@ const HeaderWithHooks = (args: HeaderNavItemProps) => { - + { const headerClass = useClassNamePrefix('Header'); @@ -54,7 +57,7 @@ export const useHeaderStyleProps = ( const headerLinkClass = `${headerClass}Link`; const headerLinkCurrentClass = `${headerLinkClass}--current`; const headerDesktopActionsClass = `${headerClass}DesktopActions`; - const headerDesktopActionsColorClass = `${headerDesktopActionsClass}--${actionsColor}`; + const headerDesktopActionsAtEndClass = `${headerDesktopActionsClass}--isAtEnd`; const headerMobileActionsClass = `${headerClass}MobileActions`; const headerDialogClass = `${headerClass}Dialog`; const headerDialogPanelClass = `${headerDialogClass}__panel`; @@ -71,7 +74,7 @@ export const useHeaderStyleProps = ( const classProps = { root: classNames(headerClass, headerColorClass, { [headerSimpleClass]: isSimple }), headerButton: headerLinkClass, - headerDesktopActions: classNames(headerDesktopActionsClass, headerDesktopActionsColorClass), + headerDesktopActions: classNames(headerDesktopActionsClass, { [headerDesktopActionsAtEndClass]: isActionAtEnd }), headerDialog: { root: headerDialogClass, panel: headerDialogPanelClass, diff --git a/packages/web-react/src/types/header.ts b/packages/web-react/src/types/header.ts index f872969b57..f3466581e8 100644 --- a/packages/web-react/src/types/header.ts +++ b/packages/web-react/src/types/header.ts @@ -34,7 +34,7 @@ export interface HeaderProps extends SpiritElementProps, ChildrenProps { export interface HeaderButtonProps extends SpiritButtonElementProps, ChildrenProps {} export interface HeaderDesktopActionsProps extends SpiritElementProps, ChildrenProps { - color?: HeaderActionsColorType; + isAtEnd?: boolean; } export interface HeaderDialogProps extends SpiritDialogElementProps, HeaderDialogHandlingProps, ChildrenProps {