feat: redesign of Live Collaboration dialog (#6635)

* feat: redesiged Live Collaboration dialog

* fix: address lints

* fix: inactive dialog dark mode improvements

* fix: follow styleguide with event parameter, add FilledButton size prop

* fix: change timer to be imperative

* fix: add spacing after emoji

* fix: remove unused useEffect

* fix: change margin into whitespace

* fix: add share button check back
This commit is contained in:
Are 2023-05-31 18:27:29 +02:00 committed by GitHub
parent 253c5c7866
commit 7bf4de5892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 698 additions and 253 deletions

View file

@ -0,0 +1,95 @@
@import "../css/variables.module";
.excalidraw {
.ExcButton {
&--color-primary {
color: var(--input-bg-color);
--accent-color: var(--color-primary);
--accent-color-hover: var(--color-primary-darker);
--accent-color-active: var(--color-primary-darkest);
}
&--color-danger {
color: var(--input-bg-color);
--accent-color: var(--color-danger);
--accent-color-hover: #d65550;
--accent-color-active: #d1413c;
}
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
flex-wrap: nowrap;
border-radius: 0.5rem;
font-family: "Assistant";
user-select: none;
transition: all 150ms ease-out;
&--size-large {
font-weight: 400;
font-size: 0.875rem;
height: 3rem;
padding: 0.5rem 1.5rem;
gap: 0.75rem;
letter-spacing: 0.4px;
}
&--size-medium {
font-weight: 600;
font-size: 0.75rem;
height: 2.5rem;
padding: 0.5rem 1rem;
gap: 0.5rem;
letter-spacing: normal;
}
&--variant-filled {
background: var(--accent-color);
border: 1px solid transparent;
&:hover {
background: var(--accent-color-hover);
}
&:active {
background: var(--accent-color-active);
}
}
&--variant-outlined,
&--variant-icon {
border: 1px solid var(--accent-color);
color: var(--accent-color);
background: transparent;
&:hover {
border: 1px solid var(--accent-color-hover);
color: var(--accent-color-hover);
}
&:active {
border: 1px solid var(--accent-color-active);
color: var(--accent-color-active);
}
}
&--variant-icon {
padding: 0.5rem 0.75rem;
width: 3rem;
}
&__icon {
width: 1.25rem;
height: 1.25rem;
}
}
}

View file

@ -0,0 +1,61 @@
import React, { forwardRef } from "react";
import clsx from "clsx";
import "./FilledButton.scss";
export type ButtonVariant = "filled" | "outlined" | "icon";
export type ButtonColor = "primary" | "danger";
export type ButtonSize = "medium" | "large";
export type FilledButtonProps = {
label: string;
children?: React.ReactNode;
onClick?: () => void;
variant?: ButtonVariant;
color?: ButtonColor;
size?: ButtonSize;
className?: string;
startIcon?: React.ReactNode;
};
export const FilledButton = forwardRef<HTMLButtonElement, FilledButtonProps>(
(
{
children,
startIcon,
onClick,
label,
variant = "filled",
color = "primary",
size = "medium",
className,
},
ref,
) => {
return (
<button
className={clsx(
"ExcButton",
`ExcButton--color-${color}`,
`ExcButton--variant-${variant}`,
`ExcButton--size-${size}`,
className,
)}
onClick={onClick}
type="button"
aria-label={label}
ref={ref}
>
{startIcon && (
<div className="ExcButton__icon" aria-hidden>
{startIcon}
</div>
)}
{variant !== "icon" && (children ?? label)}
</button>
);
},
);

View file

@ -167,48 +167,6 @@
flex-basis: 100%;
justify-content: center;
}
&__button {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 8px 16px;
flex-shrink: 0;
width: fit-content;
gap: 8px;
height: 40px;
border: 0;
border-radius: 8px;
user-select: none;
font-family: "Assistant";
font-style: normal;
font-weight: 600;
font-size: 0.75rem;
line-height: 100%;
transition: 150ms ease-out;
transition-property: background, color;
background: var(--color-primary);
color: var(--color-icon-white);
&:hover {
background: var(--color-primary-darker);
color: var(--color-icon-white);
}
&:active {
background: var(--color-primary-darkest);
}
& > svg {
width: 20px;
height: 20px;
}
}
}
}
}

View file

@ -26,7 +26,6 @@ import { getSelectedElements, isSomeElementSelected } from "../scene";
import { exportToCanvas } from "../packages/utils";
import { copyIcon, downloadIcon, helpIcon } from "./icons";
import { Button } from "./Button";
import { Dialog } from "./Dialog";
import { RadioGroup } from "./RadioGroup";
import { Switch } from "./Switch";
@ -34,6 +33,7 @@ import { Tooltip } from "./Tooltip";
import "./ImageExportDialog.scss";
import { useAppProps } from "./App";
import { FilledButton } from "./FilledButton";
const supportsContextFilters =
"filter" in document.createElement("canvas").getContext("2d")!;
@ -236,37 +236,37 @@ const ImageExportModal = ({
</ExportSetting>
<div className="ImageExportModal__settings__buttons">
<Button
<FilledButton
className="ImageExportModal__settings__buttons__button"
title={t("imageExportDialog.title.exportToPng")}
aria-label={t("imageExportDialog.title.exportToPng")}
onSelect={() =>
label={t("imageExportDialog.title.exportToPng")}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.png, exportedElements)
}
startIcon={downloadIcon}
>
{downloadIcon} {t("imageExportDialog.button.exportToPng")}
</Button>
<Button
{t("imageExportDialog.button.exportToPng")}
</FilledButton>
<FilledButton
className="ImageExportModal__settings__buttons__button"
title={t("imageExportDialog.title.exportToSvg")}
aria-label={t("imageExportDialog.title.exportToSvg")}
onSelect={() =>
label={t("imageExportDialog.title.exportToSvg")}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.svg, exportedElements)
}
startIcon={downloadIcon}
>
{downloadIcon} {t("imageExportDialog.button.exportToSvg")}
</Button>
{t("imageExportDialog.button.exportToSvg")}
</FilledButton>
{(probablySupportsClipboardBlob || isFirefox) && (
<Button
<FilledButton
className="ImageExportModal__settings__buttons__button"
title={t("imageExportDialog.title.copyPngToClipboard")}
aria-label={t("imageExportDialog.title.copyPngToClipboard")}
onSelect={() =>
label={t("imageExportDialog.title.copyPngToClipboard")}
onClick={() =>
onExportImage(EXPORT_IMAGE_TYPES.clipboard, exportedElements)
}
startIcon={copyIcon}
>
{copyIcon} {t("imageExportDialog.button.copyPngToClipboard")}
</Button>
{t("imageExportDialog.button.copyPngToClipboard")}
</FilledButton>
)}
</div>
</div>

View file

@ -27,8 +27,8 @@ export const Switch = ({
checked={checked}
disabled={disabled}
onChange={() => onChange(!checked)}
onKeyDown={(e) => {
if (e.key === " ") {
onKeyDown={(event) => {
if (event.key === " ") {
onChange(!checked);
}
}}

View file

@ -0,0 +1,118 @@
@import "../css/variables.module";
.excalidraw {
--ExcTextField--color: var(--color-gray-80);
--ExcTextField--label-color: var(--color-gray-80);
--ExcTextField--background: white;
--ExcTextField--readonly--background: var(--color-gray-10);
--ExcTextField--readonly--color: var(--color-gray-80);
--ExcTextField--border: var(--color-gray-40);
--ExcTextField--border-hover: var(--color-gray-50);
--ExcTextField--placeholder: var(--color-gray-40);
&.theme--dark {
--ExcTextField--color: var(--color-gray-10);
--ExcTextField--label-color: var(--color-gray-20);
--ExcTextField--background: var(--color-gray-85);
--ExcTextField--readonly--background: var(--color-gray-80);
--ExcTextField--readonly--color: var(--color-gray-40);
--ExcTextField--border: var(--color-gray-70);
--ExcTextField--border-hover: var(--color-gray-60);
--ExcTextField--placeholder: var(--color-gray-80);
}
.ExcTextField {
&--fullWidth {
width: 100%;
flex-grow: 1;
}
&__label {
font-family: "Assistant";
font-style: normal;
font-weight: 600;
font-size: 0.875rem;
line-height: 150%;
color: var(--ExcTextField--label-color);
margin-bottom: 0.25rem;
user-select: none;
}
&__input {
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
padding: 0 1rem;
height: 3rem;
background: var(--ExcTextField--background);
border: 1px solid var(--ExcTextField--border);
border-radius: 0.5rem;
&:not(&--readonly) {
&:hover {
border-color: var(--ExcTextField--border-hover);
}
&:active,
&:focus-within {
border-color: var(--color-primary);
}
}
& input {
display: flex;
align-items: center;
border: none;
outline: none;
padding: 0;
margin: 0;
height: 1.5rem;
color: var(--ExcTextField--color);
font-family: "Assistant";
font-style: normal;
font-weight: 400;
font-size: 1rem;
line-height: 150%;
text-overflow: ellipsis;
background: transparent;
width: 100%;
&::placeholder {
color: var(--ExcTextField--placeholder);
}
&:not(:focus) {
&:hover {
background-color: initial;
}
}
&:focus {
outline: initial;
box-shadow: initial;
}
}
&--readonly {
background: var(--ExcTextField--readonly--background);
border-color: transparent;
& input {
color: var(--ExcTextField--readonly--color);
}
}
}
}
}

View file

@ -0,0 +1,57 @@
import { forwardRef, useRef, useImperativeHandle, KeyboardEvent } from "react";
import clsx from "clsx";
import "./TextField.scss";
export type TextFieldProps = {
value?: string;
onChange?: (value: string) => void;
onClick?: () => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
readonly?: boolean;
fullWidth?: boolean;
label?: string;
placeholder?: string;
};
export const TextField = forwardRef<HTMLInputElement, TextFieldProps>(
(
{ value, onChange, label, fullWidth, placeholder, readonly, onKeyDown },
ref,
) => {
const innerRef = useRef<HTMLInputElement | null>(null);
useImperativeHandle(ref, () => innerRef.current!);
return (
<div
className={clsx("ExcTextField", {
"ExcTextField--fullWidth": fullWidth,
})}
onClick={() => {
innerRef.current?.focus();
}}
>
<div className="ExcTextField__label">{label}</div>
<div
className={clsx("ExcTextField__input", {
"ExcTextField__input--readonly": readonly,
})}
>
<input
readOnly={readonly}
type="text"
value={value}
placeholder={placeholder}
ref={innerRef}
onChange={(event) => onChange?.(event.target.value)}
onKeyDown={onKeyDown}
/>
</div>
</div>
);
},
);

View file

@ -1579,3 +1579,31 @@ export const helpIcon = createIcon(
</>,
tablerIconProps,
);
export const playerPlayIcon = createIcon(
<>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M7 4v16l13 -8z"></path>
</>,
tablerIconProps,
);
export const playerStopFilledIcon = createIcon(
<>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M17 4h-10a3 3 0 0 0 -3 3v10a3 3 0 0 0 3 3h10a3 3 0 0 0 3 -3v-10a3 3 0 0 0 -3 -3z"
strokeWidth="0"
fill="currentColor"
></path>
</>,
tablerIconProps,
);
export const tablerCheckIcon = createIcon(
<>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5 12l5 5l10 -10"></path>
</>,
tablerIconProps,
);