diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor index 55576f49..1577e741 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeSenderPage.razor @@ -441,6 +441,8 @@ CloseOnOutsideClick="true" ShowFooter="false" CssClass="sender-preview-popup" + HeaderCssClass="sender-preview-popup__header" + BodyCssClass="sender-preview-popup__body" Closed="OnPreviewClosed" AllowResize="true" ApplyBackgroundShading="true" @@ -451,7 +453,7 @@ @if (_previewLoading) { -
+
Lädt... @@ -462,27 +464,24 @@ } else if (_previewErrorMessage is not null) { -
-
- - - - -
-
-

- Dokument konnte nicht geladen werden -

-

- @_previewErrorMessage -

+
+
+
+ + + + +
+
+

Dokument konnte nicht geladen werden

+

@_previewErrorMessage

+
} else if (_previewPdfBytes is not null && _previewViewerMounted) { -
+
element (the outermost element) + * → use selector: .sender-preview-popup { ... } + * + * HeaderCssClass="sender-preview-popup__header" + * → added directly to the header element + * → use selector: .sender-preview-popup__header { ... } + * + * BodyCssClass="sender-preview-popup__body" + * → added directly to the body element + * → use selector: .sender-preview-popup__body { ... } + * + * NOTE: .sender-preview-popup .dxbl-modal::before does NOT work because + * CssClass is on the root, NOT on a parent of dxbl-modal. + * Instead use the root element itself with pseudo-elements or direct selectors. + * + * SCROLLING: + * Scrollable="true" → DevExpress adds vertical scrollbar. + * For horizontal scroll we override the body's overflow-x. + * + * DESIGN LANGUAGE: + * - Purple→blue gradient accent top border (same as .sender-grid-container::before) + * - Glassmorphism surface + * - Consistent with sender-page design system */ -.sender-preview-popup .dxbl-popup-body { - padding: 0 !important; - overflow: hidden; + +/* ── Popup root shell ────────────────────────────────────────────────────── */ +/* Accent stripe at top — uses outline trick since ::before on CssClass root works */ +.sender-preview-popup { + border-top: 4px solid transparent; + border-image: linear-gradient(90deg, #7e22ce 0%, #2a5298 100%) 1; + box-shadow: + 0 32px 64px -12px rgba(0, 0, 0, 0.35), + 0 0 0 1px rgba(126, 34, 206, 0.12) !important; + border-radius: 20px !important; + overflow: hidden !important; } +/* ── Header (via HeaderCssClass) ─────────────────────────────────────────── */ +.sender-preview-popup__header { + background: rgba(255, 255, 255, 0.97) !important; + backdrop-filter: blur(20px) !important; + border-bottom: 1px solid rgba(126, 34, 206, 0.14) !important; + padding: 0.875rem 1.5rem !important; +} + +/* Header title text */ +.sender-preview-popup__header .dxbl-popup-header-text { + font-size: 0.9375rem !important; + font-weight: 700 !important; + color: #1e293b !important; + letter-spacing: -0.02em !important; +} + +/* Close button inside header */ +.sender-preview-popup__header .dxbl-btn { + border-radius: 8px !important; + color: #64748b !important; + transition: all 0.18s ease !important; +} + +.sender-preview-popup__header .dxbl-btn:hover { + background: rgba(126, 34, 206, 0.08) !important; + color: #7e22ce !important; +} + +/* ── Body (via BodyCssClass) ─────────────────────────────────────────────── */ +.sender-preview-popup__body { + padding: 0 !important; + background: #f8fafc !important; + overflow: auto !important; /* both axes — enables horizontal scroll */ + overflow-x: auto !important; + display: flex !important; + flex-direction: column !important; +} + +/* ── PDF viewer wrapper ──────────────────────────────────────────────────── */ +/* + * Scrollable=true on DxPopup adds a vertical scroll to the body. + * The wrapper fills the body via flex and adds breathing-room padding. + * min-width: 0 prevents flex children from pushing the wrapper wider + * than the popup — which would kill horizontal scroll at the body level. + */ .sender-preview-viewer-wrap { + flex: 1 1 auto; display: flex; flex-direction: column; width: 100%; - height: calc(90vh - 114px); /* 52px header + 58px footer + 4px borders */ - min-height: 480px; - overflow: hidden; /* inner DxPdfViewer manages its own scroll */ + min-height: 0; + padding: 1rem 1.5rem 1.25rem; + box-sizing: border-box; } +/* ── DxPdfViewer instance ────────────────────────────────────────────────── */ +/* + * DO NOT set overflow:hidden here — it would clip the PDF at high zoom. + * overflow:auto lets horizontal scroll appear when the PDF page is wider + * than the viewer container (e.g. 100% zoom on a narrow popup). + */ .sender-preview-pdf-viewer { flex: 1 1 auto; width: 100%; - height: 100%; - overflow: auto; + min-height: 0; + border-radius: 12px; + overflow: auto; /* was: hidden — changed to allow h-scroll */ + box-shadow: + 0 4px 24px rgba(0, 0, 0, 0.10), + 0 0 0 1px rgba(126, 34, 206, 0.10); +} + +/* + * DevExpress PDF viewer internal DOM (confirmed from browser DevTools): + * + * .dxbrv-wrapper ← CssClass="sender-preview-pdf-viewer" + * .dxbrv-surface-container ← width: calc(100% + 0rem) — fills wrap + * .dxbrv-gridlayout-content + * .dxbrv-surface-wrapper + * .dxbrv-toolbar ← page nav / zoom bar + * .dxbrv-viewbox ← THE scroll container for the document + * .dxbrv-document-surface + * .dxbrv-report-preview-content-flex-item + * .dxbrv-report-preview-content ← width: 1133px (at 100%) + * + * Goal: + * • Center the PDF page horizontally when it is narrower than the viewbox. + * • Allow horizontal scrollbar when the page is wider than the viewbox. + * • Keep vertical scroll handled by DxPopup Scrollable=true (body level). + */ + +/* Allow both axes of scroll on the DevExpress viewbox */ +.sender-preview-pdf-viewer .dxbrv-viewbox { + overflow: auto !important; +} + +/* Center pages horizontally inside the viewbox */ +.sender-preview-pdf-viewer .dxbrv-document-surface { + display: flex !important; + flex-direction: column !important; + align-items: center !important; + min-width: min-content !important; /* shrink-wraps to page width at high zoom */ +} + +.sender-preview-pdf-viewer .dxbrv-report-preview-content-flex-item { + display: flex !important; + justify-content: center !important; + width: 100% !important; +} + +.sender-preview-pdf-viewer .dxbrv-report-preview-content { + margin-left: auto !important; + margin-right: auto !important; +} + +/* ── Custom scrollbars (WebKit) ─────────────────────────────────────────── */ +.sender-preview-popup__body *::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.sender-preview-popup__body *::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.04); + border-radius: 4px; +} + +.sender-preview-popup__body *::-webkit-scrollbar-thumb { + background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%); + border-radius: 4px; +} + +.sender-preview-popup__body *::-webkit-scrollbar-thumb:hover { + background: linear-gradient(135deg, #6b1cb0 0%, #1e3a72 100%); +} + +/* ── Loading / error states ──────────────────────────────────────────────── */ +.sender-preview-state-wrap { + flex: 1 1 auto; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + min-height: 300px; +} + +.sender-preview-error-card { + display: flex; + gap: 1rem; + align-items: flex-start; + background: #fff1f2; + border: 1px solid rgba(220, 38, 38, 0.2); + border-left: 4px solid #e11d48; + border-radius: 12px; + padding: 1.25rem 1.5rem; + max-width: 560px; + box-shadow: 0 4px 16px rgba(220, 38, 38, 0.08); +} + +.sender-preview-error-icon { + flex-shrink: 0; + width: 40px; + height: 40px; + border-radius: 50%; + background: #fee2e2; + display: flex; + align-items: center; + justify-content: center; +} + +.sender-preview-error-title { + margin: 0 0 0.4rem; + font-weight: 700; + color: #1f2937; + font-size: 0.95rem; +} + +.sender-preview-error-msg { + margin: 0; + color: #6b7280; + font-size: 0.85rem; + line-height: 1.5; + word-break: break-word; +} + +/* ── Theme: Purple ───────────────────────────────────────────────────────── */ +[data-sf-theme="purple"] .sender-preview-popup { + border-image: linear-gradient(90deg, #9333ea 0%, #6b21a8 100%) 1; +} + +[data-sf-theme="purple"] .sender-preview-popup__header { + border-bottom-color: rgba(147, 51, 234, 0.15) !important; +} + +[data-sf-theme="purple"] .sender-preview-popup__header .dxbl-btn:hover { + background: rgba(147, 51, 234, 0.08) !important; + color: #9333ea !important; +} + +[data-sf-theme="purple"] .sender-preview-pdf-viewer { + box-shadow: + 0 4px 24px rgba(0, 0, 0, 0.10), + 0 0 0 1px rgba(147, 51, 234, 0.12); +} + +/* ── Theme: Blazing Dark ─────────────────────────────────────────────────── */ +[data-sf-theme="blazing-dark"] .sender-preview-popup { + border-image: linear-gradient(90deg, #6366f1 0%, #4338ca 100%) 1; + box-shadow: + 0 32px 64px -12px rgba(0, 0, 0, 0.6), + 0 0 0 1px rgba(99, 102, 241, 0.18) !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-popup__header { + background: rgba(15, 20, 40, 0.96) !important; + border-bottom-color: rgba(99, 102, 241, 0.18) !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-popup__header .dxbl-popup-header-text { + color: rgba(255, 255, 255, 0.92) !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-popup__header .dxbl-btn { + color: rgba(255, 255, 255, 0.50) !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-popup__header .dxbl-btn:hover { + background: rgba(99, 102, 241, 0.12) !important; + color: #818cf8 !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-popup__body { + background: #0d1117 !important; +} + +[data-sf-theme="blazing-dark"] .sender-preview-pdf-viewer { + box-shadow: + 0 4px 24px rgba(0, 0, 0, 0.3), + 0 0 0 1px rgba(99, 102, 241, 0.15); +} + +[data-sf-theme="blazing-dark"] .sender-preview-state-wrap { + color: rgba(255, 255, 255, 0.7); +} + +[data-sf-theme="blazing-dark"] .sender-preview-error-card { + background: rgba(239, 68, 68, 0.10); + border-color: rgba(239, 68, 68, 0.25); + border-left-color: rgba(239, 68, 68, 0.6); +} + +[data-sf-theme="blazing-dark"] .sender-preview-error-icon { + background: rgba(239, 68, 68, 0.15); +} + +[data-sf-theme="blazing-dark"] .sender-preview-error-title { + color: rgba(255, 255, 255, 0.88); +} + +[data-sf-theme="blazing-dark"] .sender-preview-error-msg { + color: rgba(255, 255, 255, 0.50); }