Skip to content

Commit

Permalink
Make dialogs look better on mobile (excalidraw#908)
Browse files Browse the repository at this point in the history
* Standardize mobile media query

* Refactor & add mobile support to dialogs

* back & close icons
  • Loading branch information
j-f1 authored Mar 13, 2020
1 parent c853156 commit 668f8ec
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 197 deletions.
1 change: 1 addition & 0 deletions src/_variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$media-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
49 changes: 49 additions & 0 deletions src/components/Dialog.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@import "../_variables";

.Dialog__title {
--metric: calc(var(--space-factor) * 4);
display: grid;
align-items: center;
margin-top: 0;
grid-template-columns: 1fr calc(var(--space-factor) * 7);
grid-gap: var(--metric);
}

.Dialog__titleContent {
flex: 1;
}

.Dialog .Modal__close {
margin: 0;
}

@media #{$media-query} {
.Dialog__title {
grid-template-columns: calc(var(--space-factor) * 7) 1fr calc(
var(--space-factor) * 7
);
position: sticky;
top: calc(-1 * var(--metric));
margin: calc(-1 * var(--metric));
margin-bottom: var(--metric);
padding: calc(var(--space-factor) * 2) var(--metric);
background: white;
font-size: 1.25em;

box-sizing: border-box;
border-bottom: 1px solid #ccc;
z-index: 1;
}
.Dialog__titleContent {
text-align: center;
}
.Dialog .Island {
height: 100%;
box-sizing: border-box;
overflow-y: auto;
}

.Dialog .Modal__close {
order: -1;
}
}
41 changes: 41 additions & 0 deletions src/components/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { Modal } from "./Modal";
import { Island } from "./Island";
import { t } from "../i18n";
import useIsMobile from "../is-mobile";
import { back, close } from "./icons";

import "./Dialog.scss";

