feat(component): add ComposedModal

Closes #13
This commit is contained in:
Eric Liu 2019-12-23 18:38:33 -08:00
commit fc366a9366
21 changed files with 518 additions and 23 deletions

View file

@ -22,6 +22,10 @@ Currently, the following components are supported:
- CheckboxSkeleton
- CodeSnippet
- CodeSnippetSkeleton
- ComposedModal
- ModalHeader
- ModalBody
- ModalFooter
- ContentSwitcher
- Switch
- Copy

View file

@ -70,4 +70,4 @@
}</script><style>#root[hidden],
#docs-root[hidden] {
display: none !important;
}</style></head><body><div class="sb-nopreview sb-wrapper"><div class="sb-nopreview_main"><h1 class="sb-nopreview_heading sb-heading">No Preview</h1><p>Sorry, but you either have no stories or none are selected somehow.</p><ul><li>Please check the Storybook config.</li><li>Try reloading the page.</li></ul><p>If the problem persists, check the browser console, or the terminal you've run Storybook from.</p></div></div><div class="sb-errordisplay sb-wrapper"><pre id="error-message" class="sb-heading"></pre><pre class="sb-errordisplay_code"><code id="error-stack"></code></pre></div><div id="root"></div><div id="docs-root"></div><script src="runtime~main.eb1985332fcdaa83ba10.bundle.js"></script><script src="vendors~main.eb1985332fcdaa83ba10.bundle.js"></script><script src="main.eb1985332fcdaa83ba10.bundle.js"></script></body></html>
}</style></head><body><div class="sb-nopreview sb-wrapper"><div class="sb-nopreview_main"><h1 class="sb-nopreview_heading sb-heading">No Preview</h1><p>Sorry, but you either have no stories or none are selected somehow.</p><ul><li>Please check the Storybook config.</li><li>Try reloading the page.</li></ul><p>If the problem persists, check the browser console, or the terminal you've run Storybook from.</p></div></div><div class="sb-errordisplay sb-wrapper"><pre id="error-message" class="sb-heading"></pre><pre class="sb-errordisplay_code"><code id="error-stack"></code></pre></div><div id="root"></div><div id="docs-root"></div><script src="runtime~main.e24a7c3ee0d97007ea8a.bundle.js"></script><script src="vendors~main.e24a7c3ee0d97007ea8a.bundle.js"></script><script src="main.e24a7c3ee0d97007ea8a.bundle.js"></script></body></html>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"version":3,"file":"main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///main.e24a7c3ee0d97007ea8a.bundle.js"],"mappings":"AAAA","sourceRoot":""}

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
{"version":3,"file":"main.eb1985332fcdaa83ba10.bundle.js","sources":["webpack:///main.eb1985332fcdaa83ba10.bundle.js"],"mappings":"AAAA","sourceRoot":""}

View file

@ -1,2 +1,2 @@
!function(modules){function webpackJsonpCallback(data){for(var moduleId,chunkId,chunkIds=data[0],moreModules=data[1],executeModules=data[2],i=0,resolves=[];i<chunkIds.length;i++)chunkId=chunkIds[i],Object.prototype.hasOwnProperty.call(installedChunks,chunkId)&&installedChunks[chunkId]&&resolves.push(installedChunks[chunkId][0]),installedChunks[chunkId]=0;for(moduleId in moreModules)Object.prototype.hasOwnProperty.call(moreModules,moduleId)&&(modules[moduleId]=moreModules[moduleId]);for(parentJsonpFunction&&parentJsonpFunction(data);resolves.length;)resolves.shift()();return deferredModules.push.apply(deferredModules,executeModules||[]),checkDeferredModules()}function checkDeferredModules(){for(var result,i=0;i<deferredModules.length;i++){for(var deferredModule=deferredModules[i],fulfilled=!0,j=1;j<deferredModule.length;j++){var depId=deferredModule[j];0!==installedChunks[depId]&&(fulfilled=!1)}fulfilled&&(deferredModules.splice(i--,1),result=__webpack_require__(__webpack_require__.s=deferredModule[0]))}return result}var installedModules={},installedChunks={1:0},deferredModules=[];function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{enumerable:!0,get:getter})},__webpack_require__.r=function(exports){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})},__webpack_require__.t=function(value,mode){if(1&mode&&(value=__webpack_require__(value)),8&mode)return value;if(4&mode&&"object"==typeof value&&value&&value.__esModule)return value;var ns=Object.create(null);if(__webpack_require__.r(ns),Object.defineProperty(ns,"default",{enumerable:!0,value:value}),2&mode&&"string"!=typeof value)for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module.default}:function getModuleExports(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="";var jsonpArray=window.webpackJsonp=window.webpackJsonp||[],oldJsonpFunction=jsonpArray.push.bind(jsonpArray);jsonpArray.push=webpackJsonpCallback,jsonpArray=jsonpArray.slice();for(var i=0;i<jsonpArray.length;i++)webpackJsonpCallback(jsonpArray[i]);var parentJsonpFunction=oldJsonpFunction;checkDeferredModules()}([]);
//# sourceMappingURL=runtime~main.eb1985332fcdaa83ba10.bundle.js.map
//# sourceMappingURL=runtime~main.e24a7c3ee0d97007ea8a.bundle.js.map

