Compare commits

...
Author SHA1 Message Date
bflatastic 6526cac55e dialog, model buttons, ratio button 2025-02-26 17:13:57 +07:00
17 changed files with 512 additions and 269 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
@layer base {
:root {
@apply bg-ui-background font-sans text-white;
@apply select-none bg-ui-background font-sans text-white;
}
.pegboard {
background-image: radial-gradient(#ffffff25 1px, transparent 0);
@@ -42,7 +42,7 @@
}
input {
@apply bg-ui-controls border-0 px-2 py-1 outline-none -outline-offset-2 focus:outline-primary-500 focus:ring-0;
@apply border-0 bg-ui-controls px-2 py-1 outline-none -outline-offset-2 focus:outline-primary-500 focus:ring-0;
}
textarea {
+13 -4
View File
@@ -9,6 +9,7 @@ import { ToolbarUserProfile } from "~/components/features";
import { KonvaRootComponent } from "~/KonvaRootComponent";
import { ToolbarTopLeft } from "~/components/features/ToolbarTopLeft";
import { ToolbarTopRight } from "~/components/features/ToolbarTopRight";
import { ToolbarTopCenter } from "~/components/features/ToolbarTopCenter";
export const Main = withProtectionRoute(() => {
// This is a hook that will log the number of times the component has rerendered
@@ -24,10 +25,18 @@ export const Main = withProtectionRoute(() => {
sceneToken={sceneToken}
className="col-span-12 col-start-1 row-span-12 row-start-1"
/>
<div className="absolute top-0 flex w-full justify-between px-2">
<ToolbarTopLeft />
<ToolbarTopRight />
{/* <ToolbarUserProfile /> */}
<div className="absolute top-0 w-full p-3.5">
<div className="relative flex w-full">
<div className="absolute left-0">
<ToolbarTopLeft />
</div>
<div className="flex w-full justify-center">
<ToolbarTopCenter />
</div>
<div className="absolute right-0">
<ToolbarTopRight />
</div>
</div>
</div>
</div>
);
@@ -1,17 +1,12 @@
import { useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
import { DialogTitle } from "@headlessui/react";
import { FileUploader } from "../FileUploader";
import { Button } from "~/components/ui";
import { IMAGE_FILE_TYPE } from "~/constants/fileTypeEnums";
import {
paperWrapperStyles,
dialogBackgroundStyles,
dialogPanelStyles,
} from "~/components/styles";
import { BaseDialog } from "~/components/ui/BaseDialog";
export const DialogAddImage = ({
stagedImage = null,
@@ -43,48 +38,38 @@ export const DialogAddImage = ({
}
return (
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
<div className={dialogBackgroundStyles}>
<DialogPanel
className={twMerge(
paperWrapperStyles,
dialogPanelStyles,
"w-full max-w-2xl",
)}
>
<DialogTitle className="text-2xl font-bold">Upload Image</DialogTitle>
<div className="flex flex-col rounded-lg border-2 border-dashed border-ui-border">
<FileUploader
title=""
fileTypes={Object.values(IMAGE_FILE_TYPE)}
file={currFile}
setFile={(file: File | null) => {
setAssetFile(file);
if (file) {
onAddImage(file);
handleClose();
}
}}
<BaseDialog isOpen={isOpen} onClose={closeCallback}>
<DialogTitle className="text-3xl font-bold">Upload Image</DialogTitle>
<div className="flex flex-col rounded-lg border-2 border-dashed border-ui-border">
<FileUploader
title=""
fileTypes={Object.values(IMAGE_FILE_TYPE)}
file={currFile}
setFile={(file: File | null) => {
setAssetFile(file);
if (file) {
onAddImage(file);
handleClose();
}
}}
/>
{currFile && (
<div
className="relative flex items-center justify-center bg-ui-border"
style={{ height: "calc(100vh - 500px)" }}
>
<img
src={URL.createObjectURL(currFile)}
className="max-h-full object-contain"
/>
{currFile && (
<div
className="relative flex items-center justify-center bg-ui-border"
style={{ height: "calc(100vh - 500px)" }}
>
<img
src={URL.createObjectURL(currFile)}
className="max-h-full object-contain"
/>
</div>
)}
</div>
<div className="flex w-full justify-end gap-2">
<Button onClick={handleClose} variant="secondary">
Cancel
</Button>
</div>
</DialogPanel>
)}
</div>
</Dialog>
<div className="flex w-full justify-end gap-2">
<Button onClick={handleClose} variant="secondary">
Cancel
</Button>
</div>
</BaseDialog>
);
};
@@ -1,14 +1,9 @@
import { useCallback, useRef, useState, useEffect } from "react";
import { signal } from "@preact/signals-react";
import { twMerge } from "tailwind-merge";
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
import {
dialogBackgroundStyles,
paperWrapperStyles,
dialogPanelStyles,
} from "~/components/styles";
import { DialogTitle } from "@headlessui/react";
import { Button } from "~/components/ui";
import { BaseDialog } from "~/components/ui/BaseDialog";
import {
QuickTrimVideoUploader,
@@ -108,78 +103,72 @@ export const DialogAddVideo = ({
);
return (
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
<div className={dialogBackgroundStyles}>
<DialogPanel
className={twMerge("w-full", file ? "max-w-5xl" : "max-w-2xl")}
>
<div className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
<DialogTitle className="text-3xl font-bold">
Upload Video
</DialogTitle>
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
<>
<QuickTrimVideoUploader
file={currFile}
onFileStaged={(file) => {
setStates((curr) => ({ ...curr, file }));
}}
videoPropsSignal={videoProps}
trimDataSignal={trimData}
onTrimChange={(newTrimData: TrimData) => {
trimData.value = newTrimData;
}}
/>
<span className="grow" />
</>
)}
<LoadingScreens
currStatus={dialogStatus}
retryButton={
<ButtonSubmitAdd
file={file}
trimData={trimData}
onStatusChanged={changeDialogStatus}
onUploadedVideo={handleOnUploadedVideo}
retry
/>
}
/>
<BaseDialog
isOpen={isOpen}
onClose={closeCallback}
className={twMerge("w-full", file ? "max-w-5xl" : "max-w-2xl")}
>
<DialogTitle className="text-3xl font-bold">Upload Video</DialogTitle>
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
<>
<QuickTrimVideoUploader
file={currFile}
onFileStaged={(file) => {
setStates((curr) => ({ ...curr, file }));
}}
videoPropsSignal={videoProps}
trimDataSignal={trimData}
onTrimChange={(newTrimData: TrimData) => {
trimData.value = newTrimData;
}}
/>
<span className="grow" />
</>
)}
<LoadingScreens
currStatus={dialogStatus}
retryButton={
<ButtonSubmitAdd
file={file}
trimData={trimData}
onStatusChanged={changeDialogStatus}
onUploadedVideo={handleOnUploadedVideo}
retry
/>
}
/>
<div className="-mt-2 flex w-full justify-end gap-2">
<Button
onClick={handleClose}
variant="secondary"
disabled={
dialogStatus === DialogAddMediaStatuses.FILE_UPLOADING ||
dialogStatus === DialogAddMediaStatuses.FILE_RECORD_REQUESTING
}
>
Close
</Button>
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
<ButtonSubmitAdd
file={currFile}
trimData={trimData}
onStatusChanged={changeDialogStatus}
onUploadedVideo={handleOnUploadedVideo}
/>
)}
{(dialogStatus === DialogAddMediaStatuses.ERROR_FILE_UPLOAD ||
dialogStatus ===
DialogAddMediaStatuses.ERROR_FILE_RECORD_REQUEST) && (
<Button
onClick={() => {
setStates(initialState);
}}
>
Add Another Video
</Button>
)}
</div>
</div>
</DialogPanel>
<div className="-mt-2 flex w-full justify-end gap-2">
<Button
onClick={handleClose}
variant="secondary"
disabled={
dialogStatus === DialogAddMediaStatuses.FILE_UPLOADING ||
dialogStatus === DialogAddMediaStatuses.FILE_RECORD_REQUESTING
}
>
Close
</Button>
{dialogStatus === DialogAddMediaStatuses.STAGING_FILE && (
<ButtonSubmitAdd
file={currFile}
trimData={trimData}
onStatusChanged={changeDialogStatus}
onUploadedVideo={handleOnUploadedVideo}
/>
)}
{(dialogStatus === DialogAddMediaStatuses.ERROR_FILE_UPLOAD ||
dialogStatus ===
DialogAddMediaStatuses.ERROR_FILE_RECORD_REQUEST) && (
<Button
onClick={() => {
setStates(initialState);
}}
>
Add Another Video
</Button>
)}
</div>
</Dialog>
</BaseDialog>
);
};
@@ -1,14 +1,9 @@
import { useCallback, useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
import { DialogTitle } from "@headlessui/react";
import { faWandSparkles } from "@fortawesome/pro-solid-svg-icons";
import { Button } from "~/components/ui";
import {
dialogBackgroundStyles,
paperWrapperStyles,
dialogPanelStyles,
} from "~/components/styles";
import { BaseDialog } from "~/components/ui/BaseDialog";
import { ArtStyleNames, SubPanelNames } from "./enums";
import {
@@ -104,52 +99,46 @@ export const DialogAiStylize = ({
}, [isOpen]);
return (
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
<div className={dialogBackgroundStyles}>
<DialogPanel
className={twMerge(
paperWrapperStyles,
dialogPanelStyles,
"min-h-[calc(100vh-300px)] max-w-7xl",
)}
<BaseDialog
isOpen={isOpen}
onClose={closeCallback}
className="min-h-[calc(100vh-300px)] max-w-7xl"
>
<DialogTitle className="text-3xl font-bold">
Use AI to Stylize
</DialogTitle>
{panelState === SubPanelNames.BASIC && (
<SubPanelBasic
selectedArtStyle={selectedArtStyle}
positivePrompt={positivePrompt}
negativePrompt={negativePrompt}
onSelectedArtStyle={onSelectedArtStyle}
onChangeNegativePrompt={onChangeNegativePrompt}
onChangePositivePrompt={onChangePositivePrompt}
onChangePanel={onChangePanel}
/>
)}
{panelState === SubPanelNames.ADVANCED && (
<SubPanelAdvance
aiStylizeProps={aiStylizeProps}
onStylizeOptionsChanged={setStylizeOptions}
onChangePanel={onChangePanel}
/>
)}
<div className="flex w-full justify-end gap-2">
<Button onClick={closeCallback} variant="secondary">
Cancel
</Button>
<Button
className="hover:animate-pulse"
icon={faWandSparkles}
onClick={handleGenerate}
>
<DialogTitle className="text-3xl font-bold">
Use AI to Stylize
</DialogTitle>
{panelState === SubPanelNames.BASIC && (
<SubPanelBasic
selectedArtStyle={selectedArtStyle}
positivePrompt={positivePrompt}
negativePrompt={negativePrompt}
onSelectedArtStyle={onSelectedArtStyle}
onChangeNegativePrompt={onChangeNegativePrompt}
onChangePositivePrompt={onChangePositivePrompt}
onChangePanel={onChangePanel}
/>
)}
{panelState === SubPanelNames.ADVANCED && (
<SubPanelAdvance
aiStylizeProps={aiStylizeProps}
onStylizeOptionsChanged={setStylizeOptions}
onChangePanel={onChangePanel}
/>
)}
<div className="flex w-full justify-end gap-2">
<Button onClick={closeCallback} variant="secondary">
Cancel
</Button>
<Button
className="hover:animate-pulse"
icon={faWandSparkles}
onClick={handleGenerate}
>
Generate
</Button>
</div>
</DialogPanel>
Generate
</Button>
</div>
</Dialog>
</BaseDialog>
);
};
@@ -1,7 +1,6 @@
import { Dialog } from "@headlessui/react";
import { DialogPanelChromakey } from "./DialogPanelChromakey";
import { dialogBackgroundStyles } from "~/components/styles";
import { DialogChromakeyProps } from "./type";
export const DialogChromakey = ({
@@ -16,7 +15,7 @@ export const DialogChromakey = ({
className="relative z-50"
unmount={true}
>
<div className={dialogBackgroundStyles}>
<div>
<DialogPanelChromakey {...rest} onClose={onClose} />
</div>
</Dialog>
@@ -1,14 +1,9 @@
import { useState } from "react";
import { twMerge } from "tailwind-merge";
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
import { DialogTitle } from "@headlessui/react";
import { Button, TextEditor } from "~/components/ui";
import { TextNodeData } from "~/KonvaApp/types";
import {
dialogBackgroundStyles,
dialogPanelStyles,
paperWrapperStyles,
} from "~/components/styles";
import { BaseDialog } from "~/components/ui/BaseDialog";
import {
TextFormatData,
@@ -70,31 +65,26 @@ export const DialogEditText = ({
closeCallback();
};
return (
<Dialog open={isOpen} onClose={closeCallback} className="relative z-50">
<div className={dialogBackgroundStyles}>
<DialogPanel className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
<DialogTitle className="text-2xl font-bold">Edit Text</DialogTitle>
<TextEditor
text={text}
formatData={textFormatData}
onChangeText={handleOnChangeText}
onChangeFormatting={handleOnChangeFormatting}
/>
<div className="flex w-full justify-end gap-2">
<Button onClick={closeCallback} variant="secondary">
Cancel
</Button>
<Button
className="hover:animate-pulse"
onClick={handleOnDoneEditText}
disabled={text === ""}
>
Enter
</Button>
</div>
</DialogPanel>
<BaseDialog isOpen={isOpen} onClose={closeCallback} className="w-fit">
<DialogTitle className="text-2xl font-bold">Edit Text</DialogTitle>
<TextEditor
text={text}
formatData={textFormatData}
onChangeText={handleOnChangeText}
onChangeFormatting={handleOnChangeFormatting}
/>
<div className="flex w-full justify-end gap-2">
<Button onClick={closeCallback} variant="secondary">
Cancel
</Button>
<Button
className="hover:animate-pulse"
onClick={handleOnDoneEditText}
disabled={text === ""}
>
Enter
</Button>
</div>
</Dialog>
</BaseDialog>
);
};
@@ -1,10 +1,6 @@
import { twMerge } from "tailwind-merge";
import {
dialogBackgroundStyles,
dialogPanelStyles,
paperWrapperStyles,
} from "~/components/styles";
import { dialogPanelStyles, paperWrapperStyles } from "~/components/styles";
import {
Description,
Dialog,
@@ -26,7 +22,7 @@ export const DialogError = ({
}) => {
return (
<Dialog open={isShowing} onClose={onClose} className="relative z-50">
<div className={dialogBackgroundStyles}>
<div>
<DialogPanel className={twMerge(paperWrapperStyles, dialogPanelStyles)}>
<DialogTitle className="font-bold">{title}</DialogTitle>
<Description>{message}</Description>
@@ -19,6 +19,7 @@ import { Fragment, MouseEventHandler } from "react";
import { twMerge } from "tailwind-merge";
import { ToolbarButton } from "../ToolbarButton";
import { Tooltip } from "~/components/ui/Tooltip";
// style and constants
import { paperWrapperStyles, toolTipStyles } from "~/components/styles";
@@ -52,7 +53,7 @@ export const ToolbarMain = ({
paperWrapperStyles,
"glass",
disabled &&
"pointer-events-none cursor-default bg-ui-border shadow-md",
"pointer-events-none cursor-default bg-ui-border shadow-md",
)}
>
<div className="flex flex-col items-center gap-2">
@@ -160,7 +161,7 @@ export const ToolbarMain = ({
<div className="flex flex-col items-center gap-2">
<div className="flex flex-col items-center gap-2">
{ /* Conditionally show paint menu on mode selection */}
{/* Conditionally show paint menu on mode selection */}
{buttonProps.PAINT.active ? (
<div className="relative">
<PaintModeMenu
@@ -178,7 +179,12 @@ export const ToolbarMain = ({
/>
<div
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
style={{ backgroundColor: paintColor.value }}
style={{
background: `
linear-gradient(${paintColor.value}, ${paintColor.value}),
conic-gradient(rgb(204, 204, 204) 25%, rgb(255, 255, 255) 0deg, rgb(255, 255, 255) 50%, rgb(204, 204, 204) 0deg, rgb(204, 204, 204) 75%, rgb(255, 255, 255) 0deg) 0% 0% / 8px 8px
`,
}}
/>
</div>
) : (
@@ -189,7 +195,7 @@ export const ToolbarMain = ({
/>
)}
{ /* Conditionally show eraser menu on mode selection */}
{/* Conditionally show eraser menu on mode selection */}
{buttonProps.ERASER.active ? (
<EraseModeMenu
faIcon={faEraser}
@@ -199,14 +205,13 @@ export const ToolbarMain = ({
anchorGap={12}
closeOnMouseLeave={true}
/>
)
: (
<ToolbarButton
icon={faEraser}
buttonProps={buttonProps.ERASER}
tooltip="Eraser"
/>
)}
) : (
<ToolbarButton
icon={faEraser}
buttonProps={buttonProps.ERASER}
tooltip="Eraser"
/>
)}
</div>
</div>
@@ -232,26 +237,29 @@ export const ToolbarMain = ({
<hr className="w-full border-t border-white/15" />
<div className="flex flex-col items-center gap-2">
<div className="relative">
<BackgroundMenu
color={bgColor.value}
onChange={dispatchUiEvents.toolbarMain.setBgColor}
faIcon={faImage}
borderStyle="bg-transparent hover:bg-white/15 text-white"
showBar={false}
staticIconColor="white"
streamChanges
defaultOpen={false}
anchor="right"
anchorGap={12}
closeOnMouseLeave={true}
/>
<div
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
style={{ backgroundColor: bgColor.value }}
/>
<Tooltip tip="Background Color" position="right">
<div>
<BackgroundMenu
color={bgColor.value}
onChange={dispatchUiEvents.toolbarMain.setBgColor}
faIcon={faImage}
borderStyle="bg-transparent hover:bg-white/15 text-white"
showBar={false}
staticIconColor="white"
streamChanges
defaultOpen={false}
anchor="right"
anchorGap={12}
closeOnMouseLeave={true}
/>
<div
className="pointer-events-none absolute -bottom-1.5 -right-1.5 h-[18px] w-[18px] rounded-full border border-gray-400"
style={{ backgroundColor: bgColor.value }}
/>
</div>
</Tooltip>
</div>
</div>
</div>
@@ -0,0 +1,89 @@
import { twMerge } from "tailwind-merge";
import { Button } from "~/components/ui/Button";
import { useState } from "react";
import { BaseDialog } from "~/components/ui/BaseDialog";
import { DialogTitle } from "@headlessui/react";
interface ModelButtonProps {
iconSrc: string;
iconAlt: string;
label?: string;
subtitle?: string;
children?: React.ReactNode;
}
const ModelButton = ({
iconSrc,
iconAlt,
label,
subtitle,
children,
}: ModelButtonProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button
variant="secondary"
className="glass flex w-40 items-center justify-start gap-2 bg-ui-controls/70 p-1"
onClick={() => setIsOpen(true)}
>
<img
src={iconSrc}
alt={iconAlt}
className="h-8 w-8 rounded-md bg-white/10"
/>
{subtitle ? (
<div className="flex flex-col gap-0 truncate text-start">
<span className="truncate text-[11px] opacity-60">{subtitle}</span>
<span className="truncate text-[13px] opacity-80">{label}</span>
</div>
) : (
<span className="truncate">{label}</span>
)}
</Button>
<BaseDialog isOpen={isOpen} onClose={() => setIsOpen(false)}>
{children}
</BaseDialog>
</>
);
};
export const ToolbarTopCenter = () => {
return (
<>
{import.meta.env.DEV && ( // only show in dev mode for now
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-2")}>
<ModelButton
iconSrc="/resources/icons/model.png"
iconAlt="Model"
label="Realistic"
subtitle="Model"
>
<div className="flex flex-col gap-2 text-sm">
<DialogTitle className="text-2xl font-bold">
Select Model
</DialogTitle>
<p>Model configuration options will go here</p>
</div>
</ModelButton>
<ModelButton
iconSrc="/resources/icons/lora.png"
iconAlt="LoRA"
label="Detailed Tweaker"
subtitle="LoRA"
>
<div className="flex flex-col gap-2 text-sm">
<DialogTitle className="text-2xl font-bold">
Select LoRA
</DialogTitle>
<p>LoRA configuration options will go here</p>
</div>
</ModelButton>
</div>
)}
</>
);
};
@@ -6,11 +6,7 @@ import { twMerge } from "tailwind-merge";
export const ToolbarTopLeft = () => {
return (
<div
className={twMerge(
"z-20 mt-2 flex h-fit w-fit items-center gap-1 py-2.5 pl-4",
)}
>
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-1")}>
<img
src="/brand/mira-logo.png"
alt="logo"
@@ -1,9 +1,16 @@
import { faSave } from "@fortawesome/pro-solid-svg-icons";
import {
faRectangle,
faRectanglePortrait,
faSave,
faSquare,
} from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
import { Button } from "~/components/ui/Button";
import { toolbarMain } from "~/signals/uiAccess/toolbarMain";
import { dispatchers } from "~/signals/uiEvents/toolbarMain";
import { ToolbarMainButtonNames } from "../ToolbarMain/enum";
import { ToggleButton } from "~/components/ui/ToggleButton";
import { useState } from "react";
// import { paperWrapperStyles } from "~/components/styles";
// import { faPlus, faQuestion } from "@fortawesome/pro-solid-svg-icons";
@@ -12,13 +19,23 @@ import { ToolbarMainButtonNames } from "../ToolbarMain/enum";
export const ToolbarTopRight = () => {
const saveButtonState =
toolbarMain.signal.value.buttonStates[ToolbarMainButtonNames.SAVE];
const [aspectRatio, setAspectRatio] = useState("1:1");
return (
<div
className={twMerge(
"z-20 mt-2 flex h-fit w-fit items-center gap-1 p-4 py-2.5",
<div className={twMerge("z-20 flex h-fit w-fit items-center gap-2")}>
{import.meta.env.DEV && ( // only show in dev mode for now
<ToggleButton
label="Ratio"
value={aspectRatio}
onChange={setAspectRatio}
options={["1:1", "3:2", "2:3"]}
icons={{
"1:1": faSquare,
"3:2": faRectangle,
"2:3": faRectanglePortrait,
}}
/>
)}
>
<Button
icon={faSave}
onClick={dispatchers[ToolbarMainButtonNames.SAVE]}
@@ -5,9 +5,6 @@ export const paperWrapperStyles =
export const transitionTimingStyles = "transition duration-150 ease-in-out";
export const dialogBackgroundStyles =
"fixed inset-0 flex w-screen items-center justify-center bg-black/50";
export const toolTipStyles = {
base: twMerge(
"relative",
@@ -0,0 +1,65 @@
import { Fragment } from "react";
import {
Dialog,
DialogPanel,
Transition,
TransitionChild,
} from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { dialogPanelStyles, paperWrapperStyles } from "~/components/styles";
interface BaseDialogProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
className?: string;
}
export const BaseDialog = ({
isOpen,
onClose,
children,
className = "max-w-2xl",
}: BaseDialogProps) => {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onClose}>
<TransitionChild
as={Fragment}
enter="ease-out duration-150"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
</TransitionChild>
<div className="fixed inset-0 flex items-center justify-center p-4">
<TransitionChild
as={Fragment}
enter="ease-out duration-150"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<DialogPanel
className={twMerge(
paperWrapperStyles,
dialogPanelStyles,
"w-full",
"max-w-2xl",
className,
)}
>
{children}
</DialogPanel>
</TransitionChild>
</div>
</Dialog>
</Transition>
);
};
@@ -0,0 +1,68 @@
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { Button } from "~/components/ui/Button";
import { useState, useRef } from "react";
export interface LabeledSelectProps {
label: string;
value: string;
onChange: (value: string) => void;
options?: string[];
}
// TODO: finish up this hover drop down component (WIP) - BFlat
export const LabeledSelect = ({
label,
value,
onChange,
options = ["1", "2", "3", "4"],
}: LabeledSelectProps) => {
const [isOpen, setIsOpen] = useState(false);
const timeoutRef = useRef<number>();
const handleMouseEnter = () => {
clearTimeout(timeoutRef.current);
setIsOpen(true);
};
const handleMouseLeave = () => {
timeoutRef.current = window.setTimeout(() => {
setIsOpen(false);
}, 100);
};
return (
<Popover className="relative">
<div onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
<PopoverButton as={Button} variant="secondary" className="px-3 py-1.5">
{label} — {value}
</PopoverButton>
{isOpen && (
<PopoverPanel
static
className="absolute z-10 mt-1 w-fit origin-top-left rounded-md border bg-white p-1 shadow-lg transition-all duration-200 ease-out"
style={{
opacity: isOpen ? 1 : 0,
transform: `scale(${isOpen ? 1 : 0.95}) translateY(${isOpen ? 0 : -4}px)`,
}}
>
<div className="flex flex-col gap-1">
{options.map((option) => (
<Button
key={option}
className="justify-start px-3 py-1.5"
onClick={() => {
onChange(option);
setIsOpen(false);
}}
>
{option}
</Button>
))}
</div>
</PopoverPanel>
)}
</div>
</Popover>
);
};
@@ -0,0 +1,41 @@
import { faArrowsRotate } from "@fortawesome/pro-solid-svg-icons";
import { Button } from "~/components/ui/Button";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export interface ToggleButtonProps {
label?: string;
value: string;
onChange: (value: string) => void;
options?: string[];
icons?: { [key: string]: IconDefinition };
}
export const ToggleButton = ({
label,
value,
onChange,
options = ["1", "2", "3"],
icons,
}: ToggleButtonProps) => {
const handleClick = () => {
const currentIndex = options.indexOf(value);
const nextIndex = (currentIndex + 1) % options.length;
onChange(options[nextIndex]);
};
return (
<Button
icon={icons?.[value] ?? faArrowsRotate}
variant="secondary"
className="glass px-3 py-1.5"
onClick={handleClick}
>
{label && (
<>
{label} <span className="opacity-60">—</span>
</>
)}{" "}
{value}
</Button>
);
};
@@ -11,7 +11,12 @@ import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { paperWrapperStyles } from "~/components/styles";
import { BRUSH_MAX_SIZE, BRUSH_MIN_SIZE, paintBrushSize, setPaintBrushSize } from "~/signals/uiEvents/toolbarMain/paintMode";
import {
BRUSH_MAX_SIZE,
BRUSH_MIN_SIZE,
paintBrushSize,
setPaintBrushSize,
} from "~/signals/uiEvents/toolbarMain/paintMode";
import { Button } from "../Button";
import { Input } from "../Input";
import { Slider } from "../Slider";
@@ -92,7 +97,7 @@ export const BackgroundMenu = ({
<>
<PopoverButton
className={twMerge(
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2",
"flex size-10 items-center gap-1 rounded-lg bg-ui-controls p-2 transition-all duration-100",
borderStyle,
showBar ? "flex-col" : "justify-center",
)}