Skip to content

Commit

Permalink
BREAKING CHANGE(web-react): Rename height and maxHeight ModalDialog p…
Browse files Browse the repository at this point in the history
…rops and enhance them #DS-1134
  • Loading branch information
crishpeen committed May 23, 2024
1 parent 0ec482b commit b5d7170
Show file tree
Hide file tree
Showing 14 changed files with 560 additions and 115 deletions.
32 changes: 31 additions & 1 deletion docs/migrations/web-react/MIGRATION-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Introducing version 2 of the _spirit-web-react_ package
- [Grid: GridSpan Component](#grid-gridspan-component)
- [Modal: ModalDialog `isExpandedOnMobile` Prop](#modal-modaldialog-isexpandedonmobile-prop)
- [Modal: ModalDialog `isScrollable` Prop](#modal-modaldialog-isscrollable-prop)
- [Modal: ModalDialog Custom Height](#modal-modaldialog-custom-height)
- [Modal: ModalDialog Uniform Appearance](#modal-modaldialog-uniform-appearance)
- [Tabs: TabItem and TabPane Props](#tabs-tabitem-and-tabpane-props)
- [TextField: `label` prop](#textfield-label-prop)
Expand Down Expand Up @@ -231,7 +232,7 @@ Examples:
- `columnStart` = 1 + (12 - 4) / 2 = 5
- `<GridSpan over="6">…</GridSpan>``<GridItem columnStart="4" columnEnd="span 6">…</GridItem>`
- `columnStart` = 1 + (12 - 6) / 2 = 4
- `<GridSpan over="8" tablet="6" desktop="4">…</GridSpan>``<GridItem columnStart="{{ { mobile: 3, tablet: 4, desktop: 5 } }}" columnEnd="{{ { mobile: 'span 8', tablet: 'span 6', desktop: 'span 4' } }}">…</GridItem>`
- `<GridSpan over="8" tablet="6" desktop="4">…</GridSpan>``<GridItem columnStart={{ mobile: 3, tablet: 4, desktop: 5 }} columnEnd={{ mobile: 'span 8', tablet: 'span 6', desktop: 'span 4' }}>…</GridItem>`
- `columnStart` = 1 + (12 - 8) / 2 = 3
- `columnStart` = 1 + (12 - 6) / 2 = 4
- `columnStart` = 1 + (12 - 4) / 2 = 5
Expand Down Expand Up @@ -274,6 +275,35 @@ Or manually add `isScrollable` prop to the `ModalDialog` component.
If you use `ScrollView` for scrolling the content of your modal, you must also make the
`ModalDialog` scrollable by setting the `isScrollable` prop.

### Modal: ModalDialog Custom Height

The `preferredHeightOnMobile` and `preferredHeightFromTabletUp` props were removed and
replaced with one prop `height` which accepts either a single value or
object with breakpoint keys and values.

Also, the prop `maxHeightFromTabletUp` was removed and replaced with the `maxHeight` prop,
which also accepts either a single value or object with breakpoint keys and values.

#### Migration Guide

Use codemod to automatically update your codebase.

```sh
npx @lmc-eu/spirit-codemods -p <path> -t v2/web-react/modal-custom-height
```

See [Codemods documentation][readme-codemods] for more details.

Or manually update the `preferredHeightOnMobile` and `preferredHeightFromTabletUp` props to the new `height` prop.

- `<ModalDialog preferredHeightOnMobile="333px" … />``<ModalDialog height="333px" … />`
- `<ModalDialog preferredHeightFromTabletUp="444px" … />``<ModalDialog height={{ tablet: '444px' }} … />`
- `<ModalDialog preferredHeightOnMobile="333px" preferredHeightFromTabletUp="444px" … />``<ModalDialog height={{ mobile: '333px', tablet: '444px' }} … />`

Update the `maxHeightFromTabletUp` prop to the new `maxHeight` prop.

- `<ModalDialog maxHeightFromTabletUp="555px" … />``<ModalDialog maxHeight={{ tablet: '555px' }} … />`

### Modal: ModalDialog Uniform Appearance

The uniform `ModalDialog` appearance replaced the current behavior. Current mobile appearance
Expand Down
19 changes: 19 additions & 0 deletions packages/codemods/src/transforms/v2/web-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ npx @lmc-eu/spirit-codemods -p <path> -t v2/web-react/grid-gridspan
+ <GridItem columnStart={5} columnEnd="span 4" … />
```

### `v2/web-react/modal-custom-height` — Modal Custom Height

This codemod updates the `ModalDialog` component to use the `height` and
`maxHeight` props instead of the removed `preferredHeightOnMobile`,
`preferredHeightFromTabletUp` and `maxHeightFromTabletUp` props.

#### Usage

```sh
npx @lmc-eu/spirit-codemods -p <path> -t v2/web-react/modal-custom-height
```

#### Example

```diff
- <ModalDialog preferredHeightOnMobile="300px" preferredHeightFromTabletUp="400px" maxHeightFromTabletUp="500px" … />
+ <ModalDialog height={{ mobile: "300px", tablet: "400px" }} maxHeight={{ tablet: "500px" }} … />
```

### `v2/web-react/modal-isdockedonmobile-prop` — Modal isDockedOnMobile Prop

This codemod adds the `isDockedOnMobile` prop to the `ModalDialog` component,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures.
import { ModalDialog } from '@lmc-eu/spirit-web-react';

export const MyComponent = () => (
<>
<ModalDialog preferredHeightOnMobile="400px" />
<ModalDialog preferredHeightFromTabletUp="500px" />
<ModalDialog maxHeightFromTabletUp="600px" />
<ModalDialog preferredHeightOnMobile="400px" preferredHeightFromTabletUp="500px" />
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
// @ts-ignore: No declaration -- The library is not installed; we don't need to install it for fixtures.
import { ModalDialog } from '@lmc-eu/spirit-web-react';

export const MyComponent = () => (
<>
<ModalDialog height={{
mobile: '400px'
}} />
<ModalDialog height={{
tablet: '500px'
}} />
<ModalDialog maxHeight={{
tablet: '600px'
}} />
<ModalDialog
height={{
mobile: '400px',
tablet: '500px'
}} />
</>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { testTransform } from '../../../../../tests/testUtils';

testTransform(__dirname, 'modal-custom-height');
108 changes: 108 additions & 0 deletions packages/codemods/src/transforms/v2/web-react/modal-custom-height.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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 ModalDialog 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 modalDialogSpecifier = importStatements.find(j.ImportSpecifier, {
imported: {
type: 'Identifier',
name: 'ModalDialog',
},
});

// Check if ModalDialog specifier is present
if (modalDialogSpecifier.length > 0) {
// Find ModalDialog components in the module
const modalDialogComponents = root.find(j.JSXOpeningElement, {
name: {
type: 'JSXIdentifier',
name: 'ModalDialog',
},
});

modalDialogComponents.forEach((path) => {
const attributes = path.node.attributes || [];

let preferredHeightOnMobile: string | null = null;
let preferredHeightFromTabletUp: string | null = null;
let maxHeightFromTabletUp: string | null = null;

// Iterate over attributes to find and remove the specific props
path.node.attributes = attributes.filter((attr) => {
if (j.JSXAttribute.check(attr)) {
const attrName = attr.name.name;

if (attrName === 'preferredHeightOnMobile' && attr.value && j.Literal.check(attr.value)) {
preferredHeightOnMobile = attr.value.value as string;

return false;
}

if (attrName === 'preferredHeightFromTabletUp' && attr.value && j.Literal.check(attr.value)) {
preferredHeightFromTabletUp = attr.value.value as string;

return false;
}

if (attrName === 'maxHeightFromTabletUp' && attr.value && j.Literal.check(attr.value)) {
maxHeightFromTabletUp = attr.value.value as string;

return false;
}
}

return true;
});

// Create new height and maxHeight attributes
if (preferredHeightOnMobile || preferredHeightFromTabletUp) {
const heightObjectProperties = [];

if (preferredHeightOnMobile) {
heightObjectProperties.push(j.property('init', j.identifier('mobile'), j.literal(preferredHeightOnMobile)));
}

if (preferredHeightFromTabletUp) {
heightObjectProperties.push(
j.property('init', j.identifier('tablet'), j.literal(preferredHeightFromTabletUp)),
);
}

const heightAttribute = j.jsxAttribute(
j.jsxIdentifier('height'),
j.jsxExpressionContainer(j.objectExpression(heightObjectProperties)),
);

path.node.attributes.push(heightAttribute);
}

if (maxHeightFromTabletUp) {
const maxHeightObjectProperties = [
j.property('init', j.identifier('tablet'), j.literal(maxHeightFromTabletUp)),
];

const maxHeightAttribute = j.jsxAttribute(
j.jsxIdentifier('maxHeight'),
j.jsxExpressionContainer(j.objectExpression(maxHeightObjectProperties)),
);

path.node.attributes.push(maxHeightAttribute);
}
});
}
}

return root.toSource({ quote: 'single' });
};

export default transform;
32 changes: 8 additions & 24 deletions packages/web-react/src/components/Modal/ModalDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import React, { CSSProperties, ElementType, ForwardedRef, forwardRef } from 'react';
import React, { ElementType, ForwardedRef, forwardRef, HTMLAttributes } from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { ModalDialogProps, ModalDialogElementType } from '../../types';
import { ModalDialogElementType, ModalDialogProps } from '../../types';
import { useModalDialogStyleProps } from './useModalDialogStyleProps';
import { useModalStyleProps } from './useModalStyleProps';

interface CustomizedHeightCSSProperties extends CSSProperties {
'--modal-max-height-tablet'?: string;
'--modal-preferred-height-mobile'?: string;
'--modal-preferred-height-tablet'?: string;
}

const ModalDialog = <E extends ElementType = ModalDialogElementType>(
props: ModalDialogProps<E>,
ref: ForwardedRef<HTMLDivElement>,
Expand All @@ -20,31 +15,20 @@ const ModalDialog = <E extends ElementType = ModalDialogElementType>(
isDockedOnMobile,
isExpandedOnMobile,
isScrollable,
maxHeightFromTabletUp,
preferredHeightOnMobile,
preferredHeightFromTabletUp,
...restProps
} = props;

const { classProps } = useModalStyleProps({ isDockedOnMobile, isExpandedOnMobile, isScrollable });
const { styleProps, props: otherProps } = useStyleProps(restProps);

const customizedHeightStyle: CustomizedHeightCSSProperties = {
'--modal-max-height-tablet': maxHeightFromTabletUp,
'--modal-preferred-height-mobile': preferredHeightOnMobile,
'--modal-preferred-height-tablet': preferredHeightFromTabletUp,
};
const { modalDialogStyleProps, props: otherStyleProps } = useModalDialogStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(otherStyleProps);

styleProps.style = {
...styleProps.style,
...customizedHeightStyle,
};
const combinedStyleProps = { ...styleProps.style, ...modalDialogStyleProps };

return (
<ElementTag
ref={ref}
{...otherProps}
{...styleProps}
{...(otherProps as HTMLAttributes<HTMLElement>)}
style={{ ...(combinedStyleProps as HTMLAttributes<HTMLElement>) }}
className={classNames(classProps.dialog, styleProps.className)}
>
{children}
Expand Down
45 changes: 27 additions & 18 deletions packages/web-react/src/components/Modal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,15 @@ By default, the docked dialog on mobile screens shrinks to fit the height of its

### API

| Name | Type | Default | Required | Description |
| ----------------------------- | --------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` ||| Children node |
| `elementType` | [`article` \| `form`] | `article` || ModalDialog element type |
| `isDockedOnMobile` | `bool` | `false` || Dock the ModalDialog to the bottom of the screen on mobile |
| `isExpandedOnMobile` | `bool` | `false` || If true, ModalDialog expands to fit the viewport on mobile |
| `isScrollable` | `bool` | `true` || If the ModalDialog should be scrollable. If set to `false`, the dialog will not scroll and will expand to fit the content |
| `maxHeightFromTabletUp` | `string` | `null` || Max height of the modal. Accepts any valid CSS value |
| `preferredHeightFromTabletUp` | `string` | `null` || Preferred height of the modal on tablet and larger. Accepts any valid CSS value |
| `preferredHeightOnMobile` | `string` | `null` || Preferred height of the modal on mobile. Accepts any valid CSS value |
| Name | Type | Default | Required | Description |
| -------------------- | ---------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `children` | `ReactNode` ||| Children node |
| `elementType` | [`article` \| `form`] | `article` || ModalDialog element type |
| `height` | [`string` \| `object`] | `null` || Height of the modal. Accepts any valid CSS value or an object with breakpoint keys for responsive values |
| `isDockedOnMobile` | `bool` | `false` || Dock the ModalDialog to the bottom of the screen on mobile |
| `isExpandedOnMobile` | `bool` | `false` || If true, ModalDialog expands to fit the viewport on mobile |
| `isScrollable` | `bool` | `true` || If the ModalDialog should be scrollable. If set to `false`, the dialog will not scroll and will expand to fit the content |
| `maxHeight` | [`string` \| `object`] | `null` || Max height of the modal. Accepts any valid CSS value or an object with breakpoint keys for responsive values |

Also, all properties of the [`<article>` element][mdn-article] and [`<form>` element][mdn-form] are supported.

Expand Down Expand Up @@ -301,15 +300,19 @@ takes over the responsibility for scrolling and provides visual overflow decorat
By default, ModalDialog grows to the height of its content until it reaches the [maximum height](#custom-max-height)
limit.
You can set a custom preferred height of ModalDialog using a custom property:
- `preferredHeightOnMobile` for mobile screens, and
- `preferredHeightFromTabletUp` for tablet screens and up.
You can set a custom preferred height of ModalDialog using the `height` prop.
The prop accepts any valid CSS value, either as a string or an object with breakpoints as keys.
The custom properties fall back to the previous breakpoint using the mobile-first approach. For example, if you set
`height={{ tablet: '500px' }}` while not setting the `desktop` breakpoint, the value will be used for
both tablet and desktop screens. The single non-object value will be used for all breakpoints.
This is useful for Modals with dynamic content, e.g. a list of items that can be added or removed, or a multistep wizard.
```jsx
<ModalDialog isScrollable preferredHeightOnMobile="400px" preferredHeightFromTabletUp="500px">
<ModalDialog isScrollable height="500px">
</ModalDialog>
<ModalDialog isScrollable height={{ mobile: '300px', tablet: '500px', desktop: '600px' }}>
</ModalDialog>
```
Expand All @@ -326,11 +329,17 @@ The default maximum height of a scrollable ModalDialog is **600 px**, as long as
If the viewport is smaller, scrollable ModalDialog will shrink to fit the viewport. In such case, the ModalDialog height
will calculate as "viewport height (`100dvh`) minus 1100 spacing".
You can use the prop `maxHeightFromTabletUp` to override the default maximum height limit on tablet
screens and up:
You can use the prop `maxHeight` to override the default maximum height limit.
The custom properties fall back to the previous breakpoint using the mobile-first approach. For example, if you set
`maxHeight={{ tablet: '500px' }}` while not setting the `desktop` breakpoint, the value will be used for
both tablet and desktop screens. The single non-object value will be used for all breakpoints.
```jsx
<ModalDialog isScrollable maxHeightFromTabletUp="700px">
<ModalDialog isScrollable maxHeight="700px">
</ModalDialog>
<ModalDialog isScrollable maxHeight={{ mobile: '500px', tablet: '700px', desktop: '800px' }}>
</ModalDialog>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,38 @@ describe('ModalDialog', () => {

expect(screen.getByRole('article')).toHaveClass('ModalDialog--scrollable');
});

it('should have height CSS variable', () => {
render(
<ModalDialog height="400px">
<div>Test</div>
</ModalDialog>,
);

expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height: 400px');
});

it('should have all height CSS variables', () => {
render(
<ModalDialog height={{ mobile: '400px', tablet: '500px', desktop: '600px' }}>
<div>Test</div>
</ModalDialog>,
);

expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height: 400px');
expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height-tablet: 500px');
expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height-desktop: 600px');
});

it('should have all max height CSS variables', () => {
render(
<ModalDialog maxHeight={{ mobile: '400px', tablet: '500px', desktop: '600px' }}>
<div>Test</div>
</ModalDialog>,
);

expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-max-height: 400px');
expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-max-height-tablet: 500px');
expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-max-height-desktop: 600px');
});
});
Loading

0 comments on commit b5d7170

Please sign in to comment.