diff --git a/src/_variables.scss b/src/_variables.scss new file mode 100644 index 000000000..532dd244e --- /dev/null +++ b/src/_variables.scss @@ -0,0 +1 @@ +$media-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)"; diff --git a/src/components/Dialog.scss b/src/components/Dialog.scss new file mode 100644 index 000000000..f696e0429 --- /dev/null +++ b/src/components/Dialog.scss @@ -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; + } +} diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx new file mode 100644 index 000000000..80595b595 --- /dev/null +++ b/src/components/Dialog.tsx @@ -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; + title: React.ReactNode; +}) { + return ( + + +

+ {props.title} + +

+ {props.children} +
+
+ ); +} diff --git a/src/components/ExportDialog.css b/src/components/ExportDialog.scss similarity index 59% rename from src/components/ExportDialog.css rename to src/components/ExportDialog.scss index 59a313590..71c335db4 100644 --- a/src/components/ExportDialog.css +++ b/src/components/ExportDialog.scss @@ -1,3 +1,5 @@ +@import "../_variables"; + .ExportDialog__preview { --preview-padding: calc(var(--space-factor) * 4); @@ -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; + } +} diff --git a/src/components/ExportDialog.tsx b/src/components/ExportDialog.tsx index 79c93567b..8cd6845a5 100644 --- a/src/components/ExportDialog.tsx +++ b/src/components/ExportDialog.tsx @@ -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"; @@ -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; @@ -36,7 +35,7 @@ function ExportModal({ onExportToSvg, onExportToClipboard, onExportToBackend, - onCloseRequest, + closeButton, }: { appState: AppState; elements: readonly ExcalidrawElement[]; @@ -47,6 +46,7 @@ function ExportModal({ onExportToClipboard: ExportCB; onExportToBackend: ExportCB; onCloseRequest: () => void; + closeButton: React.RefObject; }) { const someElementIsSelected = isSomeElementSelected(elements, appState); const [scale, setScale] = useState(defaultScale); @@ -54,7 +54,6 @@ function ExportModal({ const previewRef = useRef(null); const { exportBackground, viewBackgroundColor } = appState; const pngButton = useRef(null); - const closeButton = useRef(null); const onlySelectedInput = useRef(null); const exportedElements = exportSelected @@ -113,92 +112,80 @@ function ExportModal({ return (
- - -

{t("buttons.export")}

