mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
Compare commits
25
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28b6034cfb | ||
|
|
e8d3b0bef9 | ||
|
|
c7b1a361b1 | ||
|
|
074e340cb5 | ||
|
|
f0dbeca36a | ||
|
|
a43b34c1d9 | ||
|
|
b8b90476a1 | ||
|
|
04420f1dd3 | ||
|
|
7577bd977a | ||
|
|
1708c6094f | ||
|
|
194229516e | ||
|
|
8e8da70c8d | ||
|
|
6c76e3e248 | ||
|
|
49474c3c45 | ||
|
|
f5a09d69b6 | ||
|
|
281d7b2da2 | ||
|
|
723f2cba87 | ||
|
|
26986d6757 | ||
|
|
1b19909159 | ||
|
|
c58c465128 | ||
|
|
4efa7d5a95 | ||
|
|
f93c62e8e1 | ||
|
|
caf1c05b9a | ||
|
|
df27435564 | ||
|
|
654456ac40 |
@@ -1 +0,0 @@
|
||||
export { Badge } from "./Badge";
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Canvas, Meta } from "@storybook/blocks";
|
||||
|
||||
import * as ButtonDropdownStories from "./ButtonDropdown.stories";
|
||||
|
||||
<Meta of={ButtonDropdownStories} />
|
||||
|
||||
# Button for dropdown actions
|
||||
|
||||
Dropdown options that open up a dialog
|
||||
|
||||
<Canvas of={ButtonDropdownStories.OpenDialog} />
|
||||
|
||||
Dropdown options with onClick event
|
||||
|
||||
<Canvas of={ButtonDropdownStories.ClickEvent} />
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ButtonDropdown } from "./ButtonDropdown";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { faAngleLeft, faFile } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
const meta: Meta<typeof ButtonDropdown> = {
|
||||
component: ButtonDropdown,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof ButtonDropdown>;
|
||||
|
||||
export const OpenDialog: Story = {
|
||||
render: () => (
|
||||
<ButtonDropdown
|
||||
label="Dialog Options"
|
||||
icon={faFile}
|
||||
options={[
|
||||
{
|
||||
label: "Open Dialog",
|
||||
description: "Desc 1",
|
||||
onDialogOpen: () => {
|
||||
//console.log("Opened Dialog");
|
||||
},
|
||||
dialogProps: {
|
||||
title: "Dialog Title",
|
||||
content: <div className="text-white">Dialog content here...</div>,
|
||||
confirmButtonProps: {
|
||||
label: "Create",
|
||||
disabled: false,
|
||||
onClick: () => {
|
||||
//console.log("Action on Dialog");
|
||||
},
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
showClose: true,
|
||||
onClose: () => {
|
||||
//console.log("Closed Dialog");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Disabled",
|
||||
description: "Desc 2",
|
||||
disabled: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
export const ClickEvent: Story = {
|
||||
render: () => (
|
||||
<ButtonDropdown
|
||||
label="Event Options"
|
||||
icon={faFile}
|
||||
options={[
|
||||
{
|
||||
label: "Click Event",
|
||||
description: "Desc",
|
||||
onClick: () => {
|
||||
//console.log("Clicked");
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ButtonDropdown";
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as ButtonIconStories from './ButtonIcon.stories';
|
||||
|
||||
<Meta of={ButtonIconStories} />
|
||||
|
||||
# ButtonIcon
|
||||
|
||||
A button denotes a possible action to be taken on behalf of the user.
|
||||
|
||||
This button shows only a single icon.
|
||||
|
||||
<Canvas of={ButtonIconStories.Default} />
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ButtonIcon } from "./ButtonIcon";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { faAngleLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
const meta: Meta<typeof ButtonIcon> = {
|
||||
component: ButtonIcon,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof ButtonIcon>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <ButtonIcon icon={faAngleLeft} onClick={() => {}} />,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ButtonIcon";
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as ButtonIconSelectStories from './ButtonIconSelect.stories';
|
||||
|
||||
<Meta of={ButtonIconSelectStories} />
|
||||
|
||||
# ButtonIconSelectStories
|
||||
|
||||
A button denotes a possible action to be taken on behalf of the user.
|
||||
|
||||
This button shows only a single icon.
|
||||
|
||||
<Canvas of={ButtonIconSelectStories.Default} />
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ButtonIconSelect } from "./ButtonIconSelect";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { faAngleLeft, faAngleRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
const meta: Meta<typeof ButtonIconSelect> = {
|
||||
component: ButtonIconSelect,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof ButtonIconSelect>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<ButtonIconSelect
|
||||
onOptionChange={() => {}}
|
||||
options={[
|
||||
{
|
||||
value: "V1",
|
||||
icon: faAngleLeft,
|
||||
text: "Select 1",
|
||||
},
|
||||
{
|
||||
value: "V2",
|
||||
icon: faAngleRight,
|
||||
text: "Select 2",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ButtonIconSelect";
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as ButtonIconStackStories from './ButtonIconStack.stories';
|
||||
|
||||
<Meta of={ButtonIconStackStories} />
|
||||
|
||||
# ButtonIconStackStories
|
||||
|
||||
A button denotes a possible action to be taken on behalf of the user.
|
||||
|
||||
This button shows only a single icon.
|
||||
|
||||
<Canvas of={ButtonIconStackStories.Default} />
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { faImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ButtonIconStack } from "./ButtonIconStack";
|
||||
|
||||
const meta: Meta<typeof ButtonIconStack> = {
|
||||
component: ButtonIconStack,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof ButtonIconStack>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<ButtonIconStack
|
||||
onClick={() => { }}
|
||||
icon={faImage}
|
||||
text={"Start Frame"}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ButtonIconStackProps {
|
||||
icon: IconDefinition;
|
||||
text: string;
|
||||
onClick?: () => void;
|
||||
style?: string;
|
||||
additionalStyle?: string;
|
||||
}
|
||||
|
||||
export function ButtonIconStack({
|
||||
icon,
|
||||
text = "",
|
||||
onClick,
|
||||
style,
|
||||
additionalStyle,
|
||||
}: ButtonIconStackProps) {
|
||||
|
||||
const defaultStyle = twMerge(
|
||||
"flex flex-col items-center justify-center text-xs transition-all duration-150",
|
||||
"h-full w-full gap-1 p-2",
|
||||
additionalStyle
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<button
|
||||
className={style ?? defaultStyle}
|
||||
onClick={onClick}
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} size="xl" />
|
||||
{text && (
|
||||
<span className="text-nowrap text-xs font-medium">{text}</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ButtonIconStack";
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as ButtonLinkStories from './ButtonLink.stories';
|
||||
|
||||
<Meta of={ButtonLinkStories} />
|
||||
|
||||
# ButtonLink
|
||||
|
||||
A button denotes a possible action to be taken on behalf of the user.
|
||||
|
||||
The button label comes via "children".
|
||||
|
||||
There are three types of Button: primary, secondary, action.
|
||||
|
||||
<Canvas of={ButtonLinkStories.Primary} />
|
||||
<Canvas of={ButtonLinkStories.Secondary} />
|
||||
|
||||
It can be shown with an optional icon.
|
||||
|
||||
<Canvas of={ButtonLinkStories.WithIcon} />
|
||||
@@ -1,42 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ButtonLink } from "./ButtonLink";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { faAngleLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
const meta: Meta<typeof ButtonLink> = {
|
||||
component: ButtonLink,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof ButtonLink>;
|
||||
|
||||
export const Primary: Story = {
|
||||
render: () => (
|
||||
<ButtonLink to="google.com" variant="primary">
|
||||
Primary
|
||||
</ButtonLink>
|
||||
),
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
render: () => (
|
||||
<ButtonLink to="google.com" variant="secondary">
|
||||
Secondary
|
||||
</ButtonLink>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
render: () => (
|
||||
<ButtonLink to="google.com" variant="secondary" icon={faAngleLeft}>
|
||||
Icon
|
||||
</ButtonLink>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./ButtonLink";
|
||||
@@ -1,34 +0,0 @@
|
||||
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface CloseButtonProps {
|
||||
onClick: () => void;
|
||||
className?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
}
|
||||
|
||||
export const CloseButton = ({
|
||||
onClick,
|
||||
className = "",
|
||||
size = "md",
|
||||
}: CloseButtonProps) => {
|
||||
const sizeClasses = {
|
||||
sm: "h-5 w-5 text-sm",
|
||||
md: "h-7 w-7 text-md",
|
||||
lg: "h-9 w-9 text-xl",
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={twMerge(
|
||||
"flex items-center justify-center rounded-full bg-black/40 text-white/60 transition-all hover:bg-black/70 hover:text-white",
|
||||
sizeClasses[size],
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -108,4 +108,4 @@ export const ConfirmationModal = ({
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./FileUploader";
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as FilterButtonsStories from './FilterButtons.stories';
|
||||
|
||||
<Meta of={FilterButtonsStories} />
|
||||
|
||||
# Three buttons which appear at the top of some of the media tabs.
|
||||
|
||||
The value can be set to Featured
|
||||
<Canvas of={FilterButtonsStories.Featured} />
|
||||
Mine
|
||||
<Canvas of={FilterButtonsStories.Mine} />
|
||||
or Bookmarked
|
||||
<Canvas of={FilterButtonsStories.Bookmarked} />
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { FilterButtons } from "./FilterButtons";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { AssetFilterOption } from "~/enums";
|
||||
|
||||
const meta: Meta<typeof FilterButtons> = {
|
||||
component: FilterButtons,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["click"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof FilterButtons>;
|
||||
|
||||
export const Featured: Story = {
|
||||
render: () => (
|
||||
<FilterButtons onClick={() => null} value={AssetFilterOption.FEATURED} />
|
||||
),
|
||||
};
|
||||
|
||||
export const Mine: Story = {
|
||||
render: () => (
|
||||
<FilterButtons onClick={() => null} value={AssetFilterOption.MINE} />
|
||||
),
|
||||
};
|
||||
|
||||
export const Bookmarked: Story = {
|
||||
render: () => (
|
||||
<FilterButtons onClick={() => null} value={AssetFilterOption.BOOKMARKED} />
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./FilterButtons";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Gravatar";
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as InputStories from './Input.stories';
|
||||
|
||||
<Meta of={InputStories} />
|
||||
|
||||
# Input control
|
||||
|
||||
Here is the input with placeholder
|
||||
<Canvas of={InputStories.Default} />
|
||||
|
||||
Here is the input with icon
|
||||
<Canvas of={InputStories.WithIcon} />
|
||||
|
||||
Here is the input with label
|
||||
<Canvas of={InputStories.WithLabel} />
|
||||
|
||||
Here is the input with error message
|
||||
<Canvas of={InputStories.WithError} />
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Input } from "./Input";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
import { faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
const meta: Meta<typeof Input> = {
|
||||
component: Input,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["change", "blur", "focus"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof Input>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Input placeholder="field placeholder" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Input icon={faUser} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Input label="Input label" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Input errorMessage="Error message" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Label, H6 } from "~/components";
|
||||
import { kebabCase } from "~/utilities";
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
enableHotkeyInput,
|
||||
DomLevels,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
inputClassName?: string;
|
||||
iconClassName?: string;
|
||||
label?: string;
|
||||
icon?: IconDefinition;
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const Input = React.forwardRef(
|
||||
(
|
||||
{
|
||||
label,
|
||||
icon,
|
||||
inputClassName,
|
||||
iconClassName,
|
||||
className,
|
||||
id,
|
||||
isError,
|
||||
onBlur,
|
||||
onFocus,
|
||||
errorMessage,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref: React.ForwardedRef<HTMLInputElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
|
||||
|
||||
<div className="relative w-full">
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={twMerge("text-md absolute pl-3 pt-3", iconClassName)}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id ? id : label ? kebabCase(label) : undefined}
|
||||
className={twMerge(
|
||||
"h-10 w-full rounded-lg bg-[#242424] px-3 py-2.5 text-white placeholder-white/50 outline-none",
|
||||
"hover:border-primary/60 focus:border-primary border border-[#363636] transition-all duration-150 ease-in-out focus:!outline-none",
|
||||
icon && "pl-10",
|
||||
isError && "outline-red focus:outline-red",
|
||||
inputClassName,
|
||||
)}
|
||||
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onFocus) {
|
||||
onFocus(e);
|
||||
}
|
||||
disableHotkeyInput(DomLevels.INPUT);
|
||||
}}
|
||||
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onBlur) {
|
||||
onBlur(e);
|
||||
}
|
||||
enableHotkeyInput(DomLevels.INPUT);
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<H6 className="absolute z-10 text-red">{errorMessage}</H6>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Input.displayName = "Input";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Input";
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as InputVectorStories from './InputVector.stories';
|
||||
|
||||
<Meta of={InputVectorStories} />
|
||||
|
||||
# 3 Input controls: one for x, y, and z
|
||||
It allows entry os plus or minus numbers with decimals. It can also be adjusted via dragging the mouse left or right.
|
||||
|
||||
<Canvas of={InputVectorStories.Default} />
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { InputVector } from "./InputVector";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
|
||||
const meta: Meta<typeof InputVector> = {
|
||||
component: InputVector,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["change", "blur", "focus"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof InputVector>;
|
||||
|
||||
const InputVectorRender = () => {
|
||||
// Sets the hooks for both the label and primary props
|
||||
const [xyz, setXyz] = useState<Record<string, string>>({
|
||||
x: "0",
|
||||
y: "0",
|
||||
z: "0",
|
||||
});
|
||||
|
||||
return (
|
||||
<InputVector
|
||||
x={xyz.x}
|
||||
y={xyz.y}
|
||||
z={xyz.z}
|
||||
onChange={(value) => setXyz(value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<InputVectorRender />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./InputVector";
|
||||
@@ -1,27 +0,0 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const LoadingSpinner = ({ className }: LoadingSpinnerProps) => {
|
||||
return (
|
||||
<svg
|
||||
className={twMerge("h-6 w-6 animate-spin", className)}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24">
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"></circle>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./NumberInput";
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as PaginationStories from './Pagination.stories';
|
||||
|
||||
<Meta of={PaginationStories} />
|
||||
|
||||
# Controls for pagination.
|
||||
|
||||
Here is what it looks like when empty
|
||||
<Canvas of={PaginationStories.Empty} />
|
||||
First Page
|
||||
<Canvas of={PaginationStories.FirstPage} />
|
||||
Middle
|
||||
<Canvas of={PaginationStories.Middle} />
|
||||
Last Page
|
||||
<Canvas of={PaginationStories.LastPage} />
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Pagination } from "./Pagination";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
|
||||
const meta: Meta<typeof Pagination> = {
|
||||
component: Pagination,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["pageChange"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof Pagination>;
|
||||
|
||||
export const Empty: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] bg-brand-secondary-200">
|
||||
<Pagination
|
||||
className=""
|
||||
currentPage={0}
|
||||
totalPages={0}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const FirstPage: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] bg-brand-secondary-200">
|
||||
<Pagination
|
||||
className=""
|
||||
currentPage={0}
|
||||
totalPages={5}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const Middle: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] bg-brand-secondary-200">
|
||||
<Pagination
|
||||
className=""
|
||||
currentPage={2}
|
||||
totalPages={5}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const LastPage: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] bg-brand-secondary-200">
|
||||
<Pagination
|
||||
className=""
|
||||
currentPage={4}
|
||||
totalPages={5}
|
||||
onPageChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Pagination";
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as PillStories from './Pill.stories';
|
||||
|
||||
<Meta of={PillStories} />
|
||||
|
||||
# Pill
|
||||
|
||||
A pill is a container around the children.
|
||||
|
||||
<Canvas of={PillStories.Default} />
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Pill } from "./Pill";
|
||||
|
||||
const meta: Meta<typeof Pill> = {
|
||||
component: Pill,
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof Pill>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => <Pill>A Pill</Pill>,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1,21 +0,0 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const Pill = ({
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-fit rounded-md bg-brand-primary px-1.5 py-0.5 text-xs font-bold",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Pill";
|
||||
@@ -1,213 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
|
||||
export interface PopoverItem {
|
||||
label: string;
|
||||
selected: boolean;
|
||||
icon?: ReactNode;
|
||||
action?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface PopoverMenuProps {
|
||||
items?: PopoverItem[];
|
||||
onSelect?: (item: PopoverItem) => void;
|
||||
onAdd?: () => void;
|
||||
triggerIcon?: ReactNode;
|
||||
showAddButton?: boolean;
|
||||
disableAddButton?: boolean;
|
||||
showIconsInList?: boolean;
|
||||
mode?: "default" | "toggle" | "button";
|
||||
triggerLabel?: string;
|
||||
children?: ReactNode;
|
||||
buttonClassName?: string;
|
||||
panelClassName?: string;
|
||||
onPanelAction?: (action: string) => void;
|
||||
panelTitle?: string;
|
||||
position?: "top" | "bottom";
|
||||
align?: "start" | "center" | "end";
|
||||
panelActionLabel?: string;
|
||||
}
|
||||
|
||||
export const PopoverMenu = ({
|
||||
items = [],
|
||||
onSelect,
|
||||
onAdd,
|
||||
triggerIcon,
|
||||
showAddButton = false,
|
||||
disableAddButton = false,
|
||||
showIconsInList = false,
|
||||
mode = "default",
|
||||
triggerLabel,
|
||||
children,
|
||||
buttonClassName,
|
||||
panelClassName,
|
||||
onPanelAction,
|
||||
panelTitle,
|
||||
position = "top",
|
||||
align = "start",
|
||||
panelActionLabel,
|
||||
}: PopoverMenuProps) => {
|
||||
const selectedItem = items.find((item) => item.selected);
|
||||
|
||||
const handleItemClick = (item: PopoverItem, close: () => void) => {
|
||||
if (mode === "button" && item.action && onPanelAction) {
|
||||
onPanelAction(item.action);
|
||||
close();
|
||||
} else {
|
||||
onSelect?.(item);
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const className = twMerge(
|
||||
"text-sm font-medium rounded-lg px-3 py-2 shadow-sm",
|
||||
"flex gap-2 items-center justify-center outline-none",
|
||||
"transition-all duration-150",
|
||||
"bg-[#5F5F68]/60 px-3 text-white hover:bg-[#5F5F68]/90",
|
||||
buttonClassName,
|
||||
);
|
||||
|
||||
const positionClasses = {
|
||||
top: "bottom-full",
|
||||
bottom: "top-full",
|
||||
};
|
||||
|
||||
const alignClasses = {
|
||||
start: "left-0",
|
||||
center: "left-1/2 -translate-x-1/2",
|
||||
end: "right-0",
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className={className}>
|
||||
<>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? (
|
||||
<span className="truncate">{selectedItem.label}</span>
|
||||
) : null}
|
||||
{mode === "default" && triggerLabel ? (
|
||||
<span className="truncate">{triggerLabel}</span>
|
||||
) : null}
|
||||
</>
|
||||
</Popover.Button>
|
||||
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute transform-gpu",
|
||||
positionClasses[position],
|
||||
alignClasses[align],
|
||||
)}
|
||||
>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom={
|
||||
position === "bottom"
|
||||
? "translate-y-2 opacity-0"
|
||||
: "-translate-y-2 opacity-0"
|
||||
}
|
||||
enterTo="translate-y-0 opacity-100"
|
||||
leave="transition duration-100 ease-in"
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo={
|
||||
position === "bottom"
|
||||
? "translate-y-2 opacity-0"
|
||||
: "-translate-y-2 opacity-0"
|
||||
}
|
||||
>
|
||||
<Popover.Panel
|
||||
className={twMerge(
|
||||
position === "bottom" ? "origin-top" : "origin-bottom",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"z-10 min-w-48 rounded-lg bg-[#46464B] p-1.5 shadow-lg",
|
||||
position === "top" && "mb-2",
|
||||
position === "bottom" && "mt-2",
|
||||
panelClassName,
|
||||
)}
|
||||
>
|
||||
{panelTitle && (
|
||||
<div className="mb-2 mt-0.5 flex justify-between px-1.5 text-sm font-normal text-white opacity-70">
|
||||
{panelTitle}
|
||||
{panelActionLabel && (
|
||||
<button
|
||||
onClick={() => {
|
||||
onPanelAction?.(panelActionLabel);
|
||||
close();
|
||||
}}
|
||||
className="text-end text-sm text-white/85 hover:underline"
|
||||
>
|
||||
{panelActionLabel}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{mode === "default" && children ? (
|
||||
<div className="text-sm text-white">{children}</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-0 text-sm text-white">
|
||||
{items.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
className={twMerge(
|
||||
"flex items-center justify-between border-transparent bg-transparent px-1.5",
|
||||
"hover:bg-[#63636B]/60",
|
||||
mode === "toggle" && item.selected
|
||||
? "hover:bg-[#63636B]"
|
||||
: "bg-transparent",
|
||||
item.disabled
|
||||
? "!cursor-not-allowed opacity-50"
|
||||
: "",
|
||||
)}
|
||||
onClick={() =>
|
||||
!item.disabled && handleItemClick(item, close)
|
||||
}
|
||||
variant="secondary"
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<div className="flex items-center gap-2 truncate">
|
||||
{showIconsInList && item.icon}
|
||||
<span className="truncate">{item.label}</span>
|
||||
</div>
|
||||
{mode === "toggle" && (
|
||||
<input
|
||||
type="radio"
|
||||
checked={item.selected}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
))}
|
||||
{showAddButton && onAdd && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={twMerge(
|
||||
"mb-0.5 mt-2 border-none py-1",
|
||||
disableAddButton
|
||||
? "cursor-not-allowed bg-[#7B7B84]/50 opacity-50"
|
||||
: "bg-[#7B7B84] hover:bg-[#8c8c96]",
|
||||
)}
|
||||
onClick={onAdd}
|
||||
disabled={disableAddButton}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Popover";
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./SearchFilter";
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { faCheck, faChevronDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type SelectValue = string | number;
|
||||
export type SelectOption = { label: string; value: SelectValue };
|
||||
|
||||
interface ListDropdownProps {
|
||||
options: SelectOption[];
|
||||
onChange: (val: SelectValue) => void;
|
||||
placeholder?: string;
|
||||
value?: SelectValue;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export const Select = ({
|
||||
onChange,
|
||||
options,
|
||||
placeholder,
|
||||
value,
|
||||
id,
|
||||
}: ListDropdownProps) => {
|
||||
const selectedOption = options.find(
|
||||
(option: SelectOption) => option.value === value,
|
||||
) || { label: placeholder || "", value: "" };
|
||||
|
||||
return (
|
||||
<div className="relative mt-1">
|
||||
<Listbox value={value || ""} onChange={onChange}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Button
|
||||
id={id}
|
||||
className="relative h-10 w-full cursor-pointer rounded-md bg-brand-secondary py-2 pl-3 pr-10 text-left outline-none outline-offset-0 transition-all duration-150 ease-in-out focus:outline-brand-primary sm:text-sm"
|
||||
>
|
||||
<span
|
||||
className={twMerge("block truncate", !value && "opacity-50")}
|
||||
>
|
||||
{selectedOption.label}
|
||||
</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2.5">
|
||||
<FontAwesomeIcon icon={faChevronDown} aria-hidden="true" />
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-brand-secondary py-1 text-base shadow-xl focus:outline-none sm:text-sm">
|
||||
{options.map((option, itemIdx) => (
|
||||
<Listbox.Option
|
||||
key={itemIdx}
|
||||
className={({ active, selected }) =>
|
||||
twMerge(
|
||||
"relative cursor-pointer select-none py-2.5 pl-8 pr-2 text-white transition-all duration-150 ease-in-out",
|
||||
active && "bg-brand-primary/20 text-white",
|
||||
selected && "bg-brand-primary/40",
|
||||
!selected && "text-white/90",
|
||||
)
|
||||
}
|
||||
value={option.value}
|
||||
>
|
||||
{({ selected }) => (
|
||||
<>
|
||||
<span
|
||||
className={`block truncate ${
|
||||
selected ? "font-medium" : "font-normal"
|
||||
}`}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
{selected ? (
|
||||
<span className="absolute inset-y-0 left-0 flex items-center pl-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
import { Transition } from "@headlessui/react";
|
||||
import React, { useState, useRef, useEffect, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface SliderProps {
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
step: number;
|
||||
className?: string;
|
||||
showTooltip?: boolean;
|
||||
tooltipContent?: ReactNode;
|
||||
suffix?: string;
|
||||
}
|
||||
|
||||
export const Slider: React.FC<SliderProps> = ({
|
||||
min,
|
||||
max,
|
||||
value,
|
||||
onChange,
|
||||
step,
|
||||
className,
|
||||
showTooltip,
|
||||
tooltipContent,
|
||||
suffix,
|
||||
}) => {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [showHandleTooltip, setShowHandleTooltip] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalValue(value);
|
||||
}, [value]);
|
||||
|
||||
const updatePosition = (x: number) => {
|
||||
if (!sliderRef.current) return;
|
||||
const rect = sliderRef.current.getBoundingClientRect();
|
||||
const fraction = (x - rect.left) / rect.width;
|
||||
const newValue = fraction * (max - min) + min;
|
||||
const roundedValue = Math.round(newValue / step) * step;
|
||||
const clampedValue = Math.min(Math.max(roundedValue, min), max);
|
||||
setLocalValue(clampedValue);
|
||||
onChange(clampedValue);
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
setShowHandleTooltip(true);
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
updatePosition(e.clientX);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
updatePosition(e.clientX);
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
setShowHandleTooltip(false);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!isDragging) {
|
||||
setShowHandleTooltip(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!isDragging) {
|
||||
setShowHandleTooltip(false);
|
||||
}
|
||||
};
|
||||
|
||||
const percentage = ((localValue - min) / (max - min)) * 100;
|
||||
const displayValue =
|
||||
localValue.toFixed(step.toString().split(".")[1]?.length || 0) +
|
||||
(suffix || "");
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className={twMerge(
|
||||
"relative h-2.5 w-full cursor-pointer rounded-full bg-brand-secondary-900",
|
||||
className,
|
||||
)}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div
|
||||
className="absolute h-2.5 rounded-full bg-brand-primary"
|
||||
style={{ width: `${percentage}%` }}
|
||||
></div>
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute -top-1 h-[18px] w-[18px] -translate-x-1/2 transform rounded-full bg-gray-200 transition-colors",
|
||||
isDragging ? "bg-white" : "bg-gray-300 hover:bg-white",
|
||||
)}
|
||||
style={{ left: `${percentage}%` }}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
/>
|
||||
<Transition
|
||||
show={showHandleTooltip}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
{showTooltip && showHandleTooltip && (
|
||||
<div
|
||||
className="absolute -top-8 z-10 rounded-md bg-ui-controls px-2 py-1 text-xs text-white shadow-lg"
|
||||
style={{ left: `${percentage}%`, transform: "translateX(-50%)" }}
|
||||
>
|
||||
{tooltipContent ? tooltipContent : displayValue}
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Slider";
|
||||
@@ -1,184 +0,0 @@
|
||||
import { Transition } from "@headlessui/react";
|
||||
import React, { useState, useRef, useEffect, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Button } from "../Button";
|
||||
import { faMinus, faPlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
interface SliderProps {
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
step: number;
|
||||
className?: string;
|
||||
showTooltip?: boolean;
|
||||
tooltipContent?: ReactNode;
|
||||
suffix?: string;
|
||||
innerLabel?: string;
|
||||
showDecrement?: boolean;
|
||||
showIncrement?: boolean;
|
||||
}
|
||||
|
||||
export const SliderV2 = ({
|
||||
min,
|
||||
max,
|
||||
value,
|
||||
onChange,
|
||||
step,
|
||||
className,
|
||||
showTooltip,
|
||||
tooltipContent,
|
||||
suffix,
|
||||
innerLabel,
|
||||
showDecrement,
|
||||
showIncrement,
|
||||
}: SliderProps) => {
|
||||
const [localValue, setLocalValue] = useState(value);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [showHandleTooltip, setShowHandleTooltip] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalValue(value);
|
||||
}, [value]);
|
||||
|
||||
const setClampedValue = (value: number) => {
|
||||
const roundedValue = Math.round(value / step) * step;
|
||||
const clampedValue = Math.min(Math.max(roundedValue, min), max);
|
||||
setLocalValue(clampedValue);
|
||||
onChange(clampedValue);
|
||||
};
|
||||
|
||||
const updatePosition = (x: number) => {
|
||||
if (!sliderRef.current) return;
|
||||
const rect = sliderRef.current.getBoundingClientRect();
|
||||
const fraction = (x - rect.left) / rect.width;
|
||||
const newValue = fraction * (max - min) + min;
|
||||
setClampedValue(newValue);
|
||||
};
|
||||
|
||||
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(true);
|
||||
setShowHandleTooltip(true);
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
document.addEventListener("mouseup", handleMouseUp);
|
||||
updatePosition(e.clientX);
|
||||
};
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
updatePosition(e.clientX);
|
||||
};
|
||||
|
||||
const handleMouseUp = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsDragging(false);
|
||||
setShowHandleTooltip(false);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
document.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (!isDragging) {
|
||||
setShowHandleTooltip(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (!isDragging) {
|
||||
setShowHandleTooltip(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDecrement = () => {
|
||||
setClampedValue(localValue - step);
|
||||
};
|
||||
|
||||
const handleIncrement = () => {
|
||||
setClampedValue(localValue + step);
|
||||
};
|
||||
|
||||
const percentage = ((localValue - min) / (max - min)) * 100;
|
||||
const displayValue =
|
||||
localValue.toFixed(step.toString().split(".")[1]?.length || 0) +
|
||||
(suffix || "");
|
||||
|
||||
return (
|
||||
<div className="flex w-full">
|
||||
{showDecrement && (
|
||||
<Button
|
||||
icon={faMinus}
|
||||
className="focus-visible:outline-primary my-auto mr-1 size-6 rounded-full bg-transparent text-white/80 hover:bg-white/10 active:bg-brand-primary/30"
|
||||
onClick={handleDecrement}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
ref={sliderRef}
|
||||
className={twMerge(
|
||||
"glass border-ui-border group relative h-7 w-full cursor-pointer overflow-hidden rounded-lg border",
|
||||
isDragging && "!bg-ui-controls/90",
|
||||
className,
|
||||
)}
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute h-full bg-brand-primary/30 transition-colors duration-300 group-hover:bg-brand-primary/50",
|
||||
isDragging && "!bg-brand-primary/50",
|
||||
)}
|
||||
style={{ width: `${percentage}%` }}
|
||||
>
|
||||
{innerLabel && (
|
||||
<span
|
||||
className={twMerge(
|
||||
"absolute top-1/2 ml-2.5 -translate-y-1/2 text-nowrap text-sm font-medium text-white/60 transition-colors duration-300 group-hover:text-white",
|
||||
isDragging && "!text-white",
|
||||
)}
|
||||
>
|
||||
{innerLabel}
|
||||
</span>
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"absolute right-0 top-1/2 mr-1 h-4 w-0.5 -translate-y-1/2 rounded-full",
|
||||
isDragging ? "bg-white" : "bg-white/50",
|
||||
)}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showIncrement && (
|
||||
<Button
|
||||
icon={faPlus}
|
||||
className="focus-visible:outline-primary my-auto ml-1 size-6 rounded-full bg-transparent text-white/80 hover:bg-white/10 active:bg-brand-primary/30"
|
||||
onClick={handleIncrement}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showTooltip && (
|
||||
<Transition
|
||||
as={"div"}
|
||||
show={showHandleTooltip}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
className="absolute -top-8 z-10 rounded-md bg-ui-panel px-2 py-1 text-xs text-black shadow-lg"
|
||||
style={{ left: `${percentage}%`, transform: "translateX(-50%)" }}
|
||||
>
|
||||
{tooltipContent ? tooltipContent : displayValue}
|
||||
</div>
|
||||
</Transition>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./SliderV2";
|
||||
@@ -1,109 +0,0 @@
|
||||
import React, { useRef, useEffect, useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Tooltip } from "~/components";
|
||||
|
||||
export interface TabItem {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface TabSelectorProps {
|
||||
tabs: TabItem[];
|
||||
activeTab: string;
|
||||
onTabChange: (tabId: string) => void;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
disabledMessage?: string;
|
||||
}
|
||||
|
||||
export const TabSelector: React.FC<TabSelectorProps> = ({
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
className,
|
||||
disabled,
|
||||
disabledMessage,
|
||||
}) => {
|
||||
// Find the index of the active tab
|
||||
const selectedIndex = tabs.findIndex((tab) => tab.id === activeTab);
|
||||
const tabsRef = useRef<(HTMLElement | null)[]>([]);
|
||||
const [indicatorStyle, setIndicatorStyle] = useState({
|
||||
left: 0,
|
||||
width: 0,
|
||||
});
|
||||
|
||||
// Handle tab change
|
||||
const handleTabChange = (index: number) => {
|
||||
onTabChange(tabs[index].id);
|
||||
};
|
||||
|
||||
// Update the indicator position when the selected tab changes
|
||||
useEffect(() => {
|
||||
if (selectedIndex >= 0 && tabsRef.current[selectedIndex]) {
|
||||
const currentTab = tabsRef.current[selectedIndex];
|
||||
if (currentTab) {
|
||||
setIndicatorStyle({
|
||||
left: currentTab.offsetLeft,
|
||||
width: currentTab.offsetWidth,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [selectedIndex, tabs]);
|
||||
|
||||
const TabGroupElement = (
|
||||
<div
|
||||
className={twMerge(
|
||||
"w-full",
|
||||
className,
|
||||
disabled ? "cursor-not-allowed opacity-60" : "",
|
||||
)}
|
||||
>
|
||||
<Tab.Group selectedIndex={selectedIndex} onChange={handleTabChange}>
|
||||
<Tab.List className="glass relative inline-flex min-w-fit overflow-x-auto rounded-lg p-0.5 py-1 !shadow-none">
|
||||
{/* Animated indicator */}
|
||||
<div
|
||||
className="absolute top-1 z-10 h-[calc(100%-8px)] rounded-md bg-brand-primary/30 transition-all duration-200 ease-in-out"
|
||||
style={{
|
||||
left: indicatorStyle.left,
|
||||
width: indicatorStyle.width,
|
||||
}}
|
||||
/>
|
||||
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab
|
||||
key={tab.id}
|
||||
ref={(el) => (tabsRef.current[index] = el)}
|
||||
disabled={disabled}
|
||||
className={({ selected }) =>
|
||||
twMerge(
|
||||
"relative z-20 mx-0.5 min-w-max rounded-md border-2 border-transparent px-4 py-0.5 text-center text-sm font-semibold transition-all duration-200 ease-in-out",
|
||||
selected ? "text-white" : "text-gray-300 hover:text-white",
|
||||
disabled ? "cursor-not-allowed opacity-60" : "",
|
||||
)
|
||||
}
|
||||
>
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{disabled && (
|
||||
<Tooltip
|
||||
content={
|
||||
disabledMessage ?? "Cannot change tab. Generation in progress."
|
||||
}
|
||||
position="bottom"
|
||||
>
|
||||
{TabGroupElement}
|
||||
</Tooltip>
|
||||
)}
|
||||
{!disabled && TabGroupElement}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TabType = {
|
||||
header: string;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const Tabs = ({ tabs: tabProps }: { tabs: TabType[] }) => {
|
||||
const [tabs] = useState(tabProps);
|
||||
|
||||
const tabHeaderClassName = (selected: boolean) =>
|
||||
twMerge(
|
||||
"px-4 py-4 text-md font-medium leading-5 focus:outline-none transition duration-150 ease-in-out border-b-[3px] border-white/[.1]",
|
||||
selected
|
||||
? "text-white border-brand-primary"
|
||||
: "text-white/[0.5] hover:text-white hover:border-white/[.3]",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col px-2 sm:px-0">
|
||||
<Tab.Group defaultIndex={3}>
|
||||
<Tab.List className="flex">
|
||||
{tabs.map((tab, idx) => (
|
||||
<Tab
|
||||
key={idx}
|
||||
className={({ selected }) => tabHeaderClassName(selected)}
|
||||
>
|
||||
{tab.header}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<Tab.Panels className="grow overflow-auto p-4">
|
||||
{tabs.map((tab, idx) => (
|
||||
<Tab.Panel key={idx}>{tab.children}</Tab.Panel>
|
||||
))}
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { Tabs } from './Tabs';
|
||||
@@ -1,13 +0,0 @@
|
||||
import { Canvas, Meta } from '@storybook/blocks';
|
||||
|
||||
import * as TextareaStories from './Textarea.stories';
|
||||
|
||||
<Meta of={TextareaStories} />
|
||||
|
||||
# Textarea control
|
||||
|
||||
Here is the Textarea with placeholder
|
||||
<Canvas of={TextareaStories.Default} />
|
||||
|
||||
Here is the Textarea with label
|
||||
<Canvas of={TextareaStories.WithLabel} />
|
||||
@@ -1,33 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { Textarea } from "./Textarea";
|
||||
import { withActions } from "@storybook/addon-actions/decorator";
|
||||
|
||||
const meta: Meta<typeof Textarea> = {
|
||||
component: Textarea,
|
||||
parameters: {
|
||||
actions: {
|
||||
handles: ["change", "blur", "focus"],
|
||||
},
|
||||
},
|
||||
decorators: [withActions],
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof Textarea>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Textarea placeholder="field placeholder" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
render: () => (
|
||||
<div className="bg-action p-8">
|
||||
<Textarea label="Label" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1,54 +0,0 @@
|
||||
import { TextareaHTMLAttributes } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Label } from "~/components";
|
||||
import { kebabCase } from "~/utilities";
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
enableHotkeyInput,
|
||||
DomLevels,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
|
||||
type ResizeType =
|
||||
| "none"
|
||||
| "both"
|
||||
| "horizontal"
|
||||
| "vertical"
|
||||
| "block"
|
||||
| "inline"
|
||||
| undefined;
|
||||
|
||||
export const Textarea = ({
|
||||
className,
|
||||
label,
|
||||
resize = "vertical",
|
||||
id,
|
||||
...rest
|
||||
}: TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
||||
label?: string;
|
||||
resize?: ResizeType;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{label && <Label htmlFor={id ? id : kebabCase(label)}>{label}</Label>}
|
||||
|
||||
<textarea
|
||||
id={id ? id : label ? kebabCase(label) : undefined}
|
||||
className={twMerge(
|
||||
"rounded-lg border border-[#363636] bg-brand-secondary px-3 py-2 placeholder-white/50 outline-none transition-all duration-150 ease-in-out hover:border-brand-primary/60 focus:border-brand-primary focus:outline-none",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
resize: resize,
|
||||
}}
|
||||
onFocus={() => {
|
||||
disableHotkeyInput(DomLevels.INPUT);
|
||||
}}
|
||||
onBlur={() => {
|
||||
enableHotkeyInput(DomLevels.INPUT);
|
||||
}}
|
||||
onKeyDown={(event) => event.stopPropagation()}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./Textarea";
|
||||
@@ -1,149 +0,0 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface TooltipProps {
|
||||
children: React.ReactElement;
|
||||
content: React.ReactNode;
|
||||
position: "top" | "bottom" | "left" | "right";
|
||||
className?: string;
|
||||
delay?: number;
|
||||
closeOnClick?: boolean;
|
||||
}
|
||||
|
||||
export const Tooltip = ({
|
||||
children,
|
||||
content,
|
||||
position,
|
||||
className,
|
||||
delay = 300,
|
||||
closeOnClick = false,
|
||||
}: TooltipProps) => {
|
||||
const [isShowing, setIsShowing] = useState(false);
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const checkForOpenPopovers = () => {
|
||||
if (!triggerRef.current) return false;
|
||||
return (
|
||||
triggerRef.current.querySelectorAll('[data-headlessui-state="open"]')
|
||||
.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (
|
||||
mutation.type === "attributes" &&
|
||||
mutation.attributeName === "data-headlessui-state"
|
||||
) {
|
||||
const target = mutation.target as HTMLElement;
|
||||
if (target.getAttribute("data-headlessui-state") === "open") {
|
||||
setIsShowing(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (triggerRef.current) {
|
||||
observer.observe(triggerRef.current, {
|
||||
attributes: true,
|
||||
subtree: true,
|
||||
attributeFilter: ["data-headlessui-state"],
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getStyleForPosition = () => {
|
||||
if (triggerRef.current) {
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
switch (position) {
|
||||
case "top":
|
||||
return {
|
||||
bottom: rect.height + 10,
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
};
|
||||
case "bottom":
|
||||
return {
|
||||
top: rect.height + 10,
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
};
|
||||
case "left":
|
||||
return {
|
||||
right: rect.width + 10,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
};
|
||||
case "right":
|
||||
return {
|
||||
left: rect.width + 10,
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (e: React.MouseEvent) => {
|
||||
if (closeOnClick) {
|
||||
setIsShowing(false);
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={triggerRef}
|
||||
onMouseEnter={() => {
|
||||
if (!checkForOpenPopovers()) {
|
||||
setIsShowing(true);
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsShowing(false);
|
||||
}}
|
||||
onClick={handleClick}
|
||||
className="relative"
|
||||
>
|
||||
{children}
|
||||
<Transition
|
||||
show={isShowing}
|
||||
enter={twMerge(
|
||||
"transition ease-out duration-200",
|
||||
delay ? `delay-[${delay}ms]` : "delay-[300ms]",
|
||||
)}
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
style={{
|
||||
...getStyleForPosition(),
|
||||
transitionDelay: `${delay}ms`,
|
||||
transitionProperty: "opacity",
|
||||
transitionDuration: "200ms",
|
||||
transitionTimingFunction: "ease-out",
|
||||
}}
|
||||
className={twMerge(
|
||||
"pointer-events-none absolute z-10 w-max rounded-lg bg-[#5F5F68] px-2.5 py-1.5 text-[13px] font-medium text-white shadow-xl",
|
||||
className ? className : "",
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,157 +0,0 @@
|
||||
import { useEffect, Fragment, ReactNode } from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
enableHotkeyInput,
|
||||
DomLevels,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
IconDefinition,
|
||||
faDownLeftAndUpRightToCenter,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Tooltip } from "~/components";
|
||||
|
||||
const DialogBackdrop = ({ className }: { className?: string }) => {
|
||||
useEffect(() => {
|
||||
disableHotkeyInput(DomLevels.DIALOGUE);
|
||||
return () => {
|
||||
enableHotkeyInput(DomLevels.DIALOGUE);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
className={twMerge("fixed inset-0 bg-black/60", className)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</Transition.Child>
|
||||
);
|
||||
};
|
||||
|
||||
export const TransitionDialogue = ({
|
||||
isOpen,
|
||||
title,
|
||||
titleIcon,
|
||||
onTitleIconClick,
|
||||
onClose,
|
||||
className,
|
||||
backdropClassName,
|
||||
width,
|
||||
children,
|
||||
childPadding = true,
|
||||
titleIconClassName,
|
||||
showClose = true,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
title?: ReactNode;
|
||||
titleIcon?: IconDefinition;
|
||||
onTitleIconClick?: () => void;
|
||||
titleIconClassName?: string;
|
||||
onClose: () => void;
|
||||
className?: string;
|
||||
backdropClassName?: string;
|
||||
width?: number;
|
||||
children: ReactNode;
|
||||
childPadding?: boolean;
|
||||
showClose?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-[70]" onClose={onClose}>
|
||||
<DialogBackdrop className={backdropClassName} />
|
||||
<div className="fixed inset-0 overflow-y-auto">
|
||||
<div className="flex min-h-full items-center justify-center p-4 text-center">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-200"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel
|
||||
className={twMerge(
|
||||
"w-full max-w-lg transform rounded-xl",
|
||||
"border border-ui-panel-border bg-[#2C2C2C]",
|
||||
"text-left align-middle shadow-xl transition-all",
|
||||
className,
|
||||
)}
|
||||
style={{ minWidth: width }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<Dialog.Title
|
||||
as="div"
|
||||
className="mb-5 flex justify-between p-5 pb-0 text-xl font-bold text-white"
|
||||
>
|
||||
<>
|
||||
{onTitleIconClick ? (
|
||||
<button
|
||||
className="flex items-center gap-3"
|
||||
onClick={onTitleIconClick}
|
||||
>
|
||||
{titleIcon && (
|
||||
<FontAwesomeIcon
|
||||
icon={titleIcon}
|
||||
className={titleIconClassName}
|
||||
/>
|
||||
)}
|
||||
{title}
|
||||
</button>
|
||||
) : (
|
||||
<div className="flex items-center gap-3">
|
||||
{titleIcon && (
|
||||
<FontAwesomeIcon
|
||||
icon={titleIcon}
|
||||
className={titleIconClassName}
|
||||
/>
|
||||
)}
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
{showClose && (
|
||||
<Tooltip position="top" content="Close">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="opacity-50 transition-opacity duration-150 hover:opacity-80 focus:outline-none"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faDownLeftAndUpRightToCenter}
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Dialog.Title>
|
||||
)}
|
||||
<div
|
||||
className={`h-full ${childPadding ? "p-5 pt-0" : ""}`.trim()}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { NavLink, NavLinkProps } from "react-router-dom";
|
||||
import { LabelHTMLAttributes, ReactNode } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface TypoProps {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
const baseTypo = "text-white";
|
||||
|
||||
export const H1 = ({ className, children }: TypoProps) => (
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
|
||||
export const H2 = ({ className, children }: TypoProps) => (
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const H3 = ({ className, children }: TypoProps) => (
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const H4 = ({ className, children }: TypoProps) => (
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
|
||||
export const Label = ({
|
||||
className,
|
||||
children,
|
||||
required,
|
||||
...rest
|
||||
}: LabelHTMLAttributes<HTMLLabelElement> & { required?: boolean }) => (
|
||||
<label
|
||||
className={twMerge(baseTypo, "mb-2 text-[15px] font-medium", className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{required && <span className="ml-0.5 text-[#ff6467]">*</span>}
|
||||
</label>
|
||||
);
|
||||
|
||||
export const H5 = ({ className, children }: TypoProps) => (
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
|
||||
export const H6 = ({ className, children }: TypoProps) => (
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-normal", className)}>
|
||||
{children}
|
||||
</h6>
|
||||
);
|
||||
|
||||
export const Link = ({ className, ...rest }: NavLinkProps) => (
|
||||
<NavLink
|
||||
className={twMerge(
|
||||
"text-brand-primary transition-all duration-150 hover:text-brand-primary-400",
|
||||
className as string,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const P = ({ className, children }: TypoProps) => (
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>
|
||||
);
|
||||
@@ -1,53 +0,0 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { P } from "~/components";
|
||||
import { getFileName } from "~/utilities";
|
||||
|
||||
interface Props {
|
||||
file: File | null;
|
||||
fileTypes: string[];
|
||||
}
|
||||
|
||||
export const DragAndDropZone = ({ file, fileTypes }: Props) => {
|
||||
const fileSize =
|
||||
file && file.size >= 1024 * 1024
|
||||
? (file.size / 1024 / 1024).toFixed(2) + " MB"
|
||||
: file
|
||||
? `${Math.floor(file.size / 1024)} KB`
|
||||
: null;
|
||||
|
||||
const fileName = file && getFileName(file).toUpperCase();
|
||||
|
||||
if (!file) {
|
||||
return (
|
||||
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
|
||||
<FontAwesomeIcon icon={faFileArrowUp} className="text-4xl" />
|
||||
<div className="flex flex-col gap-0">
|
||||
<P className="font-medium">
|
||||
<u>Upload a file</u> or drop it here
|
||||
</P>
|
||||
<P className="flex items-center gap-2 text-sm font-normal opacity-50">
|
||||
{fileTypes.join(", ").toString().toUpperCase()} supported
|
||||
</P>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="flex cursor-pointer items-center justify-between gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
|
||||
<FontAwesomeIcon icon={faFileAudio} className="text-4xl" />
|
||||
<div className="flex grow flex-col gap-0">
|
||||
<P className="font-medium">
|
||||
{file.name.slice(0, file.name.lastIndexOf("."))}
|
||||
</P>
|
||||
<P className="flex items-center gap-2 text-sm font-normal">
|
||||
<span className="opacity-50">
|
||||
{`${fileName} file size: ${fileSize} `}
|
||||
</span>
|
||||
<u className="transition-all hover:text-white/80">Change File</u>
|
||||
</P>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { FileUploader as DragDropFilesUploader } from "react-drag-drop-files";
|
||||
import { DragAndDropZone } from "./DragAndDropZone";
|
||||
import { Label } from "../Typography";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
fileTypes: string[];
|
||||
file: File | null;
|
||||
setFile: (file: File | null) => void;
|
||||
}
|
||||
|
||||
export const FileUploader = ({ fileTypes, title, file, setFile }: Props) => {
|
||||
return (
|
||||
<div className="flex w-full flex-col">
|
||||
<Label required>{title}</Label>
|
||||
<DragDropFilesUploader
|
||||
handleChange={(file: File | null) => {
|
||||
setFile(file);
|
||||
}}
|
||||
name="file"
|
||||
types={fileTypes}
|
||||
maxSize={50}
|
||||
>
|
||||
<DragAndDropZone file={file} fileTypes={fileTypes} />
|
||||
</DragDropFilesUploader>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./UploadModal";
|
||||
+10
-10
@@ -1,9 +1,9 @@
|
||||
import { useCallback, useEffect, useState, useRef } from "react";
|
||||
import { H6, Input, Label, ListDropdown } from "~/components";
|
||||
import {
|
||||
Button
|
||||
} from "@storyteller/ui-button"
|
||||
import { FileUploader } from "../UploadModal/FileUploader";
|
||||
import { ListDropdown } from "@storyteller/ui-list-dropdown";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { FileUploader } from "@storyteller/ui-file-uploader";
|
||||
import { loadPreviewOnCanvas, snapshotCanvasAsThumbnail } from "./utilities";
|
||||
import { upload3DObjects } from "./utilities/upload3DObjects";
|
||||
import { UploaderState } from "~/models";
|
||||
@@ -152,7 +152,7 @@ export const UploadFiles3D = ({
|
||||
/>
|
||||
)}
|
||||
{assetFile.error && (
|
||||
<H6 className="z-10 text-red">{assetFile.error}</H6>
|
||||
<h6 className="z-10 text-red">{assetFile.error}</h6>
|
||||
)}
|
||||
|
||||
<div className="relative m-auto w-full overflow-hidden rounded-lg bg-brand-secondary">
|
||||
@@ -161,18 +161,18 @@ export const UploadFiles3D = ({
|
||||
ref={canvasCallbackRef}
|
||||
/>
|
||||
{!assetFile.value && (
|
||||
<H6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
|
||||
<h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 flex w-full items-center justify-center gap-2.5 text-center opacity-50">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
Your model preview will appear here
|
||||
</H6>
|
||||
</h6>
|
||||
)}
|
||||
{previewStatus.type.includes("Error") && (
|
||||
<>
|
||||
<H6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
|
||||
<h6 className="pointer-events-auto absolute left-0 top-1/2 -mt-5 w-full text-center">
|
||||
{previewStatus.type}
|
||||
{previewStatus.message && <br />}
|
||||
{previewStatus.message}
|
||||
</H6>
|
||||
</h6>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { LoadingDots } from "~/components";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { UploadAssetError } from "../UploadModal/UploadAssetError";
|
||||
import { UploadSuccess } from "../UploadModal/UploadSuccess";
|
||||
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
|
||||
import { UploadFiles3D } from "./UploadFiles3D";
|
||||
import { initialUploaderState, UploaderState } from "~/models";
|
||||
import { Select } from "../Select";
|
||||
import { Select } from "@storyteller/ui-select";
|
||||
import {
|
||||
FilterEngineCategories,
|
||||
UploaderStates,
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import { H6, Input } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
import { FileUploader } from "../UploadModal/FileUploader";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { FileUploader } from "@storyteller/ui-file-uploader";
|
||||
import { uploadPlane } from "./uploadPlane";
|
||||
import { UploaderState } from "~/models";
|
||||
|
||||
@@ -81,14 +81,14 @@ export const UploadFilesMedia = ({
|
||||
}}
|
||||
/>
|
||||
{assetFile.error && (
|
||||
<H6 className="z-10 text-red">{assetFile.error}</H6>
|
||||
<h6 className="z-10 text-red">{assetFile.error}</h6>
|
||||
)}
|
||||
|
||||
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary">
|
||||
{!assetFile.value && (
|
||||
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
|
||||
<h6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
|
||||
File Preview
|
||||
</H6>
|
||||
</h6>
|
||||
)}
|
||||
{assetFile.value &&
|
||||
(assetFile.value.type === "video/mp4" ? (
|
||||
|
||||
+14
-19
@@ -1,9 +1,8 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { H6, Input } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
import { H6 } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { FileUploader } from "../UploadModal/FileUploader";
|
||||
|
||||
import { UploaderState } from "~/models";
|
||||
import { uploadImage } from "./uploadImage";
|
||||
|
||||
@@ -59,7 +58,7 @@ export const UploadImageMedia = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 flex flex-col gap-4" >
|
||||
<div className="mb-4 flex flex-col gap-4">
|
||||
<Input
|
||||
placeholder="Enter the title here"
|
||||
errorMessage={uploadTitle.error}
|
||||
@@ -82,34 +81,30 @@ export const UploadImageMedia = ({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{
|
||||
assetFile.error && (
|
||||
<H6 className="z-10 text-red" > {assetFile.error} </H6>
|
||||
)
|
||||
}
|
||||
{assetFile.error && (
|
||||
<H6 className="z-10 text-red"> {assetFile.error} </H6>
|
||||
)}
|
||||
|
||||
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary" >
|
||||
<div className="relative m-auto aspect-square w-full overflow-hidden rounded-lg bg-brand-secondary">
|
||||
{!assetFile.value && (
|
||||
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center" >
|
||||
<H6 className="absolute left-0 top-1/2 -mt-5 w-full text-center">
|
||||
File Preview
|
||||
</H6>
|
||||
)}
|
||||
{
|
||||
assetFile.value &&
|
||||
{assetFile.value && (
|
||||
<img
|
||||
alt="file upload preview"
|
||||
className="m-auto max-h-full max-w-full"
|
||||
src={assetFile.src}
|
||||
/>
|
||||
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
|
||||
< div className="flex justify-end gap-4" >
|
||||
<Button variant="primary" onClick={handleSubmit} >
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button variant="primary" onClick={handleSubmit}>
|
||||
Upload
|
||||
</Button>
|
||||
< Button variant="secondary" onClick={onClose} >
|
||||
<Button variant="secondary" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+4
-5
@@ -1,10 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { TransitionDialogue, LoadingDots } from "~/components";
|
||||
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { UploadAssetError } from "../UploadModal/UploadAssetError";
|
||||
import { UploadSuccess } from "../UploadModal/UploadSuccess";
|
||||
import { UploadFilesMedia } from "./UploadFilesMedia";
|
||||
import { FilterEngineCategories, UploaderStates } from "~/enums";
|
||||
import { initialUploaderState, UploaderState } from "~/models";
|
||||
import { UploadImageMedia } from "./UploadImageMedia";
|
||||
@@ -113,8 +112,8 @@ export function UploadImageMediaModal({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue isOpen={isOpen} onClose={onClose} title={title}>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title}>
|
||||
<UploaderModalContent />
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { TransitionDialogue, LoadingDots } from "~/components";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
import { UploadAssetError } from "../UploadModal/UploadAssetError";
|
||||
import { UploadSuccess } from "../UploadModal/UploadSuccess";
|
||||
import { UploadAssetError, UploadSuccess } from "@storyteller/ui-upload-modal";
|
||||
import { UploadFilesMedia } from "./UploadFilesMedia";
|
||||
import { FilterEngineCategories, UploaderStates } from "~/enums";
|
||||
import { initialUploaderState, UploaderState } from "~/models";
|
||||
@@ -99,8 +99,8 @@ export function UploadModalMedia({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue isOpen={isOpen} onClose={onClose} title={title}>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={title}>
|
||||
<UploaderModalContent />
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import { useCallback, useEffect, useState, useRef } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import WaveSurfer from "wavesurfer.js";
|
||||
import {
|
||||
faCirclePlay,
|
||||
faCirclePause,
|
||||
faSpinnerThird,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { ButtonIcon } from "~/components";
|
||||
|
||||
export const WaveformPlayer = ({
|
||||
audio,
|
||||
hasPlayButton,
|
||||
onLoad,
|
||||
}: {
|
||||
audio: string;
|
||||
hasPlayButton?: boolean;
|
||||
onLoad?: ({ duration }: { duration: number }) => void;
|
||||
}) => {
|
||||
const waveSurferRef = useRef<WaveSurfer | undefined>(undefined);
|
||||
const [{ isPlaying, isLoading }, setState] = useState<{
|
||||
isLoading: boolean;
|
||||
isPlaying: boolean;
|
||||
}>({
|
||||
isLoading: true,
|
||||
isPlaying: false,
|
||||
});
|
||||
|
||||
const containerRef = useCallback(
|
||||
(node: HTMLDivElement) => {
|
||||
const toggleIsPlaying = (newIsPlaying: boolean) => {
|
||||
setState((curr) => ({ ...curr, isPlaying: newIsPlaying }));
|
||||
};
|
||||
const toggleIsLoading = (newIsLoading: boolean) => {
|
||||
setState((curr) => ({ ...curr, isLoading: newIsLoading }));
|
||||
};
|
||||
if (node) {
|
||||
if (waveSurferRef.current) {
|
||||
//need to destroy previous wavesurfer to not have doubles
|
||||
waveSurferRef.current.destroy();
|
||||
toggleIsLoading(true);
|
||||
}
|
||||
const waveSurfer = WaveSurfer.create({
|
||||
container: node,
|
||||
barWidth: 2,
|
||||
height: 24,
|
||||
cursorWidth: 0,
|
||||
waveColor: "#D7C8C8",
|
||||
progressColor: "#FB8381",
|
||||
});
|
||||
|
||||
waveSurfer.load(audio);
|
||||
waveSurfer.on("ready", () => {
|
||||
waveSurferRef.current = waveSurfer;
|
||||
// console.log("here");
|
||||
toggleIsLoading(false);
|
||||
if (onLoad) onLoad({ duration: waveSurfer.getDuration() });
|
||||
});
|
||||
waveSurfer.on("play", () => {
|
||||
toggleIsPlaying(true);
|
||||
});
|
||||
waveSurfer.on("pause", () => {
|
||||
toggleIsPlaying(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
[audio],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
//destructor on unmount
|
||||
waveSurferRef.current?.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 py-1">
|
||||
{hasPlayButton && (
|
||||
<ButtonIcon
|
||||
icon={
|
||||
isLoading
|
||||
? faSpinnerThird
|
||||
: isPlaying
|
||||
? faCirclePause
|
||||
: faCirclePlay
|
||||
}
|
||||
className="w-auto bg-transparent p-0 text-2xl hover:bg-transparent hover:opacity-75"
|
||||
spin={isLoading}
|
||||
onClick={() => {
|
||||
if (!isLoading) waveSurferRef.current?.playPause();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="relative h-10 grow overflow-hidden">
|
||||
<span
|
||||
className={twMerge(
|
||||
"absolute top-[18px] w-full border-t border-dotted border-t-white transition-opacity",
|
||||
isLoading ? "opacity-100" : "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={twMerge(
|
||||
"absolute top-[7px] w-full transition-opacity",
|
||||
isLoading ? "opacity-0" : "opacity-100",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,32 +1,2 @@
|
||||
export * from "./Badge";
|
||||
export * from "./ButtonDropdown";
|
||||
export * from "./ButtonIcon";
|
||||
export * from "./ButtonIconSelect";
|
||||
export * from "./ButtonLink";
|
||||
export * from "./CloseButton";
|
||||
export * from "./ConfirmationModal";
|
||||
export * from "./FileUploader";
|
||||
export * from "./FilterButtons";
|
||||
export * from "./Gravatar";
|
||||
export * from "./Input";
|
||||
export * from "./InputVector";
|
||||
export * from "./ListDropdown";
|
||||
export * from "./LoadingBar";
|
||||
export * from "./LoadingDots";
|
||||
export * from "./LoadingSpinner";
|
||||
export * from "./NumberInput";
|
||||
export * from "./Pagination";
|
||||
export * from "./Pill";
|
||||
export * from "./Popover";
|
||||
export * from "./SearchFilter";
|
||||
export * from "./Select";
|
||||
export * from "./Slider";
|
||||
export * from "./Tabs";
|
||||
export * from "./Textarea";
|
||||
export * from "./Tooltip";
|
||||
export * from "./TransitionDialogue";
|
||||
export * from "./Typography";
|
||||
export * from "./UploadModal";
|
||||
export * from "./UploadModal3D";
|
||||
export * from "./UploadModalMedia";
|
||||
export * from "./WaveformPlayer";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { faTriangleExclamation } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { TransitionDialogue } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import {
|
||||
showErrorDialog,
|
||||
errorDialogMessage,
|
||||
@@ -13,7 +13,7 @@ export function ErrorDialog() {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
<Modal
|
||||
title={errorDialogTitle.value}
|
||||
titleIcon={faTriangleExclamation}
|
||||
titleIconClassName="text-brand-primary"
|
||||
@@ -33,6 +33,6 @@ export function ErrorDialog() {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication } from "~/signals";
|
||||
import { ButtonLink } from "~/components";
|
||||
import { ButtonLink } from "@storyteller/ui-button-link";
|
||||
import ProfileDropdown from "./ProfileDropdown";
|
||||
|
||||
export const AuthButtons = () => {
|
||||
|
||||
@@ -7,9 +7,8 @@ import {
|
||||
faUser,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { Gravatar } from "~/components/reusable";
|
||||
import { Gravatar } from "@storyteller/ui-gravatar";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { authentication, logout } from "~/signals";
|
||||
|
||||
export default function ProfileDropdown() {
|
||||
|
||||
@@ -3,15 +3,14 @@ import { twMerge } from "tailwind-merge";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { faPencil, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { scene, signalScene, authentication, currentPage } from "~/signals";
|
||||
import { scene, signalScene, authentication } from "~/signals";
|
||||
import {
|
||||
showErrorDialog,
|
||||
errorDialogMessage,
|
||||
errorDialogTitle,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { Input } from "~/components";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
|
||||
import { Pages } from "~/pages/PageEnigma/constants/page";
|
||||
|
||||
interface Props {
|
||||
pageName: string;
|
||||
|
||||
@@ -9,9 +9,8 @@ import {
|
||||
faImage,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useLocation, useParams } from "react-router-dom";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
|
||||
import { ButtonLink } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { ButtonLink } from "@storyteller/ui-button-link";
|
||||
import { AuthButtons } from "./AuthButtons";
|
||||
import { SceneTitleInput } from "./SceneTitleInput";
|
||||
import { getCurrentLocationWithoutParams } from "~/utilities";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ComponentType, useEffect, useRef } from "react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
|
||||
import { LoadingDots } from "~/components";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { authentication, persistLogin } from "~/signals";
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { TWO_SECONDS } from "~/constants";
|
||||
|
||||
@@ -55,9 +55,7 @@ const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
|
||||
pageWidth.value = window.innerWidth;
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setPage();
|
||||
window.addEventListener("resize", setPage);
|
||||
return () => {
|
||||
|
||||
@@ -13,7 +13,8 @@ import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication, login, logout } from "~/signals";
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import { Input, ConfirmationModal } from "~/components";
|
||||
import { ConfirmationModal } from "~/components/reusable/ConfirmationModal";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
|
||||
import "./styles/normalize.css";
|
||||
|
||||
+2
-5
@@ -30,12 +30,9 @@ export type TextFormatData = {
|
||||
export type TextNodeData = {
|
||||
text: string;
|
||||
} & TextFormatData;
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
import { DialogTitle } from "@headlessui/react";
|
||||
import { Button, TextEditor } from "../../../components/ui";
|
||||
|
||||
import { TextEditor } from "../../../components/ui";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
export const DialogEditText = ({
|
||||
|
||||
+5
-2
@@ -1,13 +1,16 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { dialogPanelStyles, paperWrapperStyles } from "../../../components/styles";
|
||||
import {
|
||||
dialogPanelStyles,
|
||||
paperWrapperStyles,
|
||||
} from "../../../components/styles";
|
||||
import {
|
||||
Description,
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
DialogTitle,
|
||||
} from "@headlessui/react";
|
||||
import { Button } from "../../../components/ui";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
|
||||
export const DialogError = ({
|
||||
isShowing,
|
||||
|
||||
+3
-5
@@ -1,5 +1,6 @@
|
||||
import { faCameraMovie, faPlayCircle } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, Tooltip } from "~/components/ui";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { ToolbarButtonProps } from "../ToolbarButton";
|
||||
|
||||
export const ButtonPreviewAndRender = ({
|
||||
@@ -9,10 +10,7 @@ export const ButtonPreviewAndRender = ({
|
||||
buttonPreviewProps: ToolbarButtonProps;
|
||||
buttonRenderProps: ToolbarButtonProps;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex">
|
||||
</div>
|
||||
);
|
||||
return <div className="flex"></div>;
|
||||
};
|
||||
const ButtonPreview = (buttonProps: ToolbarButtonProps) => {
|
||||
const {
|
||||
|
||||
+8
-10
@@ -1,8 +1,6 @@
|
||||
import { faFileArrowUp, faFileAudio } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { P } from "~/components";
|
||||
|
||||
export const DragAndDropZone = ({
|
||||
file,
|
||||
fileTypes,
|
||||
@@ -15,13 +13,13 @@ export const DragAndDropZone = ({
|
||||
<div className="flex cursor-pointer items-center gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
|
||||
<FontAwesomeIcon icon={faFileArrowUp} className="text-4xl" />
|
||||
<div className="flex flex-col gap-0">
|
||||
<P className="font-medium">
|
||||
<p className="font-medium">
|
||||
<u>Upload a file</u> or drop it here
|
||||
</P>
|
||||
</p>
|
||||
|
||||
<P className="flex items-center gap-2 text-sm font-normal opacity-50">
|
||||
<p className="flex items-center gap-2 text-sm font-normal opacity-50">
|
||||
{fileTypes.join(", ").toString()} supported
|
||||
</P>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -37,15 +35,15 @@ export const DragAndDropZone = ({
|
||||
<div className="flex cursor-pointer items-center justify-between gap-3.5 rounded-lg border border-dashed border-[#363636] bg-brand-secondary p-3">
|
||||
<FontAwesomeIcon icon={faFileAudio} className="text-4xl" />
|
||||
<div className="flex grow flex-col gap-0">
|
||||
<P className="font-medium">
|
||||
<p className="font-medium">
|
||||
{file.name.slice(0, file.name.lastIndexOf("."))}
|
||||
</P>
|
||||
<P className="flex items-center gap-2 text-sm font-normal">
|
||||
</p>
|
||||
<p className="flex items-center gap-2 text-sm font-normal">
|
||||
<span className="opacity-50">
|
||||
{`${fileName} file size: ${fileSize} `}
|
||||
</span>
|
||||
<u className="transition-all hover:text-white/80">Change File</u>
|
||||
</P>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { faBell, faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverMenu } from "~/components/reusable/Popover";
|
||||
import { PopoverMenu } from "@storyteller/ui-popover";
|
||||
import { CompletedCard } from "./CompletedCard";
|
||||
import { InProgressCard } from "./InProgressCard";
|
||||
import { useJobContext } from "~/components/JobContext";
|
||||
import { Api } from "~/KonvaApp/Api";
|
||||
import { toast } from "sonner";
|
||||
import { toast } from "@storyteller/ui-toaster";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
// Dummy data types
|
||||
@@ -133,7 +133,7 @@ export function Activity() {
|
||||
</>
|
||||
</span>
|
||||
{jobs.length > 0 && (
|
||||
<div className="absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full bg-blue-500" />
|
||||
<div className="bg-blue-500 absolute -right-1 -top-1 h-3 w-3 animate-pulse rounded-full" />
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { kebabCase } from "~/utilities";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
inputClassName?: string;
|
||||
iconClassName?: string;
|
||||
label?: string;
|
||||
icon?: IconDefinition;
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
export const Input = React.forwardRef(
|
||||
(
|
||||
{
|
||||
label,
|
||||
icon,
|
||||
inputClassName,
|
||||
iconClassName,
|
||||
className,
|
||||
id,
|
||||
isError,
|
||||
onBlur,
|
||||
onFocus,
|
||||
errorMessage,
|
||||
...rest
|
||||
}: InputProps,
|
||||
ref: React.ForwardedRef<HTMLInputElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className={twMerge("flex flex-col", className)}>
|
||||
{label && (
|
||||
<label
|
||||
htmlFor={id ? id : kebabCase(label)}
|
||||
className="pb-2 font-medium"
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="relative w-full">
|
||||
{icon && (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
className={twMerge(
|
||||
"absolute pl-3.5 pt-3 text-sm opacity-70",
|
||||
iconClassName,
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<input
|
||||
ref={ref}
|
||||
id={id ? id : label ? kebabCase(label) : undefined}
|
||||
className={twMerge(
|
||||
"h-10 w-full rounded-md px-3 py-2.5 outline-1 outline-ui-border",
|
||||
// on focus
|
||||
"outline-offset-0 transition-all duration-150 ease-in-out focus:outline-primary",
|
||||
icon && "pl-10",
|
||||
isError && "outline-red focus:outline-red",
|
||||
inputClassName,
|
||||
)}
|
||||
onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onFocus) {
|
||||
onFocus(e);
|
||||
}
|
||||
}}
|
||||
onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
|
||||
if (onBlur) {
|
||||
onBlur(e);
|
||||
}
|
||||
}}
|
||||
{...rest}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<h6 className="text-red absolute z-10">{errorMessage}</h6>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Input.displayName = "Input";
|
||||
@@ -2,8 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faChevronDown, faChevronUp } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { Input } from "./Input";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
|
||||
interface NumberInputProps {
|
||||
value: number;
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { paperWrapperStyles } from "../../../components/styles";
|
||||
import { Button } from "../Button";
|
||||
import { Input } from "../Input";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { HexEyedropPicker } from "../HexEyedropPicker";
|
||||
|
||||
export const ColorPicker = ({
|
||||
|
||||
+3
-4
@@ -8,12 +8,11 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { HexColorPicker } from "react-colorful";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { paperWrapperStyles } from "~/components/styles";
|
||||
import { Button } from "../Button";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { HexEyedropPicker } from "../HexEyedropPicker";
|
||||
import { Input } from "../Input";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
|
||||
export const BackgroundMenu = ({
|
||||
color: prevColor,
|
||||
@@ -90,7 +89,7 @@ export const BackgroundMenu = ({
|
||||
if (streamChanges) {
|
||||
onChange(color);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Rerender the component when these signals change:
|
||||
useSignals();
|
||||
|
||||
+18
-11
@@ -11,11 +11,18 @@ 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 { Button } from "../Button";
|
||||
import { Input } from "../Input";
|
||||
import { Slider } from "../Slider";
|
||||
import { eraseBrushSize, setEraseBrushSize } from "~/signals/uiEvents/toolbarMain/eraseMode";
|
||||
import {
|
||||
BRUSH_MAX_SIZE,
|
||||
BRUSH_MIN_SIZE,
|
||||
paintBrushSize,
|
||||
setPaintBrushSize,
|
||||
} from "~/signals/uiEvents/toolbarMain/paintMode";
|
||||
|
||||
import { Slider } from "@storyteller/ui-slider";
|
||||
import {
|
||||
eraseBrushSize,
|
||||
setEraseBrushSize,
|
||||
} from "~/signals/uiEvents/toolbarMain/eraseMode";
|
||||
|
||||
export const EraseModeMenu = ({
|
||||
faIcon = faFont,
|
||||
@@ -60,9 +67,7 @@ export const EraseModeMenu = ({
|
||||
)}
|
||||
onMouseEnter={() => defaultOpen && setIsOpen(true)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faIcon}
|
||||
/>
|
||||
<FontAwesomeIcon icon={faIcon} />
|
||||
</PopoverButton>
|
||||
{(open || isOpen) && (
|
||||
<div onMouseLeave={() => handleMouseLeave(close)}>
|
||||
@@ -70,7 +75,7 @@ export const EraseModeMenu = ({
|
||||
anchor={anchor}
|
||||
className={twMerge(
|
||||
paperWrapperStyles,
|
||||
"flex flex-col items-center gap-2 overflow-hidden w-[198px]",
|
||||
"flex w-[198px] flex-col items-center gap-2 overflow-hidden",
|
||||
)}
|
||||
style={
|
||||
{
|
||||
@@ -79,8 +84,10 @@ export const EraseModeMenu = ({
|
||||
}
|
||||
static
|
||||
>
|
||||
<div className="flex flex-col w-full gap-2">
|
||||
<p className="text-white font-medium text-sm w-full justify-start">Eraser Size:</p>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<p className="w-full justify-start text-sm font-medium text-white">
|
||||
Eraser Size:
|
||||
</p>
|
||||
<Slider
|
||||
min={BRUSH_MIN_SIZE}
|
||||
max={BRUSH_MAX_SIZE}
|
||||
|
||||
+4
-5
@@ -8,7 +8,6 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
|
||||
import { ChangeEvent, useEffect, useState, useRef } from "react";
|
||||
import { HexAlphaColorPicker } from "react-colorful";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { paperWrapperStyles } from "../../../components/styles";
|
||||
import {
|
||||
@@ -17,9 +16,9 @@ import {
|
||||
paintBrushSize,
|
||||
setPaintBrushSize,
|
||||
} from "../../../signals/uiEvents/toolbarMain/paintMode";
|
||||
import { Button } from "../Button";
|
||||
import { Input } from "../Input";
|
||||
import { Slider } from "../Slider";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { SliderV2 } from "@storyteller/ui-sliderv2";
|
||||
import { HexEyedropPicker } from "../HexEyedropPicker";
|
||||
|
||||
export const PaintModeMenu = ({
|
||||
@@ -206,7 +205,7 @@ export const PaintModeMenu = ({
|
||||
<p className="w-full justify-start text-sm font-medium text-white">
|
||||
Brush Size:
|
||||
</p>
|
||||
<Slider
|
||||
<SliderV2
|
||||
min={BRUSH_MIN_SIZE}
|
||||
max={BRUSH_MAX_SIZE}
|
||||
value={brushSize}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export * from "./Button";
|
||||
export * from "./Input";
|
||||
export * from "./InputNumber";
|
||||
export * from "./LoadingBar";
|
||||
export * from "./LoadingSpinner";
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { P, H1, ButtonLink } from "~/components";
|
||||
import { P, H1 } from "~/components";
|
||||
import { json } from "react-router-dom";
|
||||
import { ButtonLink } from "@storyteller/ui-button-link";
|
||||
|
||||
export const loader = () => {
|
||||
return json(null, { status: 404 });
|
||||
};
|
||||
|
||||
export const Page404 = () => {
|
||||
return(
|
||||
return (
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{height: "calc(100% - 72px)"}}
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{ height: "calc(100% - 72px)" }}
|
||||
>
|
||||
<div
|
||||
className='bg-ui-panel w-10/12 max-w-7xl h-1/2 mx-auto my-24 rounded-lg p-6 flex flex-col items-center justify-center gap-8 text-center'
|
||||
>
|
||||
<div className="mx-auto my-24 flex h-1/2 w-10/12 max-w-7xl flex-col items-center justify-center gap-8 rounded-lg bg-ui-panel p-6 text-center">
|
||||
<P>Oops, seems like this is not a page that exists!</P>
|
||||
<H1>Server Error: 404</H1>
|
||||
|
||||
<ButtonLink to="/" icon={faChevronLeft}>Back to Dashboard</ButtonLink>
|
||||
<ButtonLink to="/" icon={faChevronLeft}>
|
||||
Back to Dashboard
|
||||
</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import EnvironmentVariables from "~/Classes/EnvironmentVariables";
|
||||
import { persistLogin } from "~/signals";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
import { useEffect } from "react";
|
||||
import { showWizard } from "~/pages/PageEnigma/Wizard/signals/wizard";
|
||||
|
||||
|
||||
|
||||
export const GlobalSettingsManager = ({ env }: { env: Record<string, string> }) => {
|
||||
@@ -35,17 +35,7 @@ export const GlobalSettingsManager = ({ env }: { env: Record<string, string> })
|
||||
pageWidth.value = window.innerWidth;
|
||||
}
|
||||
|
||||
function initWizard() {
|
||||
if (showWizard.value) {
|
||||
return;
|
||||
}
|
||||
const wizard = localStorage.getItem("storyteller-wizard");
|
||||
showWizard.value = wizard ? "" : "initial";
|
||||
localStorage.setItem("storyteller-wizard", "shown");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
initWizard();
|
||||
setPage();
|
||||
window.addEventListener("resize", setPage);
|
||||
return () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useEffect } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { LoadingDots, TopBar } from "~/components";
|
||||
import { TopBar } from "~/components";
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
|
||||
import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject";
|
||||
@@ -28,6 +28,7 @@ import { Outliner } from "./comps/Outliner";
|
||||
import { CameraAspectRatio } from "./enums";
|
||||
import { PromptBox3D } from "@storyteller/ui-promptbox";
|
||||
import { PopoverItem } from "@storyteller/ui-popover";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
import { OnboardingHelper } from "./comps/OnboardingHelper";
|
||||
import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDisplay";
|
||||
import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab";
|
||||
@@ -456,7 +457,7 @@ export const PageEditor = () => {
|
||||
</div>
|
||||
|
||||
<LoginModal
|
||||
onClose={() => {}}
|
||||
onClose={() => { }}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
openAiLogo="/resources/images/openai-logo.png"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { EngineProvider } from "./contexts/EngineContext";
|
||||
import { useActiveJobs } from "~/hooks/useActiveJobs";
|
||||
import { useBackgroundLoadingMedia } from "~/hooks/useBackgroundLoadingMedia";
|
||||
import { useQueueHandler } from "./hooks/useQueueHandler";
|
||||
import { ErrorDialog, LoadingDots } from "~/components";
|
||||
import { ErrorDialog } from "~/components";
|
||||
import { GenerateModals } from "~/pages/PageEnigma/comps/GenerateModals/GenerateModals";
|
||||
import { Toaster } from "@storyteller/ui-toaster";
|
||||
import { EditorLoadingBar } from "./comps/EditorLoadingBar";
|
||||
@@ -25,6 +25,7 @@ import { useImageGenerationFailureEvent } from "./TauriEvents/useImageGeneration
|
||||
import { useImageGenerationSuccessEvent } from "./TauriEvents/useImageGenerationSuccessEvent";
|
||||
import { PageEditor } from "~/pages/PageEnigma/PageEditor";
|
||||
import { GalleryDragComponent } from "@storyteller/ui-gallery-modal";
|
||||
import { LoadingDots } from "@storyteller/ui-loading";
|
||||
|
||||
export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
useSignals();
|
||||
@@ -106,23 +107,23 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<EngineProvider sceneToken={sceneToken}>
|
||||
<PageEditor />
|
||||
<DragComponent />
|
||||
<GalleryDragComponent />
|
||||
<PrecisionSelector
|
||||
showSignal={showPrecisionSelector}
|
||||
coordSignal={precisionSelectorMenuCoords}
|
||||
valuesSignal={precisionSelectorValues}
|
||||
selectedValueSignal={precisionSelectedValue}
|
||||
/>
|
||||
<GenerateModals />
|
||||
<ErrorDialog />
|
||||
|
||||
<EditorLoadingBar/>
|
||||
|
||||
<EngineProvider sceneToken={sceneToken}>
|
||||
<PageEditor />
|
||||
<DragComponent />
|
||||
<GalleryDragComponent />
|
||||
<PrecisionSelector
|
||||
showSignal={showPrecisionSelector}
|
||||
coordSignal={precisionSelectorMenuCoords}
|
||||
valuesSignal={precisionSelectorValues}
|
||||
selectedValueSignal={precisionSelectedValue}
|
||||
/>
|
||||
<GenerateModals />
|
||||
<ErrorDialog />
|
||||
|
||||
<EditorLoadingBar />
|
||||
<Toaster offsetTop={70} offsetRight={12} />
|
||||
</EngineProvider>
|
||||
|
||||
</EngineProvider>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import { StyleMoreOptions } from "~/pages/PageEnigma/comps/StyleMoreOptions/Styl
|
||||
import Queue, { QueueNames } from "~/pages/PageEnigma/Queue";
|
||||
import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions";
|
||||
import { appTabId, is3DEditorInitialized, setAppTabId } from "~/signals/appTab";
|
||||
|
||||
// TODO: Delete Page Styling?
|
||||
export const PageStyling = () => {
|
||||
useSignals();
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { cameraAspectRatio } from "~/pages/PageEnigma/signals";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { ButtonDropdown } from "~/components";
|
||||
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
|
||||
|
||||
export const AspectRatioMenu = () => {
|
||||
useSignals();
|
||||
|
||||
+8
-6
@@ -1,13 +1,15 @@
|
||||
import { TransitionDialogue } from "~/components/reusable/TransitionDialogue";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { faPlus, faTrashAlt } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverItem } from "~/components/reusable/Popover";
|
||||
import { Input, Label, Tooltip } from "~/components";
|
||||
import { PopoverItem } from "@storyteller/ui-popover";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { SliderV2 } from "~/components/reusable/SliderV2/SliderV2";
|
||||
import { SliderV2 } from "@storyteller/ui-sliderv2";
|
||||
import { focalLengthDragging } from "~/pages/PageEnigma/signals/camera";
|
||||
import { useState, useEffect } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
|
||||
interface ExtendedPopoverItem extends PopoverItem {
|
||||
id: string;
|
||||
@@ -91,7 +93,7 @@ export const CameraSettingsModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className={twMerge(
|
||||
@@ -223,6 +225,6 @@ export const CameraSettingsModal = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionDialogue>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-6
@@ -10,13 +10,14 @@ import {
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
enableHotkeyInput,
|
||||
objectPanel as objectPanelSignals,
|
||||
timelineHeight,
|
||||
} from "../../signals";
|
||||
import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext";
|
||||
import { H5, InputVector } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
|
||||
import { InputVector } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { editorState } from "~/pages/PageEnigma/signals/engine";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { EditorStates } from "~/pages/PageEnigma/enums";
|
||||
@@ -222,7 +223,7 @@ export const ControlPanelSceneObject = () => {
|
||||
className={"flex flex-col gap-2 overflow-y-auto"}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<H5>Color</H5>
|
||||
<h5>Color</h5>
|
||||
<input
|
||||
className="h-0 w-0 cursor-pointer opacity-0"
|
||||
id={colorInputId}
|
||||
@@ -246,18 +247,20 @@ export const ControlPanelSceneObject = () => {
|
||||
></Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<H5>Location</H5>
|
||||
<h5>Location</h5>
|
||||
<InputVector
|
||||
x={localPosition.x.toString()}
|
||||
y={localPosition.y.toString()}
|
||||
z={localPosition.z.toString()}
|
||||
onChange={handlePositionChange}
|
||||
disabled={locked}
|
||||
enableHotkeyInput={enableHotkeyInput}
|
||||
disableHotkeyInput={disableHotkeyInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<H5>Rotation</H5>
|
||||
<h5>Rotation</h5>
|
||||
<InputVector
|
||||
x={localRotation.x.toString()}
|
||||
y={localRotation.y.toString()}
|
||||
@@ -265,6 +268,8 @@ export const ControlPanelSceneObject = () => {
|
||||
onChange={handleRotationChange}
|
||||
increment={1}
|
||||
disabled={locked}
|
||||
enableHotkeyInput={enableHotkeyInput}
|
||||
disableHotkeyInput={disableHotkeyInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -278,6 +283,8 @@ export const ControlPanelSceneObject = () => {
|
||||
z={localScale.z.toString()}
|
||||
onChange={handleScaleChange}
|
||||
disabled={locked}
|
||||
enableHotkeyInput={enableHotkeyInput}
|
||||
disableHotkeyInput={disableHotkeyInput}
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
faPlus,
|
||||
faUpRightAndDownLeftFromCenter,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ButtonIconSelect, Tooltip } from "~/components";
|
||||
import { Button } from "@storyteller/ui-button"
|
||||
|
||||
import { ButtonIconSelect } from "@storyteller/ui-button-icon-select";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { Tooltip } from "@storyteller/ui-tooltip";
|
||||
import { EngineContext } from "../../contexts/EngineContext";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { assetModalVisibleDuringDrag, assetModalVisible } from "../../signals";
|
||||
|
||||
+32
-40
@@ -4,9 +4,9 @@ import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
|
||||
import {
|
||||
faCheckSquare,
|
||||
faFile,
|
||||
faQuestion,
|
||||
faSquare,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import {
|
||||
EditorExpandedI,
|
||||
EngineContext,
|
||||
@@ -14,15 +14,9 @@ import {
|
||||
import { ToastTypes, getArtStyle } from "~/enums";
|
||||
import { scene, signalScene, authentication, addToast } from "~/signals";
|
||||
import { outlinerIsShowing } from "~/pages/PageEnigma/signals/outliner/outliner";
|
||||
|
||||
import {
|
||||
ButtonDropdown,
|
||||
Input,
|
||||
H4,
|
||||
} from "~/components";
|
||||
|
||||
import {Button} from "@storyteller/ui-button";
|
||||
|
||||
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { TestFeaturesButtons } from "./TestFeaturesButtons";
|
||||
import { LoadUserScenes } from "./LoadUserScenes";
|
||||
import { getCurrentLocationWithoutParams, isNumberString } from "~/utilities";
|
||||
@@ -40,14 +34,15 @@ import {
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
import { CameraAspectRatio } from "~/pages/PageEnigma/enums";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Help } from "./Help";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
|
||||
export const ControlsTopButtons = () => {
|
||||
useSignals();
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [helpIsShowing, setHelpIsShowing] = useState(false);
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const [sceneTitleInput, setSceneTitleInput] = useState<string>("");
|
||||
@@ -191,8 +186,7 @@ export const ControlsTopButtons = () => {
|
||||
label: "New scene",
|
||||
description: "Ctrl+N",
|
||||
onClick: () => {
|
||||
textInput.value = "Untitled New Scene";
|
||||
showWizard.value = "new_scene";
|
||||
setSceneTitleInput("Untitled New Scene");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -216,19 +210,21 @@ export const ControlsTopButtons = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
disabled:
|
||||
!(scene.value.isModified &&
|
||||
(scene.value.ownerToken === undefined || scene.value.ownerToken ===
|
||||
authentication.userInfo.value?.user_token)),
|
||||
disabled: !(
|
||||
scene.value.isModified &&
|
||||
(scene.value.ownerToken === undefined ||
|
||||
scene.value.ownerToken ===
|
||||
authentication.userInfo.value?.user_token)
|
||||
),
|
||||
// save scene should be disabled if there are no changes
|
||||
label: "Save scene",
|
||||
description: "Ctrl+S",
|
||||
dialogProps: {
|
||||
title: "Save Scene",
|
||||
content: (
|
||||
<H4>
|
||||
<h4>
|
||||
Save scene to <b>{scene.value.title}</b>?
|
||||
</H4>
|
||||
</h4>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
@@ -295,29 +291,25 @@ export const ControlsTopButtons = () => {
|
||||
Outliner
|
||||
</Button>
|
||||
|
||||
{/* <ButtonDialogue
|
||||
buttonProps={{
|
||||
variant: "secondary",
|
||||
label: "Help",
|
||||
className: "shadow-xl",
|
||||
icon: faQuestion,
|
||||
}}
|
||||
dialogProps={{
|
||||
className: "max-w-6xl w-auto",
|
||||
}}
|
||||
title={
|
||||
<>
|
||||
Help
|
||||
<span className="text-sm font-medium opacity-60">
|
||||
@%CURRENT_STORYTELLER_GIT_VERSION%
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
<Button
|
||||
icon={faQuestion}
|
||||
variant="secondary"
|
||||
className="shadow-xl"
|
||||
onClick={() => setHelpIsShowing(true)}
|
||||
>
|
||||
<Help />
|
||||
</ButtonDialogue> */}
|
||||
Help
|
||||
</Button>
|
||||
</div>
|
||||
<TestFeaturesButtons debug={false} />
|
||||
|
||||
<Modal
|
||||
isOpen={helpIsShowing}
|
||||
onClose={() => setHelpIsShowing(false)}
|
||||
title="Help"
|
||||
className="h-[500px] max-w-4xl"
|
||||
>
|
||||
<Help />
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { Label } from "~/components";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
|
||||
export const ShortcutsGroup = (props: {
|
||||
label: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user