aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/welcome-screen
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/components/welcome-screen
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/welcome-screen')
-rw-r--r--packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx195
-rw-r--r--packages/excalidraw/components/welcome-screen/WelcomeScreen.Hints.tsx52
-rw-r--r--packages/excalidraw/components/welcome-screen/WelcomeScreen.scss272
-rw-r--r--packages/excalidraw/components/welcome-screen/WelcomeScreen.tsx26
4 files changed, 545 insertions, 0 deletions
diff --git a/packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx b/packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx
new file mode 100644
index 0000000..4faa41b
--- /dev/null
+++ b/packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx
@@ -0,0 +1,195 @@
+import type { JSX } from "react";
+import { actionLoadScene, actionShortcuts } from "../../actions";
+import { getShortcutFromShortcutName } from "../../actions/shortcuts";
+import { t, useI18n } from "../../i18n";
+import { useDevice, useExcalidrawActionManager } from "../App";
+import { useTunnels } from "../../context/tunnels";
+import { HelpIcon, LoadIcon, usersIcon } from "../icons";
+import { useUIAppState } from "../../context/ui-appState";
+import { ExcalidrawLogo } from "../ExcalidrawLogo";
+
+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.editor.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 }) => {
+ const { WelcomeScreenCenterTunnel } = useTunnels();
+ return (
+ <WelcomeScreenCenterTunnel.In>
+ <div className="welcome-screen-center">
+ {children || (
+ <>
+ <Logo />
+ <Heading>{t("welcomeScreen.defaults.center_heading")}</Heading>
+ <Menu>
+ <MenuItemLoadScene />
+ <MenuItemHelp />
+ </Menu>
+ </>
+ )}
+ </div>
+ </WelcomeScreenCenterTunnel.In>
+ );
+};
+Center.displayName = "Center";
+
+const Logo = ({ children }: { children?: React.ReactNode }) => {
+ return (
+ <div className="welcome-screen-center__logo excalifont welcome-screen-decor">
+ {children || <ExcalidrawLogo withText />}
+ </div>
+ );
+};
+Logo.displayName = "Logo";
+
+const Heading = ({ children }: { children: React.ReactNode }) => {
+ return (
+ <div className="welcome-screen-center__heading welcome-screen-decor excalifont">
+ {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 = useUIAppState();
+ 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";
+
+const MenuItemLiveCollaborationTrigger = ({
+ onSelect,
+}: {
+ onSelect: () => any;
+}) => {
+ const { t } = useI18n();
+ return (
+ <WelcomeScreenMenuItem shortcut={null} onSelect={onSelect} icon={usersIcon}>
+ {t("labels.liveCollaboration")}
+ </WelcomeScreenMenuItem>
+ );
+};
+MenuItemLiveCollaborationTrigger.displayName =
+ "MenuItemLiveCollaborationTrigger";
+
+// -----------------------------------------------------------------------------
+
+Center.Logo = Logo;
+Center.Heading = Heading;
+Center.Menu = Menu;
+Center.MenuItem = WelcomeScreenMenuItem;
+Center.MenuItemLink = WelcomeScreenMenuItemLink;
+Center.MenuItemHelp = MenuItemHelp;
+Center.MenuItemLoadScene = MenuItemLoadScene;
+Center.MenuItemLiveCollaborationTrigger = MenuItemLiveCollaborationTrigger;
+
+export { Center };
diff --git a/packages/excalidraw/components/welcome-screen/WelcomeScreen.Hints.tsx b/packages/excalidraw/components/welcome-screen/WelcomeScreen.Hints.tsx
new file mode 100644
index 0000000..896f401
--- /dev/null
+++ b/packages/excalidraw/components/welcome-screen/WelcomeScreen.Hints.tsx
@@ -0,0 +1,52 @@
+import { t } from "../../i18n";
+import { useTunnels } from "../../context/tunnels";
+import {
+ WelcomeScreenHelpArrow,
+ WelcomeScreenMenuArrow,
+ WelcomeScreenTopToolbarArrow,
+} from "../icons";
+
+const MenuHint = ({ children }: { children?: React.ReactNode }) => {
+ const { WelcomeScreenMenuHintTunnel } = useTunnels();
+ return (
+ <WelcomeScreenMenuHintTunnel.In>
+ <div className="excalifont 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>
+ </WelcomeScreenMenuHintTunnel.In>
+ );
+};
+MenuHint.displayName = "MenuHint";
+
+const ToolbarHint = ({ children }: { children?: React.ReactNode }) => {
+ const { WelcomeScreenToolbarHintTunnel } = useTunnels();
+ return (
+ <WelcomeScreenToolbarHintTunnel.In>
+ <div className="excalifont 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>
+ </WelcomeScreenToolbarHintTunnel.In>
+ );
+};
+ToolbarHint.displayName = "ToolbarHint";
+
+const HelpHint = ({ children }: { children?: React.ReactNode }) => {
+ const { WelcomeScreenHelpHintTunnel } = useTunnels();
+ return (
+ <WelcomeScreenHelpHintTunnel.In>
+ <div className="excalifont welcome-screen-decor welcome-screen-decor-hint welcome-screen-decor-hint--help">
+ <div>{children || t("welcomeScreen.defaults.helpHint")}</div>
+ {WelcomeScreenHelpArrow}
+ </div>
+ </WelcomeScreenHelpHintTunnel.In>
+ );
+};
+HelpHint.displayName = "HelpHint";
+
+export { HelpHint, MenuHint, ToolbarHint };
diff --git a/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss b/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss
new file mode 100644
index 0000000..8e3a010
--- /dev/null
+++ b/packages/excalidraw/components/welcome-screen/WelcomeScreen.scss
@@ -0,0 +1,272 @@
+.excalidraw {
+ .excalifont {
+ font-family: "Excalifont", "Xiaolai";
+ }
+
+ // WelcomeSreen common
+ // ---------------------------------------------------------------------------
+
+ .welcome-screen-decor {
+ pointer-events: none;
+
+ color: var(--color-gray-40);
+
+ a {
+ --color: var(--color-primary);
+ color: var(--color);
+ text-decoration: none;
+ margin-bottom: -6px;
+ }
+ }
+
+ &.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;
+ }
+
+ .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: var(--ui-pointerEvents);
+
+ 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: 1px solid transparent;
+
+ 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;
+ }
+ }
+
+ .welcome-screen-menu-item:hover {
+ text-decoration: none;
+ background: var(--button-hover-bg);
+
+ .welcome-screen-menu-item__shortcut,
+ .welcome-screen-menu-item__icon,
+ .welcome-screen-menu-item__text {
+ color: var(--color-gray-100);
+ }
+ }
+
+ .welcome-screen-menu-item:active {
+ background: var(--button-hover-bg);
+ border-color: var(--color-brand-active);
+
+ .welcome-screen-menu-item__shortcut,
+ .welcome-screen-menu-item__icon,
+ .welcome-screen-menu-item__text {
+ color: var(--color-gray-100);
+ }
+ }
+
+ &.theme--dark {
+ .welcome-screen-menu-item {
+ color: var(--color-gray-60);
+
+ &__shortcut {
+ color: var(--color-gray-60);
+ }
+ }
+
+ .welcome-screen-menu-item:hover {
+ background-color: var(--color-surface-low);
+
+ .welcome-screen-menu-item__icon,
+ .welcome-screen-menu-item__shortcut,
+ .welcome-screen-menu-item__text {
+ color: var(--color-gray-10);
+ }
+ }
+
+ .welcome-screen-menu-item:active {
+ .welcome-screen-menu-item__icon,
+ .welcome-screen-menu-item__shortcut,
+ .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;
+ }
+ }
+
+ // ---------------------------------------------------------------------------
+}
diff --git a/packages/excalidraw/components/welcome-screen/WelcomeScreen.tsx b/packages/excalidraw/components/welcome-screen/WelcomeScreen.tsx
new file mode 100644
index 0000000..1f38b1c
--- /dev/null
+++ b/packages/excalidraw/components/welcome-screen/WelcomeScreen.tsx
@@ -0,0 +1,26 @@
+import { Center } from "./WelcomeScreen.Center";
+import { MenuHint, ToolbarHint, HelpHint } from "./WelcomeScreen.Hints";
+
+import "./WelcomeScreen.scss";
+
+const WelcomeScreen = (props: { children?: React.ReactNode }) => {
+ return (
+ <>
+ {props.children || (
+ <>
+ <Center />
+ <MenuHint />
+ <ToolbarHint />
+ <HelpHint />
+ </>
+ )}
+ </>
+ );
+};
+
+WelcomeScreen.displayName = "WelcomeScreen";
+
+WelcomeScreen.Center = Center;
+WelcomeScreen.Hints = { MenuHint, ToolbarHint, HelpHint };
+
+export default WelcomeScreen;