mirror of
https://github.com/primer/css.git
synced 2024-12-23 06:01:54 +03:00
OverlayBase ongoing stylesheets
This commit is contained in:
parent
0cca3c1e33
commit
74a4d200c3
@ -1,620 +0,0 @@
|
||||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import ConditionalWrapper from '../../helpers/ConditionalWrapper'
|
||||
import {PatternFullBleed} from '../ActionList/ActionListFeatures.stories.jsx'
|
||||
const variant = {}
|
||||
export default {
|
||||
title: 'UI Patterns/Overlay',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
excludeStories: ['OverlayTemplate'],
|
||||
argTypes: {
|
||||
|
||||
// Header
|
||||
|
||||
title: {
|
||||
name: 'title',
|
||||
type: {name: 'string', required: true},
|
||||
description: 'The heading element of the overlay',
|
||||
defaultValue: 'Title',
|
||||
table: {
|
||||
category: 'Header'
|
||||
}
|
||||
},
|
||||
description: {
|
||||
name: 'description',
|
||||
type: 'string',
|
||||
description: 'The sub-heading element of the overlay',
|
||||
defaultValue: '',
|
||||
table: {
|
||||
category: 'Header'
|
||||
}
|
||||
},
|
||||
headerVariant: {
|
||||
options: ['medium', 'large', 'custom'],
|
||||
defaultValue: 'medium',
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
},
|
||||
description: 'medium title (default), large title, or custom header',
|
||||
table: {
|
||||
category: 'Header'
|
||||
}
|
||||
},
|
||||
|
||||
toggleOverlay: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'show/hide overlay',
|
||||
defaultValue: false,
|
||||
table: {
|
||||
category: 'Demo'
|
||||
}
|
||||
},
|
||||
showCloseButton: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'show/hide close button',
|
||||
defaultValue: false,
|
||||
table: {
|
||||
category: 'Demo'
|
||||
}
|
||||
},
|
||||
showFooterButton: {
|
||||
control: {type: 'boolean'},
|
||||
description: 'show/hide footer button',
|
||||
defaultValue: false,
|
||||
table: {
|
||||
category: 'Demo'
|
||||
}
|
||||
},
|
||||
|
||||
// Properties
|
||||
|
||||
width: {
|
||||
options: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
},
|
||||
description: 'Width options: xsmall: 192px, small: 256px, medium: 320px, large: 480px, xlarge: 640px, xxlarge: 960px',
|
||||
table: {
|
||||
category: 'Properties'
|
||||
}
|
||||
},
|
||||
height: {
|
||||
options: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
},
|
||||
description: 'Height options: auto: adjusts to content, xsmall: 192px, small: 256px, medium: 320px, large: 432px, xlarge: 600px',
|
||||
table: {
|
||||
category: 'Properties'
|
||||
}
|
||||
},
|
||||
bodyPaddingVariant: {
|
||||
options: [0, 1, 2], // iterator
|
||||
mapping: ['', 'Overlay-body--paddingCondensed', 'Overlay-body--paddingNone'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['normal (default)', 'condensed', 'none']
|
||||
},
|
||||
description: 'body spacing',
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
|
||||
// Variant
|
||||
|
||||
variant: {
|
||||
options: ['center', 'anchor', 'side', 'full'],
|
||||
type: {
|
||||
name: 'select',
|
||||
required: true,
|
||||
},
|
||||
description: '',
|
||||
table: {
|
||||
category: 'Variant'
|
||||
}
|
||||
},
|
||||
variantWhenNarrow: {
|
||||
options: ['inherit', 'center', 'anchor', 'side', 'full'],
|
||||
defaultValue: 'inherit',
|
||||
type: {
|
||||
name: 'select',
|
||||
required: false,
|
||||
},
|
||||
description: '',
|
||||
table: {
|
||||
category: 'Variant'
|
||||
}
|
||||
},
|
||||
placementNarrow: {
|
||||
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
mapping: [
|
||||
'Overlay-backdrop--placement-top-whenNarrow',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-bottom-whenNarrow',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-right-whenNarrow',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-left-whenNarrow',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
''
|
||||
],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-center',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-center',
|
||||
'bottom-end',
|
||||
'right',
|
||||
'right-start',
|
||||
'right-center',
|
||||
'right-end',
|
||||
'left',
|
||||
'left-start',
|
||||
'left-center',
|
||||
'left-end',
|
||||
'reset'
|
||||
]
|
||||
},
|
||||
description: 'Positions overlay for narrow viewport range',
|
||||
table: {
|
||||
category: 'Placement'
|
||||
}
|
||||
},
|
||||
placementRegular: {
|
||||
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
|
||||
mapping: [
|
||||
'Overlay-backdrop--placement-top',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-bottom',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-right',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'Overlay-backdrop--placement-left',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
''
|
||||
],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: [
|
||||
'top',
|
||||
'top-start',
|
||||
'top-center',
|
||||
'top-end',
|
||||
'bottom',
|
||||
'bottom-start',
|
||||
'bottom-center',
|
||||
'bottom-end',
|
||||
'right',
|
||||
'right-start',
|
||||
'right-center',
|
||||
'right-end',
|
||||
'left',
|
||||
'left-start',
|
||||
'left-center',
|
||||
'left-end',
|
||||
'reset'
|
||||
]
|
||||
},
|
||||
description: 'Positions overlay for regular viewport range',
|
||||
table: {
|
||||
category: 'Placement'
|
||||
}
|
||||
},
|
||||
hasHeader: {
|
||||
control: {type: 'boolean'},
|
||||
description:
|
||||
'A header region may be used to provide context to the user by displaying a title, description, and offering an easy-to-escape route with a Close button. Headers may also provide ways for the user to interact with the content, such as with search and tabs.',
|
||||
defaultValue: true,
|
||||
table: {
|
||||
category: 'Header'
|
||||
}
|
||||
},
|
||||
hasFooter: {
|
||||
control: {type: 'boolean'},
|
||||
description:
|
||||
'The footer region may be used to show confirmation actions, navigation links, or other important elements that should appear outside of the content scrolling region.',
|
||||
defaultValue: true,
|
||||
table: {
|
||||
category: 'Footer'
|
||||
}
|
||||
},
|
||||
showFooterDivider: {
|
||||
control: {type: 'boolean'},
|
||||
defaultValue: false,
|
||||
description: 'Show dividers above footer',
|
||||
table: {
|
||||
category: 'CSS'
|
||||
}
|
||||
},
|
||||
showHeaderDivider: {
|
||||
control: {type: 'boolean'},
|
||||
defaultValue: false,
|
||||
description: 'Show dividers below header',
|
||||
table: {
|
||||
category: 'Header'
|
||||
}
|
||||
},
|
||||
headerSlot: {
|
||||
description: 'Slot for custom header content. Only shown if header variant is set to `custom`.',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
subHeaderSlot: {
|
||||
description: 'Slot for sub header content, present below the header and before the body.',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
actionContentSlot: {
|
||||
description: 'Slot for additional header action',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
motion: {
|
||||
options: ['auto', 'none', 'scaleFade', 'slide', 'slideFade'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
},
|
||||
description: 'Animation options for show/hide overlay',
|
||||
table: {
|
||||
category: 'Properties'
|
||||
}
|
||||
},
|
||||
footerContentAlign: {
|
||||
options: [0, 1, 2], // iterator
|
||||
mapping: ['Overlay-footer--alignStart', 'Overlay-footer--alignCenter', 'Overlay-footer--alignEnd'], // values
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
labels: ['start', 'center', 'end']
|
||||
},
|
||||
description: 'Align footer contents',
|
||||
table: {
|
||||
category: 'Footer'
|
||||
}
|
||||
},
|
||||
role: {
|
||||
description: 'Semantic role',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
ariaLabelledy: {
|
||||
description: 'aria-labelledby',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
ariaDescribedby: {
|
||||
description: 'aria-describedby',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
dataFocusTrap: {
|
||||
description: 'data-focus-trap',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
titleId: {
|
||||
description: 'title id',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
},
|
||||
descriptionId: {
|
||||
description: 'description id',
|
||||
control: {type: 'string'},
|
||||
table: {
|
||||
category: 'HTML'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusMethod = function getFocus() {
|
||||
const dialog = document.getElementById('overlay-backdrop')[0]
|
||||
dialog.focus()
|
||||
}
|
||||
|
||||
const toggleDialog = () => {
|
||||
const dialog = document.getElementById('overlay-backdrop')
|
||||
dialog.classList.toggle('Overlay--hidden')
|
||||
focusMethod()
|
||||
}
|
||||
|
||||
export const OverlayTemplate = ({
|
||||
title,
|
||||
description,
|
||||
toggleOverlay,
|
||||
variantWhenNarrow,
|
||||
variant,
|
||||
width,
|
||||
height,
|
||||
showFooterDivider,
|
||||
showHeaderDivider,
|
||||
hasHeader,
|
||||
hasFooter,
|
||||
headerSlot,
|
||||
subHeaderSlot,
|
||||
motion,
|
||||
footerContentAlign,
|
||||
showCloseButton,
|
||||
showFooterButton,
|
||||
actionContentSlot,
|
||||
headerVariant,
|
||||
bodyPaddingVariant,
|
||||
role,
|
||||
ariaLabelledby,
|
||||
ariaDescribedby,
|
||||
dataFocusTrap,
|
||||
bodySlot,
|
||||
titleId,
|
||||
descriptionId,
|
||||
placementNarrow,
|
||||
placementRegular
|
||||
}) => {
|
||||
|
||||
// Default values
|
||||
width = width ?? 'auto';
|
||||
height = height ?? 'auto';
|
||||
motion = motion ?? 'auto';
|
||||
variant = variant ?? 'center';
|
||||
variantWhenNarrow = variantWhenNarrow ?? 'inherit';
|
||||
headerVariant = headerVariant ?? 'medium';
|
||||
|
||||
// Inherit values
|
||||
variantWhenNarrow = variantWhenNarrow === 'inherit' ? variant : variantWhenNarrow;
|
||||
|
||||
// Leave `null` values for states that don't require a modifier class
|
||||
headerVariant = headerVariant === 'medium' ? null : headerVariant;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<button class="btn" onClick={toggleDialog}>
|
||||
<span>Open overlay</span>
|
||||
</button>
|
||||
<div
|
||||
id="overlay-backdrop"
|
||||
className={clsx(
|
||||
toggleOverlay && 'Overlay--hidden',
|
||||
variant && `Overlay-backdrop--${variant}`,
|
||||
variantWhenNarrow && `Overlay-backdrop--${variantWhenNarrow}-whenNarrow`,
|
||||
placementRegular && `${placementRegular}`,
|
||||
placementNarrow && `${placementNarrow}`,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'Overlay',
|
||||
width && `Overlay--width-${width}`,
|
||||
height && `Overlay--height-${height}`,
|
||||
motion && `Overlay--motion-${motion}`,
|
||||
)}
|
||||
data-focus-trap={dataFocusTrap}
|
||||
role={role}
|
||||
aria-labelledby={ariaLabelledby}
|
||||
aria-describedby={ariaDescribedby}
|
||||
open
|
||||
>
|
||||
{hasHeader && (
|
||||
<header
|
||||
className={clsx(
|
||||
'Overlay-header',
|
||||
showHeaderDivider && 'Overlay-header--divided',
|
||||
headerVariant && `Overlay-header--${headerVariant}`
|
||||
)}
|
||||
aria-role="none"
|
||||
>
|
||||
<div className="Overlay-headerContentWrap">
|
||||
<div className="Overlay-titleWrap">
|
||||
{title && (
|
||||
<h1 id={titleId} className="Overlay-title">
|
||||
{title}
|
||||
</h1>
|
||||
)}
|
||||
{description && (
|
||||
<h2 id={descriptionId} className="Overlay-description">
|
||||
{description}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
{headerVariant == 'custom' && headerSlot && (
|
||||
<div className="Overlay-customHeader">
|
||||
{headerSlot}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showCloseButton && (
|
||||
<div className="Overlay-actionWrap">
|
||||
{actionContentSlot && <div dangerouslySetInnerHTML={{__html: actionContentSlot}} />}
|
||||
<button className="Overlay-closeButton" aria-label="Close" onClick={toggleDialog}>
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{subHeaderSlot && (
|
||||
<div className="Overlay-subHeader">
|
||||
{subHeaderSlot}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
<div className={clsx('Overlay-body', bodyPaddingVariant && `${bodyPaddingVariant}`)}>{bodySlot}</div>
|
||||
{hasFooter && (
|
||||
<footer
|
||||
className={clsx(
|
||||
'Overlay-footer',
|
||||
showFooterDivider && 'Overlay-footer--divided',
|
||||
footerContentAlign && `${footerContentAlign}`
|
||||
)}
|
||||
>
|
||||
{showFooterButton && (
|
||||
<button class="btn" onClick={toggleDialog}>
|
||||
<span>Continue</span>
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Playground = OverlayTemplate.bind();
|
||||
Playground.storyName = 'Playground';
|
||||
Playground.args = {
|
||||
title: 'This is the title of the dialog',
|
||||
description: 'This is the subtitle of the dialog',
|
||||
motion: 1,
|
||||
footerContentAlign: 2,
|
||||
showCloseButton: true,
|
||||
showFooterButton: false,
|
||||
headerSlot: '',
|
||||
actionContentSlot: '',
|
||||
headerVariant: 'medium',
|
||||
bodyPaddingVariant: 0,
|
||||
width: 'small',
|
||||
height: 'small',
|
||||
hasHeader: true,
|
||||
hasFooter: true,
|
||||
showFooterDivider: false,
|
||||
showHeaderDivider: false,
|
||||
role: '',
|
||||
ariaDescribedby: '',
|
||||
dataFocusTrap: '',
|
||||
bodySlot: (
|
||||
<>
|
||||
<p>This is the body of the dialog</p>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export const Dialog = OverlayTemplate.bind();
|
||||
Dialog.storyName = 'Dialog';
|
||||
Dialog.args = {
|
||||
variant: 'center',
|
||||
|
||||
// Header
|
||||
hasHeader: true,
|
||||
title: 'Dialog title',
|
||||
description: 'This is the subtitle of the dialog',
|
||||
|
||||
// Properties
|
||||
width: 'medium',
|
||||
height: 'small',
|
||||
motion: 'auto',
|
||||
|
||||
footerContentAlign: 2,
|
||||
showCloseButton: true,
|
||||
showFooterButton: false,
|
||||
headerSlot: '',
|
||||
actionContentSlot: '',
|
||||
headerVariant: 'large',
|
||||
bodyPaddingVariant: 0,
|
||||
hasFooter: false,
|
||||
showFooterDivider: false,
|
||||
showHeaderDivider: false,
|
||||
role: '',
|
||||
ariaDescribedby: '',
|
||||
dataFocusTrap: '',
|
||||
bodySlot: (
|
||||
<>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
export const CustomHeader = OverlayTemplate.bind();
|
||||
CustomHeader.storyName = 'Custom header';
|
||||
CustomHeader.args = {
|
||||
variant: 'center',
|
||||
|
||||
// Header
|
||||
hasHeader: true,
|
||||
title: 'Dialog title',
|
||||
description: 'This is the subtitle of the dialog',
|
||||
|
||||
// Properties
|
||||
width: 'medium',
|
||||
height: 'small',
|
||||
motion: 'auto',
|
||||
|
||||
// Header
|
||||
headerVariant: 'custom',
|
||||
headerSlot: (
|
||||
<><div style={{background: 'pink', height: '32px', width: '100%'}}>Custom header</div></>
|
||||
),
|
||||
subHeaderSlot: (
|
||||
<>UnderlineNav</>
|
||||
),
|
||||
|
||||
footerContentAlign: 2,
|
||||
showCloseButton: true,
|
||||
showFooterButton: false,
|
||||
actionContentSlot: '',
|
||||
bodyPaddingVariant: 0,
|
||||
hasFooter: false,
|
||||
showFooterDivider: false,
|
||||
showHeaderDivider: false,
|
||||
role: '',
|
||||
ariaDescribedby: '',
|
||||
dataFocusTrap: '',
|
||||
|
||||
bodySlot: (
|
||||
<>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
<div>Lorem ipsum dolor sit amet.</div>
|
||||
</>
|
||||
)
|
||||
};
|
275
docs/src/stories/ui-patterns/Overlay/OverlayBase.stories.jsx
Normal file
275
docs/src/stories/ui-patterns/Overlay/OverlayBase.stories.jsx
Normal file
@ -0,0 +1,275 @@
|
||||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import ConditionalWrapper from '../../helpers/ConditionalWrapper'
|
||||
import {PatternFullBleed} from '../ActionList/ActionListFeatures.stories.jsx'
|
||||
|
||||
export default {
|
||||
title: 'UI Patterns/Overlay base',
|
||||
parameters: {
|
||||
layout: 'padded'
|
||||
},
|
||||
excludeStories: ['OverlayBaseTemplate'],
|
||||
argTypes: {
|
||||
|
||||
// # backdrop
|
||||
// # - visible
|
||||
// # - transparent
|
||||
// # - none
|
||||
// # motion
|
||||
// # - auto
|
||||
// # - none
|
||||
// # placement
|
||||
// # - viewport
|
||||
// # - top, left, right, bottom, full, center
|
||||
// # - anchored
|
||||
// # - [directions]
|
||||
// # open/dismiss behavior
|
||||
// # - open: boolean
|
||||
// # - API client-side:
|
||||
// # - open
|
||||
// # - dismiss
|
||||
// # sizing
|
||||
// # - width: auto, xsmall, small, medium, large, xlarge, xxlarge
|
||||
// # - height: auto, xsmall, small, medium, large, xlarge
|
||||
|
||||
// backdrop
|
||||
backdrop: {
|
||||
options: ['visible', 'transparent', 'none'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
}
|
||||
},
|
||||
placement: {
|
||||
// Todo: fix placement options
|
||||
options: ['top', 'left', 'right', 'bottom', 'full', 'center', 'anchor'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
}
|
||||
},
|
||||
motion: {
|
||||
options: ['auto', 'none'],
|
||||
control: {
|
||||
type: 'inline-radio',
|
||||
}
|
||||
},
|
||||
width: {
|
||||
options: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge'],
|
||||
control: {
|
||||
type: 'select',
|
||||
}
|
||||
},
|
||||
height: {
|
||||
options: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xlarge'],
|
||||
control: {
|
||||
type: 'select',
|
||||
}
|
||||
},
|
||||
open: {
|
||||
control: {
|
||||
type: 'boolean',
|
||||
}
|
||||
},
|
||||
|
||||
// content
|
||||
contentSlot: {
|
||||
description: 'Slot for the overlay contents.',
|
||||
control: {type: 'string'},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const focusOverlay = function getFocus() {
|
||||
const overlay = document.querySelector('.Overlay');
|
||||
overlay.focus()
|
||||
}
|
||||
|
||||
const toggleOverlay = () => {
|
||||
const overlay = document.querySelector('.Overlay');
|
||||
overlay.classList.toggle('Overlay--open');
|
||||
focusOverlay();
|
||||
}
|
||||
|
||||
export const OverlayBaseTemplate = ({
|
||||
placement,
|
||||
backdrop,
|
||||
motion,
|
||||
width,
|
||||
height,
|
||||
open,
|
||||
contentSlot
|
||||
}) => {
|
||||
|
||||
// Default values
|
||||
backdrop = backdrop ?? 'visible';
|
||||
placement = placement ?? 'center';
|
||||
width = width ?? 'auto';
|
||||
height = height ?? 'auto';
|
||||
motion = motion ?? 'auto';
|
||||
|
||||
// Inherit values
|
||||
// variantWhenNarrow = variantWhenNarrow === 'inherit' ? variant : variantWhenNarrow;
|
||||
|
||||
// Leave `null` values for states that don't require a modifier class
|
||||
// headerVariant = headerVariant === 'medium' ? null : headerVariant;
|
||||
|
||||
return (
|
||||
<>
|
||||
<button class="btn" onClick={toggleOverlay}>
|
||||
<span>Open overlay</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={clsx(
|
||||
'Overlay',
|
||||
open && 'Overlay--open',
|
||||
placement && `Overlay--${placement}`,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'Overlay-wrapper',
|
||||
)}
|
||||
// data-focus-trap={dataFocusTrap}
|
||||
// role={role}
|
||||
// aria-labelledby={ariaLabelledby}
|
||||
// aria-describedby={ariaDescribedby}
|
||||
// open
|
||||
>
|
||||
<div className={clsx(
|
||||
'Overlay-content',
|
||||
width && `Overlay-content--width-${width}`,
|
||||
height && `Overlay-content--height-${height}`,
|
||||
motion && `Overlay-content--motion-${motion}`,
|
||||
)}>
|
||||
{contentSlot}
|
||||
</div>
|
||||
</div>
|
||||
{backdrop !== 'none' && (
|
||||
<div className={clsx(
|
||||
'Overlay-backdrop',
|
||||
backdrop && `Overlay-backdrop--${backdrop}`,
|
||||
)}></div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const Playground = OverlayBaseTemplate.bind();
|
||||
Playground.storyName = 'Playground';
|
||||
Playground.args = {
|
||||
placement: 'viewport-top',
|
||||
width: 'small',
|
||||
height: 'small',
|
||||
open: true,
|
||||
// title: 'This is the title of the dialog',
|
||||
// description: 'This is the subtitle of the dialog',
|
||||
// motion: 1,
|
||||
// footerContentAlign: 2,
|
||||
// showCloseButton: true,
|
||||
// showFooterButton: false,
|
||||
// headerSlot: '',
|
||||
// actionContentSlot: '',
|
||||
// headerVariant: 'medium',
|
||||
// bodyPaddingVariant: 0,
|
||||
// hasHeader: true,
|
||||
// hasFooter: true,
|
||||
// showFooterDivider: false,
|
||||
// showHeaderDivider: false,
|
||||
// role: '',
|
||||
// ariaDescribedby: '',
|
||||
// dataFocusTrap: '',
|
||||
contentSlot: (
|
||||
<>
|
||||
<p>This is the body of the dialog</p>
|
||||
</>
|
||||
)
|
||||
};
|
||||
|
||||
// export const Dialog = OverlayBaseTemplate.bind();
|
||||
// Dialog.storyName = 'Dialog';
|
||||
// Dialog.args = {
|
||||
// /*
|
||||
// variant: 'center',
|
||||
|
||||
// // Header
|
||||
// hasHeader: true,
|
||||
// title: 'Dialog title',
|
||||
// description: 'This is the subtitle of the dialog',
|
||||
|
||||
// // Properties
|
||||
// width: 'medium',
|
||||
// height: 'small',
|
||||
// motion: 'auto',
|
||||
|
||||
// footerContentAlign: 2,
|
||||
// showCloseButton: true,
|
||||
// showFooterButton: false,
|
||||
// headerSlot: '',
|
||||
// actionContentSlot: '',
|
||||
// headerVariant: 'large',
|
||||
// bodyPaddingVariant: 0,
|
||||
// hasFooter: false,
|
||||
// showFooterDivider: false,
|
||||
// showHeaderDivider: false,
|
||||
// role: '',
|
||||
// ariaDescribedby: '',
|
||||
// dataFocusTrap: '',
|
||||
// */
|
||||
// contentSlot: (
|
||||
// <>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// </>
|
||||
// )
|
||||
// };
|
||||
|
||||
// export const CustomHeader = OverlayBaseTemplate.bind();
|
||||
// CustomHeader.storyName = 'Custom header';
|
||||
// CustomHeader.args = {
|
||||
// variant: 'center',
|
||||
|
||||
// // Header
|
||||
// hasHeader: true,
|
||||
// title: 'Dialog title',
|
||||
// description: 'This is the subtitle of the dialog',
|
||||
|
||||
// // Properties
|
||||
// width: 'medium',
|
||||
// height: 'small',
|
||||
// motion: 'auto',
|
||||
|
||||
// // Header
|
||||
// headerVariant: 'custom',
|
||||
// headerSlot: (
|
||||
// <><div style={{background: 'pink', height: '32px', width: '100%'}}>Custom header</div></>
|
||||
// ),
|
||||
// subHeaderSlot: (
|
||||
// <>UnderlineNav</>
|
||||
// ),
|
||||
|
||||
// footerContentAlign: 2,
|
||||
// showCloseButton: true,
|
||||
// showFooterButton: false,
|
||||
// actionContentSlot: '',
|
||||
// bodyPaddingVariant: 0,
|
||||
// hasFooter: false,
|
||||
// showFooterDivider: false,
|
||||
// showHeaderDivider: false,
|
||||
// role: '',
|
||||
// ariaDescribedby: '',
|
||||
// dataFocusTrap: '',
|
||||
|
||||
// bodySlot: (
|
||||
// <>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// <div>Lorem ipsum dolor sit amet.</div>
|
||||
// </>
|
||||
// )
|
||||
// };
|
@ -1,2 +1,2 @@
|
||||
@import '../support/index.scss';
|
||||
@import './overlay.scss';
|
||||
@import './overlay-base.scss';
|
||||
|
@ -23,40 +23,171 @@ $Overlay-height: (
|
||||
xlarge: 600px
|
||||
) !default;
|
||||
|
||||
// Helpers
|
||||
|
||||
@mixin Overlay--fullViewportHeight() {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
max-height: 100vh;
|
||||
|
||||
@supports(height: 100dvh) {
|
||||
height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay structure
|
||||
// =================
|
||||
//
|
||||
// .Overlay-backdrop
|
||||
// ├─ .Overlay
|
||||
// │ ├─ .Overlay-header
|
||||
// │ │ ├─ .Overlay-headerContentWrap
|
||||
// │ │ │ ├─ .Overlay-titleWrap
|
||||
// │ │ │ │ ├─ .Overlay-title
|
||||
// │ │ │ │ ├─ .Overlay-description
|
||||
// │ │ ├─ .Overlay-actionWrap
|
||||
// │ │ │ ├─ .Overlay-closeButton
|
||||
// │ ├─ .Overlay-body
|
||||
// │ ├─ .Overlay-footer
|
||||
//
|
||||
// Todo:
|
||||
// - Overlay-form?
|
||||
// - Deprecate Overlay-closeButton in favor of redesigned iconButton
|
||||
|
||||
// Visibility
|
||||
// Todo: these are applied to `Overlay-backdrop`, not to `Overlay`
|
||||
|
||||
.Overlay--hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.Overlay--visibilityHidden {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
// .Overlay
|
||||
// ├─ .Overlay-wrapper
|
||||
// │ │ ├─ .Overlay-content
|
||||
// ├─ .Overlay-backdrop
|
||||
|
||||
.Overlay {
|
||||
isolation: isolate;
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
@include Overlay--fullViewportHeight;
|
||||
}
|
||||
.Overlay--open {
|
||||
visibility: visible;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
// Overlay-wrapper
|
||||
|
||||
.Overlay-wrapper {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.Overlay--anchor {
|
||||
// Todo: anchored position
|
||||
outline: 10px solid pink;
|
||||
}
|
||||
|
||||
.Overlay--center {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Overlay--full {
|
||||
.Overlay-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.Overlay-content {
|
||||
flex-grow: 1;
|
||||
border-radius: unset;
|
||||
width: 100%;
|
||||
max-width: 100vw;
|
||||
|
||||
@include Overlay--fullViewportHeight;
|
||||
}
|
||||
}
|
||||
|
||||
.Overlay--top {
|
||||
align-items: start;
|
||||
justify-content: center;
|
||||
|
||||
.Overlay-content {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-slideInDown;
|
||||
}
|
||||
border-radius: $primer-borderRadius-large;
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Overlay--left {
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
|
||||
.Overlay-content {
|
||||
border-radius: $primer-borderRadius-large;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
@include Overlay--fullViewportHeight;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-slideInRight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Overlay--bottom {
|
||||
align-items: end;
|
||||
justify-content: center;
|
||||
|
||||
.Overlay-content {
|
||||
border-radius: $primer-borderRadius-large;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-slideInUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Overlay--right {
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
|
||||
.Overlay-content {
|
||||
border-radius: $primer-borderRadius-large;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
||||
@include Overlay--fullViewportHeight;
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-slideInLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overlay-backdrop
|
||||
|
||||
.Overlay-backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
background-color: var(--color-neutral-muted);
|
||||
|
||||
@include Overlay--fullViewportHeight;
|
||||
}
|
||||
|
||||
.Overlay-backdrop--transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
// .Overlay--hidden {
|
||||
// display: none !important;
|
||||
// }
|
||||
|
||||
// Question: what's visibilityHidden used for?
|
||||
// .Overlay--visibilityHidden {
|
||||
// height: 0;
|
||||
// overflow: hidden;
|
||||
// visibility: hidden;
|
||||
// opacity: 0;
|
||||
// }
|
||||
|
||||
// Overlay-content
|
||||
|
||||
.Overlay-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: #{map-get($Overlay-width, 'xsmall')};
|
||||
@ -70,88 +201,95 @@ $Overlay-height: (
|
||||
@supports (height: 100dvh) {
|
||||
max-height: 100dvh;
|
||||
}
|
||||
|
||||
// Dimensions
|
||||
// Todo: auto-generate based on Scss variables?
|
||||
|
||||
&.Overlay--height-auto {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.Overlay--height-xsmall {
|
||||
height: min(#{map-get($Overlay-height, 'xsmall')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--height-small {
|
||||
height: min(#{map-get($Overlay-height, 'small')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--height-medium {
|
||||
height: min(#{map-get($Overlay-height, 'medium')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--height-large {
|
||||
height: min(#{map-get($Overlay-height, 'large')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--height-xlarge {
|
||||
height: min(#{map-get($Overlay-height, 'xlarge')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&.Overlay--width-xsmall {
|
||||
width: min(#{map-get($Overlay-width, 'xsmall')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-small {
|
||||
width: min(#{map-get($Overlay-width, 'small')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-medium {
|
||||
width: min(#{map-get($Overlay-width, 'medium')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-large {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'large')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-xlarge {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'xlarge')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
&.Overlay--width-xxlarge {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'xxlarge')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
// Motion
|
||||
|
||||
&.Overlay--motion-scaleFade {
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
// Todo: replace with primer-duration-slow token
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-scaleFade;
|
||||
}
|
||||
}
|
||||
|
||||
// &.Overlay--motion-slide {
|
||||
// @media (prefers-reduced-motion: no-preference) {
|
||||
// animation: $primer-duration-slow $primer-easing-out Overlay--motion-slide;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// Dimensions
|
||||
|
||||
.Overlay-content--height-auto {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.Overlay-content--height-xsmall {
|
||||
height: min(#{map-get($Overlay-height, 'xsmall')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--height-small {
|
||||
height: min(#{map-get($Overlay-height, 'small')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--height-medium {
|
||||
height: min(#{map-get($Overlay-height, 'medium')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--height-large {
|
||||
height: min(#{map-get($Overlay-height, 'large')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--height-xlarge {
|
||||
height: min(#{map-get($Overlay-height, 'xlarge')}, 100vh - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-auto {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.Overlay-content--width-xsmall {
|
||||
width: min(#{map-get($Overlay-width, 'xsmall')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-small {
|
||||
width: min(#{map-get($Overlay-width, 'small')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-medium {
|
||||
width: min(#{map-get($Overlay-width, 'medium')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-large {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'large')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-xlarge {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'xlarge')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
.Overlay-content--width-xxlarge {
|
||||
// stylelint-disable-next-line primer/responsive-widths
|
||||
width: min(#{map-get($Overlay-width, 'xxlarge')}, 100vw - 2rem);
|
||||
}
|
||||
|
||||
// Motion
|
||||
|
||||
.Overlay--motion-auto {
|
||||
// Todo: Apply animation to Overlay-wrapper?
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
// Todo: replace with primer-duration-slow token
|
||||
animation: $primer-duration-slow $primer-easing-out Overlay--motion-scaleFade;
|
||||
}
|
||||
}
|
||||
|
||||
.Overlay--motion-none {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
|
||||
// &.Overlay--motion-slide {
|
||||
// @media (prefers-reduced-motion: no-preference) {
|
||||
// animation: $primer-duration-slow $primer-easing-out Overlay--motion-slide;
|
||||
// }
|
||||
// }
|
||||
|
||||
// for <form> element that wraps entire contents of overlay
|
||||
.Overlay-form {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
// .Overlay-form {
|
||||
// display: flex;
|
||||
// overflow: auto;
|
||||
// flex-direction: column;
|
||||
// flex-grow: 1;
|
||||
// }
|
||||
|
||||
// Move overlay header to specialized dialog component?
|
||||
|
||||
.Overlay-header {
|
||||
z-index: 1;
|
Loading…
Reference in New Issue
Block a user