Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 24 additions & 2 deletions demo/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';

import ImageEditor, {
ImageEditorRef,
Expand All @@ -16,6 +16,10 @@ const SAMPLE_IMAGES = [

const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;

// Matches the @media breakpoint in styles.css, where the sidebar becomes an
// overlay. Starting it open at these widths hides the editor behind it.
const WIDE_VIEWPORT = '(min-width: 768px)';

const allToolsEnabled = () =>
Object.fromEntries(TOOL_NAMES.map((tool) => [tool, true])) as Record<
ToolName,
Expand All @@ -30,10 +34,19 @@ export default function App() {
const [locale, setLocale] = useState<UnlayerLocale>('en');
const [tools, setTools] =
useState<Record<ToolName, boolean>>(allToolsEnabled);
const [sidebarOpen, setSidebarOpen] = useState(true);
const [sidebarOpen, setSidebarOpen] = useState(
() => window.matchMedia(WIDE_VIEWPORT).matches
);
const [saved, setSaved] = useState<ImageEditorSaveResult | null>(null);
const [status, setStatus] = useState('Loading editor…');

// Drive the page chrome off the same theme the editor gets, so the
// control demonstrates the real thing rather than a dark shell that never
// changes.
useEffect(() => {
document.documentElement.dataset.theme = theme;
}, [theme]);

// Invalidates in-flight file reads so a slow read can never overwrite a
// newer image choice (upload or sample) after the fact.
const readTokenRef = useRef(0);
Expand Down Expand Up @@ -109,6 +122,15 @@ export default function App() {
</header>

<div className="body">
{sidebarOpen && (
<button
type="button"
className="backdrop"
aria-label="Close controls"
onClick={() => setSidebarOpen(false)}
/>
)}

{sidebarOpen && (
<Sidebar
theme={theme}
Expand Down
165 changes: 135 additions & 30 deletions demo/src/styles.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
* {
box-sizing: border-box;
/*
* The page chrome follows the same `theme` the editor is given, so the
* Light/Dark control actually demonstrates something. Everything themed
* goes through these variables — no hardcoded colours below.
*/
:root {
--bg: #ffffff;
--fg: #16181d;
--chrome-bg: #f4f5f7;
--chrome-fg: #16181d;
--chrome-border: #d9dce3;
--control-bg: #ffffff;
--control-border: #c9cdd6;
--control-hover: #eceef2;
--muted: #6f7684;
--accent: #3b82f6;
--overlay: rgba(0, 0, 0, 0.4);
color-scheme: light;
}

html {
color-scheme: light;
[data-theme='dark'] {
--bg: #0f1115;
--fg: #e6e8ee;
--chrome-bg: #16181d;
--chrome-fg: #e6e8ee;
--chrome-border: #2a2e37;
--control-bg: #232732;
--control-border: #3a3f4b;
--control-hover: #2c313d;
--muted: #8f96a3;
--accent: #3b82f6;
--overlay: rgba(0, 0, 0, 0.6);
color-scheme: dark;
}

* {
box-sizing: border-box;
}

html,
Expand All @@ -13,8 +44,8 @@ body,
height: 100%;
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fff;
color: #16181d;
background: var(--bg);
color: var(--fg);
}

.app {
Expand All @@ -28,10 +59,9 @@ body,
align-items: center;
gap: 10px;
padding: 8px 14px;
border-bottom: 1px solid #2a2e37;
background: #16181d;
color: #e6e8ee;
color-scheme: dark;
border-bottom: 1px solid var(--chrome-border);
background: var(--chrome-bg);
color: var(--chrome-fg);
}

.topbar h1 {
Expand All @@ -44,20 +74,20 @@ body,
font-size: 15px;
line-height: 1;
padding: 5px 9px;
border: 1px solid #3a3f4b;
border: 1px solid var(--control-border);
border-radius: 6px;
background: #232732;
color: #e6e8ee;
background: var(--control-bg);
color: inherit;
cursor: pointer;
}

.sidebar-toggle:hover {
background: #2c313d;
background: var(--control-hover);
}

.status {
margin-left: auto;
color: #8f96a3;
color: var(--muted);
font-size: 13px;
white-space: nowrap;
overflow: hidden;
Expand All @@ -68,6 +98,7 @@ body,
flex: 1;
display: flex;
min-height: 0;
position: relative;
}

.sidebar {
Expand All @@ -78,10 +109,13 @@ body,
display: flex;
flex-direction: column;
gap: 18px;
border-right: 1px solid #2a2e37;
background: #16181d;
color: #e6e8ee;
color-scheme: dark;
border-right: 1px solid var(--chrome-border);
background: var(--chrome-bg);
color: var(--chrome-fg);
}

.backdrop {
display: none;
}

.section {
Expand All @@ -95,24 +129,24 @@ body,
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #6f7684;
color: var(--muted);
margin: 0;
}

.section button {
font: inherit;
font-size: 13px;
padding: 6px 10px;
border: 1px solid #3a3f4b;
border: 1px solid var(--control-border);
border-radius: 6px;
background: #232732;
background: var(--control-bg);
color: inherit;
cursor: pointer;
text-align: left;
}

.section button:hover {
background: #2c313d;
background: var(--control-hover);
}

.field {
Expand All @@ -127,9 +161,9 @@ body,
font: inherit;
font-size: 13px;
padding: 4px 8px;
border: 1px solid #3a3f4b;
border: 1px solid var(--control-border);
border-radius: 6px;
background: #232732;
background: var(--control-bg);
color: inherit;
max-width: 130px;
}
Expand All @@ -151,7 +185,7 @@ body,
}

.tool-toggle input {
accent-color: #3b82f6;
accent-color: var(--accent);
margin: 0;
}

Expand All @@ -167,18 +201,31 @@ body,
right: 16px;
bottom: 16px;
width: 320px;
background: #fff;
border: 1px solid #ddd;
/*
* The panel is anchored to the bottom, so an image taller than the
* viewport grows upward and takes the header off the top of the screen —
* which is what happens in landscape on a phone. Bound the height and let
* the image absorb the difference. dvh so mobile browser chrome counts.
*/
max-height: calc(100vh - 32px);
max-height: calc(100dvh - 32px);
display: flex;
flex-direction: column;
background: var(--chrome-bg);
color: var(--chrome-fg);
border: 1px solid var(--chrome-border);
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
padding: 12px;
z-index: 10;
z-index: 20;
}

.preview-header {
display: flex;
align-items: center;
gap: 8px;
/* Close and Download stay reachable no matter how tall the image is. */
flex: none;
}

.preview-header h2 {
Expand All @@ -187,8 +234,66 @@ body,
flex: 1;
}

.preview-header a {
color: var(--accent);
}

.preview-header button {
font: inherit;
border: 1px solid var(--control-border);
border-radius: 6px;
background: var(--control-bg);
color: inherit;
cursor: pointer;
}

.preview img {
width: 100%;
margin-top: 8px;
border-radius: 4px;
/* min-height:0 lets it shrink below its intrinsic size in the column;
contain keeps the aspect ratio while it does. */
flex: 1 1 auto;
min-height: 0;
object-fit: contain;
}

/*
* Below this width a 240px sidebar plus the editor leaves the editor
* unusable (~135px on a 375px phone), so the sidebar overlays the editor
* instead of squeezing it. App.tsx also starts it closed at these widths.
*/
@media (max-width: 767px) {
.sidebar {
position: absolute;
z-index: 15;
top: 0;
bottom: 0;
left: 0;
width: min(280px, 85vw);
box-shadow: 0 0 24px rgba(0, 0, 0, 0.35);
}

.backdrop {
display: block;
position: absolute;
inset: 0;
z-index: 10;
background: var(--overlay);
border: 0;
padding: 0;
}

.preview {
right: 8px;
bottom: 8px;
left: 8px;
width: auto;
max-height: calc(100vh - 16px);
max-height: calc(100dvh - 16px);
}

.topbar h1 {
font-size: 14px;
}
}
Loading