View file

@ -1 +1 @@
{"version":3,"file":"runtime~main.eb1985332fcdaa83ba10.bundle.js","sources":["webpack:///runtime~main.ce61f8335d8fdea2cda4.bundle.js"],"mappings":"AAAA","sourceRoot":""}
{"version":3,"file":"runtime~main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///runtime~main.ce61f8335d8fdea2cda4.bundle.js"],"mappings":"AAAA","sourceRoot":""}

View file

@ -0,0 +1 @@
{"version":3,"file":"vendors~main.e24a7c3ee0d97007ea8a.bundle.js","sources":["webpack:///vendors~main.e24a7c3ee0d97007ea8a.bundle.js"],"mappings":"AAAA;;;;;AAwqeA;;;;;AAi9JA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AAufA;;;;;;;AAfA","sourceRoot":""}

View file

@ -1 +0,0 @@
{"version":3,"file":"vendors~main.eb1985332fcdaa83ba10.bundle.js","sources":["webpack:///vendors~main.eb1985332fcdaa83ba10.bundle.js"],"mappings":"AAAA;;;;;AAwqeA;;;;;AAi9JA;;;;;AAkkEA;;;;;;;;;AAukBA;;;AA8odA;;;;;;;;AAg/BA;;;;;;;;AAqEA;;;;;;;;AAkTA;;;;;;;AAyrDA;;;;;;;AAy7CA;;;;;;;AAsfA;;;;;;;AAfA","sourceRoot":""}

View file

@ -30,7 +30,7 @@
kind,
disabled,
size,
renderIcon: Add16,
icon: Add16,
iconDescription,
tooltipPosition,
tooltipAlignment

View file

