diff --git a/.changeset/slow-poets-mate.md b/.changeset/slow-poets-mate.md new file mode 100644 index 0000000000..80b58387b9 --- /dev/null +++ b/.changeset/slow-poets-mate.md @@ -0,0 +1,5 @@ +--- +"@primer/css": minor +--- + +Styles for the new Dialog Component diff --git a/docs/content/components/dialog.md b/docs/content/components/dialog.md new file mode 100644 index 0000000000..61e4e749ed --- /dev/null +++ b/docs/content/components/dialog.md @@ -0,0 +1,58 @@ +--- +title: Dialog +path: components/dialog +status: Alpha +source: 'https://github.com/github/github/tree/master/app/assets/stylesheets/experiments/modal-dialog.scss' +bundle: dialog +--- + +Please note that the `
` element with `id="fake-container"` is not included in the component. + +```html live +
+ + +
+``` + +[aria attributes]: https://www.w3.org/TR/html-aria/#allowed-aria-roles-states-and-properties diff --git a/docs/src/stories/components/Dialog/Dialog.stories.jsx b/docs/src/stories/components/Dialog/Dialog.stories.jsx new file mode 100644 index 0000000000..c43b6a8f0e --- /dev/null +++ b/docs/src/stories/components/Dialog/Dialog.stories.jsx @@ -0,0 +1,388 @@ +import React from 'react' +import clsx from 'clsx' +import {OverlayTemplate} from '../../ui-patterns/Overlay/Overlay.stories' + +export default { + title: 'Components/Dialog', + parameters: { + layout: 'padded' + }, + + excludeStories: ['DialogTemplate'], + argTypes: { + title: { + name: 'title', + type: {name: 'string', required: false}, + description: 'The heading element of the dialog', + defaultValue: '', + table: { + category: 'HTML' + } + }, + description: { + name: 'description', + type: 'string', + description: 'The sub-heading element of the dialog', + defaultValue: '', + table: { + category: 'HTML' + } + }, + toggleOverlay: { + control: {type: 'boolean'}, + description: 'show/hide overlay', + defaultValue: false, + table: { + category: 'Demo' + } + }, + showCloseButton: { + control: {type: 'boolean'}, + description: 'show/hide close button', + defaultValue: true, + table: { + category: 'Demo' + } + }, + showFooterButton: { + control: {type: 'boolean'}, + description: 'show/hide footer button', + defaultValue: false, + table: { + category: 'Demo' + } + }, + width: { + options: [0, 1, 2, 3, 4, 5], // iterator + mapping: [ + 'Overlay--width-auto', + 'Overlay--width-small', + 'Overlay--width-medium', + 'Overlay--width-large', + 'Overlay--width-xlarge', + 'Overlay--width-xxlarge' + ], // values + control: { + type: 'inline-radio', + labels: ['auto', 'small', 'medium', 'large', 'xlarge', 'xxlarge'] + }, + description: 'Width options: small: 256px, medium: 320px, large: 480px, xlarge: 640px, xxlarge: 960px', + table: { + category: 'CSS' + } + }, + height: { + options: [0, 1, 2, 3, 4, 5], // iterator + mapping: [ + 'Overlay--height-auto', + 'Overlay--height-xsmall', + 'Overlay--height-small', + 'Overlay--height-medium', + 'Overlay--height-large', + 'Overlay--height-xlarge' + ], // values + control: { + type: 'inline-radio', + labels: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge'] + }, + description: + 'Height options: auto: adjusts to content, xsmall: 192px, small: 256px, medium: 320px, large: 432px, xlarge: 600px', + table: { + category: 'CSS' + } + }, + headerVariant: { + options: [0, 1], // iterator + mapping: ['', 'Overlay-header--large'], // values + control: { + type: 'inline-radio', + labels: ['medium (default)', 'large'] + }, + description: 'medium (default), large header/description font-size + spacing', + table: { + category: 'CSS' + } + }, + 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' + } + }, + variantNarrow: { + options: [0, 1, 2, 3], // iterator + mapping: [ + 'Overlay-backdrop--center-whenNarrow', + 'Overlay-backdrop--anchor-whenNarrow', + 'Overlay-backdrop--side-whenNarrow', + 'Overlay-backdrop--full-whenNarrow' + ], // values + control: { + type: 'inline-radio', + labels: ['center', 'anchored', 'side', 'full'] + }, + description: '', + table: { + category: 'Variant' + } + }, + variantRegular: { + options: [0, 1, 2, 3], // iterator + mapping: [ + 'Overlay-backdrop--center', + 'Overlay-backdrop--anchor', + 'Overlay-backdrop--side', + 'Overlay-backdrop--full' + ], // values + control: { + type: 'inline-radio', + labels: ['center', 'anchored', 'side', 'full'] + }, + 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 narrow viewport range', + table: { + category: 'Placement' + } + }, + headerRegion: { + 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: 'Demo' + } + }, + footerRegion: { + 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: 'Demo' + } + }, + 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: 'CSS' + } + }, + headerContentSlot: { + description: 'Slot for additional header content', + control: {type: 'string'}, + table: { + category: 'HTML' + } + }, + actionContentSlot: { + description: 'Slot for additional header action', + control: {type: 'string'}, + table: { + category: 'HTML' + } + }, + motion: { + options: [0, 1], // iterator + mapping: [null, 'Overlay--motion-scaleFade'], // values + control: { + type: 'inline-radio', + labels: ['none', 'scaleFade'] + }, + description: 'Animation options for show/hide overlay', + table: { + category: 'CSS' + } + }, + 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: 'CSS' + } + }, + 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' + } + } + } +} + +export const Playground = OverlayTemplate.bind({}) +Playground.args = { + ...OverlayTemplate.args, + title: 'Dialog title', + description: 'Optional dialog description', + role: 'dialog', + width: 2, + height: 3, + ariaLabelledby: 'title-id', + ariaDescribedby: 'description-id', + dataFocusTrap: 'active', + footerContentAlign: 2, + showCloseButton: true, + headerVariant: 0, + bodyPaddingVariant: 0, + motion: 1, + descriptionId: 'description-id', + titleId: 'title-id', + showFooterDivider: false, + children:

Dialog body

, + headerRegion: true, + variantNarrow: 3, + variantRegular: 0 +} diff --git a/docs/src/stories/components/Popover/Popover.stories.jsx b/docs/src/stories/components/Popover/Popover.stories.jsx deleted file mode 100644 index 89119ea548..0000000000 --- a/docs/src/stories/components/Popover/Popover.stories.jsx +++ /dev/null @@ -1,135 +0,0 @@ -import React from 'react' -import clsx from 'clsx' -// import { StoryTemplateName } from './OtherStoryFile.stories' // import stories for component compositions - -export default { - title: 'Components/Popover', - excludeStories: ['PopoverTemplateName'], - layout: 'padded', - argTypes: { - size: { - options: [0, 1], // iterator - mapping: ['', 'Popover-message--large'], // values - control: { - type: 'select', - labels: ['default', 'large'] - }, - table: { - category: 'CSS' - } - }, - caretPosition: { - options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // iterator - mapping: [ - '', - 'Popover-message--bottom', - 'Popover-message--bottom-left', - 'Popover-message--bottom-right', - 'Popover-message--left', - 'Popover-message--left-bottom', - 'Popover-message--left-top', - 'Popover-message--right', - 'Popover-message--right-bottom', - 'Popover-message--right-top', - 'Popover-message--top-left', - 'Popover-message--top-right', - 'Popover-message--no-caret' - ], // values - control: { - type: 'inline-radio', - labels: [ - 'default (top)', - 'bottom', - 'bottom_left', - 'bottom_right', - 'left', - 'left_bottom', - 'left_top', - 'right', - 'right_bottom', - 'right_top', - 'top_left', - 'top_right', - 'none' - ] - }, - table: { - category: 'CSS' - } - }, - messagePosition: { - options: ['position-relative', 'position-absolute'], - control: { - type: 'inline-radio' - }, - description: '', - table: { - category: 'CSS' - } - }, - headingText: { - name: 'headingText', - type: 'string', - description: 'string', - table: { - category: 'HTML' - } - }, - primerUtilities: { - name: 'headingText', - type: 'string', - description: 'Primer utility classes', - table: { - category: 'HTML' - } - }, - tag: { - options: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], - control: {type: 'inline-radio'}, - description: 'h4 default', - table: { - category: 'HTML' - } - } - } -} - -export const PopoverTemplateName = ({ - size, - caretPosition, - messagePosition, - headingText, - tag, - children, - trigger, - triggerBottom, - primerUtilities -}) => ( - <> - {trigger} -
-
- {tag === 'h1' &&

{headingText}

} - {tag === 'h2' &&

{headingText}

} - {tag === 'h3' &&

{headingText}

} - {tag === 'h4' &&

{headingText}

} - {tag === 'h5' &&
{headingText}
} - {tag === 'h6' &&
{headingText}
} - {children} -
-
- {triggerBottom} - -) - -export const Playground = PopoverTemplateName.bind({}) -Playground.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: '', - messagePosition: 'position-relative', - primerUtilities: 'text-left p-4 mt-2 mx-aut' -} diff --git a/docs/src/stories/components/Popover/PopoverFeatures.stories.jsx b/docs/src/stories/components/Popover/PopoverFeatures.stories.jsx deleted file mode 100644 index d908f14537..0000000000 --- a/docs/src/stories/components/Popover/PopoverFeatures.stories.jsx +++ /dev/null @@ -1,229 +0,0 @@ -import React from 'react' -import clsx from 'clsx' -import {PopoverTemplateName} from './Popover.stories.jsx' - -export default { - title: 'Components/Popover/Features', - parameters: { - design: { - type: 'figma', - url: 'https://www.figma.com/file/GCvY3Qv8czRgZgvl1dG6lp/Primer-Web?node-id=410%3A3890' - } - } -} - -export const Bottom = PopoverTemplateName.bind({}) -Bottom.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--bottom', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mx-auto mb-2 text-left' -} -Bottom.decorators = [ - Story => ( -
- -
- ) -] - -export const BottomRight = PopoverTemplateName.bind({}) -BottomRight.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--bottom-right', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mb-2 text-left' -} -BottomRight.decorators = [ - Story => ( -
- -
- ) -] - -export const BottomLeft = PopoverTemplateName.bind({}) -BottomLeft.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--bottom-left', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mb-2' -} -BottomLeft.decorators = [ - Story => ( -
- -
- ) -] - -export const Left = PopoverTemplateName.bind({}) -Left.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--left', - messagePosition: 'position-relative', - trigger: [], - primerUtilities: 'p-4 ml-2' -} -Left.decorators = [ - Story => ( -
- -
- ) -] - -export const LeftBottom = PopoverTemplateName.bind({}) -LeftBottom.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--left-bottom', - messagePosition: 'position-relative', - trigger: [], - primerUtilities: 'p-4 ml-2' -} -LeftBottom.decorators = [ - Story => ( -
- -
- ) -] - -export const LeftTop = PopoverTemplateName.bind({}) -LeftTop.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--left-top', - messagePosition: 'position-relative', - trigger: [], - primerUtilities: 'p-4 ml-2' -} -LeftTop.decorators = [ - Story => ( -
- -
- ) -] - -export const Right = PopoverTemplateName.bind({}) -Right.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--right', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mr-2' -} -Right.decorators = [ - Story => ( -
- -
- ) -] - -export const RightBottom = PopoverTemplateName.bind({}) -RightBottom.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--right-bottom', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mr-2' -} -RightBottom.decorators = [ - Story => ( -
- -
- ) -] - -export const RightTop = PopoverTemplateName.bind({}) -RightTop.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--right-top', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mr-2' -} -RightTop.decorators = [ - Story => ( -
- -
- ) -] - -export const TopLeft = PopoverTemplateName.bind({}) -TopLeft.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--top-left', - messagePosition: 'position-relative', - trigger: [], - primerUtilities: 'p-4 mt-2' -} -TopLeft.decorators = [ - Story => ( -
- -
- ) -] - -export const TopRight = PopoverTemplateName.bind({}) -TopRight.args = { - tag: 'h4', - headingText: 'Popover heading', - size: '', - caretPosition: 'Popover-message--top-right', - messagePosition: 'position-relative', - trigger: [], - primerUtilities: 'text-left p-4 mt-2' -} -TopRight.decorators = [ - Story => ( -
- -
- ) -] - -export const Large = PopoverTemplateName.bind({}) -Large.args = { - tag: 'h4', - headingText: 'Popover heading', - size: 'Popover-message--large', - caretPosition: 'Popover-message--bottom', - messagePosition: 'position-relative', - triggerBottom: [], - primerUtilities: 'p-4 mx-auto mb-2 text-left' -} -Large.decorators = [ - Story => ( -
- -
- ) -] diff --git a/docs/src/stories/ui-patterns/Overlay/Overlay.stories.jsx b/docs/src/stories/ui-patterns/Overlay/Overlay.stories.jsx new file mode 100644 index 0000000000..b5a33ee15a --- /dev/null +++ b/docs/src/stories/ui-patterns/Overlay/Overlay.stories.jsx @@ -0,0 +1,518 @@ +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: { + title: { + name: 'title', + type: {name: 'string', required: false}, + description: 'The heading element of the dialog', + defaultValue: '', + table: { + category: 'HTML' + } + }, + description: { + name: 'description', + type: 'string', + description: 'The sub-heading element of the dialog', + defaultValue: '', + table: { + category: 'HTML' + } + }, + 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' + } + }, + width: { + options: [0, 1, 2, 3, 4, 5], // iterator + mapping: [ + 'Overlay--width-auto', + 'Overlay--width-small', + 'Overlay--width-medium', + 'Overlay--width-large', + 'Overlay--width-xlarge', + 'Overlay--width-xxlarge' + ], // values + control: { + type: 'inline-radio', + labels: ['auto', 'small', 'medium', 'large', 'xlarge', 'xxlarge'] + }, + description: 'Width options: small: 256px, medium: 320px, large: 480px, xlarge: 640px, xxlarge: 960px', + table: { + category: 'CSS' + } + }, + height: { + options: [0, 1, 2, 3, 4, 5], // iterator + mapping: [ + 'Overlay--height-auto', + 'Overlay--height-xsmall', + 'Overlay--height-small', + 'Overlay--height-medium', + 'Overlay--height-large', + 'Overlay--height-xlarge' + ], // values + control: { + type: 'inline-radio', + labels: ['auto', 'xsmall', 'small', 'medium', 'large', 'xlarge', 'xxlarge'] + }, + description: + 'Height options: auto: adjusts to content, xsmall: 192px, small: 256px, medium: 320px, large: 432px, xlarge: 600px', + table: { + category: 'CSS' + } + }, + headerVariant: { + options: [0, 1], // iterator + mapping: ['', 'Overlay-header--large'], // values + control: { + type: 'inline-radio', + labels: ['medium (default)', 'large'] + }, + description: 'medium (default), large header/description font-size + spacing', + table: { + category: 'CSS' + } + }, + 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' + } + }, + variantNarrow: { + options: [0, 1, 2, 3], // iterator + mapping: [ + 'Overlay-backdrop--center-whenNarrow', + 'Overlay-backdrop--anchor-whenNarrow', + 'Overlay-backdrop--side-whenNarrow', + 'Overlay-backdrop--full-whenNarrow' + ], // values + control: { + type: 'inline-radio', + labels: ['center', 'anchored', 'side', 'full'] + }, + description: '', + table: { + category: 'Variant' + } + }, + variantRegular: { + options: [0, 1, 2, 3], // iterator + mapping: [ + 'Overlay-backdrop--center', + 'Overlay-backdrop--anchor', + 'Overlay-backdrop--side', + 'Overlay-backdrop--full' + ], // values + control: { + type: 'inline-radio', + labels: ['center', 'anchored', 'side', 'full'] + }, + 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 narrow viewport range', + table: { + category: 'Placement' + } + }, + headerRegion: { + 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: 'Demo' + } + }, + footerRegion: { + 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: 'Demo' + } + }, + 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: 'CSS' + } + }, + headerContentSlot: { + description: 'Slot for additional header content', + control: {type: 'string'}, + table: { + category: 'HTML' + } + }, + actionContentSlot: { + description: 'Slot for additional header action', + control: {type: 'string'}, + table: { + category: 'HTML' + } + }, + motion: { + options: [0, 1], // iterator + mapping: [null, 'Overlay--motion-scaleFade'], // values + control: { + type: 'inline-radio', + labels: ['none', 'scaleFade'] + }, + description: 'Animation options for show/hide overlay', + table: { + category: 'CSS' + } + }, + 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: 'CSS' + } + }, + 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, + width, + height, + showFooterDivider, + showHeaderDivider, + headerRegion, + footerRegion, + headerContentSlot, + motion, + footerContentAlign, + showCloseButton, + showFooterButton, + actionContentSlot, + headerVariant, + bodyPaddingVariant, + role, + ariaLabelledby, + ariaDescribedby, + dataFocusTrap, + children, + titleId, + descriptionId, + variantNarrow, + variantRegular, + placementNarrow, + placementRegular +}) => ( + <> + +
+
+ {headerRegion && ( +
+
+
+ {title && ( +

+ {title} +

+ )} + {description && ( +

+ {description} +

+ )} +
+ {showCloseButton && ( +
+ {actionContentSlot &&
} + +
+ )} +
+ {headerContentSlot && ( +
+ )} +
+ )} +
{children}
+ {footerRegion && ( +
+ {showFooterButton && ( + + )} +
+ )} +
+
+ +) + +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, + headerContentSlot: '', + actionContentSlot: '', + headerVariant: 0, + bodyPaddingVariant: 0, + width: 1, + height: 3, + headerVariant: 0, + headerRegion: true, + footerRegion: true, + showFooterDivider: false, + showHeaderDivider: false, + role: '', + ariaDescribedby: '', + dataFocusTrap: '' +} diff --git a/src/core/index.scss b/src/core/index.scss index 166da98b06..bc704ee041 100644 --- a/src/core/index.scss +++ b/src/core/index.scss @@ -24,6 +24,7 @@ @import '../pagination/index.scss'; @import '../tooltips/index.scss'; @import '../truncate/index.scss'; +@import '../overlay/index.scss'; // Utilities always go last so that they can override components @import '../utilities/index.scss'; diff --git a/src/overlay/README.md b/src/overlay/README.md new file mode 100644 index 0000000000..7d91b0f79a --- /dev/null +++ b/src/overlay/README.md @@ -0,0 +1,24 @@ +--- +bundle: 'overlay' +generated: true +--- + +# Primer CSS: `overlay` bundle + +## Usage + +Primer CSS source files are written in [SCSS]. To include this Primer CSS module in your own build, ensure that your `node_modules` directory is listed in your Sass include paths, then import it with: + +```scss +@import '@primer/css/overlay/index.scss'; +``` + +## Build + +The `@primer/css` npm package includes a standalone CSS build of this module in `dist/overlay.css`. + +## License + +[MIT](https://github.com/primer/css/blob/main/LICENSE) © [GitHub](https://github.com/) + +[scss]: https://sass-lang.com/documentation/syntax#scss diff --git a/src/overlay/index.scss b/src/overlay/index.scss new file mode 100644 index 0000000000..381d309c08 --- /dev/null +++ b/src/overlay/index.scss @@ -0,0 +1,2 @@ +@import '../support/index.scss'; +@import './overlay.scss'; diff --git a/src/overlay/overlay.scss b/src/overlay/overlay.scss new file mode 100644 index 0000000000..88fdaedc93 --- /dev/null +++ b/src/overlay/overlay.scss @@ -0,0 +1,384 @@ +// stylelint-disable selector-max-compound-selectors, max-nesting-depth, selector-max-specificity, primer/borders +// replace with primitive +$primer-borderRadius-large: 0.75rem; + +.Overlay--hidden { + display: none !important; +} + +.Overlay--visibilityHidden { + height: 0; + overflow: hidden; + visibility: hidden; + opacity: 0; +} + +.Overlay { + display: flex; + min-width: 192px; + flex-direction: column; + background-color: var(--color-canvas-overlay); + border-radius: $primer-borderRadius-large; + box-shadow: var(--color-overlay-shadow); + opacity: 1; + + &.Overlay--height-auto { + height: auto; + } + + &.Overlay--height-xsmall { + height: min(192px, 100% - 2rem); + } + + &.Overlay--height-small { + height: min(256px, 100% - 2rem); + } + + &.Overlay--height-medium { + height: min(320px, 100% - 2rem); + } + + &.Overlay--height-large { + height: min(432px, 100% - 2rem); + } + + &.Overlay--height-xlarge { + height: min(600px, 100% - 2rem); + } + + &.Overlay--width-auto { + width: auto; + } + + &.Overlay--width-small { + width: min(256px, 100% - 2rem); + } + + &.Overlay--width-medium { + width: min(320px, 100% - 2rem); + } + + &.Overlay--width-large { + // stylelint-disable-next-line primer/responsive-widths + width: min(480px, 100% - 2rem); + } + + &.Overlay--width-xlarge { + // stylelint-disable-next-line primer/responsive-widths + width: min(640px, 100% - 2rem); + } + + &.Overlay--width-xxlarge { + // stylelint-disable-next-line primer/responsive-widths + width: min(960px, 100% - 2rem); + } + + &.Overlay--motion-scaleFade { + @media screen and (prefers-reduced-motion: no-preference) { + animation: 200ms cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running Overlay--motion-scaleFade; + } + } + + @keyframes Overlay--motion-scaleFade { + 0% { + opacity: 0; + transform: scale(0.5); + } + + 100% { + opacity: 1; + transform: scale(1); + } + } +} + +// for
element that wraps entire contents of overlay +.Overlay-form { + display: flex; + overflow: auto; + flex-direction: column; + flex-grow: 1; +} + +.Overlay-header { + z-index: 1; + display: flex; + flex-direction: column; + + &.Overlay-header--divided { + padding-bottom: $spacer-2; + // stylelint-disable-next-line primer/box-shadow + box-shadow: inset 0 #{-$border-width} var(--color-border-default); + + + .Overlay-body { + padding-top: $spacer-3; + } + } + + &.Overlay-header--large { + .Overlay-headerContentWrap { + .Overlay-titleWrap { + gap: $spacer-2; + + .Overlay-title { + font-size: $h3-size; + } + + .Overlay-description { + font-size: $body-font-size; + } + } + } + } + + .Overlay-headerContentWrap { + display: flex; + align-items: flex-start; + gap: $spacer-2; + padding: $spacer-2 $spacer-2 0 $spacer-2; + + .Overlay-actionWrap { + display: flex; + flex-direction: row; + gap: $spacer-2; + } + + .Overlay-titleWrap { + display: flex; + padding: ($spacer-2 * 0.75) 0 ($spacer-2 * 0.75) $spacer-2; + flex-direction: column; + flex-grow: 1; + gap: $spacer-1; + + .Overlay-title { + margin: 0; + font-size: $body-font-size; + font-weight: $font-weight-bold; + } + + .Overlay-description { + margin: 0; + font-size: $font-size-small; + font-weight: $font-weight-normal; + color: var(--color-fg-muted); + } + } + } +} + +// generic body content slot +.Overlay-body { + padding: $spacer-3; + padding-top: 0; + overflow-y: auto; + scrollbar-width: thin; + font-size: $body-font-size; + flex-grow: 1; + + &.Overlay-body--paddingCondensed { + padding: $spacer-2; + padding-top: 0; + } + + &.Overlay-body--paddingNone { + padding: 0; + } +} + +// generic footer slot +.Overlay-footer { + z-index: 1; + display: flex; + padding: 0 $spacer-3 $spacer-3 $spacer-3; + flex-direction: row; + flex-shrink: 0; + flex-wrap: wrap; + + &.Overlay-footer--divided { + padding-top: $spacer-3; + // stylelint-disable-next-line primer/box-shadow + box-shadow: inset 0 $border-width var(--color-border-default); + } + + &.Overlay-footer--alignStart { + justify-content: flex-start; + gap: $spacer-2; + } + + &.Overlay-footer--alignCenter { + justify-content: center; + gap: $spacer-2; + } + + &.Overlay-footer--alignEnd { + justify-content: flex-end; + gap: $spacer-2; + } +} + +// TODO: replace with refactored IconButton +.Overlay-closeButton { + position: relative; + display: grid; + width: $spacer-5; + height: $spacer-5; + padding: 0; + color: var(--color-fg-muted); + cursor: pointer; + user-select: none; + background-color: transparent; + border: $border-width $border-style transparent; + border-radius: $border-radius; + transition: 0.2s cubic-bezier(0.3, 0, 0.5, 1); + transition-property: color, background-color, border-color; + place-content: center; + align-self: flex-start; + flex-shrink: 0; + + &:hover, + &:focus { + background-color: var(--color-btn-hover-bg); + border: $border-width $border-style var(--color-btn-hover-bg); + } +} + +@mixin Overlay-backdrop() { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + display: flex; + background-color: var(--color-neutral-muted); +} + +@mixin Overlay-backdrop--transparent() { + position: absolute; + background-color: transparent; +} + +// variants must be mixins so we can extend within a media query (@extend is not supported inside media queries) + +// border-radius repeats within placement options to ensure the original radius is reset when two classes co-exist + +// center +@mixin Overlay-backdrop--center { + @include Overlay-backdrop; + + align-items: center; + justify-content: center; +} + +// anchor +@mixin Overlay-backdrop--anchor { + @include Overlay-backdrop--transparent; + + .Overlay { + width: auto; + } +} + +// anchor side(s) +@mixin Overlay-backdrop--side($responsiveVariant: '') { + @include Overlay-backdrop; + + // default left + align-items: center; + justify-content: left; + + &.Overlay-backdrop--placement-left#{$responsiveVariant} { + align-items: center; + justify-content: left; + + .Overlay#{$responsiveVariant} { + border-radius: $primer-borderRadius-large; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + &.Overlay-backdrop--placement-right#{$responsiveVariant} { + align-items: center; + justify-content: right; + + .Overlay#{$responsiveVariant} { + border-radius: $primer-borderRadius-large; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + &.Overlay-backdrop--placement-bottom#{$responsiveVariant} { + align-items: end; + justify-content: center; + + .Overlay#{$responsiveVariant} { + border-radius: $primer-borderRadius-large; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + } + + &.Overlay-backdrop--placement-top#{$responsiveVariant} { + align-items: start; + justify-content: center; + + .Overlay#{$responsiveVariant} { + border-radius: $primer-borderRadius-large; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + } +} + +// full width +@mixin Overlay-backdrop--full { + @include Overlay-backdrop; + + .Overlay { + width: 100%; + max-width: 100vw; + height: 100%; + max-height: 100vh; + border-radius: unset; + flex-grow: 1; + } +} + +// Overlay variant classnames +.Overlay-backdrop--center { + @include Overlay-backdrop--center; +} + +.Overlay-backdrop--anchor { + @include Overlay-backdrop--anchor; +} + +.Overlay-backdrop--side { + @include Overlay-backdrop--side; +} + +.Overlay-backdrop--full { + @include Overlay-backdrop--full; +} + +// responsive variants +// up to 767px +@media (max-width: #{map-get($breakpoints, 'md') - 0.02px}) { + .Overlay-backdrop--center-whenNarrow { + @include Overlay-backdrop--center; + } + + .Overlay-backdrop--anchor-whenNarrow { + @include Overlay-backdrop--anchor; + } + + .Overlay-backdrop--side-whenNarrow { + @include Overlay-backdrop--side('-whenNarrow'); + } + + .Overlay-backdrop--full-whenNarrow { + @include Overlay-backdrop--full; + } +} diff --git a/src/product/index.scss b/src/product/index.scss index 38430eb4ca..83113e06d7 100644 --- a/src/product/index.scss +++ b/src/product/index.scss @@ -24,4 +24,4 @@ @import '../select-menu/index.scss'; @import '../subhead/index.scss'; @import '../timeline/index.scss'; -@import '../toasts/index.scss' +@import '../toasts/index.scss';