mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: reuse dropdown instead of radix select
This commit is contained in:
parent
c578e4b640
commit
bb847e391c
7 changed files with 86 additions and 441 deletions
|
@ -12,10 +12,9 @@ import { register } from "./register";
|
|||
import { CheckboxItem } from "../components/CheckboxItem";
|
||||
import { getExportSize } from "../scene/export";
|
||||
import {
|
||||
DEFAULT_FANCY_BACKGROUND_IMAGE,
|
||||
DEFAULT_EXPORT_PADDING,
|
||||
FANCY_BACKGROUND_IMAGES,
|
||||
EXPORT_SCALES,
|
||||
FANCY_BACKGROUND_IMAGES,
|
||||
THEME,
|
||||
} from "../constants";
|
||||
import { getSelectedElements, isSomeElementSelected } from "../scene";
|
||||
|
@ -25,7 +24,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
|
|||
import { Theme } from "../element/types";
|
||||
|
||||
import "../components/ToolIcon.scss";
|
||||
import Select, { convertToSelectItems } from "../components/Select";
|
||||
import { Select } from "../components/Select";
|
||||
|
||||
export const actionChangeProjectName = register({
|
||||
name: "changeProjectName",
|
||||
|
@ -124,19 +123,18 @@ export const actionChangeFancyBackgroundImageUrl = register({
|
|||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => {
|
||||
const items = convertToSelectItems(
|
||||
FANCY_BACKGROUND_IMAGES,
|
||||
(item) => item.label,
|
||||
);
|
||||
PanelComponent: ({ updateData, appState }) => {
|
||||
return (
|
||||
<Select
|
||||
items={items}
|
||||
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
||||
placeholder={t("imageExportDialog.label.backgroundImage")}
|
||||
value={DEFAULT_FANCY_BACKGROUND_IMAGE}
|
||||
onChange={(value) => {
|
||||
updateData(value);
|
||||
value={appState.fancyBackgroundImageKey}
|
||||
options={Object.entries(FANCY_BACKGROUND_IMAGES).map(
|
||||
([value, { label }]) => ({
|
||||
value,
|
||||
label,
|
||||
}),
|
||||
)}
|
||||
onSelect={(key) => {
|
||||
updateData(key);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -43,14 +43,26 @@ import { Tooltip } from "./Tooltip";
|
|||
import "./ImageExportDialog.scss";
|
||||
import { useAppProps } from "./App";
|
||||
import { FilledButton } from "./FilledButton";
|
||||
import Select, { convertToSelectItems } from "./Select";
|
||||
import { getCommonBounds } from "../element";
|
||||
import { convertToExportPadding, defaultExportScale, distance } from "../utils";
|
||||
import {
|
||||
convertToExportPadding,
|
||||
defaultExportScale,
|
||||
distance,
|
||||
isBackgroundImageKey,
|
||||
} from "../utils";
|
||||
import { getFancyBackgroundPadding } from "../scene/fancyBackground";
|
||||
import { Select } from "./Select";
|
||||
|
||||
const supportsContextFilters =
|
||||
"filter" in document.createElement("canvas").getContext("2d")!;
|
||||
|
||||
const fancyBackgroundImageOptions = Object.entries(FANCY_BACKGROUND_IMAGES).map(
|
||||
([value, { label }]) => ({
|
||||
value,
|
||||
label,
|
||||
}),
|
||||
);
|
||||
|
||||
export const ErrorCanvasPreview = () => {
|
||||
return (
|
||||
<div>
|
||||
|
@ -71,17 +83,6 @@ type ImageExportModalProps = {
|
|||
onExportImage: AppClassProperties["onExportImage"];
|
||||
};
|
||||
|
||||
function isBackgroundImageKey(
|
||||
key: string,
|
||||
): key is keyof typeof FANCY_BACKGROUND_IMAGES {
|
||||
return key in FANCY_BACKGROUND_IMAGES;
|
||||
}
|
||||
|
||||
const backgroundSelectItems = convertToSelectItems(
|
||||
FANCY_BACKGROUND_IMAGES,
|
||||
(item) => item.label,
|
||||
);
|
||||
|
||||
const ImageExportModal = ({
|
||||
appState,
|
||||
elements,
|
||||
|
@ -283,17 +284,15 @@ const ImageExportModal = ({
|
|||
>
|
||||
{exportWithBackground && (
|
||||
<Select
|
||||
items={backgroundSelectItems}
|
||||
ariaLabel={t("imageExportDialog.label.backgroundImage")}
|
||||
placeholder={t("imageExportDialog.label.backgroundImage")}
|
||||
value={exportBackgroundImage}
|
||||
onChange={(value) => {
|
||||
if (isBackgroundImageKey(value)) {
|
||||
setExportBackgroundImage(value);
|
||||
options={fancyBackgroundImageOptions}
|
||||
onSelect={(key) => {
|
||||
if (isBackgroundImageKey(key)) {
|
||||
setExportBackgroundImage(key);
|
||||
actionManager.executeAction(
|
||||
actionChangeFancyBackgroundImageUrl,
|
||||
"ui",
|
||||
value,
|
||||
key,
|
||||
);
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -1,68 +1,11 @@
|
|||
.Select {
|
||||
&__trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
gap: 5px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
|
||||
&__trigger-icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
position: relative;
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-island);
|
||||
border-radius: var(--border-radius-md);
|
||||
padding: calc(var(--padding) * var(--space-factor));
|
||||
background-color: var(--input-bg-color);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__viewport {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
&__item {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
border-radius: var(--border-radius-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary-color);
|
||||
|
||||
&[data-highlighted] {
|
||||
outline: none;
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
|
||||
&[data-state="checked"] {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--input-bg-color);
|
||||
}
|
||||
|
||||
&[data-highlighted][data-state="checked"] {
|
||||
background-color: var(--color-primary-darker);
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 25px;
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.excalidraw .Modal .Select .Island {
|
||||
padding: 1rem !important;
|
||||
}
|
||||
|
|
|
@ -1,95 +1,49 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import clxs from "clsx";
|
||||
import * as RadixSelect from "@radix-ui/react-select";
|
||||
import React, { useMemo } from "react";
|
||||
import DropdownMenu from "./dropdownMenu/DropdownMenu";
|
||||
import "./Select.scss";
|
||||
import { tablerChevronDownIcon, tablerChevronUpIcon } from "./icons";
|
||||
|
||||
type SelectItems<T extends string> = Record<T, string>;
|
||||
|
||||
export type SelectProps<T extends string> = {
|
||||
items: SelectItems<T>;
|
||||
export type SelectProps<T> = {
|
||||
onSelect: (value: T) => void;
|
||||
options: { value: T; label: string }[];
|
||||
value: T;
|
||||
onChange: (value: T) => void;
|
||||
placeholder?: string;
|
||||
ariaLabel?: string;
|
||||
};
|
||||
|
||||
type ConverterFunction<T> = (
|
||||
items: Record<string, T>,
|
||||
getLabel: (item: T) => string,
|
||||
) => SelectItems<string>;
|
||||
export const Select = <T,>({ onSelect, options, value }: SelectProps<T>) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
export const convertToSelectItems: ConverterFunction<any> = (
|
||||
items,
|
||||
getLabel,
|
||||
) => {
|
||||
const result: SelectItems<string> = {};
|
||||
for (const key in items) {
|
||||
result[key] = getLabel(items[key]);
|
||||
}
|
||||
return result;
|
||||
const currentLabel = useMemo(() => {
|
||||
return options.find((option) => option.value === value)?.label;
|
||||
}, [value, options]);
|
||||
|
||||
return (
|
||||
<div className="Select">
|
||||
<DropdownMenu open={isOpen}>
|
||||
<DropdownMenu.Trigger onToggle={() => setIsOpen(!isOpen)}>
|
||||
{currentLabel}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content
|
||||
onClickOutside={() => {
|
||||
console.log("click outside");
|
||||
setIsOpen(false);
|
||||
}}
|
||||
onSelect={() => setIsOpen(false)}
|
||||
className="Select__content"
|
||||
>
|
||||
{options.map(({ value, label }) => {
|
||||
return (
|
||||
<DropdownMenu.Item
|
||||
key={label}
|
||||
onSelect={() => {
|
||||
onSelect(value);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</DropdownMenu.Item>
|
||||
);
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Select = <T extends string>({
|
||||
items,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
ariaLabel,
|
||||
}: SelectProps<T>) => (
|
||||
<RadixSelect.Root value={value} onValueChange={onChange}>
|
||||
<RadixSelect.Trigger
|
||||
className="Select__trigger"
|
||||
aria-label={ariaLabel ?? placeholder}
|
||||
>
|
||||
{placeholder && <RadixSelect.Value placeholder={placeholder} />}
|
||||
<RadixSelect.Icon className="Select__trigger-icon">
|
||||
{tablerChevronDownIcon}
|
||||
</RadixSelect.Icon>
|
||||
</RadixSelect.Trigger>
|
||||
<RadixSelect.Content
|
||||
className="Select__content"
|
||||
position="popper"
|
||||
align="center"
|
||||
>
|
||||
<RadixSelect.ScrollUpButton className="Select__scroll-button">
|
||||
{tablerChevronUpIcon}
|
||||
</RadixSelect.ScrollUpButton>
|
||||
|
||||
<RadixSelect.Viewport className="Select__viewport">
|
||||
{(Object.entries(items) as [T, string][]).map(
|
||||
([itemValue, itemLabel]) => (
|
||||
<SelectItem value={itemValue} key={itemValue}>
|
||||
{itemLabel}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</RadixSelect.Viewport>
|
||||
|
||||
<RadixSelect.ScrollDownButton className="Select__scroll-button">
|
||||
{tablerChevronDownIcon}
|
||||
</RadixSelect.ScrollDownButton>
|
||||
</RadixSelect.Content>
|
||||
</RadixSelect.Root>
|
||||
);
|
||||
|
||||
type SelectItemProps = React.ComponentProps<typeof RadixSelect.Item>;
|
||||
|
||||
const SelectItem = forwardRef(
|
||||
(
|
||||
{ children, className, ...props }: SelectItemProps,
|
||||
forwardedRef: React.ForwardedRef<HTMLDivElement>,
|
||||
) => {
|
||||
return (
|
||||
<RadixSelect.Item
|
||||
className={clxs("Select__item", className)}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<RadixSelect.ItemText>{children}</RadixSelect.ItemText>
|
||||
</RadixSelect.Item>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default Select;
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
DEFAULT_VERSION,
|
||||
EVENT,
|
||||
EXPORT_SCALES,
|
||||
FANCY_BACKGROUND_IMAGES,
|
||||
FONT_FAMILY,
|
||||
isDarwin,
|
||||
MIME_TYPES,
|
||||
|
@ -1081,3 +1082,9 @@ export const convertToExportPadding = (
|
|||
|
||||
throw new Error("Invalid padding value");
|
||||
};
|
||||
|
||||
export function isBackgroundImageKey(
|
||||
key: string,
|
||||
): key is keyof typeof FANCY_BACKGROUND_IMAGES {
|
||||
return key in FANCY_BACKGROUND_IMAGES;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue