From 505e40a57fa23851a2df1953a74ec4194a7009cc Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 26 Jun 2025 15:51:05 +0000 Subject: [PATCH] update modals --- ts_web/elements/dees-modal.demo.ts | 292 +++++++++++++++++++++++++---- ts_web/elements/dees-modal.ts | 93 ++++++--- 2 files changed, 328 insertions(+), 57 deletions(-) diff --git a/ts_web/elements/dees-modal.demo.ts b/ts_web/elements/dees-modal.demo.ts index d16a332..569a511 100644 --- a/ts_web/elements/dees-modal.demo.ts +++ b/ts_web/elements/dees-modal.demo.ts @@ -1,37 +1,263 @@ -import { html } from '@design.estate/dees-element'; +import { html, css, cssManager } from '@design.estate/dees-element'; import { DeesModal } from './dees-modal.js'; export const demoFunc = () => html` - { - DeesModal.createAndShow({ - heading: 'This is a heading', - content: html` - - - - - - - `, - menuOptions: [{ - name: 'Cancel', - iconName: null, - action: async (deesModalArg) => { - deesModalArg.destroy(); - return null; - } - }, { - name: 'Ok', - iconName: null, - action: async (deesModalArg) => { - deesModalArg.destroy(); - return null; - } - }], - }); - }}>open modal + + +
+
+

Modal Width Variations

+

Modals can have different widths: small, medium, large, fullscreen, or custom pixel values.

+
+ { + DeesModal.createAndShow({ + heading: 'Small Modal', + width: 'small', + content: html` +

This is a small modal with a width of 380px. Perfect for simple confirmations or brief messages.

+ `, + menuOptions: [{ + name: 'Cancel', + action: async (modal) => modal.destroy() + }, { + name: 'OK', + action: async (modal) => modal.destroy() + }], + }); + }}>Small Modal
+ + { + DeesModal.createAndShow({ + heading: 'Medium Modal (Default)', + width: 'medium', + content: html` + + + + + + `, + menuOptions: [{ + name: 'Cancel', + action: async (modal) => modal.destroy() + }, { + name: 'Sign Up', + action: async (modal) => modal.destroy() + }], + }); + }}>Medium Modal + + { + DeesModal.createAndShow({ + heading: 'Large Modal', + width: 'large', + content: html` +

Wide Content Area

+

This large modal is 800px wide and perfect for displaying more complex content like forms with multiple columns, tables, or detailed information.

+
+ + + + +
+ `, + menuOptions: [{ + name: 'Cancel', + action: async (modal) => modal.destroy() + }, { + name: 'Save', + action: async (modal) => modal.destroy() + }], + }); + }}>Large Modal
+ + { + DeesModal.createAndShow({ + heading: 'Fullscreen Modal', + width: 'fullscreen', + content: html` +

Fullscreen Experience

+

This modal takes up almost the entire viewport with a 20px margin on all sides. Great for immersive experiences, detailed editors, or when you need maximum space.

+

The content area can be as tall as needed and will scroll if necessary.

+
+ Large content area +
+ `, + menuOptions: [{ + name: 'Close', + action: async (modal) => modal.destroy() + }], + }); + }}>Fullscreen Modal
+
+
+ +
+

Custom Width & Constraints

+

You can also set custom pixel widths and min/max constraints.

+
+ { + DeesModal.createAndShow({ + heading: 'Custom Width (700px)', + width: 700, + content: html` +

This modal has a custom width of exactly 700 pixels.

+ `, + menuOptions: [{ + name: 'Close', + action: async (modal) => modal.destroy() + }], + }); + }}>Custom 700px
+ + { + DeesModal.createAndShow({ + heading: 'With Max Width', + width: 'large', + maxWidth: 600, + content: html` +

This modal is set to 'large' but constrained by a maxWidth of 600px.

+ `, + menuOptions: [{ + name: 'Got it', + action: async (modal) => modal.destroy() + }], + }); + }}>Max Width 600px
+ + { + DeesModal.createAndShow({ + heading: 'With Min Width', + width: 300, + minWidth: 400, + content: html` +

This modal width is set to 300px but has a minWidth of 400px, so it will be 400px wide.

+ `, + menuOptions: [{ + name: 'OK', + action: async (modal) => modal.destroy() + }], + }); + }}>Min Width 400px
+
+
+ +
+

Button Variations

+

Modals can have different button configurations with proper spacing.

+
+ { + DeesModal.createAndShow({ + heading: 'Multiple Actions', + content: html` +

This modal demonstrates multiple buttons with proper spacing between them.

+ `, + menuOptions: [{ + name: 'Delete', + action: async (modal) => modal.destroy() + }, { + name: 'Cancel', + action: async (modal) => modal.destroy() + }, { + name: 'Save Changes', + action: async (modal) => modal.destroy() + }], + }); + }}>Three Buttons
+ + { + DeesModal.createAndShow({ + heading: 'Single Action', + content: html` +

Sometimes you just need one button.

+ `, + menuOptions: [{ + name: 'Acknowledge', + action: async (modal) => modal.destroy() + }], + }); + }}>Single Button
+ + { + DeesModal.createAndShow({ + heading: 'No Actions', + content: html` +

This modal has no buttons. Click outside or press ESC to close.

+

This is useful for informational modals that don't require user action.

+ `, + menuOptions: [], + }); + }}>No Buttons
+ + { + DeesModal.createAndShow({ + heading: 'Long Button Labels', + content: html` +

Testing button layout with longer labels.

+ `, + menuOptions: [{ + name: 'Discard All Changes', + action: async (modal) => modal.destroy() + }, { + name: 'Save and Continue Editing', + action: async (modal) => modal.destroy() + }], + }); + }}>Long Labels
+
+
+ +
+

Responsive Behavior

+

All modals automatically become full-width on mobile devices (< 768px viewport width) for better usability.

+ { + DeesModal.createAndShow({ + heading: 'Responsive Modal', + width: 'large', + content: html` +

Resize your browser window to see how this modal adapts. On mobile viewports, it will automatically take the full width minus margins.

+ `, + menuOptions: [{ + name: 'Close', + action: async (modal) => modal.destroy() + }], + }); + }}>Test Responsive
+
+
` \ No newline at end of file diff --git a/ts_web/elements/dees-modal.ts b/ts_web/elements/dees-modal.ts index 3f445fe..1794789 100644 --- a/ts_web/elements/dees-modal.ts +++ b/ts_web/elements/dees-modal.ts @@ -35,12 +35,18 @@ export class DeesModal extends DeesElement { heading: string; content: TemplateResult; menuOptions: plugins.tsclass.website.IMenuItem[]; + width?: 'small' | 'medium' | 'large' | 'fullscreen' | number; + maxWidth?: number; + minWidth?: number; }) { const body = document.body; const modal = new DeesModal(); modal.heading = optionsArg.heading; modal.content = optionsArg.content; modal.menuOptions = optionsArg.menuOptions; + if (optionsArg.width) modal.width = optionsArg.width; + if (optionsArg.maxWidth) modal.maxWidth = optionsArg.maxWidth; + if (optionsArg.minWidth) modal.minWidth = optionsArg.minWidth; modal.windowLayer = await DeesWindowLayer.createAndShow({ blur: true, }); @@ -49,6 +55,7 @@ export class DeesModal extends DeesElement { }); body.append(modal.windowLayer); body.append(modal); + return modal; } // INSTANCE @@ -64,6 +71,15 @@ export class DeesModal extends DeesElement { @state({}) public menuOptions: plugins.tsclass.website.IMenuItem[] = []; + @property({ type: String }) + public width: 'small' | 'medium' | 'large' | 'fullscreen' | number = 'medium'; + + @property({ type: Number }) + public maxWidth: number; + + @property({ type: Number }) + public minWidth: number; + constructor() { super(); } @@ -92,7 +108,6 @@ export class DeesModal extends DeesElement { will-change: transform; transform: translateY(0px) scale(0.95); opacity: 0; - width: 480px; min-height: 120px; background: ${cssManager.bdTheme('#ffffff', '#111')}; border-radius: 8px; @@ -100,6 +115,33 @@ export class DeesModal extends DeesElement { transition: all 0.2s; overflow: hidden; box-shadow: ${cssManager.bdTheme('0px 2px 10px rgba(0, 0, 0, 0.1)', '0px 2px 5px rgba(0, 0, 0, 0.5)')}; + margin: 20px; + } + + /* Width variations */ + .modal.width-small { + width: 380px; + } + + .modal.width-medium { + width: 560px; + } + + .modal.width-large { + width: 800px; + } + + .modal.width-fullscreen { + width: calc(100vw - 40px); + height: calc(100vh - 40px); + max-height: calc(100vh - 40px); + } + + @media (max-width: 768px) { + .modal { + width: calc(100vw - 40px) !important; + max-width: none !important; + } } .modal.show { @@ -130,26 +172,22 @@ export class DeesModal extends DeesElement { flex-direction: row; border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#333')}; justify-content: flex-end; + gap: 8px; + padding: 8px; } .modal .bottomButtons .bottomButton { - margin: 8px 0px; - padding: 8px 12px; - border-radius: 4px; + padding: 8px 16px; + border-radius: 6px; line-height: 16px; text-align: center; font-size: 14px; + font-weight: 500; cursor: pointer; user-select: none; transition: all 0.2s; background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')}; - } - - .modal .bottomButtons .bottomButton:first-child { - margin-left: 8px; - } - .modal .bottomButtons .bottomButton:last-child { - margin-right: 8px; + white-space: nowrap; } .modal .bottomButtons .bottomButton:hover { @@ -178,25 +216,32 @@ export class DeesModal extends DeesElement { ]; public render(): TemplateResult { + const widthClass = typeof this.width === 'string' ? `width-${this.width}` : ''; + const customWidth = typeof this.width === 'number' ? `${this.width}px` : ''; + const maxWidthStyle = this.maxWidth ? `${this.maxWidth}px` : ''; + const minWidthStyle = this.minWidth ? `${this.minWidth}px` : ''; + return html`
- `;