@ -9,15 +9,26 @@
export let href = undefined;
export let tabindex = '0';
export let type = 'button';
export let renderIcon = undefined;
export let icon = undefined;
export let iconDescription = undefined;
export let hasIconOnly = false;
export let tooltipPosition = undefined;
export let tooltipAlignment = undefined;
export let style = undefined;
import { getContext } from 'svelte';
import { cx } from '../../lib';
const ctx = getContext('ComposedModal');
let buttonRef = undefined;
$: {
if (ctx && buttonRef) {
ctx.declareRef({ name: 'buttonRef', ref: buttonRef });
}
}
const _class = cx(
'--btn',
size === 'field' && '--btn--field',
@ -51,28 +62,34 @@
<slot props={buttonProps} />
{:else}
{#if href && !disabled}
<a {...buttonProps} {href} on:click on:mouseover on:mouseenter on:mouseleave>
<a {...buttonProps} on:click on:mouseover on:mouseenter on:mouseleave {href}>
{#if hasIconOnly}
<span class={cx('--assistive-text')}>{iconDescription}</span>
{/if}
<slot />
{#if renderIcon}
{#if icon}
<svelte:component
this={renderIcon}
this={icon}
aria-hidden="true"
class={cx('--btn__icon')}
aria-label={iconDescription} />
{/if}
</a>
{:else}
<button {...buttonProps} on:click on:mouseover on:mouseenter on:mouseleave>
<button
{...buttonProps}
bind:this={buttonRef}
on:click
on:mouseover
on:mouseenter
on:mouseleave>
{#if hasIconOnly}
<span class={cx('--assistive-text')}>{iconDescription}</span>
{/if}
<slot />
{#if renderIcon}
{#if icon}
<svelte:component
this={renderIcon}
this={icon}
aria-hidden="true"
class={cx('--btn__icon')}
aria-label={iconDescription} />

View file

@ -0,0 +1,135 @@
<script>
export let story = undefined;
const { modalBody } = $$props;
import Layout from '../../internal/ui/Layout.svelte';
import Button from '../Button';
import ComposedModal from './ComposedModal.svelte';
import ModalHeader from './ModalHeader.svelte';
import ModalBody from './ModalBody.svelte';
import ModalFooter from './ModalFooter.svelte';
let open = false;
</script>
<Layout>
{#if story === undefined}
<ComposedModal {...$$props.composedModal}>
<ModalHeader {...$$props.modalHeader} />
<ModalBody
{...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p>
{#if modalBody.hasScrollingContent}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>
<ModalFooter {...$$props.modalFooter} />
</ComposedModal>
{/if}
{#if story === 'child nodes'}
<ComposedModal {...$$props.composedModal}>
<ModalHeader {...$$props.modalHeader}>
<h1>Testing</h1>
</ModalHeader>
<ModalBody
{...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p>
{#if modalBody.hasScrollingContent}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>
<ModalFooter>
<Button kind="secondary">Cancel</Button>
<Button kind={$$props.composedModal.danger ? 'danger' : 'primary'}>Primary</Button>
</ModalFooter>
</ComposedModal>
{/if}
{#if story === 'title'}
<ComposedModal {...$$props.composedModal} open on:close={() => {}} on:submit={() => {}}>
<ModalHeader
{...$$props.modalHeader}
title="Passive modal title as the message. Should be direct and 3 lines or less." />
<ModalBody {...$$props.modalBody} />
<ModalFooter {...$$props.modalFooter} />
</ComposedModal>
{/if}
{#if story === 'trigger'}
<div>
<Button
on:click={() => {
open = true;
}}>
Launch composed modal
</Button>
</div>
<ComposedModal {...$$props.composedModal} {open} on:close={() => (open = false)}>
<ModalHeader {...$$props.modalHeader} />
<ModalBody
{...$$props.modalBody}
aria-label={modalBody.hasScrollingContent ? 'Modal content' : undefined}>
<p>Please see ModalWrapper for more examples and demo of the functionality.</p>
{#if modalBody.hasScrollingContent}
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<h3>Lorem ipsum</h3>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean id accumsan augue.
Phasellus consequat augue vitae tellus tincidunt posuere. Curabitur justo urna,
consectetur vel elit iaculis, ultrices condimentum risus. Nulla facilisi. Etiam
venenatis molestie tellus. Quisque consectetur non risus eu rutrum.{' '}
</p>
{/if}
</ModalBody>
<ModalFooter {...$$props.modalFooter} />
</ComposedModal>
{/if}
</Layout>

View file

@ -0,0 +1,135 @@
import { withKnobs, select, boolean, text } from '@storybook/addon-knobs';
import Component from './ComposedModal.Story.svelte';
export default { title: 'ComposedModal', decorators: [withKnobs] };
const sizes = {
Default: '',
'Extra small (xs)': 'xs',
'Small (sm)': 'sm',
'Large (lg)': 'lg'
};
export const Default = () => ({
Component,
props: {
composedModal: {
open: boolean('Open (open in <ComposedModal>)', true),
danger: boolean('Danger mode (danger)', false),
selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)',
'[data-modal-primary-focus]'
),
size: select('Size (size)', sizes, 'sm')
},
modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'),
title: text('Optional title (title in <ModalHeader>)', 'Example'),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close')
},
modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true),
'aria-label': text('ARIA label for content', 'Example modal content')
},
modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'),
primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)',
false
),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '')
}
}
});
export const ChildNodes = () => ({
Component,
props: {
story: 'child nodes',
composedModal: {
open: boolean('Open (open in <ComposedModal>)', true),
danger: boolean('Danger mode (danger)', false),
selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)',
'[data-modal-primary-focus]'
),
size: select('Size (size)', sizes, 'sm')
},
modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'),
title: text('Optional title (title in <ModalHeader>)', 'Example'),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close')
},
modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true),
'aria-label': text('ARIA label for content', 'Example modal content')
},
modalFooter: {}
}
});
export const TitleOnly = () => ({
Component,
props: {
story: 'title',
composedModal: {
open: boolean('Open (open in <ComposedModal>)', true),
danger: boolean('Danger mode (danger)', false),
selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)',
'[data-modal-primary-focus]'
),
size: select('Size (size)', sizes, 'sm')
},
modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'),
title: text('Optional title (title in <ModalHeader>)', 'Example'),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close')
},
modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true),
'aria-label': text('ARIA label for content', 'Example modal content')
},
modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'),
primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)',
false
),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '')
}
}
});
export const Trigger = () => ({
Component,
props: {
story: 'trigger',
composedModal: {
open: boolean('Open (open in <ComposedModal>)', true),
danger: boolean('Danger mode (danger)', false),
selectorPrimaryFocus: text(
'Primary focus element selector (selectorPrimaryFocus)',
'[data-modal-primary-focus]'
),
size: select('Size (size)', sizes, 'sm')
},
modalHeader: {
label: text('Optional Label (label in <ModalHeader>)', 'Optional Label'),
title: text('Optional title (title in <ModalHeader>)', 'Example'),
iconDescription: text('Close icon description (iconDescription in <ModalHeader>)', 'Close')
},
modalBody: {
hasScrollingContent: boolean('Modal contains scrollable content (hasScrollingContent)', true),
'aria-label': text('ARIA label for content', 'Example modal content')
},
modalFooter: {
primaryButtonText: text('Primary button text (primaryButtonText in <ModalFooter>)', 'Save'),
primaryButtonDisabled: boolean(
'Primary button disabled (primaryButtonDisabled in <ModalFooter>)',
false
),
secondaryButtonText: text('Secondary button text (secondaryButtonText in <ModalFooter>)', '')
}
}
});