export function Dialog(props: {
children: React.ReactNode;
className?: string;
maxWidth?: number;
onCloseRequest(): void;
closeButtonRef?: React.Ref<HTMLButtonElement>;
title: React.ReactNode;
}) {
return (
<Modal
className={`${props.className ?? ""} Dialog`}
labelledBy="dialog-title"
maxWidth={props.maxWidth}
onCloseRequest={props.onCloseRequest}
>
<Island padding={4}>
<h2 id="dialog-title" className="Dialog__title">
<span className="Dialog__titleContent">{props.title}</span>
<button
className="Modal__close"
onClick={props.onCloseRequest}
aria-label={t("buttons.close")}
ref={props.closeButtonRef}
>
{useIsMobile() ? back : close}
</button>
</h2>
{props.children}
</Island>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "../_variables";

.ExportDialog__preview {
--preview-padding: calc(var(--space-factor) * 4);

Expand Down Expand Up @@ -25,3 +27,26 @@
align-items: baseline;
justify-content: flex-end;
}

@media #{$media-query} {
.ExportDialog__preview canvas {
max-height: 30vh;
}
.ExportDialog__dialog,
.ExportDialog__dialog .Island {
height: 100%;
box-sizing: border-box;
}
.ExportDialog__dialog .Island {
overflow-y: auto;
}
.ExportDialog__actions {
flex-direction: column;
}
.ExportDialog__actions > * {
margin-bottom: calc(var(--space-factor) * 3);
}
.ExportDialog__scales {
justify-content: flex-start;
}
}
164 changes: 77 additions & 87 deletions src/components/ExportDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import "./ExportDialog.css";
import "./ExportDialog.scss";

import React, { useState, useEffect, useRef } from "react";

import { Modal } from "./Modal";
import { ToolButton } from "./ToolButton";
import { clipboard, exportFile, link } from "./icons";
import { Island } from "./Island";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
import { exportToCanvas } from "../scene/export";
Expand All @@ -18,6 +16,7 @@ import { KEYS } from "../keys";
import { probablySupportsClipboardBlob } from "../clipboard";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import useIsMobile from "../is-mobile";
import { Dialog } from "./Dialog";

const scales = [1, 2, 3];
const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
Expand All @@ -36,7 +35,7 @@ function ExportModal({
onExportToSvg,
onExportToClipboard,
onExportToBackend,
onCloseRequest,
closeButton,
}: {
appState: AppState;
elements: readonly ExcalidrawElement[];
Expand All @@ -47,14 +46,14 @@ function ExportModal({
onExportToClipboard: ExportCB;
onExportToBackend: ExportCB;
onCloseRequest: () => void;
closeButton: React.RefObject<HTMLButtonElement>;
}) {
const someElementIsSelected = isSomeElementSelected(elements, appState);
const [scale, setScale] = useState(defaultScale);
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
const previewRef = useRef<HTMLDivElement>(null);
const { exportBackground, viewBackgroundColor } = appState;
const pngButton = useRef<HTMLButtonElement>(null);
const closeButton = useRef<HTMLButtonElement>(null);
const onlySelectedInput = useRef<HTMLInputElement>(null);

const exportedElements = exportSelected
Expand Down Expand Up @@ -113,92 +112,80 @@ function ExportModal({

return (
<div onKeyDown={handleKeyDown}>
<Island padding={4}>
<button
className="Modal__close"
onClick={onCloseRequest}
aria-label={t("buttons.close")}
ref={closeButton}
>
</button>
<h2 id="export-title">{t("buttons.export")}</h2>
<div className="ExportDialog__preview" ref={previewRef}></div>
<div className="ExportDialog__actions">
<Stack.Col gap={1}>
<Stack.Row gap={2}>
<ToolButton
type="button"
label="PNG"
title={t("buttons.exportToPng")}
aria-label={t("buttons.exportToPng")}
onClick={() => onExportToPng(exportedElements, scale)}
ref={pngButton}
/>
<div className="ExportDialog__preview" ref={previewRef}></div>
<div className="ExportDialog__actions">
<Stack.Col gap={1}>
<Stack.Row gap={2}>
<ToolButton
type="button"
label="PNG"
title={t("buttons.exportToPng")}
aria-label={t("buttons.exportToPng")}
onClick={() => onExportToPng(exportedElements, scale)}
ref={pngButton}
/>
<ToolButton
type="button"
label="SVG"
title={t("buttons.exportToSvg")}
aria-label={t("buttons.exportToSvg")}
onClick={() => onExportToSvg(exportedElements, scale)}
/>
{probablySupportsClipboardBlob && (
<ToolButton
type="button"
label="SVG"
title={t("buttons.exportToSvg")}
aria-label={t("buttons.exportToSvg")}
onClick={() => onExportToSvg(exportedElements, scale)}
icon={clipboard}
title={t("buttons.copyToClipboard")}
aria-label={t("buttons.copyToClipboard")}
onClick={() => onExportToClipboard(exportedElements, scale)}
/>
{probablySupportsClipboardBlob && (
)}
<ToolButton
type="button"
icon={link}
title={t("buttons.getShareableLink")}
aria-label={t("buttons.getShareableLink")}
onClick={() => onExportToBackend(exportedElements)}
/>
</Stack.Row>
</Stack.Col>
{actionManager.renderAction("changeProjectName")}
<Stack.Col gap={1}>
<div className="ExportDialog__scales">
<Stack.Row gap={2} align="baseline">
{scales.map(s => (
<ToolButton
type="button"
icon={clipboard}
title={t("buttons.copyToClipboard")}
aria-label={t("buttons.copyToClipboard")}
onClick={() => onExportToClipboard(exportedElements, scale)}
key={s}
size="s"
type="radio"
icon={`x${s}`}
name="export-canvas-scale"
aria-label={`Scale ${s} x`}
id="export-canvas-scale"
checked={s === scale}
onChange={() => setScale(s)}
/>
)}
<ToolButton
type="button"
icon={link}
title={t("buttons.getShareableLink")}
aria-label={t("buttons.getShareableLink")}
onClick={() => onExportToBackend(exportedElements)}
/>
))}
</Stack.Row>
</Stack.Col>

{actionManager.renderAction("changeProjectName")}
<Stack.Col gap={1}>
<div className="ExportDialog__scales">
<Stack.Row gap={2} align="baseline">
{scales.map(s => (
<ToolButton
key={s}
size="s"
type="radio"
icon={`x${s}`}
name="export-canvas-scale"
aria-label={`Scale ${s} x`}
id="export-canvas-scale"
checked={s === scale}
onChange={() => setScale(s)}
/>
))}
</Stack.Row>
</div>
{actionManager.renderAction("changeExportBackground")}
{someElementIsSelected && (
<div>
<label>
<input
type="checkbox"
checked={exportSelected}
onChange={event =>
setExportSelected(event.currentTarget.checked)
}
ref={onlySelectedInput}
/>{" "}
{t("labels.onlySelected")}
</label>
</div>
{actionManager.renderAction("changeExportBackground")}
{someElementIsSelected && (
<div>
<label>
<input
type="checkbox"
checked={exportSelected}
onChange={event =>
setExportSelected(event.currentTarget.checked)
}
ref={onlySelectedInput}
/>{" "}
{t("labels.onlySelected")}
</label>
</div>
)}
</Stack.Col>
</div>
</Island>
)}
</Stack.Col>
</div>
</div>
);
}
Expand All @@ -224,6 +211,7 @@ export function ExportDialog({
}) {
const [modalIsShown, setModalIsShown] = useState(false);
const triggerButton = useRef<HTMLButtonElement>(null);
const closeButton = useRef<HTMLButtonElement>(null);

const handleClose = React.useCallback(() => {
setModalIsShown(false);
Expand All @@ -242,10 +230,11 @@ export function ExportDialog({
ref={triggerButton}
/>
{modalIsShown && (
<Modal
<Dialog
maxWidth={800}
onCloseRequest={handleClose}
labelledBy="export-title"
title={t("buttons.export")}
closeButtonRef={closeButton}
>
<ExportModal
elements={elements}
Expand All @@ -257,8 +246,9 @@ export function ExportDialog({
onExportToClipboard={onExportToClipboard}
onExportToBackend={onExportToBackend}
onCloseRequest={handleClose}
closeButton={closeButton}
/>
</Modal>
</Dialog>
)}
</>
);
Expand Down
19 changes: 9 additions & 10 deletions src/components/HintViewer.css → src/components/HintViewer.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "../_variables";

.HintViewer {
color: #868e96; /* OC: GRAY 6*/
font-size: 0.8rem;
Expand All @@ -6,19 +8,16 @@
position: absolute;
top: 54px;
transform: translateX(calc(-50% - 16px)); /* 16px is half of lock icon */
}

.HintViewer > span {
background-color: rgba(255, 255, 255, 0.88);
padding: 0.2rem 0.4rem;
border-radius: 3px;
}

@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px) {
.HintViewer {
@media #{$media-query} {
position: static;
transform: none;
margin-top: 0.5rem;
text-align: center;
}

> span {
background-color: rgba(255, 255, 255, 0.88);
padding: 0.2rem 0.4rem;
border-radius: 3px;
}
}
Loading

0 comments on commit 668f8ec

Please sign in to comment.