feat: redesign toolbar & tweaks (#4387)

This commit is contained in:
David Luzar 2021-12-15 15:31:44 +01:00 committed by GitHub
parent 390da3fd0f
commit 7db63bd397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 233 additions and 60 deletions

View File

@ -3,15 +3,22 @@ import OpenColor from "open-color";
import "./Card.scss"; import "./Card.scss";
export const Card: React.FC<{ export const Card: React.FC<{
color: keyof OpenColor; color: keyof OpenColor | "primary";
}> = ({ children, color }) => { }> = ({ children, color }) => {
return ( return (
<div <div
className="Card" className="Card"
style={{ style={{
["--card-color" as any]: OpenColor[color][7], ["--card-color" as any]:
["--card-color-darker" as any]: OpenColor[color][8], color === "primary" ? "var(--color-primary)" : OpenColor[color][7],
["--card-color-darkest" as any]: OpenColor[color][9], ["--card-color-darker" as any]:
color === "primary"
? "var(--color-primary-darker)"
: OpenColor[color][8],
["--card-color-darkest" as any]:
color === "primary"
? "var(--color-primary-darkest)"
: OpenColor[color][9],
}} }}
> >
{children} {children}

View File

@ -22,7 +22,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
&:focus { &:focus-visible {
outline: transparent; outline: transparent;
background-color: var(--button-gray-2); background-color: var(--button-gray-2);
& svg { & svg {

View File

@ -3,7 +3,7 @@
--padding: 0; --padding: 0;
background-color: var(--island-bg-color); background-color: var(--island-bg-color);
box-shadow: var(--shadow-island); box-shadow: var(--shadow-island);
border-radius: 4px; border-radius: var(--border-radius-lg);
padding: calc(var(--padding) * var(--space-factor)); padding: calc(var(--padding) * var(--space-factor));
position: relative; position: relative;
transition: box-shadow 0.5s ease-in-out; transition: box-shadow 0.5s ease-in-out;

View File

@ -19,7 +19,6 @@ import { ExportCB, ImageExportDialog } from "./ImageExportDialog";
import { FixedSideContainer } from "./FixedSideContainer"; import { FixedSideContainer } from "./FixedSideContainer";
import { HintViewer } from "./HintViewer"; import { HintViewer } from "./HintViewer";
import { Island } from "./Island"; import { Island } from "./Island";
import "./LayerUI.scss";
import { LoadingMessage } from "./LoadingMessage"; import { LoadingMessage } from "./LoadingMessage";
import { LockButton } from "./LockButton"; import { LockButton } from "./LockButton";
import { MobileMenu } from "./MobileMenu"; import { MobileMenu } from "./MobileMenu";
@ -35,6 +34,9 @@ import { LibraryButton } from "./LibraryButton";
import { isImageFileHandle } from "../data/blob"; import { isImageFileHandle } from "../data/blob";
import { LibraryMenu } from "./LibraryMenu"; import { LibraryMenu } from "./LibraryMenu";
import "./LayerUI.scss";
import "./Toolbar.scss";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
appState: AppState; appState: AppState;
@ -305,7 +307,12 @@ const LayerUI = ({
<Section heading="shapes"> <Section heading="shapes">
{(heading) => ( {(heading) => (
<Stack.Col gap={4} align="start"> <Stack.Col gap={4} align="start">
<Stack.Row gap={1}> <Stack.Row
gap={1}
className={clsx("App-toolbar-container", {
"zen-mode": zenModeEnabled,
})}
>
<LockButton <LockButton
zenModeEnabled={zenModeEnabled} zenModeEnabled={zenModeEnabled}
checked={appState.elementLocked} checked={appState.elementLocked}
@ -314,7 +321,9 @@ const LayerUI = ({
/> />
<Island <Island
padding={1} padding={1}
className={clsx({ "zen-mode": zenModeEnabled })} className={clsx("App-toolbar", {
"zen-mode": zenModeEnabled,
})}
> >
<HintViewer <HintViewer
appState={appState} appState={appState}

View File

@ -16,18 +16,18 @@ const LIBRARY_ICON = (
export const LibraryButton: React.FC<{ export const LibraryButton: React.FC<{
appState: AppState; appState: AppState;
setAppState: React.Component<any, AppState>["setState"]; setAppState: React.Component<any, AppState>["setState"];
}> = ({ appState, setAppState }) => { isMobile?: boolean;
}> = ({ appState, setAppState, isMobile }) => {
return ( return (
<label <label
className={clsx( className={clsx(
"ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility", "ToolIcon ToolIcon_type_floating ToolIcon__library",
`ToolIcon_size_medium`, `ToolIcon_size_medium`,
{ {
"zen-mode-visibility--hidden": appState.zenModeEnabled, "is-mobile": isMobile,
}, },
)} )}
title={`${capitalizeString(t("toolBar.library"))} — 0`} title={`${capitalizeString(t("toolBar.library"))} — 0`}
style={{ marginInlineStart: "var(--space-factor)" }}
> >
<input <input
className="ToolIcon_type_checkbox" className="ToolIcon_type_checkbox"

View File

@ -10,6 +10,7 @@ type LockIconProps = {
checked: boolean; checked: boolean;
onChange?(): void; onChange?(): void;
zenModeEnabled?: boolean; zenModeEnabled?: boolean;
isMobile?: boolean;
}; };
const DEFAULT_SIZE: ToolButtonSize = "medium"; const DEFAULT_SIZE: ToolButtonSize = "medium";
@ -42,10 +43,10 @@ export const LockButton = (props: LockIconProps) => {
return ( return (
<label <label
className={clsx( className={clsx(
"ToolIcon ToolIcon__lock ToolIcon_type_floating zen-mode-visibility", "ToolIcon ToolIcon__lock ToolIcon_type_floating",
`ToolIcon_size_${DEFAULT_SIZE}`, `ToolIcon_size_${DEFAULT_SIZE}`,
{ {
"zen-mode-visibility--hidden": props.zenModeEnabled, "is-mobile": props.isMobile,
}, },
)} )}
title={`${props.title} — Q`} title={`${props.title} — Q`}

View File

@ -64,8 +64,8 @@ export const MobileMenu = ({
<Section heading="shapes"> <Section heading="shapes">
{(heading) => ( {(heading) => (
<Stack.Col gap={4} align="center"> <Stack.Col gap={4} align="center">
<Stack.Row gap={1}> <Stack.Row gap={1} className="App-toolbar-container">
<Island padding={1}> <Island padding={1} className="App-toolbar">
{heading} {heading}
<Stack.Row gap={1}> <Stack.Row gap={1}>
<ShapesSwitcher <ShapesSwitcher
@ -85,8 +85,13 @@ export const MobileMenu = ({
checked={appState.elementLocked} checked={appState.elementLocked}
onChange={onLockToggle} onChange={onLockToggle}
title={t("toolBar.lock")} title={t("toolBar.lock")}
isMobile
/>
<LibraryButton
appState={appState}
setAppState={setAppState}
isMobile
/> />
<LibraryButton appState={appState} setAppState={setAppState} />
</Stack.Row> </Stack.Row>
{libraryMenu} {libraryMenu}
</Stack.Col> </Stack.Col>

View File

@ -8,17 +8,7 @@
position: relative; position: relative;
cursor: pointer; cursor: pointer;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
border-radius: var(--space-factor);
user-select: none; user-select: none;
background-color: var(--button-gray-1);
&:hover {
background-color: var(--button-gray-2);
}
&:active {
background-color: var(--button-gray-3);
}
} }
.ToolIcon--plain { .ToolIcon--plain {
@ -29,6 +19,20 @@
} }
} }
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
& + .ToolIcon__icon {
background-color: var(--button-gray-1);
&:hover {
background-color: var(--button-gray-2);
}
&:active {
background-color: var(--button-gray-3);
}
}
}
.ToolIcon__icon { .ToolIcon__icon {
width: 2.5rem; width: 2.5rem;
height: 2.5rem; height: 2.5rem;
@ -38,7 +42,11 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: var(--space-factor); border-radius: var(--border-radius-lg);
& + .ToolIcon__label {
margin-inline-start: 0;
}
svg { svg {
position: relative; position: relative;
@ -46,10 +54,6 @@
fill: var(--icon-fill-color); fill: var(--icon-fill-color);
color: var(--icon-fill-color); color: var(--icon-fill-color);
} }
& + .ToolIcon__label {
margin-inline-start: 0;
}
} }
.ToolIcon__label { .ToolIcon__label {
@ -79,7 +83,7 @@
margin: 0; margin: 0;
font-size: inherit; font-size: inherit;
&:focus { &:focus-visible {
box-shadow: 0 0 0 2px var(--focus-highlight-color); box-shadow: 0 0 0 2px var(--focus-highlight-color);
} }
@ -121,7 +125,7 @@
} }
} }
&:focus + .ToolIcon__icon { &:focus-visible + .ToolIcon__icon {
box-shadow: 0 0 0 2px var(--focus-highlight-color); box-shadow: 0 0 0 2px var(--focus-highlight-color);
} }
@ -141,10 +145,6 @@
background-color: transparent; background-color: transparent;
} }
&:focus {
box-shadow: none;
}
.ToolIcon__icon { .ToolIcon__icon {
background-color: var(--button-gray-1); background-color: var(--button-gray-1);
&:hover { &:hover {
@ -159,13 +159,6 @@
} }
} }
.ToolIcon.ToolIcon__lock {
margin-inline-end: var(--space-factor);
&.ToolIcon_type_floating {
margin-left: 0.1rem;
}
}
.ToolIcon__keybinding { .ToolIcon__keybinding {
position: absolute; position: absolute;
bottom: 2px; bottom: 2px;

112
src/components/Toolbar.scss Normal file
View File

@ -0,0 +1,112 @@
@import "open-color/open-color.scss";
@mixin toolbarButtonColorStates {
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
& + .ToolIcon__icon:active {
background: var(--color-primary-light);
}
&:checked + .ToolIcon__icon {
background: var(--color-primary);
--icon-fill-color: #{$oc-white};
--keybinding-color: #{$oc-white};
}
&:checked + .ToolIcon__icon:active {
background: var(--color-primary-darker);
}
}
.ToolIcon__keybinding {
bottom: 4px;
right: 4px;
}
}
.excalidraw {
.App-toolbar-container {
.ToolIcon_type_floating {
@include toolbarButtonColorStates;
&:not(.is-mobile) {
.ToolIcon__icon {
padding: 1px;
background-color: var(--island-bg-color);
box-shadow: 1px 3px 4px 0px rgb(0 0 0 / 15%);
border-radius: 50%;
transition: box-shadow 0.5s ease, transform 0.5s ease;
}
}
.ToolIcon_type_radio,
.ToolIcon_type_checkbox {
&:focus-within + .ToolIcon__icon {
// override for custom floating button shadow
box-shadow: 0 0 0 2px var(--focus-highlight-color);
}
}
}
.ToolIcon.ToolIcon__lock {
margin-inline-end: var(--space-factor);
&.ToolIcon_type_floating {
margin-left: 0.1rem;
}
}
.ToolIcon__library {
margin-inline-start: var(--space-factor);
}
&.zen-mode {
.ToolIcon_type_floating {
.ToolIcon__icon {
box-shadow: none;
transform: scale(0.9);
}
.ToolIcon_type_checkbox:not(:checked):not(:hover):not(:active) {
& + .ToolIcon__icon {
svg {
fill: $oc-gray-5;
color: $oc-gray-5;
}
}
}
}
}
}
.App-toolbar {
border-radius: var(--border-radius-lg);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.01), 1px 1px 5px rgb(0 0 0 / 15%);
.ToolIcon {
&:hover {
--icon-fill-color: var(--color-primary-chubb);
--keybinding-color: var(--color-primary-chubb);
}
&:active {
--icon-fill-color: #{$oc-gray-9};
--keybinding-color: #{$oc-gray-9};
}
.ToolIcon__icon {
background: transparent;
border-radius: var(--border-radius-lg);
}
@include toolbarButtonColorStates;
}
&.zen-mode {
.ToolIcon__keybinding,
.HintViewer {
display: none;
}
}
}
&.theme--dark .App-toolbar .ToolIcon:active {
--icon-fill-color: #{$oc-gray-3};
--keybinding-color: #{$oc-gray-3};
}
}

View File

@ -15,8 +15,9 @@ import { THEME } from "../constants";
const activeElementColor = (theme: Theme) => const activeElementColor = (theme: Theme) =>
theme === THEME.LIGHT ? oc.orange[4] : oc.orange[9]; theme === THEME.LIGHT ? oc.orange[4] : oc.orange[9];
const iconFillColor = (theme: Theme) =>
theme === THEME.LIGHT ? oc.black : oc.gray[4]; const iconFillColor = (theme: Theme) => "var(--icon-fill-color)";
const handlerColor = (theme: Theme) => const handlerColor = (theme: Theme) =>
theme === THEME.LIGHT ? oc.white : "#1e1e1e"; theme === THEME.LIGHT ? oc.white : "#1e1e1e";

View File

@ -180,7 +180,7 @@
} }
.buttonList label:focus-within, .buttonList label:focus-within,
input:focus { input:focus-visible {
outline: transparent; outline: transparent;
box-shadow: 0 0 0 2px var(--focus-highlight-color); box-shadow: 0 0 0 2px var(--focus-highlight-color);
} }
@ -190,14 +190,14 @@
user-select: none; user-select: none;
background-color: var(--button-gray-1); background-color: var(--button-gray-1);
border: 0; border: 0;
border-radius: 4px; border-radius: var(--border-radius-md);
margin: 0.125rem 0; margin: 0.125rem 0;
padding: 0.25rem; padding: 0.25rem;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
&:focus { &:focus-visible {
outline: transparent; outline: transparent;
box-shadow: 0 0 0 2px var(--focus-highlight-color); box-shadow: 0 0 0 2px var(--focus-highlight-color);
} }
@ -217,14 +217,16 @@
.active, .active,
.buttonList label.active { .buttonList label.active {
background-color: var(--button-gray-2); background-color: var(--color-primary);
--icon-fill-color: #{$oc-white};
&:hover { &:hover {
background-color: var(--button-gray-2); background-color: var(--color-primary-darker);
} }
&:active { &:active {
background-color: var(--button-gray-3); background-color: var(--color-primary-darkest);
} }
} }
@ -234,7 +236,7 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
svg { svg {
width: 36px; width: 35px;
height: 14px; height: 14px;
padding: 2px; padding: 2px;
opacity: 0.6; opacity: 0.6;

View File

@ -12,7 +12,7 @@
--dialog-border-color: #{$oc-gray-6}; --dialog-border-color: #{$oc-gray-6};
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>'); --dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path d="M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z"/></svg>');
--focus-highlight-color: #{$oc-blue-2}; --focus-highlight-color: #{$oc-blue-2};
--icon-fill-color: #{$oc-black}; --icon-fill-color: #{$oc-gray-9};
--icon-green-fill-color: #{$oc-green-9}; --icon-green-fill-color: #{$oc-green-9};
--default-bg-color: #{$oc-white}; --default-bg-color: #{$oc-white};
--input-bg-color: #{$oc-white}; --input-bg-color: #{$oc-white};
@ -32,10 +32,20 @@
--sar: env(safe-area-inset-right); --sar: env(safe-area-inset-right);
--sat: env(safe-area-inset-top); --sat: env(safe-area-inset-top);
--select-highlight-color: #{$oc-blue-5}; --select-highlight-color: #{$oc-blue-5};
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.85)}; --shadow-island: 0 0 0 1px rgba(0, 0, 0, 0.01), 1px 1px 5px rgb(0 0 0 / 12%);
--space-factor: 0.25rem; --space-factor: 0.25rem;
--text-primary-color: #{$oc-gray-8}; --text-primary-color: #{$oc-gray-8};
--color-primary: #6965db;
--color-primary-chubb: #625ee0; // to offset Chubb illusion
--color-primary-darker: #5b57d1;
--color-primary-darkest: #4a47b1;
--color-primary-light: #e2e1fc;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
&.theme--dark { &.theme--dark {
background: $oc-black; background: $oc-black;
@ -71,7 +81,13 @@
--popup-text-color: #{$oc-gray-4}; --popup-text-color: #{$oc-gray-4};
--popup-text-inverted-color: #2c2c2c; --popup-text-inverted-color: #2c2c2c;
--select-highlight-color: #{$oc-blue-4}; --select-highlight-color: #{$oc-blue-4};
--shadow-island: 0 1px 5px #{transparentize($oc-black, 0.7)}; --shadow-island: 1px 1px 5px #{transparentize($oc-black, 0.7)};
--text-primary-color: #{$oc-gray-4}; --text-primary-color: #{$oc-gray-4};
--color-primary: #5650f0;
--color-primary-chubb: #726dff; // to offset Chubb illusion
--color-primary-darker: #4b46d8;
--color-primary-darkest: #3e39be;
--color-primary-light: #3f3d64;
} }
} }

View File

@ -78,7 +78,7 @@ export const ExportToExcalidrawPlus: React.FC<{
onError: (error: Error) => void; onError: (error: Error) => void;
}> = ({ elements, appState, files, onError }) => { }> = ({ elements, appState, files, onError }) => {
return ( return (
<Card color="indigo"> <Card color="primary">
<div className="Card-icon">{excalidrawPlusIcon}</div> <div className="Card-icon">{excalidrawPlusIcon}</div>
<h2>Excalidraw+</h2> <h2>Excalidraw+</h2>
<div className="Card-details"> <div className="Card-details">

View File

@ -9,7 +9,7 @@
.encrypted-icon { .encrypted-icon {
border-radius: var(--space-factor); border-radius: var(--space-factor);
color: var(--icon-green-fill-color); color: var(--color-primary);
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
margin-inline-start: auto; margin-inline-start: auto;

View File

@ -19,6 +19,8 @@ Please add the latest change on the top under the correct section.
### Features ### Features
- Introduced primary colors to the app. The colors can be overriden. Check [readme](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#customizing-styles) on how to do so.
- #### BREAKING CHANGE - #### BREAKING CHANGE
Removed `getElementMap` util method. Removed `getElementMap` util method.

View File

@ -353,6 +353,31 @@ To view the full example visit :point_down:
</details> </details>
### Customizing styles
Excalidraw is using CSS variables to style certain components. To override them, you should set your own on the `.excalidraw` and `.excalidraw.theme--dark` (for dark mode variables) selectors.
Make sure the selector has higher specificity, e.g. by prefixing it with your app's selector:
```css
.your-app .excalidraw {
--color-primary: red;
}
.your-app .excalidraw.theme--dark {
--color-primary: pink;
}
```
Most notably, you can customize the primary colors, by overriding these variables:
- `--color-primary`
- `--color-primary-darker`
- `--color-primary-darkest`
- `--color-primary-light`
- `--color-primary-chubb` — a slightly darker (in light mode), or lighter (in dark mode) `--color-primary` color to account for [Chubb illusion](https://en.wikipedia.org/wiki/Chubb_illusion).
For a complete list of variables, check [theme.scss](https://github.com/excalidraw/excalidraw/blob/master/src/css/theme.scss), though most of them will not make sense to override.
### Props ### Props
| Name | Type | Default | Description | | Name | Type | Default | Description |