many new components

This commit is contained in:
BFlat authored and Brandon Thomas committed 2023-10-18 04:15:11 -04:00
1 parent e4e7fa94b3
commit ba31bf47f9
14 files changed
+345 -23

No files matched your search

@@ -0,0 +1,44 @@
@import "scss/colours.scss";
.fy-accordion-item {
border: 1px solid rgba($white, 0.12);
border-radius: 5px;
overflow: hidden;
}
.fy-accordion-header {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
background-color: $secondary;
transition: all 0.2s ease;
&.open {
background-color: lighten($secondary, 5%);
}
}
.fy-accordion-content {
overflow: hidden;
background-color: lighten($panel, 4%);
&.open {
opacity: 1;
max-height: 100%;
visibility: visible;
}
}
.fy-accordion-content table {
width: 100%;
}
.caret-icon {
transition: transform 0.2s ease;
transform-origin: center;
}
.caret-rotated {
transform: rotate(180deg);
}
@@ -0,0 +1,79 @@
import React, { useState, useRef, useEffect } from "react";
import "./Accordion.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { animated, useSpring } from "@react-spring/web";
interface AccordionProps {
children: React.ReactNode;
}
interface AccordionItemProps {
title: string;
defaultOpen?: boolean;
children: React.ReactNode;
}
function AccordionItem({
title,
defaultOpen = false,
children,
}: AccordionItemProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const [contentHeight, setContentHeight] = useState<number | undefined>(
defaultOpen ? undefined : 0
);
const contentRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen && contentRef.current) {
setContentHeight(contentRef.current.scrollHeight);
} else {
setContentHeight(0);
}
}, [isOpen]);
const heightProps = useSpring({
height: `${contentHeight}px`,
from: {
height: defaultOpen ? "auto" : "0px",
config: { tension: 300, friction: 25 },
},
});
const contentOpacityProps = useSpring({
opacity: isOpen ? 1 : 0,
config: { tension: 300, friction: 25 },
});
return (
<div className="fy-accordion-item">
<div
className={`fy-accordion-header p-3 ${isOpen ? "open" : ""}`}
onClick={() => setIsOpen(!isOpen)}
>
{title}
<span className={`caret-icon ${isOpen ? "caret-rotated" : ""}`}>
<FontAwesomeIcon icon={faChevronDown} />
</span>
</div>
<animated.div style={heightProps}>
<animated.div
ref={contentRef}
style={contentOpacityProps}
className="fy-accordion-content"
>
{children}
</animated.div>
</animated.div>
</div>
);
}
function Accordion({ children }: AccordionProps) {
return <div className="d-flex flex-column gap-3">{children}</div>;
}
Accordion.Item = AccordionItem;
export default Accordion;
@@ -0,0 +1 @@
export { default } from "./Accordion";
@@ -0,0 +1,31 @@
@import "scss/colours.scss";
table {
margin-bottom: 0 !important;
}
td {
padding: 8px 16px !important;
}
.no-outer-border {
border: none !important;
}
.no-outer-border > thead > tr > th:first-child,
.no-outer-border > tbody > tr > td:first-child {
border-left: none !important;
}
.no-outer-border > thead > tr > th:last-child,
.no-outer-border > tbody > tr > td:last-child {
border-right: none !important;
}
.no-outer-border > tbody > tr:last-child > td {
border-bottom: none !important;
}
.data-table-property {
background-color: lighten($panel, 7%) !important;
}
@@ -0,0 +1,26 @@
import React from "react";
import "./DataTable.scss";
type Data = {
property: string;
value: string;
};
interface DataTableProps {
data: Data[];
}
export default function DataTable({ data }: DataTableProps) {
return (
<table className="table no-outer-border">
<tbody>
{data.map((row, index) => (
<tr key={index}>
<td className="data-table-property">{row.property}</td>
<td>{row.value}</td>
</tr>
))}
</tbody>
</table>
);
}
@@ -0,0 +1 @@
export { default } from "./DataTable";
@@ -0,0 +1,37 @@
@import "scss/colours.scss";
.skeleton {
background: linear-gradient(
-90deg,
rgba($white, 10%) 25%,
rgba(darken($white, 20%), 10%) 50%,
rgba($white, 10%) 75%
);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 0.25rem;
width: 100%;
height: 1em;
display: inline-block;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
.skeleton.rounded {
border-radius: 50%;
}
.skeleton.medium {
width: 40%;
}
.skeleton.short {
width: 20%;
}
@@ -0,0 +1,13 @@
import React from "react";
import "./Skeleton.scss";
interface SkeletonProps {
type?: "full" | "medium" | "short";
rounded?: boolean;
}
export default function Skeleton({ type = "full", rounded }: SkeletonProps) {
const skeletonClass = `skeleton ${type} ${rounded && "rounded"}`;
return <div className={skeletonClass}></div>;
}
@@ -0,0 +1 @@
export { default } from "./Skeleton";
@@ -6,7 +6,7 @@ import Button from "components/common/Button";
interface PageHeaderProps {
titleIcon?: IconDefinition;
title: string;
title: string | React.ReactNode;
subText: string | React.ReactNode;
full?: boolean;
showButton?: boolean;
@@ -1517,7 +1517,7 @@ textarea {
padding-left: var(--bs-gutter-x, 1rem);
margin-right: auto;
margin-left: auto;
max-width: 1080px;
max-width: 1200px;
border-radius: 0.5rem;
}
@@ -8,8 +8,8 @@ interface AudioComponentProps {
const AudioComponent: React.FC<AudioComponentProps> = ({ mediaData }) => {
return (
<div>
<audio controls>
<div className="w-100">
<audio className="w-100" controls>
<source src={mediaData.public_bucket_path} />
Your browser does not support the audio element.
</audio>
@@ -1,10 +1,19 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { Link, useParams } from "react-router-dom";
import AudioComponent from "./AudioComponent";
import VideoComponent from "./VideoComponent";
import ImageComponent from "./ImageComponent";
import MediaData from "./MediaDataTypes";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import Container from "components/common/Container";
import Panel from "components/common/Panel";
import PageHeader from "components/layout/PageHeader";
import Skeleton from "components/common/Skeleton";
import Button from "components/common/Button";
import { faCircleExclamation } from "@fortawesome/pro-solid-svg-icons";
import Accordion from "components/common/Accordion";
import DataTable from "components/common/DataTable";
import { Gravatar } from "@storyteller/components/src/elements/Gravatar";
interface MediaPageProps {
sessionWrapper: SessionWrapper;
@@ -19,7 +28,7 @@ let dummyMediaData = {
maybe_creator_user: {
user_token: "u_00XGM6M2TE4J9",
username: "hanashi",
display_name: "hanashi",
display_name: "Hanashi",
gravatar_hash: "c45b453fcb1d27b348504ae7f5d6a6c",
default_avatar: {
image_index: 1,
@@ -57,7 +66,12 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
case "audio":
return <AudioComponent mediaData={data} />;
case "video":
return <VideoComponent mediaData={data} />;
return (
<div className="ratio ratio-16x9">
<VideoComponent mediaData={data} />
</div>
);
case "image":
return <ImageComponent mediaData={data} />;
default:
@@ -65,13 +79,92 @@ export default function MediaPage({ sessionWrapper }: MediaPageProps) {
}
}
if (isLoading) return <div>Loading...</div>;
if (error || !mediaData) return <div>Media not found</div>;
if (isLoading)
return (
<Container type="panel">
<PageHeader
title={<Skeleton type="medium" />}
subText={<Skeleton type="short" />}
/>
<Panel padding={true}>
<div className="row">
<div className="col-12 col-md-8">
<h1 className="mb-0">
<Skeleton />
</h1>
</div>
<div className="col-12 col-md-4">
<h1 className="mb-0">
<Skeleton />
</h1>
</div>
</div>
</Panel>
</Container>
);
if (error || !mediaData)
return (
<Container type="panel">
<PageHeader
titleIcon={faCircleExclamation}
title="Media not found"
subText="This media does not exist or is private."
extension={
<div className="d-flex">
<Button label="Back to homepage" to="/" className="d-flex" />
</div>
}
/>
</Container>
);
const mediaDetails = [
{ property: "Type", value: mediaData.media_type },
{ property: "Created at", value: mediaData.created_at },
{ property: "Visibility", value: mediaData.creator_set_visibility },
{ property: "Model Used", value: "Model Name" },
];
return (
<div>
<h1>{mediaData?.token}</h1>
{renderMediaComponent(mediaData)}
</div>
<Container type="panel">
<PageHeader
title="Model name"
subText={mediaData.maybe_creator_user.display_name}
/>
<Panel padding={true}>
<div className="row g-4">
<div className="col-12 col-lg-8">
{renderMediaComponent(mediaData)}
</div>
<div className="col-12 col-lg-4">
<div className="d-flex">
<Gravatar
size={50}
username={mediaData.maybe_creator_user.display_name}
avatarIndex={
mediaData.maybe_creator_user.default_avatar.image_index
}
backgroundIndex={
mediaData.maybe_creator_user.default_avatar.color_index
}
/>
<div className="d-flex flex-column">
<Link
to={`/profile/${mediaData.maybe_creator_user.display_name}`}
>
{mediaData.maybe_creator_user.display_name}
</Link>
</div>
</div>
<Accordion>
<Accordion.Item title="Details" defaultOpen={true}>
<DataTable data={mediaDetails} />
</Accordion.Item>
</Accordion>
</div>
</div>
</Panel>
</Container>
);
}
@@ -4,15 +4,11 @@ interface VideoComponentProps {
mediaData: MediaData;
}
const VideoComponent: React.FC<VideoComponentProps> = ({ mediaData }) => {
export default function VideoComponent({ mediaData }: VideoComponentProps) {
return (
<div>
<video controls width="640" height="360">
<source src={mediaData.public_bucket_path} />
Your browser does not support the video element.
</video>
</div>
<video className="rounded" controls width="100%" height="auto">
<source src={mediaData.public_bucket_path} />
Your browser does not support the video element.
</video>
);
};
export default VideoComponent;
}