-
-
- - +
+
+ + + onExportToPng(exportedElements, scale)} + ref={pngButton} + /> + onExportToSvg(exportedElements, scale)} + /> + {probablySupportsClipboardBlob && ( onExportToPng(exportedElements, scale)} - ref={pngButton} + icon={clipboard} + title={t("buttons.copyToClipboard")} + aria-label={t("buttons.copyToClipboard")} + onClick={() => onExportToClipboard(exportedElements, scale)} /> - onExportToSvg(exportedElements, scale)} - /> - {probablySupportsClipboardBlob && ( - onExportToClipboard(exportedElements, scale)} - /> - )} - onExportToBackend(exportedElements)} - /> - - - - {actionManager.renderAction("changeProjectName")} - -
- - {scales.map(s => ( - setScale(s)} - /> - ))} - -
- {actionManager.renderAction("changeExportBackground")} - {someElementIsSelected && ( -
- -
)} -
-
- + onExportToBackend(exportedElements)} + /> +
+
+ {actionManager.renderAction("changeProjectName")} + +
+ + {scales.map(s => ( + setScale(s)} + /> + ))} + +
+ {actionManager.renderAction("changeExportBackground")} + {someElementIsSelected && ( +
+ +
+ )} +
+
); } @@ -224,6 +211,7 @@ export function ExportDialog({ }) { const [modalIsShown, setModalIsShown] = useState(false); const triggerButton = useRef(null); + const closeButton = useRef(null); const handleClose = React.useCallback(() => { setModalIsShown(false); @@ -242,10 +230,11 @@ export function ExportDialog({ ref={triggerButton} /> {modalIsShown && ( - - + )} ); diff --git a/src/components/HintViewer.css b/src/components/HintViewer.scss similarity index 60% rename from src/components/HintViewer.css rename to src/components/HintViewer.scss index fdd44793e..109b80bfb 100644 --- a/src/components/HintViewer.css +++ b/src/components/HintViewer.scss @@ -1,3 +1,5 @@ +@import "../_variables"; + .HintViewer { color: #868e96; /* OC: GRAY 6*/ font-size: 0.8rem; @@ -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; + } } diff --git a/src/components/HintViewer.tsx b/src/components/HintViewer.tsx index 6a46e34f7..2a320c6b5 100644 --- a/src/components/HintViewer.tsx +++ b/src/components/HintViewer.tsx @@ -3,7 +3,7 @@ import { t } from "../i18n"; import { ExcalidrawElement } from "../element/types"; import { getSelectedElements } from "../scene"; -import "./HintViewer.css"; +import "./HintViewer.scss"; import { AppState } from "../types"; interface Hint { diff --git a/src/components/Island.tsx b/src/components/Island.tsx index e4d635728..f59cb6577 100644 --- a/src/components/Island.tsx +++ b/src/components/Island.tsx @@ -4,13 +4,14 @@ import React from "react"; type IslandProps = { children: React.ReactNode; padding?: number }; -export function Island({ children, padding }: IslandProps) { - return ( +export const Island = React.forwardRef( + ({ children, padding }, ref) => (
{children}
- ); -} + ), +); diff --git a/src/components/Modal.css b/src/components/Modal.scss similarity index 67% rename from src/components/Modal.css rename to src/components/Modal.scss index 2c8c53c38..4e2970e92 100644 --- a/src/components/Modal.css +++ b/src/components/Modal.scss @@ -1,3 +1,5 @@ +@import "../_variables"; + .Modal { position: fixed; top: 0; @@ -44,9 +46,31 @@ transform: translateY(0); } } - .Modal__close { + width: calc(var(--space-factor) * 7); + height: calc(var(--space-factor) * 7); + display: flex; + align-items: center; + justify-content: center; + svg { + height: calc(var(--space-factor) * 5); + } +} +.Modal__close--floating { position: absolute; right: calc(var(--space-factor) * 5); top: calc(var(--space-factor) * 5); } + +@media #{$media-query} { + .Modal { + padding: 0; + } + .Modal__content { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index ef6b19c30..0b23670d6 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -1,10 +1,11 @@ -import "./Modal.css"; +import "./Modal.scss"; import React, { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { KEYS } from "../keys"; export function Modal(props: { + className?: string; children: React.ReactNode; maxWidth?: number; onCloseRequest(): void; @@ -20,7 +21,7 @@ export function Modal(props: { }; return createPortal(
void; activeRoomLink: string; onRoomCreate: () => void; onRoomDestroy: () => void; @@ -36,75 +34,65 @@ function RoomModal({ return (
- - -

{t("labels.createRoom")}

- {!activeRoomLink && ( - <> -

{t("roomDialog.desc_intro")}

-

{`🔒 ${t("roomDialog.desc_privacy")}`}

-

{t("roomDialog.desc_start")}

-
- -
- - )} - {activeRoomLink && ( - <> -

{t("roomDialog.desc_inProgressIntro")}

-

{t("roomDialog.desc_shareLink")}

-
- - -
-

{`🔒 ${t("roomDialog.desc_privacy")}`}

-

- {" "} - {t("roomDialog.desc_persistenceWarning")} -

-

{t("roomDialog.desc_exitSession")}

-
- -
- - )} -
+ {!activeRoomLink && ( + <> +

{t("roomDialog.desc_intro")}

+

{`🔒 ${t("roomDialog.desc_privacy")}`}

+

{t("roomDialog.desc_start")}

+
+ +
+ + )} + {activeRoomLink && ( + <> +

{t("roomDialog.desc_inProgressIntro")}

+

{t("roomDialog.desc_shareLink")}

+
+ + +
+

{`🔒 ${t("roomDialog.desc_privacy")}`}

+

+ {" "} + {t("roomDialog.desc_persistenceWarning")} +

+

{t("roomDialog.desc_exitSession")}

+
+ +
+ + )}
); } @@ -155,18 +143,17 @@ export function RoomDialog({ )} {modalIsShown && ( - - + )} ); diff --git a/src/components/ToolIcon.scss b/src/components/ToolIcon.scss index a53af01a1..68a7bac37 100644 --- a/src/components/ToolIcon.scss +++ b/src/components/ToolIcon.scss @@ -13,6 +13,8 @@ cursor: pointer; background-color: var(--button-gray-1); -webkit-tap-highlight-color: transparent; + + border-radius: var(--space-factor); } .ToolIcon__icon { diff --git a/src/components/icons.tsx b/src/components/icons.tsx index 2b38b71c2..12bff754e 100644 --- a/src/components/icons.tsx +++ b/src/components/icons.tsx @@ -11,12 +11,14 @@ const createIcon = ( d: string | React.ReactNode, width = 512, height = width, + style?: React.CSSProperties, ) => ( @@ -183,24 +185,28 @@ export const sendToBack = createIcon( ); export const users = createIcon( - , + "M192 256c61.9 0 112-50.1 112-112S253.9 32 192 32 80 82.1 80 144s50.1 112 112 112zm76.8 32h-8.3c-20.8 10-43.9 16-68.5 16s-47.6-6-68.5-16h-8.3C51.6 288 0 339.6 0 403.2V432c0 26.5 21.5 48 48 48h288c26.5 0 48-21.5 48-48v-28.8c0-63.6-51.6-115.2-115.2-115.2zM480 256c53 0 96-43 96-96s-43-96-96-96-96 43-96 96 43 96 96 96zm48 32h-3.8c-13.9 4.8-28.6 8-44.2 8s-30.3-3.2-44.2-8H432c-20.4 0-39.2 5.9-55.7 15.4 24.4 26.3 39.7 61.2 39.7 99.8v38.4c0 2.2-.5 4.3-.6 6.4H592c26.5 0 48-21.5 48-48 0-61.9-50.1-112-112-112z", 640, 512, ); export const start = createIcon( - , + "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm115.7 272l-176 101c-15.8 8.8-35.7-2.5-35.7-21V152c0-18.4 19.8-29.8 35.7-21l176 107c16.4 9.2 16.4 32.9 0 42z", ); export const stop = createIcon( - , + "M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm96 328c0 8.8-7.2 16-16 16H176c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16h160c8.8 0 16 7.2 16 16v160z", +); + +export const close = createIcon( + "M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z", + 352, + 512, +); + +export const back = createIcon( + "M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z", + 320, + 512, + { marginLeft: "-0.2rem" }, ); diff --git a/src/styles.scss b/src/styles.scss index a3090bfa6..3885ab602 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,4 +1,5 @@ @import "./theme.css"; +@import "./_variables"; body { margin: 0; @@ -359,7 +360,7 @@ button, padding: 10px 20px; } -@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px) { +@media #{$media-query} { aside { display: none; }