diff --git a/packages/core/src/components/Modal/private/Inner.tsx b/packages/core/src/components/Modal/private/Inner.tsx index 9f8586f76..60d199b14 100644 --- a/packages/core/src/components/Modal/private/Inner.tsx +++ b/packages/core/src/components/Modal/private/Inner.tsx @@ -89,6 +89,8 @@ export class ModalInner extends React.Component {children} diff --git a/packages/core/src/components/Modal/private/InnerContent.tsx b/packages/core/src/components/Modal/private/InnerContent.tsx index 32661dbe9..487fc6ebb 100644 --- a/packages/core/src/components/Modal/private/InnerContent.tsx +++ b/packages/core/src/components/Modal/private/InnerContent.tsx @@ -1,5 +1,6 @@ import React from 'react'; import IconClose from '@airbnb/lunar-icons/lib/interface/IconClose'; +import Row from '../../Row'; import Text from '../../Text'; import Title from '../../Title'; import IconButton from '../../IconButton'; @@ -23,6 +24,10 @@ export type ModalInnerContentProps = { subtitle?: React.ReactNode; /** Dialog header title. */ title?: React.ReactNode; + /** Top bar, above dialog header. */ + topBar?: React.ReactNode; + /** Whether the top bar content is centered. */ + topBarCentered?: boolean; /** Callback for when the Dialog should be closed. */ onClose: (event: React.MouseEvent | React.KeyboardEvent) => void; /** Custom style sheet. */ @@ -39,6 +44,8 @@ export default function ModalInnerContent({ scrollable, subtitle, title, + topBar, + topBarCentered, styleSheet, }: ModalInnerContentProps) { const [styles, cx] = useStyles(styleSheet ?? styleSheetInnerContent); @@ -46,24 +53,38 @@ export default function ModalInnerContent({ const withHeader = Boolean(title || subtitle); const withFooter = Boolean(footer); + const closeButton = ( + + + + ); + return (
+ {topBar && ( +
+ + {topBar} + +
+ )} + {withHeader && (
- {title && {title}} - {subtitle && {subtitle}} + +
+ {title && {title}} + {subtitle && {subtitle}} +
+
)} -
- - - -
+ {!withHeader && !topBar &&
{closeButton}
}
{ @@ -36,6 +38,8 @@ class ModalDemo extends React.Component< showScrollable, showSubtitle, showTitle, + showTopBar, + showTopBarCentered, } = this.props; return ( @@ -71,6 +75,8 @@ class ModalDemo extends React.Component< small={showSmall} subtitle={showSubtitle ? 'Modal Sub-Title' : undefined} title={showTitle ? : undefined} + topBar={showTopBar ? Top bar : undefined} + topBarCentered={showTopBarCentered} onClose={this.handleClose} > @@ -151,6 +157,22 @@ withTitleAndFooter.story = { name: 'With title and footer', }; +export function withTopBar() { + return ; +} + +withTopBar.story = { + name: 'With top bar, not centered', +}; + +export function withTopBarCentered() { + return ; +} + +withTopBarCentered.story = { + name: 'With top bar, centered', +}; + export function scrollableContent() { return ; } @@ -159,6 +181,22 @@ scrollableContent.story = { name: 'Scrollable content', }; +export function withTopBarAndFooter() { + return ; +} + +withTopBarAndFooter.story = { + name: 'With top bar, topBarCentered, footer, and scrollable', +}; + +export function withTitleTopBarAndFooter() { + return ; +} + +withTitleTopBarAndFooter.story = { + name: 'With top bar, title, footer, and scrollable', +}; + export function scrollableContentWithoutFooter() { return ; } diff --git a/packages/core/src/components/Modal/styles.ts b/packages/core/src/components/Modal/styles.ts index ab552b7de..e53d696ec 100644 --- a/packages/core/src/components/Modal/styles.ts +++ b/packages/core/src/components/Modal/styles.ts @@ -107,7 +107,11 @@ export const styleSheetInnerContent: StyleSheet = ({ color, ui, unit }) => ({ }, header: { - padding: `${unit * 3}px ${unit * 4}px ${unit * 3}px ${unit * 3}px`, + padding: `${unit * 2}px ${unit * 2}px ${unit * 3}px ${unit * 3}px`, + }, + + headerInner: { + paddingTop: unit, }, header_scrollable: { @@ -127,18 +131,18 @@ export const styleSheetInnerContent: StyleSheet = ({ color, ui, unit }) => ({ }, }, - close: { - position: 'absolute', - top: unit * 2, - right: unit * 2, - zIndex: Z_INDEX_MODAL, + topBar: { + borderBottom: ui.border, + paddingBottom: unit * 2, + }, + + topBar_centered: { + textAlign: 'center', + paddingLeft: unit * 8, // width of close button + padding around to offset top bar content }, close_float: { float: 'right', - position: 'relative', - top: 0, - right: 0, margin: `${unit * 2}px ${unit * 2}px ${unit / 2}px ${unit / 2}px`, }, @@ -170,6 +174,12 @@ export const styleSheetInnerContent: StyleSheet = ({ color, ui, unit }) => ({ }, }, + body_scrollable_noHeader: { + ':before': { + display: 'none', + }, + }, + body_scrollableSmall: { maxHeight: 160, }, diff --git a/packages/core/test/components/Modal.test.tsx b/packages/core/test/components/Modal.test.tsx index f0f3b5f2c..65e1fef64 100644 --- a/packages/core/test/components/Modal.test.tsx +++ b/packages/core/test/components/Modal.test.tsx @@ -315,6 +315,21 @@ describe('', () => { expect(wrapper.find('header').find(Text).prop('children')).toBe('Subtitle'); }); + it('renders a top bar', () => { + const { wrapper } = setup( + { + title: null, + topBar: 'Top bar', + }, + false /* isShallow */, + ); + + expect(wrapper.find('header')).toHaveLength(0); + + const div = wrapper.find(ModalInnerContent).find('div').at(1); + expect(div.text()).toContain('Top bar'); + }); + it('no header if no title is provided', () => { const { wrapper } = setup( {