Merge pull request #2 from storytold/styling/tabs-css

tabs and layout styling
This commit is contained in:
ArEnSc authored and GitHub committed 2024-03-24 09:11:07 -04:00
commit c84789ac96
22 files changed
+703 -579

No files matched your search

+22 -22
View File
@@ -1,41 +1,41 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
export interface ButtonPropsI extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon?: IconDefinition;
variant?: "primary"|"secondary";
export interface ButtonPropsI
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon?: IconDefinition;
variant?: "primary" | "secondary";
}
export const Button = ({
icon,
children,
className : propsClassName,
variant = 'primary',
className: propsClassName,
variant = "primary",
...rest
}:ButtonPropsI)=>{
function getVariantClassNames(variant: string){
switch(variant){
case "secondary":{
}: ButtonPropsI) => {
function getVariantClassNames(variant: string) {
switch (variant) {
case "secondary": {
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
}
case "primary":
default:{
default: {
return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
}
}
};
const className = "text-sm font-semibold rounded-md px-3.5 py-2.5 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" + getVariantClassNames(variant)
+ (propsClassName ? ` ${propsClassName}` : "");
}
const className = twMerge(
"text-sm font-semibold rounded-md px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150",
getVariantClassNames(variant),
propsClassName,
);
return(
<button
className={className}
{...rest}
>
{icon &&
<FontAwesomeIcon className="mr-2" icon={icon}/>
}
return (
<button className={className} {...rest}>
{icon && <FontAwesomeIcon className="mr-2" icon={icon} />}
{children}
</button>
);
}
};
+20 -27
View File
@@ -1,35 +1,28 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import {
FontAwesomeIcon,
FontAwesomeIconProps
} from "@fortawesome/react-fontawesome";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
export interface ButtonIconPropsI extends React.ButtonHTMLAttributes<HTMLButtonElement> {
icon: IconDefinition;
iconClass?: string;
iconProps?: FontAwesomeIconProps
interface ButtonIconProps {
icon: IconDefinition;
fill?: boolean;
onClick: () => void;
}
export const ButtonIcon = ({
icon,
iconClass,
iconProps,
className : propsClassName,
...rest
}:ButtonIconPropsI)=>{
return(
<button
className={propsClassName}
{...rest}
>
{icon &&
<FontAwesomeIcon
{...iconProps}
className={iconClass}
icon={icon}
/>
}
onClick,
fill = false,
}: ButtonIconProps) => {
const className = twMerge(
"flex h-8 w-8 items-center justify-center rounded-lg transition-all duration-150",
fill
? "bg-ui-controls-button hover:bg-ui-controls-button/[0.75]"
: "bg-transparent hover:bg-ui-panel/[0.4]",
);
return (
<button className={className} onClick={onClick}>
<FontAwesomeIcon icon={icon} />
</button>
);
}
};
+49
View File
@@ -0,0 +1,49 @@
import { useState } from "react";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
interface Option {
value: string;
icon: IconDefinition;
}
interface ButtonIconSelectProps {
options: Option[];
onOptionChange?: (value: string) => void;
}
export function ButtonIconSelect({
options,
onOptionChange,
}: ButtonIconSelectProps) {
const [selectedOption, setSelectedOption] = useState<string>(
options[0].value,
);
const handleOptionChange = (value: string) => {
setSelectedOption(value);
if (onOptionChange) {
onOptionChange(value);
}
};
return (
<div className="flex space-x-1">
{options.map(({ value, icon }) => (
<button
key={value}
className={twMerge(
`flex h-8 w-8 items-center justify-center rounded-lg border-2 transition-all duration-150`,
selectedOption === value
? "border-brand-primary bg-ui-panel/[0.2]"
: "border-transparent hover:bg-ui-panel/[0.4]",
)}
onClick={() => handleOptionChange(value)}
>
<FontAwesomeIcon icon={icon} />
</button>
))}
</div>
);
}
+19 -16
View File
@@ -1,41 +1,44 @@
import { Link, LinkProps } from "@remix-run/react";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
interface LinkButtonProps extends LinkProps {
icon?: IconDefinition;
variant?: "primary"|"secondary";
}
variant?: "primary" | "secondary";
}
export const ButtonLink = ({
export const ButtonLink = ({
icon,
children,
className: propsClassName,
variant = "primary",
...rest
}:LinkButtonProps)=>{
}: LinkButtonProps) => {
//TODO: Duplicated from Button.tsx
function getVariantClassNames(variant: string){
switch(variant){
case "secondary":{
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
function getVariantClassNames(variant: string) {
switch (variant) {
case "secondary": {
return "bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
}
case "primary":
default:{
return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
default: {
return "bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
}
}
};
const className = "text-sm font-semibold rounded-md px-3.5 py-2.5 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" + getVariantClassNames(variant)
+ (propsClassName ? ` ${propsClassName}` : "");
}
const baseClassName =
"text-sm font-semibold rounded-md px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150";
const variantClassNames = getVariantClassNames(variant);
const className = twMerge(baseClassName, variantClassNames, propsClassName);
// END TODO
return(
return (
<Link {...rest}>
<button className={className}>
{icon && <FontAwesomeIcon className="mr-2" icon={icon}/>}
{icon && <FontAwesomeIcon className="mr-2" icon={icon} />}
{children}
</button>
</Link>
);
}
};
+2 -5
View File
@@ -1,9 +1,6 @@
import { Button } from "./Button";
import { ButtonIcon } from "./ButtonIcon";
import { ButtonLink } from "./ButtonLink";
import { ButtonIconSelect } from "./ButtonIconSelect";
export {
Button,
ButtonIcon,
ButtonLink,
};
export { Button, ButtonIcon, ButtonLink, ButtonIconSelect };
+14 -14
View File
@@ -1,20 +1,20 @@
import { useState } from 'react'
import { useState } from "react";
interface LowerPanelPropsI{
interface LowerPanelPropsI {
children: React.ReactNode;
}
export const LowerPanel =({
children,
}:LowerPanelPropsI)=>{
const [open, setOpen] = useState(false)
const handleOpen = ()=>{
export const LowerPanel = ({ children }: LowerPanelPropsI) => {
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
}
};
return (<>
<div className="fixed w-screen top-3/4 h-1/4 bg-ui-panel border-t border-ui-panel-border">
{children}
</div>
</>);
}
return (
<>
<div className="absolute bottom-0 min-h-[220px] w-screen border-t border-ui-panel-border bg-ui-panel">
{children}
</div>
</>
);
};
+50 -64
View File
@@ -1,67 +1,53 @@
import { useState } from "react";
import { Tabs } from "../Tabs";
import { Transition } from "@headlessui/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Fragment,
useState
} from 'react'
import { Dialog, Transition } from '@headlessui/react'
import {
faGears,
} from '@fortawesome/pro-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
faChevronLeft,
faChevronRight,
} from "@fortawesome/pro-solid-svg-icons";
import { Button } from '~/components/Button';
export const SidePanel = () => {
const [isVisible, setIsVisible] = useState(true);
import { SidePanelInner } from './SidePanelInner';
interface SidePanelPropsI{
title?: string;
children: React.ReactNode;
}
export const SidePanel =({
title,
children,
}:SidePanelPropsI)=>{
const [open, setOpen] = useState<boolean>(false);
const handleOpen = ()=> setOpen(true);
const handleClose = ()=>setOpen(false);
return (<>
<Button
className='fixed right-0 top-20 mt-2 mr-4'
onClick={handleOpen}
>
<FontAwesomeIcon className="fa-2xl -mx-2" icon={faGears} />
</Button>
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setOpen}>
<div className="fixed inset-0" />
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none inset-y-0 fixed top-16 mt-2 right-0 flex max-w-full pl-10 sm:pl-16">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen lg:max-w-md">
<SidePanelInner
title={title}
closeCallback={handleClose}
>
{children}
</SidePanelInner>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
</>);
}
return (
<div className="relative border-l border-l-ui-panel-border">
<button
onClick={() => setIsVisible(!isVisible)}
className="hover:bg-ui-controls-button absolute left-[-25px] top-1/2 flex rounded-l-lg bg-ui-controls px-2 py-3 align-middle text-sm text-white transition duration-150 ease-in-out"
>
<FontAwesomeIcon icon={isVisible ? faChevronRight : faChevronLeft} />
</button>
<Transition
show={isVisible}
enter="transition-transform duration-300"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition-transform duration-300"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Tabs
tabs={[
{
header: "Animation",
children: <p>Animation Tab</p>,
},
{
header: "Camera",
children: <p>Camera Tab</p>,
},
{
header: "Audio",
children: <p>Audio Tab</p>,
},
{
header: "Styling",
children: <p>Styling Tab</p>,
},
]}
/>
</Transition>
</div>
);
};
@@ -1,35 +1,29 @@
import {
useCallback,
useLayoutEffect,
useState
} from 'react';
import { Dialog } from '@headlessui/react'
import { useCallback, useLayoutEffect, useState } from "react";
import { Dialog } from "@headlessui/react";
import {
faXmark,
} from '@fortawesome/pro-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { TOP_BAR_HEIGHT } from '~/constants';
import { TOP_BAR_HEIGHT } from "~/constants";
export const SidePanelInner = ({
title,
children,
closeCallback
}:{
closeCallback,
}: {
title?: string;
children: React.ReactNode;
closeCallback: ()=>void;
})=>{
closeCallback: () => void;
}) => {
const [height, setHeight] = useState(0);
const handleWindowResize = useCallback(()=>{
if(window){
setHeight (Math.ceil(window.innerHeight * 3 / 4) - TOP_BAR_HEIGHT);
const handleWindowResize = useCallback(() => {
if (window) {
setHeight(Math.ceil((window.innerHeight * 3) / 4) - TOP_BAR_HEIGHT);
}
return 0;
},[])
}, []);
useLayoutEffect(()=>{
useLayoutEffect(() => {
handleWindowResize();
window.addEventListener("resize", handleWindowResize);
return () => {
@@ -37,19 +31,19 @@ export const SidePanelInner = ({
};
}, [handleWindowResize]);
return(
return (
<div
className="flex flex-col overflow-y-scroll bg-ui-panel py-6 shadow-xl border-y border-l border-ui-panel-border"
style={{height:height+'px'}}
className="flex flex-col overflow-y-scroll border-y border-l border-ui-panel-border bg-ui-panel py-6 shadow-xl"
style={{ height: height + "px" }}
>
<div className="px-4 sm:px-6">
<div className="flex items-start justify-between">
{!title && <span/>}
{title &&
{!title && <span />}
{title && (
<Dialog.Title className="text-base font-semibold leading-6 text-white">
{title}
</Dialog.Title>
}
)}
<div className="ml-3 flex h-7 items-center">
<button
type="button"
@@ -59,7 +53,7 @@ export const SidePanelInner = ({
{/* <span className="absolute -inset-2.5" /> */}
<span className="sr-only">Close panel</span>
<FontAwesomeIcon
className="h-6 w-6 mt-1 mx-1"
className="mx-1 mt-1 h-6 w-6"
aria-hidden="true"
icon={faXmark}
/>
@@ -67,9 +61,7 @@ export const SidePanelInner = ({
</div>
</div>
</div>
<div className="relative mt-6 flex-1 px-4 sm:px-6">
{children}
</div>
<div className="relative mt-6 flex-1">{children}</div>
</div>
);
}
};
+54
View File
@@ -0,0 +1,54 @@
import { Fragment, useState } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { faGears } from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button } from "~/components/Button";
import { SidePanelInner } from "./SidePanelInnerOld";
interface SidePanelPropsI {
title?: string;
children: React.ReactNode;
}
export const SidePanel = ({ title, children }: SidePanelPropsI) => {
const [open, setOpen] = useState<boolean>(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<>
<Button className="fixed right-0 top-20 mr-4 mt-2" onClick={handleOpen}>
<FontAwesomeIcon className="fa-2xl -mx-2" icon={faGears} />
</Button>
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setOpen}>
<div className="fixed inset-0" />
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 top-16 mt-2 flex max-w-full pl-10 sm:pl-16">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen lg:max-w-md">
<SidePanelInner title={title} closeCallback={handleClose}>
{children}
</SidePanelInner>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
};
+1
View File
@@ -0,0 +1 @@
export { SidePanel } from "./SidePanelOld";
+22 -33
View File
@@ -1,55 +1,44 @@
import { useState } from 'react'
import { Tab } from '@headlessui/react'
import { useState } from "react";
import { Tab } from "@headlessui/react";
import { twMerge } from "tailwind-merge";
import { joinClassNames } from '~/helpers';
export type Tab = {
export type TabType = {
header: string;
children: JSX.Element;
}
};
export const Tabs = ({
tabs: tabProps
}:{
tabs:Tab[]
}) =>{
const [tabs, setTabs] = useState(tabProps);
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="w-full max-w-md px-2 py-16 sm:px-0">
<div className="w-full max-w-md px-2 sm:px-0">
<Tab.Group>
<Tab.List className="flex space-x-1 rounded-xl bg-blue-900/20 p-1">
<Tab.List className="flex">
{tabs.map((tab, idx) => (
<Tab
key={idx}
className={({ selected }) =>
joinClassNames(
'w-full rounded-lg py-2.5 text-sm font-medium leading-5',
'ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2',
selected
? 'bg-white text-blue-700 shadow'
: 'text-blue-100 hover:bg-white/[0.12] hover:text-white'
)
}
className={({ selected }) => tabHeaderClassName(selected)}
>
{tab.header}
</Tab>
))}
</Tab.List>
<Tab.Panels className="mt-2">
<Tab.Panels>
{tabs.map((tab, idx) => (
<Tab.Panel
key={idx}
className={joinClassNames(
'rounded-xl bg-white p-3',
'ring-white/60 ring-offset-2 ring-offset-blue-400 focus:outline-none focus:ring-2'
)}
>
<Tab.Panel key={idx} className="p-4 text-white focus:outline-none">
{tab.children}
</Tab.Panel>
))}
</Tab.Panels>
</Tab.Group>
</div>
)
}
);
};
+44 -24
View File
@@ -1,32 +1,52 @@
import { useContext } from 'react';
import { useContext } from "react";
import { Button } from '~/components/Button'
import { faRightToBracket } from '@fortawesome/pro-solid-svg-icons'
import { TopBarInnerContext } from '~/contexts/TopBarInner';
import { Button } from "~/components/Button";
import {
faChevronLeft,
faRightToBracket,
} from "@fortawesome/pro-solid-svg-icons";
import { TopBarInnerContext } from "~/contexts/TopBarInner";
import { ButtonLink } from "~/components";
export const TopBar = () => {
return (
<header className="
fixed top-0 left-0 w-full
bg-ui-panel border-b border-ui-panel-border
">
<nav className="mx-auto flex max-w-full items-center justify-between p-4" aria-label="Global">
<div className="flex lg:flex-1">
<a href="#" className="-m-1.5 p-1.5">
<span className="sr-only">FakeYou</span>
<img className="h-10 w-auto ml-0.5" src="/resources/images/Storyteller-Logo-1.png" alt="Logo FakeYou StoryTeller.ai" />
</a>
<span className="w-4 lg:w-8"/>
<TopBarInner />
<header
className="fixed left-0 top-0 w-full
border-b border-ui-panel-border bg-ui-panel"
>
<nav
className="mx-auto flex max-w-full items-center justify-between p-4"
aria-label="Global"
>
<div className="grid w-full grid-cols-3 gap-2">
<div className="flex gap-4">
<a href="/" className="">
<span className="sr-only">Storyteller.ai</span>
<img
className="h-9 w-auto"
src="/resources/images/Storyteller-Logo-1.png"
alt="Logo FakeYou StoryTeller.ai"
/>
</a>
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
Back to Dashboard
</ButtonLink>
</div>
<div className="flex justify-center">
<TopBarInner />
</div>
<div className="flex justify-end">
<Button icon={faRightToBracket}>Login</Button>
</div>
</div>
<Button icon={faRightToBracket}>Login</Button>
</nav>
</header>
)
}
);
};
const TopBarInner = ()=>{
const { TopBarInner } = useContext(TopBarInnerContext) || {}
return(TopBarInner);
}
const TopBarInner = () => {
const { TopBarInner } = useContext(TopBarInnerContext) || {};
return TopBarInner;
};
+34 -52
View File
@@ -1,61 +1,50 @@
import {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import {
faChevronLeft
} from "@fortawesome/pro-solid-svg-icons";
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
import { Button } from '~/components/Button';
import { ButtonLink } from '~/components/ButtonLink';
import { TopBarHelmet } from '~/modules/TopBarHelmet';
import { Button } from "~/components/Button";
import { ButtonLink } from "~/components/ButtonLink";
import { TopBarHelmet } from "~/modules/TopBarHelmet";
import { SidePanel } from '~/modules/SidePanel';
import { SceneWithCube } from './SceneWithCube';
import { SidePanel } from "~/modules/SidePanelOld";
import { SceneWithCube } from "./SceneWithCube";
export const PageCube = () => {
const mountRef = useRef<HTMLDivElement | null>(null);
const sceneRef = useRef<SceneWithCube | null>(null);
const [{
x:rotationX,
y:rotationY
}, setRotations] = useState<{
x: number | undefined,
y:number | undefined
}>({x:undefined, y:undefined});
const [{ x: rotationX, y: rotationY }, setRotations] = useState<{
x: number | undefined;
y: number | undefined;
}>({ x: undefined, y: undefined });
const toggleStopped = ()=>{
if(sceneRef.current!==null){
sceneRef.current.stopped = !sceneRef.current.stopped;
}
const toggleStopped = () => {
if (sceneRef.current !== null) {
sceneRef.current.stopped = !sceneRef.current.stopped;
}
};
const listeningXYRotation = useCallback((
{x,y}:{x:number;y:number}
)=>{
setRotations({
x:Math.round(x*100)/100,
y:Math.round(y*100)/100,
})
},[]);
const listeningXYRotation = useCallback(
({ x, y }: { x: number; y: number }) => {
setRotations({
x: Math.round(x * 100) / 100,
y: Math.round(y * 100) / 100,
});
},
[],
);
useEffect(() => { //componentDidMount
useEffect(() => {
//componentDidMount
sceneRef.current = new SceneWithCube(listeningXYRotation);
mountRef.current?.appendChild(sceneRef.current.renderer.domElement);
sceneRef.current.renderer.setAnimationLoop(()=>{
sceneRef.current.renderer.setAnimationLoop(() => {
sceneRef.current?.update();
});
// Clean up
return () => {
if(sceneRef.current)
if (sceneRef.current)
mountRef.current?.removeChild(sceneRef.current.renderer.domElement);
};
}, []);
@@ -63,11 +52,7 @@ export const PageCube = () => {
return (
<div className="grid grid-cols-12 gap-2 ">
<TopBarHelmet>
<ButtonLink
to={"/"}
variant='secondary'
icon={faChevronLeft}
>
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
Back to Dashboard
</ButtonLink>
</TopBarHelmet>
@@ -76,15 +61,12 @@ export const PageCube = () => {
</div>
<div className="col-span-6 ">
<Button onClick={toggleStopped}> STOP </Button>
<p className='text-white'>Ration X: {rotationX ? rotationX : ""}</p>
<p className='text-white'>Ration Y: {rotationY ? rotationY : ""}</p>
<p className="text-white">Ration X: {rotationX ? rotationX : ""}</p>
<p className="text-white">Ration Y: {rotationY ? rotationY : ""}</p>
</div>
<SidePanel
title="Side Panel Title"
>
<SidePanel title="Side Panel Title">
<p className="text-white">Side Panel Context</p>
</SidePanel>
</div>
);
};
};
+102 -87
View File
@@ -1,118 +1,133 @@
import {
useCallback,
useEffect,
useRef,
} from 'react';
import { useCallback, useEffect, useRef, useState } from "react";
import {
faChevronLeft,
faWandSparkles,
} from "@fortawesome/pro-solid-svg-icons";
import { faWandSparkles } from "@fortawesome/pro-solid-svg-icons";
import {
Button,
ButtonLink,
} from '~/components';
import { ButtonDialogue } from '~/modules/ButtonDialogue';
import { TopBarHelmet } from '~/modules/TopBarHelmet/TopBarHelmet';
import { SidePanel } from '~/modules/SidePanel';
import { Tabs } from '~/modules/Tabs';
import { Controls3D } from './comps/Controls3D';
import { ControlsVideo } from './comps/ControlsVideo';
import { Timeline } from './comps/Timeline';
import Editor from './js/editor';
import { Button } from "~/components";
import { ButtonDialogue } from "~/modules/ButtonDialogue";
import { TopBarHelmet } from "~/modules/TopBarHelmet/TopBarHelmet";
import { Controls3D } from "./comps/Controls3D";
import { ControlsVideo } from "./comps/ControlsVideo";
import { Timeline } from "./comps/Timeline";
import { SidePanel } from "~/modules/SidePanel";
import Editor from "./js/editor";
export const PageEnigma = () => {
// const { setTopBarInner } = useContext(TopBarInnerContext) || {};
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const editorRef = useRef<Editor | null>(null);
const [timelineHeight, setTimelineHeight] = useState(0);
const timelineRef = useRef<HTMLDivElement | null>(null);
const editorCallback = useCallback(() => {
// handle editorCallback here
},[]);
}, []);
useEffect(() => {
//componentDidMount
useEffect(() => { //componentDidMount
if (editorRef.current == null) {
editorRef.current = new Editor();
editorRef.current.initialize();
}
}, []);
const handleButtonSave = ()=>{
const handleButtonSave = () => {
editorRef.current?.save();
}
};
const handleButtonCameraView = ()=> {
const handleButtonCameraView = () => {
editorRef.current?.change_camera_view();
}
};
const handleButtonPlayBack = ()=> {
const handleButtonPlayBack = () => {
editorRef.current?.start_playback();
}
};
return(
const updateTimelineHeight = useCallback(() => {
if (timelineRef.current) {
setTimelineHeight(timelineRef.current.offsetHeight);
}
}, []);
//for updating timeline/engine div height (for resizing)
useEffect(() => {
const observer = new ResizeObserver((entries) => {
for (let entry of entries) {
updateTimelineHeight();
}
});
if (timelineRef.current) {
observer.observe(timelineRef.current);
}
return () => observer.disconnect();
}, [updateTimelineHeight]);
return (
<div>
<TopBarHelmet>
<div className="flex grow justify-between">
<ButtonLink
to={"/"}
variant='secondary'
icon={faChevronLeft}
>
Back to Dashboard
</ButtonLink>
<Button
icon={faWandSparkles}
>
Generate Movie
</Button>
<span className="w-8"/>
</div>
<Button icon={faWandSparkles}>Generate Movie</Button>
</TopBarHelmet>
<canvas ref={canvasRef} id="video-scene" width="1280px" height="720px" />
<div className='fixed top-20 left-4'>
<div className="flex mt-2 gap-2">
<Button variant="secondary" onClick={handleButtonPlayBack}>Toggle Camera View</Button>
<Button variant="secondary" onClick={handleButtonSave}>Save Scene</Button>
<ButtonDialogue
buttonProps={{
variant: 'secondary',
label: 'Help'
}}
title="Help"
>
<p>Do you need help?</p>
<p>Ask Michael about this project</p>
<p>Ask Miles about ThreeJS</p>
<p>Ask Wil about React</p>
</ButtonDialogue>
<Controls3D/>
<ControlsVideo/>
<div style={{ height: "calc(100vh - 68px)" }}>
{/* Engine section/side panel */}
<div
className="flex"
style={{ height: `calc(100% - ${timelineHeight}px)` }}
>
<div className="relative w-full overflow-hidden bg-gray-400">
<canvas
ref={canvasRef}
id="video-scene"
width="1280px"
height="720px"
/>
{/* Top controls */}
<div className="absolute left-0 top-0 w-full">
<div className="grid grid-cols-3 gap-4">
<div className="flex gap-2 pl-3 pt-3">
<Button variant="secondary" onClick={handleButtonPlayBack}>
Toggle Camera View
</Button>
<Button variant="secondary" onClick={handleButtonSave}>
Save Scene
</Button>
<ButtonDialogue
buttonProps={{
variant: "secondary",
label: "Help",
}}
title="Help"
>
<p>Do you need help?</p>
<p>Ask Michael about this project</p>
<p>Ask Miles about ThreeJS</p>
<p>Ask Wil about React</p>
</ButtonDialogue>
</div>
<Controls3D />
</div>
</div>
{/* Bottom controls */}
<div className="absolute bottom-0 left-0 w-full">
<ControlsVideo />
</div>
</div>
{/* Side panel */}
<SidePanel />
</div>
{/* Timeline */}
<div className="min-h-[220px]" ref={timelineRef}>
<Timeline editorCurrent={editorRef.current} />
</div>
</div>
<SidePanel>
<Tabs tabs={[
{
header: 'Animation',
children: <p>Animation Tab</p>
},{
header: 'Camera',
children: <p>Camera Tab</p>
},{
header: 'Audio',
children: <p>Audio Tab</p>
},{
header: 'Styling',
children: <p>Styling Tab</p>
}
]}/>
</SidePanel>
<Timeline editorCurrent={editorRef.current}/>
</div>
);
}
};
@@ -1,76 +1,90 @@
import {
faArrowsRotate,
faArrowsUpDownLeftRight,
faCube,
faPlus,
faUpRightAndDownLeftFromCenter,
} from '@fortawesome/pro-solid-svg-icons';
} from "@fortawesome/pro-solid-svg-icons";
import {
fa3dCylinder,
fa3dTorus,
fa3dSphere,
} from '@awesome.me/kit-fde2be5eb0/icons/kit/custom';
} from "@awesome.me/kit-fde2be5eb0/icons/kit/custom";
import { ButtonIcon, ButtonIconSelect } from "~/components";
import { ButtonIcon } from '~/components';
export const Controls3D = ()=>{
export const Controls3D = () => {
const handlePlus = () => {
console.log('Controls 3D: Plus button clicked');
console.log("Controls 3D: Plus button clicked");
};
const handleCube = () => {
console.log("Controls 3D: Cube button clicked");
};
const handleCylinder = () => {
console.log('Controls 3D: Cylinder button clicked');
console.log("Controls 3D: Cylinder button clicked");
};
const handleTorus= () => {
console.log('Controls 3D: Torus Button clicked');
const handleTorus = () => {
console.log("Controls 3D: Torus Button clicked");
};
const handleSphere= () => {
console.log('Controls 3D: Sphere Button clicked');
const handleSphere = () => {
console.log("Controls 3D: Sphere Button clicked");
};
const handleMoveArrows = () => {
console.log('Controls 3D: Move Arrows clicked');
console.log("Controls 3D: Move Arrows clicked");
};
const handleRotateArrows = () => {
console.log('Controls 3D: Rotate Arrows clicked');
console.log("Controls 3D: Rotate Arrows clicked");
};
const handleZoomArrows = () => {
console.log('Controls 3D: Zoom Arrows clicked');
console.log("Controls 3D: Zoom Arrows clicked");
};
return(
<div
className="fixed left-1/2 -translate-x-1/2 bg-ui-panel border-b border-x border-ui-panel-border rounded-b-md px-4 py-2 text-white"
style={{top: "72px"}}
>
<div className='flex gap-4'>
<ButtonIcon
icon={faPlus}
onClick={handlePlus}
/>
<span className="w-0 h-5 border-l border-ui-panel-border" />
<ButtonIcon
icon={fa3dCylinder}
onClick={handleCylinder}
/>
<ButtonIcon
icon={fa3dTorus}
onClick={handleTorus}
/>
<ButtonIcon
icon={fa3dSphere}
onClick={handleSphere}
/>
<ButtonIcon
icon={faArrowsUpDownLeftRight}
onClick={handleMoveArrows}
/>
<ButtonIcon
icon={faArrowsRotate}
onClick={handleRotateArrows}
/>
<ButtonIcon
icon={faUpRightAndDownLeftFromCenter}
onClick={handleZoomArrows}
/>
const handleModeChange = (value: string) => {
switch (value) {
case "move":
handleMoveArrows();
break;
case "rotate":
handleRotateArrows();
break;
case "scale":
handleZoomArrows();
break;
default:
console.log("Unknown option");
}
};
const modes = [
{ value: "move", icon: faArrowsUpDownLeftRight },
{ value: "rotate", icon: faArrowsRotate },
{ value: "scale", icon: faUpRightAndDownLeftFromCenter },
];
return (
<div>
<div className="flex justify-center">
<div className="rounded-b-lg border-x border-b border-ui-panel-border bg-ui-controls p-2 text-white">
<div className="flex items-center justify-center gap-2">
<ButtonIcon icon={faPlus} onClick={handlePlus} fill={true} />
<span className="h-4 w-0 border-l border-white/[0.15]" />
<div className="flex gap-1">
<ButtonIcon icon={faCube} onClick={handleCube} />
<ButtonIcon icon={fa3dCylinder} onClick={handleCylinder} />
<ButtonIcon icon={fa3dTorus} onClick={handleTorus} />
<ButtonIcon icon={fa3dSphere} onClick={handleSphere} />
</div>
<span className="h-4 w-0 border-l border-white/[0.15]" />
<ButtonIconSelect
options={modes}
onOptionChange={handleModeChange}
/>
</div>
</div>
</div>
</div>
);
}
};
@@ -4,54 +4,38 @@ import {
faForwardFast,
faForwardStep,
faPlay,
} from '@fortawesome/pro-solid-svg-icons';
import { ButtonIcon } from '~/components';
} from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
export const ControlsVideo = ()=>{
export const ControlsVideo = () => {
const handleBackwardFast = () => {
console.log('Controls Video: Backward-Fast clicked');
console.log("Controls Video: Backward-Fast clicked");
};
const handleBackwardStep = () => {
console.log('Controls Video: Backward-Step clicked');
console.log("Controls Video: Backward-Step clicked");
};
const handlePlay= () => {
console.log('Controls Video: Play clicked');
const handlePlay = () => {
console.log("Controls Video: Play clicked");
};
const handleForwardStep = () => {
console.log('Controls Video: Forward-Step clicked');
console.log("Controls Video: Forward-Step clicked");
};
const handleForwardFast = () => {
console.log('Controls Video: Forward-Fast clicked');
console.log("Controls Video: Forward-Fast clicked");
};
return(
<div className="fixed top-3/4 left-1/2 -translate-x-1/2 -mt-10 bg-ui-panel border-t border-x border-ui-panel-border rounded-t-md px-6 py-2 text-white">
<div className='flex gap-6 content-center '>
<ButtonIcon
className="h-6"
icon={faBackwardFast}
onClick={handleBackwardFast}
/>
<ButtonIcon
className="h-6"
icon={faBackwardStep}
onClick={handleBackwardStep}
/>
<ButtonIcon
className="h-6"
icon={faPlay}
onClick={handlePlay}
/>
<ButtonIcon
className="h-6"
icon={faForwardStep}
onClick={handleForwardStep}
/>
<ButtonIcon
className="h-6"
icon={faForwardFast}
onClick={handleForwardFast}
/>
return (
<div>
<div className="flex justify-center">
<div className="rounded-t-lg border-x border-t border-ui-panel-border bg-ui-controls p-2 text-white">
<div className="flex content-center gap-2">
<ButtonIcon icon={faBackwardFast} onClick={handleBackwardFast} />
<ButtonIcon icon={faBackwardStep} onClick={handleBackwardStep} />
<ButtonIcon icon={faPlay} onClick={handlePlay} />
<ButtonIcon icon={faForwardStep} onClick={handleForwardStep} />
<ButtonIcon icon={faForwardFast} onClick={handleForwardFast} />
</div>
</div>
</div>
</div>
);
};
};
@@ -1,31 +1,32 @@
import { LowerPanel } from "~/modules/LowerPanel";
import {
Button,
} from '~/components';
import Editor from '../../js/editor';
import { Button } from "~/components";
import Editor from "../../js/editor";
export const Timeline = ({
editorCurrent
}:{
editorCurrent: Editor|null;
})=>{
const handleButtonLoad = ()=>{
editorCurrent,
}: {
editorCurrent: Editor | null;
}) => {
const handleButtonLoad = () => {
document.getElementById("load-upload")?.click();
}
const handleButtonRender = ()=>{
};
const handleButtonRender = () => {
editorCurrent?.togglePlayback();
}
const handleButtonPlay = ()=>{
};
const handleButtonPlay = () => {};
}
return(
return (
<LowerPanel>
<div className='h-10 w-full border-b border-ui-panel-border'></div>
<input style={{ display: 'none' }} type="file" id="load-upload" name="load-upload"></input>
<div className="h-10 w-full border-b border-ui-panel-border"></div>
<input
style={{ display: "none" }}
type="file"
id="load-upload"
name="load-upload"
></input>
<Button onClick={handleButtonLoad}>Load</Button>
<Button onClick={handleButtonRender}>Render</Button>
<Button onClick={handleButtonPlay}>Play</Button>
<Button onClick={handleButtonRender}>Render</Button>
<Button onClick={handleButtonPlay}>Play</Button>
</LowerPanel>
)
}
);
};
+38 -20
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState } from "react";
import { LinksFunction } from "@remix-run/deno";
import {
Links,
@@ -13,22 +13,39 @@ import tailwindCss from "./styles/tailwind.css?url";
import baseCss from "./styles/base.css?url";
import { TopBar } from "./modules/TopBar";
import { TopBarInnerContext } from '~/contexts/TopBarInner';
import { TopBarInnerContext } from "~/contexts/TopBarInner";
export const links : LinksFunction = () => [{
rel: "stylesheet",
href: normalizeCss,
},{
rel: "stylesheet",
href: tailwindCss,
},{
rel: "stylesheet",
href: baseCss,
}];
export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: normalizeCss,
},
{
rel: "stylesheet",
href: tailwindCss,
},
{
rel: "stylesheet",
href: baseCss,
},
{
rel: "preconnect",
href: "https://fonts.googleapis.com",
},
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossOrigin: "anonymous",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Fira+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap",
},
];
export default function App() {
const [topBarInnerComponent, setTopBarInnerComponent] = useState<JSX.Element | null>(null);
const [topBarInnerComponent, setTopBarInnerComponent] =
useState<JSX.Element | null>(null);
return (
<html lang="en">
@@ -38,13 +55,14 @@ export default function App() {
<Meta />
<Links />
</head>
<body className="bg-ui-background">
<TopBarInnerContext.Provider value={{
<body className="overflow-hidden bg-ui-background">
<TopBarInnerContext.Provider
value={{
TopBarInner: topBarInnerComponent,
setTopBarInner: setTopBarInnerComponent
}}>
<div className="topbar-spacer"/>
setTopBarInner: setTopBarInnerComponent,
}}
>
<div className="topbar-spacer" />
<Outlet />
<TopBar />
</TopBarInnerContext.Provider>
+7 -3
View File
@@ -1,4 +1,8 @@
.topbar-spacer{
height: 73px;
body {
font-family: "Fira Sans", sans-serif;
}
.topbar-spacer {
height: 68px;
width: 100vw;
}
}
+17 -6
View File
@@ -27,6 +27,7 @@
"isbot": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.2.2",
"three": "^0.162.0",
"uuid": "^9.0.1",
"webm-writer": "^1.0.0"
@@ -722,10 +723,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
"integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
"dev": true,
"version": "7.24.1",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz",
"integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -10600,8 +10600,7 @@
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
@@ -11720,6 +11719,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwind-merge": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.2.tgz",
"integrity": "sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==",
"dependencies": {
"@babel/runtime": "^7.24.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
+1
View File
@@ -33,6 +33,7 @@
"isbot": "^4.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.2.2",
"three": "^0.162.0",
"uuid": "^9.0.1",
"webm-writer": "^1.0.0"
+74 -64
View File
@@ -1,5 +1,5 @@
import type { Config } from 'tailwindcss';
import colors from 'tailwindcss/colors';
import type { Config } from "tailwindcss";
import colors from "tailwindcss/colors";
export default {
content: [
@@ -8,76 +8,86 @@ export default {
"./app/*.{js,jsx,ts,tsx}",
],
theme: {
// extend: {},
colors:{
transparent: 'transparent',
current: 'currentColor',
extend: {
fontFamily: {
"custom-font": ["Fira Sans", "sans-serif"],
},
},
colors: {
transparent: "transparent",
current: "currentColor",
'brand-primary': { //sunglo
DEFAULT: '#e66462', //=500
'50': '#fdf3f3',
'100': '#fce4e4',
'200': '#facfce',
'300': '#f6acab',
'400': '#ef7c7a',
'500': '#e66462',
'600': '#cf3533',
'700': '#ae2927',
'800': '#902624',
'900': '#782524',
'950': '#410f0e',
"brand-primary": {
//sunglo
DEFAULT: "#e66462", //=500
"50": "#fdf3f3",
"100": "#fce4e4",
"200": "#facfce",
"300": "#f6acab",
"400": "#ef7c7a",
"500": "#e66462",
"600": "#cf3533",
"700": "#ae2927",
"800": "#902624",
"900": "#782524",
"950": "#410f0e",
},
'brand-secondary': { //gunpowder
DEFAULT: '#39394c', //=950
'50': '#f5f6f9',
'100': '#e8eaf1',
'200': '#d7dbe6',
'300': '#bbc1d5',
'400': '#9aa2c0',
'500': '#8188b0',
'600': '#6f74a1',
'700': '#636492',
'800': '#545579',
'900': '#464762',
'950': '#39394c',
"brand-secondary": {
//gunpowder
DEFAULT: "#39394c", //=950
"50": "#f5f6f9",
"100": "#e8eaf1",
"200": "#d7dbe6",
"300": "#bbc1d5",
"400": "#9aa2c0",
"500": "#8188b0",
"600": "#6f74a1",
"700": "#636492",
"800": "#545579",
"900": "#464762",
"950": "#39394c",
},
danger: { //copper rust
DEFAULT: '#8f4951', //=700
'50': '#fbf5f5',
'100': '#f7ecec',
'200': '#efdcdc',
'300': '#e2c0bf',
'400': '#d19b9c',
'500': '#bd7679',
'600': '#a5595f',
'700': '#8f4951',
'800': '#743d45',
'900': '#64373f',
'950': '#361b1e',
danger: {
//copper rust
DEFAULT: "#8f4951", //=700
"50": "#fbf5f5",
"100": "#f7ecec",
"200": "#efdcdc",
"300": "#e2c0bf",
"400": "#d19b9c",
"500": "#bd7679",
"600": "#a5595f",
"700": "#8f4951",
"800": "#743d45",
"900": "#64373f",
"950": "#361b1e",
},
success:{ //apple
DEFAULT: '#40ad48', //=500
'50': '#f2fbf2',
'100': '#e2f6e3',
'200': '#c5edc7',
'300': '#98dd9d',
'400': '#64c46b',
'500': '#40ad48',
'600': '#2f8a35',
'700': '#286d2e',
'800': '#245728',
'900': '#1f4823',
'950': '#0c270f',
success: {
//apple
DEFAULT: "#40ad48", //=500
"50": "#f2fbf2",
"100": "#e2f6e3",
"200": "#c5edc7",
"300": "#98dd9d",
"400": "#64c46b",
"500": "#40ad48",
"600": "#2f8a35",
"700": "#286d2e",
"800": "#245728",
"900": "#1f4823",
"950": "#0c270f",
},
'ui-background': '#1a1a27',
'ui-panel': '#242433',
'ui-panel-border': '#5e6097',
"ui-background": "#1a1a27",
"ui-panel": "#242433",
"ui-panel-border": "#393948",
"ui-controls": "#39394D",
"ui-controls-button": "#676781",
gray: colors.gray,
white: colors.white
}
white: colors.white,
},
},
plugins: [],
} satisfies Config
} satisfies Config;