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

Feature: Custom modal type #1507

Merged
merged 11 commits into from
Nov 7, 2024
46 changes: 46 additions & 0 deletions examples/custom-modal/example-custom-modal-dashboard.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { EXAMPLE_MODAL_TOKEN, type ExampleModalData, type ExampleModalResult } from './example-modal-token.js';
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import './example-custom-modal-element.element.js';

@customElement('example-custom-modal-dashboard')
export class UmbExampleCustomModalDashboardElement extends UmbLitElement {

#modalManagerContext? : typeof UMB_MODAL_MANAGER_CONTEXT.TYPE;

constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT,(instance)=>{
this.#modalManagerContext = instance;
})
}

#onOpenModal(){
this.#modalManagerContext?.open(this,EXAMPLE_MODAL_TOKEN,{})
}

override render() {
return html`
<uui-box>
<p>Open the custom modal</p>
<uui-button look="primary" @click=${this.#onOpenModal}>Open Modal</uui-button>
</uui-box>
`;
}

static override styles = [css`
:host{
display:block;
padding:20px;
}
`];
}

export default UmbExampleCustomModalDashboardElement

declare global {
interface HTMLElementTagNameMap {
'example-custom-modal-dashboard': UmbExampleCustomModalDashboardElement;
}
}
50 changes: 50 additions & 0 deletions examples/custom-modal/example-custom-modal-element.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { css, html } from "@umbraco-cms/backoffice/external/lit";
import { defineElement, UUIModalElement } from "@umbraco-cms/backoffice/external/uui";

/**
* This class defines a custom design for the modal it self, in the same was as
* UUIModalSidebarElement and UUIModalDialogElement.
*/
@defineElement('example-modal-element')
export class UmbExampleCustomModalElement extends UUIModalElement {
override render() {
return html`
<dialog>
<h2>Custom Modal-wrapper</h2>
<slot></slot>
</dialog>
`;
}

static override styles = [
...UUIModalElement.styles,
css`
dialog {
width:100%;
height:100%;
max-width: 100%;
max-height: 100%;
top:0;
left:0;
right:0;
bottom:0;
background:#fff;
}
:host([index='0']) dialog {
box-shadow: var(--uui-shadow-depth-5);
}
:host(:not([index='0'])) dialog {
outline: 1px solid rgba(0, 0, 0, 0.1);
}

`,
];
}

export default UmbExampleCustomModalElement;

declare global {
interface HTMLElementTagNameMap {
'example-modal-element': UmbExampleCustomModalElement;
}
}
19 changes: 19 additions & 0 deletions examples/custom-modal/example-modal-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { UmbModalToken } from "@umbraco-cms/backoffice/modal";

export interface ExampleModalData {
unique: string | null;
}

export interface ExampleModalResult {
text : string;
}

export const EXAMPLE_MODAL_TOKEN = new UmbModalToken<
ExampleModalData,
ExampleModalResult
>('example.modal.custom.element', {
modal : {
type : 'custom',
element: () => import('./example-custom-modal-element.element.js'),
}
});
51 changes: 51 additions & 0 deletions examples/custom-modal/example-modal-view.element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { ExampleModalData, ExampleModalResult } from './example-modal-token.js';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbModalContext } from '@umbraco-cms/backoffice/modal';
import './example-custom-modal-element.element.js';

@customElement('example-modal-view')
export class UmbExampleModalViewElement extends UmbLitElement {

@property({ attribute: false })
public modalContext?: UmbModalContext<ExampleModalData, ExampleModalResult>;

onClickDone(){
this.modalContext?.submit();
}

override render() {
return html`
<div id="modal">
<p>Example content of custom modal element</p>
<uui-button look="primary" label="Submit modal" @click=${() => this.onClickDone()}></uui-button>
</div>
`;
}

static override styles = [css`
:host {
background: #eaeaea;
display: block;
box-sizing:border-box;
}

#modal {
box-sizing:border-box;
}

p {
margin:0;
padding:0;
}

`];
}

export default UmbExampleModalViewElement

declare global {
interface HTMLElementTagNameMap {
'example-modal-view': UmbExampleModalViewElement;
}
}
29 changes: 29 additions & 0 deletions examples/custom-modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';

const demoModal : ManifestModal = {
type: 'modal',
name: 'Example Custom Modal Element',
alias: 'example.modal.custom.element',
js: () => import('./example-modal-view.element.js'),
}

const demoModalsDashboard : ManifestDashboard = {
type: 'dashboard',
name: 'Example Custom Modal Dashboard',
alias: 'example.dashboard.custom.modal.element',
element: () => import('./example-custom-modal-dashboard.element.js'),
weight: 900,
meta: {
label: 'Custom Modal',
pathname: 'custom-modal',
},
conditions : [
{
alias: 'Umb.Condition.SectionAlias',
match: 'Umb.Section.Content'
}
]
}

export default [demoModal,demoModalsDashboard];
13 changes: 2 additions & 11 deletions examples/modal-routed/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import type { ManifestDashboard, ManifestModal } from '@umbraco-cms/backoffice/extension-registry';

// const section : ManifestSection = {
// type: "section",
// alias: 'demo.section',
// name: "Demo Section",
// meta: {
// label: "Demo",
// pathname: "demo"
// }
// }
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';
import type { ManifestModal } from '@umbraco-cms/backoffice/modal';

