diff --git a/src/Our.Umbraco.UiExamples.v14/public/umbraco-package.json b/src/Our.Umbraco.UiExamples.v14/public/umbraco-package.json index 20ad916..c413f4c 100644 --- a/src/Our.Umbraco.UiExamples.v14/public/umbraco-package.json +++ b/src/Our.Umbraco.UiExamples.v14/public/umbraco-package.json @@ -1,10 +1,10 @@ { - "name": "My Package Name", + "name": "Umbraco UI Examples", "version": "0.1.0", "extensions": [ { "type": "bundle", - "alias": "My.Package.Bundle", + "alias": "example.ui.bundle", "name": "Example.UI", "js": "/App_Plugins/Example.UI/manifest.js" } diff --git a/src/Our.Umbraco.UiExamples.v14/src/manifest.ts b/src/Our.Umbraco.UiExamples.v14/src/manifest.ts index 8d59391..603f224 100644 --- a/src/Our.Umbraco.UiExamples.v14/src/manifest.ts +++ b/src/Our.Umbraco.UiExamples.v14/src/manifest.ts @@ -1,21 +1,21 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ - { - "type": "section", - "name": "Example UI Dashboard", - "alias": "example.ui.section", - "weight": 900, - "meta": { - "label": "Example UI", - "pathname": "example-ui" - } - }, + { + "type": "section", + "name": "Example UI Dashboard", + "alias": "example.ui.section", + "weight": 900, + "meta": { + "label": "Example UI", + "pathname": "example-ui" + } + }, { "type": "dashboard", "alias": "example.ui.dashboard", "name": "Example UI Dashboard", - "element": () => import("./scripts/dashboards/welcome-dashboard.ts"), + "element": () => import("./scripts/dashboards/welcome-dashboard.ts"), "weight": -1, "meta": { "label": "Welcome Dashboard", @@ -29,26 +29,15 @@ export const manifests: Array = [ ] }, { - "type": "dashboard", - "alias": "example.ui.dashboard.dialogs", - "name": "Dialogs", - "element": () => import("./scripts/dashboards/custom-dialogs-dashboard.ts"), - "weight": -1, - "meta": { - "label": "Dialogs", - "pathname": "dialogs" - }, - "conditions": [ - { - "alias": "Umb.Condition.SectionAlias", - "match": "example.ui.section" - } - ] + "type": "bundle", + "alias": "example.ui.modals", + "name": "Example.UI - Modals", + "js": () => import("./scripts/modals/manifest.ts") }, { "type": "sectionView", "alias": "example.ui.dashboard.section.boxlayout", - "element": () => import("./scripts/sections/box-layout-section.ts"), + "element": () => import("./scripts/sections/box-layout-section.ts"), "name": "Box Layout", "meta": { "label": "Box Layout", @@ -62,69 +51,4 @@ export const manifests: Array = [ } ] } - // ... insert as many manifests as you like - - /*"extensions": [ - { - "type": "section", - "alias": "example.ui.section", - "name": "Example UI Dashboard", - "weight": -1, - "meta": { - "label": "Example UI", - "pathname": "my-dashboard" - } - }, - { - "type": "dashboard", - "alias": "example.ui.dashboard", - "name": "Example UI Dashboard", - "js": "/App_Plugins/Example.UI/scripts/dashboards/welcome-dashboard.js", - "weight": -1, - "meta": { - "label": "Welcome Dashboard", - "pathname": "welcome-dashboard" - }, - "conditions": [ - { - "alias": "Umb.Condition.SectionAlias", - "match": "example.ui.section" - } - ] - }, - { - "type": "dashboard", - "alias": "example.ui.dashboard.dialogs", - "name": "Dialogs", - "js": "/App_Plugins/Example.UI/scripts/dashboards/custom-dialogs-dashboard.js", - "weight": -1, - "meta": { - "label": "Dialogs", - "pathname": "dialogs" - }, - "conditions": [ - { - "alias": "Umb.Condition.SectionAlias", - "match": "example.ui.section" - } - ] - }, - { - "type": "sectionView", - "alias": "example.ui.dashboard.section.boxlayout", - "js": "/App_Plugins/Example.UI/scripts/sections/box-layout-section.js", - "name": "Box Layout", - "meta": { - "sections": [ "example.ui.section" ], - "label": "Box Layout", - "icon": "folder", - "pathname": "box-layout" - }, - "conditions": [ - { - "alias": "Umb.Condition.SectionAlias", - "match": "example.ui.section" - } - ] - }*/ ] \ No newline at end of file diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/dashboards/custom-dialogs-dashboard.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/dashboards/custom-dialogs-dashboard.ts deleted file mode 100644 index 8223e86..0000000 --- a/src/Our.Umbraco.UiExamples.v14/src/scripts/dashboards/custom-dialogs-dashboard.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { LitElement, css, html } from 'lit' -import { customElement, property } from 'lit/decorators.js' - -/** - * An example element. - * - * @slot - This element has a slot - * @csspart button - The button - */ -@customElement('uie-custom-dialogs-dashboard') -export default class UieCustomDialogsDashboard extends LitElement { - /** - * Copy for the read the docs hint. - */ - @property() - docsHint = 'Click on the Vite and Lit logos to learn more' - - - /** - * The number of times the button has been clicked. - */ - @property({ type: Number }) - count = 0 - - render() { - return html` - - -

My Modal

-

My modal content

-
-
- ` - } - - static styles = css` - :host { - padding: var(--uui-size-layout-1); - display:block; - } - - ::slotted(h1) { - font-size: 3.2em; - line-height: 1.1; - } - .header-bar { - - display: flex; - align-items: center; - justify-content: space-between; - } - .title { - font-size: 15px; - color: #000; - font-weight: 700; - margin:0; - } - a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; - } - a:hover { - color: #535bf2; - } - .sub-header { - font-size: 13px; - color: #515054; - line-height: 1.6em; - margin-top: 1px; - } - p:first-child { - margin-top:0; - } - @media (prefers-color-scheme: light) { - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } - } - ` -} - -declare global { - interface HTMLElementTagNameMap { - 'uie-custom-dialogs-dashboard': UieCustomDialogsDashboard - } -} diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.token.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.token.ts new file mode 100644 index 0000000..be8bb68 --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.token.ts @@ -0,0 +1,16 @@ +import { UmbModalToken } from "@umbraco-cms/backoffice/modal"; + +export type MyModalData = { + headline: string; +} + +export type MyModalValue = { + myData: string; +} + +export const MY_DIALOG_TOKEN = new UmbModalToken('example.ui.modals.dialog', { + modal: { + type: 'dialog', + size: 'small' + } +}); \ No newline at end of file diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.ts new file mode 100644 index 0000000..c1c1e65 --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-dialog.ts @@ -0,0 +1,41 @@ +import { html, LitElement, property, customElement } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; +import type { UmbModalContext } from "@umbraco-cms/backoffice/modal"; +import type { MyModalData, MyModalValue } from "./custom-dialog.token.ts"; +import { UmbModalExtensionElement } from "@umbraco-cms/backoffice/extension-registry"; + +@customElement('custom-dialog') +export default class MyDialogElement + extends UmbElementMixin(LitElement) + implements UmbModalExtensionElement { + + @property({ attribute: false }) + modalContext?: UmbModalContext; + + @property({ attribute: false }) + data?: MyModalData; + + private _handleCancel() { + this.modalContext?.reject(); + } + + private _handleSubmit() { + this.modalContext?.updateValue({ myData: "hello world" }); + this.modalContext?.submit(); + } + + render() { + return html` + + + + ${this.modalContext?.data.headline ?? "Default headline"} + +

This is using a slot for the headline.

+ Cancel + Submit +
+
+ `; + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-modals-dashboard.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-modals-dashboard.ts new file mode 100644 index 0000000..971a6b3 --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-modals-dashboard.ts @@ -0,0 +1,225 @@ +import { LitElement, css, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' +import { MY_DIALOG_TOKEN } from './custom-dialog.token'; +import { MY_SIDEBAR_TOKEN } from './custom-sidebar.token'; +import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONTEXT_DEBUGGER_MODAL, UMB_CONFIRM_MODAL, UMB_CODE_EDITOR_MODAL } from '@umbraco-cms/backoffice/modal'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; + +/** + * An example element. + * + * @slot - This element has a slot + * @csspart button - The button + */ +@customElement('uie-custom-modals-dashboard') +export default class UieCustomDialogsDashboard extends UmbElementMixin(LitElement) { + #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalManagerContext = instance; + // modalManagerContext is now ready to be used. + }); + } + + + @property({ attribute: false }) + message?: string; + + @property({ attribute: false }) + returnData?: string; + + render() { + return html` + Your last action was: ${this.message ?? "Nothing clicked yet..."} +With data: ${this.returnData ?? "{}"} + + +
+
+
Modals
Modals are the mechanism to display content on-top of the current display.
+
+
+
+ + + View the Storybook library + + + View the dialog tutorial +
+ +

The term "Modal" covers both types of pop-overs; Dialogs and Sidebars. You can see examples below that define how you would open an Umbraco pre-built modal.

+ +

Modals come in two parts; the element and the token.
+ The token is responsible for triggering the modal to be displayed using the ModalManagerContext
+ The element is the UI components and is responsible for showing the HTML on screen, along with any in-modal functionality +

+ +

You can see all of the prebuilt modals by checking the GitHub Source

+ + Confirm Dialog + Debug Sidebar + Code Editor Sidebar + +

+

+ +
+
+ +
+
+
Custom Modals
+
+
+
+ + + View the custom modal tutorial +
+ +

When you have specific requirements and the inbuilt modals don't suit your needs, you can create your own custom modal and put anything you want in there.

+ + Custom Sidebar + Custom Dialog +
+
` + } + + private _handleSubmit(isPositive?: boolean, data?: string) { + this.message = isPositive ? "Submitted" : "Cancelled"; + this.returnData = data; + } + + private _openCodeDialog() { + const ctx = this.#modalManagerContext?.open(this, UMB_CODE_EDITOR_MODAL, { + data: { + headline:"text", + content:"Enter something and it will be sent back from the modal", + language:"javascript" + }, + }); + ctx?.onSubmit().then((e) => { + this._handleSubmit(true, JSON.stringify(e)); + }).catch(() => { + this._handleSubmit(false); + }) + } + private _openDebugDialog() { + const ctx = this.#modalManagerContext?.open(this, UMB_CONTEXT_DEBUGGER_MODAL, { + data: { + content:"I am a debugger modal!" + }, + }); + + ctx?.onSubmit().then((e) => { + this._handleSubmit(true, JSON.stringify(e)); + }).catch(() => { + this._handleSubmit(false); + }) + } + + private _openConfirmationModal() { + const ctx = this.#modalManagerContext?.open(this, UMB_CONFIRM_MODAL, { + data: { + headline: "This is a confirmation modal", + content: "Word up modal", + cancelLabel: 'Cancel', + confirmLabel: 'Confirm', + color: 'positive' // You can change the colour of the submit button (but not cancel)! + }, + }); + + ctx?.onSubmit().then((e) => { + this._handleSubmit(true, JSON.stringify(e)); + }).catch(() => { + this._handleSubmit(false); + }) + } + + private _openCustomModal() { + const ctx = this.#modalManagerContext?.open(this, MY_DIALOG_TOKEN, { + data: { + headline: "My modal headline", + } + }); + + ctx?.onSubmit().then((e) => { + console.log("Submitted", e); + this._handleSubmit(true, JSON.stringify(e)); + }).catch(() => { + this._handleSubmit(false); + }) + } + private _openCustomSidebar() { + const ctx = this.#modalManagerContext?.open(this, MY_SIDEBAR_TOKEN, { + data: { + headline: "My sidebar headline", + } + }); + + ctx?.onSubmit().then((e) => { + console.log("Submitted", e); + this._handleSubmit(true, JSON.stringify(e)); + }).catch(() => { + this._handleSubmit(false); + }) + } + + static styles = css` + :host { + padding: var(--uui-size-layout-1); + display:block; + } + + ::slotted(h1) { + font-size: 3.2em; + line-height: 1.1; + } + .header-bar { + + display: flex; + align-items: center; + justify-content: space-between; + } + .title { + font-size: 15px; + color: #000; + font-weight: 700; + margin:0; + } + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + .sub-header { + font-size: 13px; + color: #515054; + line-height: 1.6em; + margin-top: 1px; + } + p:first-child { + margin-top:0; + } + @media (prefers-color-scheme: light) { + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'uie-custom-dialogs-dashboard': UieCustomDialogsDashboard + } +} diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.token.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.token.ts new file mode 100644 index 0000000..dcb36e0 --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.token.ts @@ -0,0 +1,17 @@ +import { UmbModalToken } from "@umbraco-cms/backoffice/modal"; + +export type MySidebarData = { + headline: string; +} + +export type MySidebarValue = { + myData: string; + myText: string; +} + +export const MY_SIDEBAR_TOKEN = new UmbModalToken('example.ui.modals.sidebar', { + modal: { + type: 'sidebar', + size: 'large' + } +}); \ No newline at end of file diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.ts new file mode 100644 index 0000000..f6b25dc --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/custom-sidebar.ts @@ -0,0 +1,67 @@ +import { html, LitElement, property, customElement } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; +import type { UmbModalContext } from "@umbraco-cms/backoffice/modal"; +import type { MySidebarData, MySidebarValue } from "./custom-sidebar.token.ts"; +import { UmbModalExtensionElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbPropertyValueChangeEvent } from "@umbraco-cms/backoffice/property-editor"; + +@customElement('my-sidebar') +export default class MySidebarElement + extends UmbElementMixin(LitElement) + implements UmbModalExtensionElement { + + @property({ attribute: false }) + modalContext?: UmbModalContext; + + @property({ attribute: false }) + data?: MySidebarData; + + private _handleCancel() { + this.modalContext?.reject(); + } + + private _handleSubmit() { + this.modalContext?.updateValue({ myData: "I am added in code!", myText: this.textInput }); + this.modalContext?.submit(); + } + + @property({ type: String }) + public textInput = ""; + + #onInput(e: InputEvent) { + this.textInput = (e.target as HTMLInputElement).value; + this.#dispatchChangeEvent(); + } + + #dispatchChangeEvent() { + this.dispatchEvent(new UmbPropertyValueChangeEvent()); + } + + render() { + return html` + + +

Some content of this box, appended in the default slot.

+

The headline is currently rendered as a h5.

+ + + + +
+ +
+ Cancel + Submit +
+
+ `; + } +} \ No newline at end of file diff --git a/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/manifest.ts b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/manifest.ts new file mode 100644 index 0000000..ff5a10b --- /dev/null +++ b/src/Our.Umbraco.UiExamples.v14/src/scripts/modals/manifest.ts @@ -0,0 +1,33 @@ +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + "type": "dashboard", + "alias": "example.ui.modals.dashboard", + "name": "Modals", + "element": () => import("./custom-modals-dashboard.ts"), + "weight": -1, + "meta": { + "label": "Modals", + "pathname": "modals" + }, + "conditions": [ + { + "alias": "Umb.Condition.SectionAlias", + "match": "example.ui.section" + } + ] + }, + { + "type": "modal", + "alias": "example.ui.modals.dialog", + "name": "My Dialog", + "element": () => import("./custom-dialog.ts"), + }, + { + "type": "modal", + "alias": "example.ui.modals.sidebar", + "name": "My Sidebar Modal", + "element": () => import("./custom-sidebar.ts"), + } +] \ No newline at end of file