Skip to content

Commit

Permalink
feat(modal): provides content-top and content-bottom slots (#6490)
Browse files Browse the repository at this point in the history
**Related Issue:** #4800

## Summary
Provides `content-top` and `content-bottom` slots. 

For simplicity, we matched the `padding` on the `content-top` and
`content-bottom` to the `content`, with one public prop for the latter.
This can further be developed if requested.

We decided to move forward with `content-top` and `content-bottom`
naming, as this should provide enough context and also aligns with
existing `<x>-start/<x>-end` naming.
  • Loading branch information
Elijbet authored Mar 2, 2023
1 parent 4e2f38c commit 4a511ba
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 14 deletions.
49 changes: 38 additions & 11 deletions src/components/modal/modal.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
--calcite-modal-scrim-background-internal: #{rgba($blk-240, 0.85)};
}

.content-top[hidden],
.content-bottom[hidden] {
@apply hidden;
}

.container {
@apply text-color-2
fixed
Expand Down Expand Up @@ -184,12 +189,31 @@
*/
.content {
@apply relative box-border block h-full overflow-auto p-0;
max-block-size: 100%;
background-color: var(--calcite-modal-content-background, theme("colors.background.foreground.1"));
padding-block: var(--calcite-modal-content-padding, var(--calcite-modal-padding-internal));
padding-inline: var(--calcite-modal-content-padding, var(--calcite-modal-padding-internal));
max-block-size: 100%;
padding: var(--calcite-modal-content-padding, var(--calcite-modal-padding-internal));
}

.content-top,
.content-bottom {
@apply bg-foreground-1 border-color-3 border-solid border-0 z-header flex;
flex: 0 0 auto;
padding: var(--calcite-modal-padding-internal);
}

.content-top {
@apply min-w-0 max-w-full border-b;
}

.content-bottom {
@apply mt-auto box-border w-full justify-between border-t;
}

.content-top:not(.header ~ .content-top) {
@apply rounded-t;
}

.content-bottom:not(.content-bottom ~ .footer),
.content--no-footer {
@apply rounded-b;
}
Expand Down Expand Up @@ -287,10 +311,10 @@ slot[name="primary"] {
}

:host([open][fullscreen]) {
.header {
border-radius: 0;
}
.footer {
.header,
.footer,
.content-top,
.content-bottom {
border-radius: 0;
}
}
Expand Down Expand Up @@ -339,7 +363,8 @@ slot[name="primary"] {
.modal {
@apply border-0 border-t-4 border-solid;
}
.header {
.header,
.content-top {
@apply rounded rounded-b-none;
}
}
Expand All @@ -348,10 +373,11 @@ slot[name="primary"] {
* Tablet
*/
@media screen and (max-width: $viewport-medium) {
@include slotted("header", "*") {
@include slotted("header", "content-top", "*") {
@apply text-1;
}
.footer {
.footer,
.content-bottom {
@apply sticky bottom-0;
}
}
Expand All @@ -360,7 +386,8 @@ slot[name="primary"] {
* Mobile
*/
@media screen and (max-width: $viewport-small) {
.footer {
.footer,
.content-bottom {
@apply flex-col;
}
.back,
Expand Down
30 changes: 29 additions & 1 deletion src/components/modal/modal.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const simple = (): string => html`
>
<h3 slot="header">Small Modal</h3>
<div slot="content">
<p>The small modal is perfect for short confirmation dialogs or very compact interfaces with few elements.</p>
The small modal is perfect for short confirmation dialogs or very compact interfaces with few elements.
</div>
<calcite-button slot="back" kind="neutral" appearance="outline" icon="chevron-left" width="full"
>Back</calcite-button
Expand All @@ -37,6 +37,34 @@ export const simple = (): string => html`
</calcite-modal>
`;

const mightyLongTextToScroll = html`
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non nisi et elit auctor aliquet ac suscipit eros. Sed nec
nibh viverra, feugiat magna ut, posuere arcu. Curabitur varius erat ut suscipit convallis. Nullam semper pellentesque
est laoreet accumsan. Aenean eget urna fermentum, porttitor dui et, tincidunt erat. Curabitur lacinia lacus in urna
lacinia, ac interdum lorem fermentum. Ut accumsan malesuada varius. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Phasellus tempus tempor magna, eu dignissim urna ornare non. Integer tempor justo blandit nunc ornare, a
interdum nisl pharetra. Sed ultricies at augue vel fermentum. Maecenas laoreet odio lorem. Aliquam in pretium turpis.
Donec quis felis a diam accumsan vehicula efficitur at orci. Donec sollicitudin gravida ultrices.
`;

export const slots = (): string => html`
<calcite-modal
${boolean("open", true)}
kind="${select("kind", ["brand", "danger", "info", "success", "warning"], "")}"
scale="${select("scale", ["s", "m", "l"], "m")}"
width="${select("width", ["s", "m", "l"], "s")}"
${boolean("fullscreen", false)}
${boolean("docked", false)}
${boolean("escape-disabled", false)}
>
<h3 slot="header">Slot for a header.</h3>
<div slot="content-top">Slot for a content-top.</div>
<div slot="content" style="height: 100px">${mightyLongTextToScroll}</div>
<div slot="content-bottom">Slot for a content-bottom.</div>
<calcite-button slot="primary" width="full">Button</calcite-button>
</calcite-modal>
`;

export const darkModeRTLCustomSizeCSSVars_TestOnly = (): string => html`
<calcite-modal
class="calcite-mode-dark"
Expand Down
41 changes: 39 additions & 2 deletions src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import {
connectConditionalSlotComponent,
disconnectConditionalSlotComponent
} from "../../utils/conditionalSlot";
import { ensureId, focusFirstTabbable, getSlotted } from "../../utils/dom";
import {
ensureId,
focusFirstTabbable,
getSlotted,
slotChangeHasAssignedElement
} from "../../utils/dom";
import {
activateFocusTrap,
connectFocusTrap,
Expand Down Expand Up @@ -50,6 +55,8 @@ import { ModalMessages } from "./assets/modal/t9n";
/**
* @slot header - A slot for adding header text.
* @slot content - A slot for adding the component's content.
* @slot contentTop - A slot for adding the component's content header.
* @slot contentBottom - A slot for adding the component's content footer.
* @slot primary - A slot for adding a primary button.
* @slot secondary - A slot for adding a secondary button.
* @slot back - A slot for adding a back button.
Expand Down Expand Up @@ -176,8 +183,8 @@ export class Modal
connectedCallback(): void {
this.mutationObserver?.observe(this.el, { childList: true, subtree: true });
this.cssVarObserver?.observe(this.el, { attributeFilter: ["style"] });
this.updateFooterVisibility();
this.updateSizeCssVars();
this.updateFooterVisibility();
connectConditionalSlotComponent(this);
connectLocalized(this);
connectMessages(this);
Expand Down Expand Up @@ -223,6 +230,7 @@ export class Modal
<slot name={CSS.header} />
</header>
</div>
{this.renderContentTop()}
<div
class={{
[CSS.content]: true,
Expand All @@ -232,6 +240,7 @@ export class Modal
>
<slot name={SLOTS.content} />
</div>
{this.renderContentBottom()}
{this.renderFooter()}
</div>
</div>
Expand All @@ -255,6 +264,22 @@ export class Modal
) : null;
}

renderContentTop(): VNode {
return (
<div class={CSS.contentTop} hidden={!this.hasContentTop}>
<slot name={SLOTS.contentTop} onSlotchange={this.contentTopSlotChangeHandler} />
</div>
);
}

renderContentBottom(): VNode {
return (
<div class={CSS.contentBottom} hidden={!this.hasContentBottom}>
<slot name={SLOTS.contentBottom} onSlotchange={this.contentBottomSlotChangeHandler} />
</div>
);
}

renderCloseButton(): VNode {
return !this.closeButtonDisabled ? (
<button
Expand Down Expand Up @@ -349,6 +374,10 @@ export class Modal

@State() hasFooter = true;

@State() hasContentTop = false;

@State() hasContentBottom = false;

/**
* We use internal variable to make sure initially open modal can transition from closed state when rendered
*
Expand Down Expand Up @@ -534,4 +563,12 @@ export class Modal
this.cssWidth = getComputedStyle(this.el).getPropertyValue("--calcite-modal-width");
this.cssHeight = getComputedStyle(this.el).getPropertyValue("--calcite-modal-height");
};

private contentTopSlotChangeHandler = (event: Event): void => {
this.hasContentTop = slotChangeHasAssignedElement(event);
};

private contentBottomSlotChangeHandler = (event: Event): void => {
this.hasContentBottom = slotChangeHasAssignedElement(event);
};
}
4 changes: 4 additions & 0 deletions src/components/modal/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const CSS = {
container: "container",
content: "content",
contentNoFooter: "content--no-footer",
contentBottom: "content-bottom",
contentTop: "content-top",
slottedInShell: "slotted-in-shell",

// these classes help apply the animation in phases to only set transform on open/close
Expand All @@ -36,6 +38,8 @@ export const ICONS = {

export const SLOTS = {
content: "content",
contentBottom: "content-bottom",
contentTop: "content-top",
header: "header",
back: "back",
secondary: "secondary",
Expand Down

0 comments on commit 4a511ba

Please sign in to comment.