const dashboard: ManifestDashboard = {
type: 'dashboard',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
@state()
_modals: Array<UmbModalContext> = [];

@property({ reflect: true, attribute: 'fill-background' })
@property({ type: Boolean, reflect: true, attribute: 'fill-background' })
fillBackground = false;

private _modalManager?: UmbModalManagerContext;
Expand Down Expand Up @@ -41,7 +41,7 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
* @param modals
*/
#createModalElements(modals: Array<UmbModalContext>) {
this.removeAttribute('fill-background');
this.fillBackground = false;
const oldValue = this._modals;
this._modals = modals;

Expand All @@ -58,26 +58,26 @@ export class UmbBackofficeModalContainerElement extends UmbLitElement {
return;
}

this._modals.forEach((modal) => {
if (this._modalElementMap.has(modal.key)) return;
this._modals.forEach(async (modalContext) => {
if (this._modalElementMap.has(modalContext.key)) return;

const modalElement = new UmbModalElement();
modalElement.modalContext = modal;
await modalElement.init(modalContext);

modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modal.key));
modal.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modal.key));
modalElement.element?.addEventListener('close-end', this.#onCloseEnd.bind(this, modalContext.key));
modalContext.addEventListener('umb:destroy', this.#onCloseEnd.bind(this, modalContext.key));

this._modalElementMap.set(modal.key, modalElement);
this._modalElementMap.set(modalContext.key, modalElement);

// If any of the modals are fillBackground, set the fillBackground property to true
if (modal.backdropBackground) {
if (modalContext.backdropBackground) {
this.fillBackground = true;
this.shadowRoot
?.getElementById('container')
?.style.setProperty('--backdrop-background', modal.backdropBackground);
?.style.setProperty('--backdrop-background', modalContext.backdropBackground);
}

this.requestUpdate();
this.requestUpdate('_modalElementMap');
});
}

Expand Down
38 changes: 18 additions & 20 deletions src/packages/core/modal/component/modal.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { html, customElement } from '@umbraco-cms/backoffice/external/lit';
import { UmbBasicState, type UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import {
UUIModalCloseEvent,
type UUIModalElement,
type UUIDialogElement,
type UUIModalDialogElement,
type UUIModalSidebarElement,
} from '@umbraco-cms/backoffice/external/uui';
import { UMB_ROUTE_CONTEXT, type UmbRouterSlotElement } from '@umbraco-cms/backoffice/router';
import { createExtensionElement } from '@umbraco-cms/backoffice/extension-api';
import { createExtensionElement, loadManifestElement } from '@umbraco-cms/backoffice/extension-api';
import type { UmbContextRequestEvent } from '@umbraco-cms/backoffice/context-api';
import {
UMB_CONTEXT_REQUEST_EVENT_TYPE,
Expand All @@ -25,22 +26,8 @@ import {
@customElement('umb-modal')
export class UmbModalElement extends UmbLitElement {
#modalContext?: UmbModalContext;
public get modalContext(): UmbModalContext | undefined {
return this.#modalContext;
}
public set modalContext(value: UmbModalContext | undefined) {
if (this.#modalContext === value) return;
this.#modalContext = value;

if (!value) {
this.destroy();
return;
}

this.#createModalElement();
}

public element?: UUIModalDialogElement | UUIModalSidebarElement;
public element?: UUIModalDialogElement | UUIModalSidebarElement | UUIModalElement;

#innerElement = new UmbBasicState<HTMLElement | undefined>(undefined);

Expand All @@ -52,11 +39,17 @@ export class UmbModalElement extends UmbLitElement {
this.#modalContext?.reject({ type: 'close' });
};

#createModalElement() {
if (!this.#modalContext) return;
async init(modalContext: UmbModalContext | undefined) {
if (this.#modalContext === modalContext) return;
this.#modalContext = modalContext;

if (!this.#modalContext) {
this.destroy();
return;
}

this.#modalContext.addEventListener('umb:destroy', this.#onContextDestroy);
this.element = this.#createContainerElement();
this.element = await this.#createContainerElement();

// Makes sure that the modal triggers the reject of the context promise when it is closed by pressing escape.
this.element.addEventListener(UUIModalCloseEvent, this.#onClose);
Expand Down Expand Up @@ -113,7 +106,12 @@ export class UmbModalElement extends UmbLitElement {
provider.hostConnected();
}

#createContainerElement() {
async #createContainerElement() {
if (this.#modalContext!.type == 'custom' && this.#modalContext?.element) {
const customWrapperElementCtor = await loadManifestElement(this.#modalContext.element);
return new customWrapperElementCtor!();
}

return this.#modalContext!.type === 'sidebar' ? this.#createSidebarElement() : this.#createDialogElement();
}

Expand Down
10 changes: 8 additions & 2 deletions src/packages/core/modal/context/modal-manager.context.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import type { UmbModalToken } from '../token/modal-token.js';
import { UmbModalContext, type UmbModalContextClassArgs } from './modal.context.js';
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
import type { UUIModalElement, UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
import { UmbBasicState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { ElementLoaderProperty } from '@umbraco-cms/backoffice/extension-api';

export type UmbModalType = 'dialog' | 'sidebar';
export type UmbModalType = 'dialog' | 'sidebar' | 'custom';

export interface UmbModalConfig {
key?: string;
type?: UmbModalType;
size?: UUIModalSidebarSize;

/**
* Used to provide a custom modal element to replace the default uui-modal-dialog or uui-modal-sidebar
*/
element?: ElementLoaderProperty<UUIModalElement>;

/**
* Set the background property of the modal backdrop
*/
Expand Down
Loading
Loading