View file

@ -0,0 +1,91 @@
<script>
let className = undefined;
export { className as class };
export let containerClass = undefined;
export let open = false;
export let danger = false;
export let selectorPrimaryFocus = '[data-modal-primary-focus]';
export let size = undefined;
export let style = undefined;
import { createEventDispatcher, setContext, onDestroy } from 'svelte';
import { writable } from 'svelte/store';
import { cx } from '../../lib';
const dispatch = createEventDispatcher();
const refs = {};
let outerModal = undefined;
let innerModal = undefined;
setContext('ComposedModal', {
closeModal: () => {
open = false;
},
submit: () => {
dispatch('submit');
},
declareRef: ({ name, ref }) => {
refs[name] = ref;
}
});
onDestroy(() => {
document.body.classList.remove(cx('--body--with-modal-open'));
});
function focus(element) {
if (element.querySelector(selectorPrimaryFocus)) {
return focusElement.focus();
}
if (refs.buttonRef) {
refs.buttonRef.focus();
}
}
const _containerClass = cx(
'--modal-container',
size && `--modal-container--${size}`,
containerClass
);
$: didOpen = open;
$: _class = cx('--modal', open && 'is-visible', danger && '--modal--danger', className);
$: if (innerModal) {
focus(innerModal);
}
$: {
if (open) {
document.body.classList.add(cx('--body--with-modal-open'));
} else {
dispatch('close');
document.body.classList.remove(cx('--body--with-modal-open'));
}
}
</script>
<div
role="presentation"
tabindex="-1"
bind:this={outerModal}
class={_class}
on:click
on:click={({ target }) => {
if (!innerModal.contains(target)) {
open = false;
}
}}
on:mouseover
on:mouseenter
on:mouseleave
on:transitionend
on:transitionend={() => {
if (didOpen) {
focus(outerModal);
didOpen = false;
}
}}
{style}>
<div bind:this={innerModal} class={_containerClass}>
<slot />
</div>
</div>

