feat: support WelcomeScreen customization API (#6048)

This commit is contained in:
David Luzar 2023-01-12 15:49:28 +01:00 committed by GitHub
parent 0982da38fe
commit 599a8f3c6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 715 additions and 379 deletions

View file

@ -0,0 +1,176 @@
import { actionLoadScene, actionShortcuts } from "../../actions";
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
import { t } from "../../i18n";
import {
useDevice,
useExcalidrawActionManager,
useExcalidrawAppState,
} from "../App";
import { ExcalLogo, HelpIcon, LoadIcon } from "../icons";
const WelcomeScreenMenuItemContent = ({
icon,
shortcut,
children,
}: {
icon?: JSX.Element;
shortcut?: string | null;
children: React.ReactNode;
}) => {
const device = useDevice();
return (
<>
<div className="welcome-screen-menu-item__icon">{icon}</div>
<div className="welcome-screen-menu-item__text">{children}</div>
{shortcut && !device.isMobile && (
<div className="welcome-screen-menu-item__shortcut">{shortcut}</div>
)}
</>
);
};
WelcomeScreenMenuItemContent.displayName = "WelcomeScreenMenuItemContent";
const WelcomeScreenMenuItem = ({
onSelect,
children,
icon,
shortcut,
className = "",
...props
}: {
onSelect: () => void;
children: React.ReactNode;
icon?: JSX.Element;
shortcut?: string | null;
} & React.ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
{...props}
type="button"
className={`welcome-screen-menu-item ${className}`}
onClick={onSelect}
>
<WelcomeScreenMenuItemContent icon={icon} shortcut={shortcut}>
{children}
</WelcomeScreenMenuItemContent>
</button>
);
};
WelcomeScreenMenuItem.displayName = "WelcomeScreenMenuItem";
const WelcomeScreenMenuItemLink = ({
children,
href,
icon,
shortcut,
className = "",
...props
}: {
children: React.ReactNode;
href: string;
icon?: JSX.Element;
shortcut?: string | null;
} & React.AnchorHTMLAttributes<HTMLAnchorElement>) => {
return (
<a
{...props}
className={`welcome-screen-menu-item ${className}`}
href={href}
target="_blank"
rel="noreferrer"
>
<WelcomeScreenMenuItemContent icon={icon} shortcut={shortcut}>
{children}
</WelcomeScreenMenuItemContent>
</a>
);
};
WelcomeScreenMenuItemLink.displayName = "WelcomeScreenMenuItemLink";
const Center = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="welcome-screen-center">
{children || (
<>
<Logo />
<Heading>{t("welcomeScreen.defaults.center_heading")}</Heading>
<Menu>
<MenuItemLoadScene />
<MenuItemHelp />
</Menu>
</>
)}
</div>
);
};
Center.displayName = "Center";
const Logo = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="welcome-screen-center__logo virgil welcome-screen-decor">
{children || <>{ExcalLogo} Excalidraw</>}
</div>
);
};
Logo.displayName = "Logo";
const Heading = ({ children }: { children: React.ReactNode }) => {
return (
<div className="welcome-screen-center__heading welcome-screen-decor virgil">
{children}
</div>
);
};
Heading.displayName = "Heading";
const Menu = ({ children }: { children?: React.ReactNode }) => {
return <div className="welcome-screen-menu">{children}</div>;
};
Menu.displayName = "Menu";
const MenuItemHelp = () => {
const actionManager = useExcalidrawActionManager();
return (
<WelcomeScreenMenuItem
onSelect={() => actionManager.executeAction(actionShortcuts)}
shortcut="?"
icon={HelpIcon}
>
{t("helpDialog.title")}
</WelcomeScreenMenuItem>
);
};
MenuItemHelp.displayName = "MenuItemHelp";
const MenuItemLoadScene = () => {
const appState = useExcalidrawAppState();
const actionManager = useExcalidrawActionManager();
if (appState.viewModeEnabled) {
return null;
}
return (
<WelcomeScreenMenuItem
onSelect={() => actionManager.executeAction(actionLoadScene)}
shortcut={getShortcutFromShortcutName("loadScene")}
icon={LoadIcon}
>
{t("buttons.load")}
</WelcomeScreenMenuItem>
);
};
MenuItemLoadScene.displayName = "MenuItemLoadScene";
// -----------------------------------------------------------------------------
Center.Logo = Logo;
Center.Heading = Heading;
Center.Menu = Menu;
Center.MenuItem = WelcomeScreenMenuItem;
Center.MenuItemLink = WelcomeScreenMenuItemLink;
Center.MenuItemHelp = MenuItemHelp;
Center.MenuItemLoadScene = MenuItemLoadScene;
export { Center };

View file

@ -0,0 +1,42 @@
import { t } from "../../i18n";
import {
WelcomeScreenHelpArrow,
WelcomeScreenMenuArrow,
WelcomeScreenTopToolbarArrow,
} from "../icons";
const MenuHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--menu">
{WelcomeScreenMenuArrow}
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.menuHint")}
</div>
</div>
);
};
MenuHint.displayName = "MenuHint";
const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--toolbar">
<div className="welcome-screen-decor-hint__label">
{children || t("welcomeScreen.defaults.toolbarHint")}
</div>
{WelcomeScreenTopToolbarArrow}
</div>
);
};
ToolbarHint.displayName = "ToolbarHint";
const HelpHint = ({ children }: { children?: React.ReactNode }) => {
return (
<div className="virgil welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
<div>{children || t("welcomeScreen.defaults.helpHint")}</div>
{WelcomeScreenHelpArrow}
</div>
);
};
HelpHint.displayName = "HelpHint";
export { HelpHint, MenuHint, ToolbarHint };

View file

@ -0,0 +1,284 @@
.excalidraw {
.virgil {
font-family: "Virgil";
}
// WelcomeSreen common
// ---------------------------------------------------------------------------
.welcome-screen-decor {
pointer-events: none;
color: var(--color-gray-40);
}
&.theme--dark {
.welcome-screen-decor {
color: var(--color-gray-60);
}
}
// WelcomeScreen.Hints
// ---------------------------------------------------------------------------
.welcome-screen-decor-hint {
@media (max-height: 599px) {
display: none !important;
}
@media (max-width: 1024px), (max-width: 800px) {
.welcome-screen-decor {
&--help,
&--menu {
display: none;
}
}
}
&--help {
display: flex;
position: absolute;
right: 0;
bottom: 100%;
:root[dir="rtl"] & {
left: 0;
right: auto;
}
svg {
margin-top: 0.5rem;
width: 85px;
height: 71px;
transform: scaleX(-1) rotate(80deg);
:root[dir="rtl"] & {
transform: rotate(80deg);
}
}
}
&--toolbar {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: 2.5rem;
display: flex;
align-items: baseline;
.welcome-screen-decor-hint__label {
width: 120px;
position: relative;
top: -0.5rem;
}
svg {
width: 38px;
height: 78px;
:root[dir="rtl"] & {
transform: scaleX(-1);
}
}
}
&--menu {
position: absolute;
width: 320px;
font-size: 1rem;
top: 100%;
margin-top: 0.25rem;
margin-inline-start: 0.6rem;
display: flex;
align-items: flex-end;
gap: 0.5rem;
svg {
width: 41px;
height: 94px;
:root[dir="rtl"] & {
transform: scaleX(-1);
}
}
@media (max-width: 860px) {
.welcome-screen-decor-hint__label {
max-width: 160px;
}
}
}
}
// WelcomeSreen.Center
// ---------------------------------------------------------------------------
.welcome-screen-center {
display: flex;
flex-direction: column;
gap: 2rem;
justify-content: center;
align-items: center;
position: absolute;
pointer-events: none;
left: 1rem;
top: 1rem;
right: 1rem;
bottom: 1rem;
}
.welcome-screen-center__logo {
display: flex;
align-items: center;
column-gap: 0.75rem;
font-size: 2.25rem;
svg {
width: 1.625rem;
height: auto;
}
}
.welcome-screen-center__heading {
font-size: 1.125rem;
text-align: center;
}
.welcome-screen-menu {
display: flex;
flex-direction: column;
gap: 2px;
justify-content: center;
align-items: center;
}
.welcome-screen-menu-item {
box-sizing: border-box;
pointer-events: all;
color: var(--color-gray-50);
font-size: 0.875rem;
width: 100%;
min-width: 300px;
max-width: 400px;
display: grid;
align-items: center;
justify-content: space-between;
background: none;
border: none;
padding: 0.75rem;
border-radius: var(--border-radius-md);
grid-template-columns: calc(var(--default-icon-size) + 0.5rem) 1fr 3rem;
&__text {
display: flex;
align-items: center;
margin-right: auto;
text-align: left;
column-gap: 0.5rem;
}
&__icon {
width: var(--default-icon-size);
height: var(--default-icon-size);
}
&__shortcut {
margin-left: auto;
color: var(--color-gray-40);
font-size: 0.75rem;
}
}
&:not(:active) .welcome-screen-menu-item:hover {
text-decoration: none;
background: var(--color-gray-10);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.welcome-screen-menu-item__text {
color: var(--color-gray-100);
}
}
.welcome-screen-menu-item:active {
background: var(--color-gray-20);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.welcome-screen-menu-item__text {
color: var(--color-gray-100);
}
&--promo {
color: var(--color-promo) !important;
&:hover {
.welcome-screen-menu-item__text {
color: var(--color-promo) !important;
}
}
}
}
&.theme--dark {
.welcome-screen-menu-item {
color: var(--color-gray-60);
&__shortcut {
color: var(--color-gray-60);
}
}
&:not(:active) .welcome-screen-menu-item:hover {
background: var(--color-gray-85);
.welcome-screen-menu-item__shortcut {
color: var(--color-gray-50);
}
.welcome-screen-menu-item__text {
color: var(--color-gray-10);
}
}
.welcome-screen-menu-item:active {
background-color: var(--color-gray-90);
.welcome-screen-menu-item__text {
color: var(--color-gray-10);
}
}
}
@media (max-height: 599px) {
.welcome-screen-center {
margin-top: 4rem;
}
}
@media (min-height: 600px) and (max-height: 900px) {
.welcome-screen-center {
margin-top: 8rem;
}
}
@media (max-height: 500px), (max-width: 320px) {
.welcome-screen-center {
display: none;
}
}
// ---------------------------------------------------------------------------
}

View file

@ -0,0 +1,17 @@
import { Center } from "./WelcomeScreen.Center";
import { MenuHint, ToolbarHint, HelpHint } from "./WelcomeScreen.Hints";
import "./WelcomeScreen.scss";
const WelcomeScreen = (props: { children: React.ReactNode }) => {
// NOTE this component is used as a dummy wrapper to retrieve child props
// from, and will never be rendered to DOM directly. As such, we can't
// do anything here (use hooks and such)
return null;
};
WelcomeScreen.displayName = "WelcomeScreen";
WelcomeScreen.Center = Center;
WelcomeScreen.Hints = { MenuHint, ToolbarHint, HelpHint };
export default WelcomeScreen;