diff --git a/docs/migrations/web-react/MIGRATION-v2.md b/docs/migrations/web-react/MIGRATION-v2.md index bf8fdbc8d9..17abb58abd 100644 --- a/docs/migrations/web-react/MIGRATION-v2.md +++ b/docs/migrations/web-react/MIGRATION-v2.md @@ -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) @@ -231,7 +232,7 @@ Examples: - `columnStart` = 1 + (12 - 4) / 2 = 5 - `` → `` - `columnStart` = 1 + (12 - 6) / 2 = 4 -- `` → `` +- `` → `` - `columnStart` = 1 + (12 - 8) / 2 = 3 - `columnStart` = 1 + (12 - 6) / 2 = 4 - `columnStart` = 1 + (12 - 4) / 2 = 5 @@ -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 +an 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 an object with breakpoint keys and values. + +#### Migration Guide + +Use codemod to automatically update your codebase. + +```sh +npx @lmc-eu/spirit-codemods -p -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. + +- `` → `` +- `` → `` +- `` → `` + +Update the `maxHeightFromTabletUp` prop to the new `maxHeight` prop. + +- `` → `` + ### Modal: ModalDialog Uniform Appearance The uniform `ModalDialog` appearance replaced the current behavior. Current mobile appearance diff --git a/packages/codemods/src/transforms/v2/web-react/README.md b/packages/codemods/src/transforms/v2/web-react/README.md index 0a3a4b9cbc..54cbf37e37 100644 --- a/packages/codemods/src/transforms/v2/web-react/README.md +++ b/packages/codemods/src/transforms/v2/web-react/README.md @@ -102,6 +102,25 @@ npx @lmc-eu/spirit-codemods -p -t v2/web-react/grid-gridspan + ``` +### `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 -t v2/web-react/modal-custom-height +``` + +#### Example + +```diff +- ++ +``` + ### `v2/web-react/modal-isdockedonmobile-prop` — Modal isDockedOnMobile Prop This codemod adds the `isDockedOnMobile` prop to the `ModalDialog` component, diff --git a/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.input.tsx b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.input.tsx new file mode 100644 index 0000000000..734714b15e --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.input.tsx @@ -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 = () => ( + <> + + + + + +); diff --git a/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.output.tsx b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.output.tsx new file mode 100644 index 0000000000..4fd3f82a1b --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__testfixtures__/modal-custom-height.output.tsx @@ -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 = () => ( + <> + + + + + +); diff --git a/packages/codemods/src/transforms/v2/web-react/__tests__/modal-custom-height.test.ts b/packages/codemods/src/transforms/v2/web-react/__tests__/modal-custom-height.test.ts new file mode 100644 index 0000000000..dcbe74381f --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/__tests__/modal-custom-height.test.ts @@ -0,0 +1,3 @@ +import { testTransform } from '../../../../../tests/testUtils'; + +testTransform(__dirname, 'modal-custom-height'); diff --git a/packages/codemods/src/transforms/v2/web-react/modal-custom-height.ts b/packages/codemods/src/transforms/v2/web-react/modal-custom-height.ts new file mode 100644 index 0000000000..de0a1c0365 --- /dev/null +++ b/packages/codemods/src/transforms/v2/web-react/modal-custom-height.ts @@ -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; diff --git a/packages/web-react/src/components/Modal/ModalDialog.tsx b/packages/web-react/src/components/Modal/ModalDialog.tsx index 0b691228af..bfd25b7381 100644 --- a/packages/web-react/src/components/Modal/ModalDialog.tsx +++ b/packages/web-react/src/components/Modal/ModalDialog.tsx @@ -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 = ( props: ModalDialogProps, ref: ForwardedRef, @@ -20,31 +15,20 @@ const ModalDialog = ( 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 ( )} + style={{ ...(combinedStyleProps as HTMLAttributes) }} className={classNames(classProps.dialog, styleProps.className)} > {children} diff --git a/packages/web-react/src/components/Modal/README.md b/packages/web-react/src/components/Modal/README.md index 0b32c3cc04..e94eec11a9 100644 --- a/packages/web-react/src/components/Modal/README.md +++ b/packages/web-react/src/components/Modal/README.md @@ -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 [`
` element][mdn-article] and [`
` element][mdn-form] are supported. @@ -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 length value, either as a string or an object with breakpoints as keys. +The height property falls 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 - + + … + + ``` @@ -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 `maxHeight` prop to override the default maximum height limit. + +The max height property falls 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 - + + … + + ``` diff --git a/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx b/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx index e2c2427c78..ee556b09a7 100644 --- a/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx +++ b/packages/web-react/src/components/Modal/__tests__/ModalDialog.test.tsx @@ -62,4 +62,49 @@ describe('ModalDialog', () => { expect(screen.getByRole('article')).toHaveClass('ModalDialog--scrollable'); }); + + it('should have height CSS variable', () => { + render( + +
Test
+
, + ); + + expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height: 400px'); + }); + + it('should have some height CSS variables', () => { + render( + +
Test
+
, + ); + + expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height-tablet: 500px'); + expect(screen.getByRole('article')).toHaveStyle('--modal-dialog-height-desktop: 600px'); + }); + + it('should have all height CSS variables', () => { + render( + +
Test
+
, + ); + + 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( + +
Test
+
, + ); + + 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'); + }); }); diff --git a/packages/web-react/src/components/Modal/__tests__/useModalDialogStyleProps.test.ts b/packages/web-react/src/components/Modal/__tests__/useModalDialogStyleProps.test.ts new file mode 100644 index 0000000000..8109db1580 --- /dev/null +++ b/packages/web-react/src/components/Modal/__tests__/useModalDialogStyleProps.test.ts @@ -0,0 +1,43 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useModalDialogStyleProps } from '../useModalDialogStyleProps'; + +describe('useModalDialogStyleProps', () => { + it('should return defaults', () => { + const { result } = renderHook(() => useModalDialogStyleProps({})); + + expect(result.current.modalDialogStyleProps).toEqual({}); + }); + + it('should return height', () => { + const { result } = renderHook(() => useModalDialogStyleProps({ height: '100px' })); + + expect(result.current.modalDialogStyleProps).toEqual({ '--modal-dialog-height': '100px' }); + }); + + it('should return maxHeight', () => { + const { result } = renderHook(() => useModalDialogStyleProps({ maxHeight: '100px' })); + + expect(result.current.modalDialogStyleProps).toEqual({ '--modal-dialog-max-height': '100px' }); + }); + + it('should return height and maxHeight', () => { + const { result } = renderHook(() => useModalDialogStyleProps({ height: '100px', maxHeight: '200px' })); + + expect(result.current.modalDialogStyleProps).toEqual({ + '--modal-dialog-height': '100px', + '--modal-dialog-max-height': '200px', + }); + }); + + it('should return height and maxHeight object', () => { + const { result } = renderHook(() => + useModalDialogStyleProps({ height: { mobile: '100px' }, maxHeight: { tablet: '200px', desktop: '300px' } }), + ); + + expect(result.current.modalDialogStyleProps).toEqual({ + '--modal-dialog-height': '100px', + '--modal-dialog-max-height-tablet': '200px', + '--modal-dialog-max-height-desktop': '300px', + }); + }); +}); diff --git a/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx b/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx index 963f7f86c3..e6bc62579c 100644 --- a/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx +++ b/packages/web-react/src/components/Modal/demo/ModalScrollingLongContent.tsx @@ -1,11 +1,66 @@ -import React, { useState } from 'react'; -import { Button, Modal, ModalBody, ModalDialog, ModalFooter, ModalHeader, ScrollView } from '../..'; +import React, { ChangeEvent, createRef, useState } from 'react'; +import { BreakpointToken } from '../../../types'; +import { + Button, + Checkbox, + Grid, + GridItem, + Modal, + ModalBody, + ModalDialog, + ModalFooter, + ModalHeader, + ScrollView, + Stack, +} from '../..'; + +const breakpointControls = { + mobile: { marginBottom: { mobile: 'space-800' }, className: '' }, + tablet: { marginBottom: { tablet: 'space-800' }, className: 'd-none d-tablet-grid' }, + desktop: { marginBottom: {}, className: 'd-none d-desktop-grid' }, +}; + +const setVerticalDimensionValueForProp = ( + prevState: { height: Record; maxHeight: Record }, + breakpoint: BreakpointToken, + event: ChangeEvent, + propName: 'height' | 'maxHeight', +) => { + return { + ...prevState, + [propName]: { + ...prevState[propName], + [breakpoint]: Number(event.target.value), + }, + }; +}; const ModalScrollingLongContent = () => { const [isModalLongContentOpen, setModalLongContentOpen] = useState(false); const [isModalScrollViewOpen, setModalScrollViewOpen] = useState(false); const [isModalScrollingInsideOpen, setModalScrollingInsideOpen] = useState(false); const [isModalCustomHeightOpen, setModalCustomHeightOpen] = useState(false); + const modalCustomHeightRef = createRef(); + const [isCustomHeightEnabled, setIsCustomHeightEnabled] = useState>({ + mobile: true, + tablet: true, + desktop: true, + }); + const [heightValue, setHeightValue] = useState<{ + height: Record; + maxHeight: Record; + }>({ + height: { + mobile: 400, + tablet: 500, + desktop: 600, + }, + maxHeight: { + mobile: 600, + tablet: 600, + desktop: 600, + }, + }); const toggleModalLongContent = () => setModalLongContentOpen(!isModalLongContentOpen); const toggleModalScrollView = () => setModalScrollViewOpen(!isModalScrollViewOpen); @@ -16,6 +71,24 @@ const ModalScrollingLongContent = () => { const handleModalScrollingInsideClose = () => setModalScrollingInsideOpen(false); const handleModalCustomHeightClose = () => setModalCustomHeightOpen(false); + const handleMaxHeightChange = (event: ChangeEvent, breakpoint: BreakpointToken) => { + setHeightValue((prevState) => setVerticalDimensionValueForProp(prevState, breakpoint, event, 'maxHeight')); + }; + + const handleHeightChange = (event: ChangeEvent, breakpoint: BreakpointToken) => { + setHeightValue((prevState) => setVerticalDimensionValueForProp(prevState, breakpoint, event, 'height')); + }; + + const generateHeightObject = (isMax = false) => { + return (['mobile', 'tablet', 'desktop'] as BreakpointToken[]).reduce((acc, breakpoint) => { + acc[breakpoint] = isCustomHeightEnabled[breakpoint] + ? `${heightValue[isMax ? 'maxHeight' : 'height'][breakpoint]}px` + : null; + + return acc; + }, {} as Record); + }; + return ( <> @@ -96,46 +169,17 @@ const ModalScrollingLongContent = () => { Modal with ScrollView -

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

-

- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam - mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus - perferendis provident unde. Eveniet, iste, molestiae? -

+ {[...Array(8)].map((_, index) => { + const key = `paragraph-${index}`; + + return ( +

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam at excepturi laudantium magnam + mollitia perferendis reprehenderit, voluptate. Cum delectus dicta ducimus eligendi excepturi natus + perferendis provident unde. Eveniet, iste, molestiae? +

+ ); + })}
@@ -151,26 +195,114 @@ const ModalScrollingLongContent = () => { Modal with Custom Height -

- This modal has a custom height of 400px. -
-
- The max height cannot be customized on mobile though. -

-

- This modal has a custom height of 500px. -
-
- The max height of this modal is 700px. -

+ + {Object.entries(breakpointControls).map(([breakpoint, { marginBottom, className }]) => ( + >} + UNSAFE_className={className} + UNSAFE_style={{ border: 0 }} + > + + + setIsCustomHeightEnabled((prevState) => ({ + ...prevState, + [breakpoint]: (event.target as HTMLInputElement).checked, + })) + } + /> + + + + {isCustomHeightEnabled[breakpoint] ? `${heightValue.height[breakpoint]} px` : '—'} + + handleHeightChange(event, breakpoint)} + step="100" + style={{ + ['--grid-item-column-end' as string]: 13, + ['--grid-item-column-start' as string]: 1, + ['--grid-item-column-start-tablet' as string]: 7, + }} + type="range" + /> + + + + + {isCustomHeightEnabled[breakpoint] ? `${heightValue.maxHeight[breakpoint]} px` : '—'} + + handleMaxHeightChange(event, breakpoint)} + step="100" + style={{ + ['--grid-item-column-end' as string]: 13, + ['--grid-item-column-start' as string]: 1, + ['--grid-item-column-start-tablet' as string]: 7, + }} + type="range" + /> + + + ))} +
diff --git a/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx b/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx index 50c00a95a8..7437fab118 100644 --- a/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx +++ b/packages/web-react/src/components/Modal/stories/ModalDialog.stories.tsx @@ -9,6 +9,9 @@ const meta: Meta = { title: 'Components/Modal', component: ModalDialog, argTypes: { + height: { + control: 'object', + }, isDockedOnMobile: { control: 'boolean', }, @@ -18,23 +21,20 @@ const meta: Meta = { isScrollable: { control: 'boolean', }, - maxHeightFromTabletUp: { - control: 'text', - }, - preferredHeightOnMobile: { - control: 'text', - }, - preferredHeightFromTabletUp: { - control: 'text', + maxHeight: { + control: 'object', }, }, args: { isDockedOnMobile: false, isExpandedOnMobile: false, isScrollable: true, - maxHeightFromTabletUp: '', - preferredHeightOnMobile: '', - preferredHeightFromTabletUp: '', + height: { + mobile: '400px', + tablet: '500px', + desktop: '600px', + }, + maxHeight: {}, }, }; diff --git a/packages/web-react/src/components/Modal/useModalDialogStyleProps.ts b/packages/web-react/src/components/Modal/useModalDialogStyleProps.ts new file mode 100644 index 0000000000..804d56dcc3 --- /dev/null +++ b/packages/web-react/src/components/Modal/useModalDialogStyleProps.ts @@ -0,0 +1,40 @@ +import { ElementType, CSSProperties } from 'react'; +import { ModalDialogCSSHeight, ModalDialogCSSHeightBreakpoints, ModalDialogProps } from '../../types'; + +interface CustomizedHeightCSSProperties extends CSSProperties { + [key: string]: string | undefined | number; +} + +const setCustomHeight = ( + baseVarName: string, + propValue: ModalDialogCSSHeight | ModalDialogCSSHeightBreakpoints | undefined, +): CustomizedHeightCSSProperties => { + if (!propValue) return {}; + + if (typeof propValue === 'object') { + return Object.keys(propValue).reduce((acc, key) => { + const suffix = key === 'mobile' ? '' : `-${key}`; + const propName = `--${baseVarName}${suffix}`; + acc[propName] = propValue[key as keyof ModalDialogCSSHeightBreakpoints]?.toString(); + + return acc; + }, {} as CustomizedHeightCSSProperties); + } + const propName = `--${baseVarName}`; + + return { [propName]: propValue?.toString() } as CustomizedHeightCSSProperties; +}; + +export const useModalDialogStyleProps = (props: ModalDialogProps) => { + const { height, maxHeight, ...otherProps } = props; + + const customizedHeightStyle = { + ...setCustomHeight('modal-dialog-height', height), + ...setCustomHeight('modal-dialog-max-height', maxHeight), + }; + + return { + modalDialogStyleProps: customizedHeightStyle, + props: otherProps, + }; +}; diff --git a/packages/web-react/src/types/modal.ts b/packages/web-react/src/types/modal.ts index b27c772c15..987be739cc 100644 --- a/packages/web-react/src/types/modal.ts +++ b/packages/web-react/src/types/modal.ts @@ -32,10 +32,16 @@ export type ModalDialogBaseProps } & ChildrenProps & StyleProps; +export type ModalDialogCSSHeight = string; +export type ModalDialogCSSHeightBreakpoints = { + mobile?: ModalDialogCSSHeight; + tablet?: ModalDialogCSSHeight; + desktop?: ModalDialogCSSHeight; +}; + export type ModalDialogProps = { - maxHeightFromTabletUp?: string; - preferredHeightOnMobile?: string; - preferredHeightFromTabletUp?: string; + height?: ModalDialogCSSHeight | ModalDialogCSSHeightBreakpoints; + maxHeight?: ModalDialogCSSHeight | ModalDialogCSSHeightBreakpoints; } & ModalDialogBaseProps & OmittedExtendedUnsafeStyleProps, keyof ModalDialogBaseProps>;