mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 17:08:13 +00:00
Merge branch 'test' into feature/audio-sidepanel-v2
This commit is contained in:
commit
45f8a13ba2
5 files changed
+277
-49
No files matched your search
@@ -5,6 +5,7 @@ import { twMerge } from "tailwind-merge";
|
||||
export interface ButtonPropsI
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon?: IconDefinition;
|
||||
iconFlip?: boolean;
|
||||
variant?: "primary" | "secondary" | "action";
|
||||
}
|
||||
|
||||
@@ -14,12 +15,13 @@ export const Button = ({
|
||||
className: propsClassName,
|
||||
variant: propsVariant = "primary",
|
||||
disabled,
|
||||
iconFlip = false,
|
||||
...rest
|
||||
}: ButtonPropsI) => {
|
||||
function getVariantClassNames(variant: string) {
|
||||
switch (variant) {
|
||||
case "secondary": {
|
||||
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
|
||||
return "bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
|
||||
}
|
||||
case "action": {
|
||||
return " bg-action hover:bg-action-500 text-white focus-visible:outline-action";
|
||||
@@ -44,8 +46,9 @@ export const Button = ({
|
||||
|
||||
return (
|
||||
<button className={className} disabled={disabled} {...rest}>
|
||||
{icon && <FontAwesomeIcon icon={icon} size="sm" />}
|
||||
{children}
|
||||
{icon && !iconFlip ? <FontAwesomeIcon icon={icon} /> : null}
|
||||
<div>{children}</div>
|
||||
{icon && iconFlip ? <FontAwesomeIcon icon={icon} /> : null}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Fragment, useState } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import {
|
||||
IconDefinition,
|
||||
faChevronDown,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Button, ButtonPropsI } from "./Button";
|
||||
import { TransitionDialogue } from "~/components/TransitionDialogue";
|
||||
|
||||
type UnionedButtonProps = { label?: string } & ButtonPropsI;
|
||||
type ModalPropsType = ButtonDropdownProps["options"][number]["dialogProps"];
|
||||
|
||||
interface ButtonDropdownProps {
|
||||
label: string;
|
||||
icon?: IconDefinition;
|
||||
options: Array<{
|
||||
label: string;
|
||||
description?: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
divider?: boolean;
|
||||
dialogProps?: {
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
className?: string;
|
||||
confirmButtonProps?: UnionedButtonProps;
|
||||
closeButtonProps?: UnionedButtonProps;
|
||||
showClose?: boolean;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export const ButtonDropdown = ({
|
||||
label,
|
||||
options,
|
||||
icon,
|
||||
}: ButtonDropdownProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [modalProps, setModalProps] = useState<ModalPropsType | null>(null);
|
||||
|
||||
const closeModal = () => setIsOpen(false);
|
||||
|
||||
const handleOptionClick = (option) => {
|
||||
if (option.onClick) {
|
||||
option.onClick();
|
||||
}
|
||||
if (option.dialogProps) {
|
||||
setModalProps(option.dialogProps);
|
||||
setIsOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Menu as="div" className="inline-block text-left">
|
||||
<Menu.Button as="div">
|
||||
<Button icon={faChevronDown} iconFlip={true} variant="secondary">
|
||||
{icon ? <FontAwesomeIcon icon={icon} className="mr-2" /> : null}
|
||||
{label}
|
||||
</Button>
|
||||
</Menu.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95">
|
||||
<Menu.Items
|
||||
static
|
||||
className="absolute left-0 mt-1 w-max divide-y divide-gray-100 overflow-hidden rounded-lg bg-brand-secondary py-1.5 focus:outline-none">
|
||||
<div>
|
||||
{options.map((option, index) => (
|
||||
<Fragment key={index}>
|
||||
{option.divider && (
|
||||
<div className="my-1.5 border-t border-ui-divider" />
|
||||
)}
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<button
|
||||
disabled={option.disabled}
|
||||
className={`duration-50 bg-brand-secondary font-medium text-white transition-all ${
|
||||
active ? "bg-ui-controls-button/60" : ""
|
||||
} ${option.disabled ? "pointer-events-none opacity-40" : ""} group flex w-full items-center py-1.5 pl-7 pr-4 text-sm`.trim()}
|
||||
onClick={() => handleOptionClick(option)}>
|
||||
<div className="flex w-full">
|
||||
<div className="grow text-start">{option.label}</div>
|
||||
<div className="ml-10 font-normal text-white/75">
|
||||
{option.description && option.description}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
|
||||
{modalProps && (
|
||||
<TransitionDialogue
|
||||
title={modalProps.title}
|
||||
isOpen={isOpen}
|
||||
onClose={closeModal}
|
||||
className={modalProps.className}>
|
||||
{modalProps.content}
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
{modalProps.showClose !== false && modalProps.closeButtonProps && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
{...modalProps.closeButtonProps}
|
||||
onClick={closeModal}>
|
||||
{modalProps.closeButtonProps.label}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{modalProps.confirmButtonProps && (
|
||||
<Button
|
||||
{...modalProps.confirmButtonProps}
|
||||
onClick={(e) => {
|
||||
if (modalProps.confirmButtonProps?.onClick) {
|
||||
modalProps.confirmButtonProps?.onClick(e);
|
||||
}
|
||||
closeModal();
|
||||
}}>
|
||||
{modalProps.confirmButtonProps.label || "Confirm"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TransitionDialogue>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -4,8 +4,11 @@ import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Label } from "./Typography";
|
||||
import { kebabCase } from "~/utilities";
|
||||
import { disableHotkeyInput,enableHotkeyInput,DomLevels } from "~/pages/PageEnigma/store";
|
||||
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
enableHotkeyInput,
|
||||
DomLevels,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
@@ -18,7 +21,7 @@ export const Input = React.forwardRef(({
|
||||
ref: React.ForwardedRef<HTMLInputElement>
|
||||
) => {
|
||||
return (
|
||||
<div className={twMerge("flex flex-col gap-2", className)}>
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
|
||||
|
||||
<div className="relative w-full">
|
||||
|
||||
@@ -1,26 +1,40 @@
|
||||
import { Button } from "./Button";
|
||||
import { ButtonDropdown } from "./ButtonDropdown";
|
||||
import { ButtonIcon } from "./ButtonIcon";
|
||||
import { ButtonLink } from "./ButtonLink";
|
||||
import { ButtonIconSelect } from "./ButtonIconSelect";
|
||||
import { Input } from "./Input";
|
||||
import { InputVector } from "./InputVector";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label, Link} from "./Typography";
|
||||
import { H1, H2, H3, H4, H5, H6, P, Label, Link } from "./Typography";
|
||||
import { ItemPicker } from "./ItemPicker";
|
||||
import { Textarea } from "./Textarea";
|
||||
import { TransitionDialogue } from "./TransitionDialogue";
|
||||
import { ListDropdown } from "./ListDropdown"
|
||||
import { ListSearchDropdown } from "./ListSearchDropdown"
|
||||
import { ListDropdown } from "./ListDropdown";
|
||||
import { ListSearchDropdown } from "./ListSearchDropdown";
|
||||
import { LoadingBar } from "./LoadingBar";
|
||||
import { LoadingDots, LoadingDotsBricks, LoadingDotsTyping } from "./LoadingDots";
|
||||
import {
|
||||
LoadingDots,
|
||||
LoadingDotsBricks,
|
||||
LoadingDotsTyping,
|
||||
} from "./LoadingDots";
|
||||
import { Pill } from "./Pill";
|
||||
import { WaveformPlayer } from "./WaveformPlayer";
|
||||
|
||||
export {
|
||||
Button,
|
||||
ButtonDropdown,
|
||||
ButtonIcon,
|
||||
ButtonLink,
|
||||
ButtonIconSelect,
|
||||
H1,H2,H3,H4,H5,H6,P,Label,Link,
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
H4,
|
||||
H5,
|
||||
H6,
|
||||
P,
|
||||
Label,
|
||||
Link,
|
||||
ItemPicker,
|
||||
Textarea,
|
||||
TransitionDialogue,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useContext, useState } from "react";
|
||||
import { Input } from "~/components";
|
||||
import { ButtonDropdown, Input } from "~/components";
|
||||
import { ButtonDialogue } from "~/modules/ButtonDialogue";
|
||||
import { EngineContext } from "../../../../contexts/EngineContext";
|
||||
import { ToasterContext } from "~/contexts/ToasterContext";
|
||||
@@ -7,6 +7,7 @@ import { ToasterContext } from "~/contexts/ToasterContext";
|
||||
|
||||
import { TestFeaturesButtons } from "./TestFeaturesButtons";
|
||||
import { Help } from "./Help";
|
||||
import { faFile } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
export const ControlsTopButtons = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
@@ -39,44 +40,112 @@ export const ControlsTopButtons = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-3 pt-3">
|
||||
<div className="flex gap-2">
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Load Scene",
|
||||
}}
|
||||
title="Load Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Load",
|
||||
disabled: sceneToken === "",
|
||||
onClick: handleButtonLoadScene,
|
||||
}}>
|
||||
<Input
|
||||
label="Please provide the Token of the scene you wished to load:"
|
||||
onChange={(e) => {
|
||||
setSceneToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ButtonDialogue>
|
||||
<div className="flex gap-1.5">
|
||||
<ButtonDropdown
|
||||
label="File"
|
||||
icon={faFile}
|
||||
options={[
|
||||
{
|
||||
label: "New scene",
|
||||
description: "Ctrl+N",
|
||||
dialogProps: {
|
||||
title: "Create a New Scene",
|
||||
content: (
|
||||
<Input
|
||||
label="Please enter a name for your new scene"
|
||||
onChange={(e) => {
|
||||
setSceneToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Create",
|
||||
disabled: sceneName === "",
|
||||
onClick: () => console.log("NEW SCENE"),
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
showClose: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Load existing scene",
|
||||
description: "Ctrl+O",
|
||||
dialogProps: {
|
||||
title: "Load a Scene",
|
||||
content: (
|
||||
<Input
|
||||
label="Please provide the token of the scene you want to load"
|
||||
onChange={(e) => {
|
||||
setSceneToken(e.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Load",
|
||||
disabled: sceneToken === "",
|
||||
onClick: handleButtonLoadScene,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
showClose: true,
|
||||
},
|
||||
},
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Save Scene",
|
||||
}}
|
||||
title="Save Scene"
|
||||
confirmButtonProps={{
|
||||
label: "Save",
|
||||
disabled: sceneName === "",
|
||||
onClick: handleButtonSave,
|
||||
}}>
|
||||
<Input
|
||||
label="Please Enter a name for your scene"
|
||||
onChange={(e) => {
|
||||
setSceneName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</ButtonDialogue>
|
||||
{
|
||||
label: "Save scene",
|
||||
description: "Ctrl+S",
|
||||
dialogProps: {
|
||||
title: "Save Scene",
|
||||
content: (
|
||||
<Input
|
||||
label="Please enter a name for your scene"
|
||||
onChange={(e) => {
|
||||
setSceneName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
disabled: sceneName === "",
|
||||
onClick: handleButtonSave,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
showClose: true,
|
||||
},
|
||||
divider: true,
|
||||
},
|
||||
|
||||
{
|
||||
label: "Save scene as copy",
|
||||
description: "Ctrl+Shift+S",
|
||||
dialogProps: {
|
||||
title: "Save Scene as Copy",
|
||||
content: (
|
||||
<Input
|
||||
label="Please enter a name for your scene"
|
||||
onChange={(e) => {
|
||||
setSceneName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
disabled: sceneName === "",
|
||||
onClick: handleButtonSave,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
showClose: true,
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<ButtonDialogue
|
||||
buttonProps={{
|
||||
|
||||
Reference in new issue
Block a user