Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modal): provides content-top and content-bottom slots #6490

Merged
merged 21 commits into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2ca9176
feat(modal): provides content-header and content-footer slots
Elijbet Feb 16, 2023
6ab5974
WIP: styling
Elijbet Feb 16, 2023
335e439
WIP: slots story
Elijbet Feb 16, 2023
3f4d382
WIP: content header/footer visibility
Elijbet Feb 16, 2023
9309a22
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 16, 2023
7abc8d0
WIP: cleanup
Elijbet Feb 16, 2023
7e9a4aa
WIP: keys and scroll text
Elijbet Feb 16, 2023
ee67199
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 16, 2023
a29e02e
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 17, 2023
aabeff0
WIP: cleanup stories, use onSlotChange instead of the getSlotted() ut…
Elijbet Feb 18, 2023
9b4e40a
WIP: tweak hidden
Elijbet Feb 21, 2023
ad74ffd
WIP: simplify
Elijbet Feb 21, 2023
4ebe033
WIP: cleanup
Elijbet Feb 21, 2023
b41f2e0
WIP: disable stylelint rule for use-logical to prevent overriding usi…
Elijbet Feb 22, 2023
7ed1cb1
WIP: cleanup
Elijbet Feb 22, 2023
1f5fb34
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 22, 2023
91083c5
WIP: try with linter update
Elijbet Feb 22, 2023
65f9c06
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 24, 2023
4742349
WIP: cleanup, eslint prevents commit because of unused var inport
Elijbet Feb 24, 2023
146164e
Merge branch 'master' into elijbet/4800-sticky-header-footer-content-…
Elijbet Feb 26, 2023
21327a9
WIP: cleanup
Elijbet Feb 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 additions & 12 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,13 +189,44 @@
*/
.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 {
@apply bg-foreground-1
border-color-3
flex
min-w-0
max-w-full
rounded-t
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
border-0
border-b
border-solid
z-header;
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
flex: 0 0 auto;
padding: var(--calcite-modal-padding-internal);
}

.content-bottom {
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
@apply bg-foreground-1
border-color-3
mt-auto
box-border
flex
w-full
justify-between
rounded-b
border-0
border-t
border-solid
z-header;
flex: 0 0 auto;
padding: var(--calcite-modal-padding-internal);
}

.content--no-footer {
.content--no-modal-footer {
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
@apply rounded-b;
}

Expand Down Expand Up @@ -287,10 +323,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 +375,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 +385,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 +398,8 @@ slot[name="primary"] {
* Mobile
*/
@media screen and (max-width: $viewport-small) {
.footer {
.footer,
.content-bottom {
@apply flex-col;
}
.back,
Expand Down
32 changes: 30 additions & 2 deletions src/components/modal/modal.stories.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { select, number } from "@storybook/addon-knobs";
import { select } from "@storybook/addon-knobs";
import { boolean, storyFilters } from "../../../.storybook/helpers";
import { modesDarkDefault } from "../../../.storybook/utils";
import readme from "./readme.md";
Expand Down 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
53 changes: 45 additions & 8 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,24 +230,26 @@ export class Modal
<slot name={CSS.header} />
</header>
</div>
{this.renderContentTop()}
<div
class={{
[CSS.content]: true,
[CSS.contentNoFooter]: !this.hasFooter
[CSS.contentNoModalFooter]: !this.hasModalFooter
}}
ref={(el) => (this.modalContent = el)}
>
<slot name={SLOTS.content} />
</div>
{this.renderFooter()}
{this.renderContentBottom()}
{this.renderModalFooter()}
</div>
</div>
</Host>
);
}

renderFooter(): VNode {
return this.hasFooter ? (
renderModalFooter(): VNode {
return this.hasModalFooter ? (
<div class={CSS.footer} key="footer">
<span class={CSS.back}>
<slot name={SLOTS.back} />
Expand All @@ -255,6 +264,22 @@ export class Modal
) : null;
}

renderContentTop(): VNode {
return (
<div class={CSS.contentTop} hidden={!this.hasContentTop} key="content-top">
Elijbet marked this conversation as resolved.
Show resolved Hide resolved
<slot name={SLOTS.contentTop} onSlotchange={this.contentTopSlotChangeHandler} />
</div>
);
}

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

renderCloseButton(): VNode {
return !this.closeButtonDisabled ? (
<button
Expand Down Expand Up @@ -347,7 +372,11 @@ export class Modal

@State() cssHeight: string | number;

@State() hasFooter = true;
@State() hasModalFooter = 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 @@ -527,11 +556,19 @@ export class Modal
}

private updateFooterVisibility = (): void => {
this.hasFooter = !!getSlotted(this.el, [SLOTS.back, SLOTS.primary, SLOTS.secondary]);
this.hasModalFooter = !!getSlotted(this.el, [SLOTS.back, SLOTS.primary, SLOTS.secondary]);
};

private updateSizeCssVars = (): void => {
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);
};
}
6 changes: 5 additions & 1 deletion src/components/modal/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export const CSS = {
overflowHidden: "overflow-hidden",
container: "container",
content: "content",
contentNoFooter: "content--no-footer",
contentNoModalFooter: "content--no-modal-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