View file

@ -0,0 +1,22 @@
<script>
let className = undefined;
export { className as class };
export let hasForm = false;
export let hasScrollingContent = false;
export let style = undefined;
import { cx } from '../../lib';
const _class = cx('--modal-content', hasForm && '--modal-content--with-form', className);
</script>
<div
tabindex={hasScrollingContent ? '0' : undefined}
role={hasScrollingContent ? 'region' : undefined}
class={_class}
{style}>
<slot />
</div>
{#if hasScrollingContent}
<div class={cx('--modal-content--overflow-indicator')} />
{/if}

View file

@ -0,0 +1,37 @@
<script>
let className = undefined;
export { className as class };
export let primaryClass = undefined;
export let primaryButtonText = '';
export let primaryButtonDisabled = false;
export let secondaryClass = undefined;
export let secondaryButtonText = '';
export let danger = false;
export let style = undefined;
import { createEventDispatcher, getContext } from 'svelte';
import { cx } from '../../lib';
import Button from '../Button';
const dispatch = createEventDispatcher();
const { closeModal, submit } = getContext('ComposedModal');
const _footerClass = cx('--modal-footer', className);
</script>
<div class={_footerClass} {style}>
{#if secondaryButtonText}
<Button kind="secondary" class={secondaryClass} on:click={closeModal}>
{secondaryButtonText}
</Button>
{/if}
{#if primaryButtonText}
<Button
class={primaryClass}
kind={danger ? 'danger' : 'primary'}
disabled={primaryButtonDisabled}
on:click={submit}>
{primaryButtonText}
</Button>
{/if}
<slot />
</div>

View file

@ -0,0 +1,43 @@
<script>
let className = undefined;
export { className as class };
export let labelClass = undefined;
export let titleClass = undefined;
export let closeClass = undefined;
export let closeIconClass = undefined;
export let label = undefined;
export let title = '';
export let iconDescription = 'Close';
export let style = undefined;
import Close20 from 'carbon-icons-svelte/lib/Close20';
import { getContext } from 'svelte';
import { cx } from '../../lib';
const { closeModal } = getContext('ComposedModal');
const _class = cx('--modal-header', className);
const _labelClass = cx('--modal-header__label', '--type-delta', labelClass);
const _titleClass = cx('--modal-header__heading', '--type-beta', titleClass);
const _closeClass = cx('--modal-close', closeClass);
const _closeIconClass = cx('--modal-close__icon', closeIconClass);
</script>
<div class={_class} {style}>
{#if label}
<p class={_labelClass}>{label}</p>
{/if}
{#if title}
<p class={_titleClass}>{title}</p>
{/if}
<slot />
<button
type="button"
title={iconDescription}
aria-label={iconDescription}
class={_closeClass}
on:click
on:click={closeModal}>
<Close20 class={_closeIconClass} />
</button>
</div>

View file

@ -0,0 +1,6 @@
import ComposedModal from './ComposedModal.svelte';
export default ComposedModal;
export { default as ModalHeader } from './ModalHeader.svelte';
export { default as ModalBody } from './ModalBody.svelte';
export { default as ModalFooter } from './ModalFooter.svelte';

View file

@ -5,6 +5,7 @@ import Checkbox, { CheckboxSkeleton } from './components/Checkbox';
import ContentSwitcher, { Switch } from './components/ContentSwitcher';
import Copy from './components/Copy';
import CopyButton from './components/CopyButton';
import ComposedModal, { ModalHeader, ModalBody, ModalFooter } from './components/ComposedModal';
import CodeSnippet, { CodeSnippetSkeleton } from './components/CodeSnippet';
import DataTableSkeleton from './components/DataTableSkeleton';
import Form from './components/Form';
@ -77,6 +78,10 @@ export {
ClickableTile,
CodeSnippet,
CodeSnippetSkeleton,
ComposedModal,
ModalHeader,
ModalBody,
ModalFooter,
ContentSwitcher,
Copy,
CopyButton,