commit
252fb1b4f1
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
|
@ -36,8 +36,4 @@
|
||||||
font-size: var(--codex-font-size);
|
font-size: var(--codex-font-size);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,6 +37,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@codex/sdk-js": "@codex/sdk-js#master",
|
"@codex/sdk-js": "@codex/sdk-js#master",
|
||||||
|
"@tanstack/react-query": "^5.51.24",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
|
@ -53,6 +54,11 @@
|
||||||
"@storybook/test": "^8.2.9",
|
"@storybook/test": "^8.2.9",
|
||||||
"@tanstack/react-query": "^5.51.24",
|
"@tanstack/react-query": "^5.51.24",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
||||||
|
"@typescript-eslint/parser": "^7.15.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.7",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"glob": "^7.2.3",
|
"glob": "^7.2.3",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
|
@ -38,10 +38,8 @@ export function Alert({
|
||||||
style={style}
|
style={style}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<p>
|
<b className="alert-message">{variant} ! </b>
|
||||||
<b className="alert-message">{variant} ! </b>
|
<div>{message}</div>
|
||||||
</p>
|
|
||||||
<p>{message}</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Menu } from "lucide-react";
|
||||||
|
import "./appBar.css";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onExpand: () => void;
|
||||||
|
|
||||||
|
Right: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function AppBar({ onExpand, Right }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="appBar">
|
||||||
|
<div className="appBar-left">
|
||||||
|
<a className="appBar-burger" onClick={onExpand}>
|
||||||
|
<Menu size={"1.25rem"} />
|
||||||
|
</a>
|
||||||
|
<span>Home</span>
|
||||||
|
</div>
|
||||||
|
<div className="appBar-right">{Right}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
.appBar {
|
||||||
|
height: 40px;
|
||||||
|
justify-content: space-between;
|
||||||
|
border-bottom: 1px solid var(--codex-border-color);
|
||||||
|
view-transition-name: main-header;
|
||||||
|
display: flex;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appBar-burger {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--codex-color);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appBar,
|
||||||
|
.appBar-left,
|
||||||
|
.appBar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appBar-left,
|
||||||
|
.appBar-right {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
.appBar-burger {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ export function EmptyPlaceholder({ title, message, onRetry }: Props) {
|
||||||
<div className="emptyPlaceholder">
|
<div className="emptyPlaceholder">
|
||||||
<EmptyPlaceholderIcon className="emptyPlaceholder-icon" width={178} />
|
<EmptyPlaceholderIcon className="emptyPlaceholder-icon" width={178} />
|
||||||
<b className="emptyPlaceholder-title">{title}</b>
|
<b className="emptyPlaceholder-title">{title}</b>
|
||||||
<p className="emptyPlaceholder-message">{message} </p>
|
<div className="emptyPlaceholder-message">{message} </div>
|
||||||
|
|
||||||
{onRetry && (
|
{onRetry && (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function Failure({
|
||||||
<div className="failure">
|
<div className="failure">
|
||||||
<h1 className="failure-code">{code}</h1>
|
<h1 className="failure-code">{code}</h1>
|
||||||
<h2 className="failure-title">{title}</h2>
|
<h2 className="failure-title">{title}</h2>
|
||||||
<p className="failure-message">{message}</p>
|
<div className="failure-message">{message}</div>
|
||||||
{onClick && <Button label={button} onClick={onClick} />}
|
{onClick && <Button label={button} onClick={onClick} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
type Props = {
|
||||||
|
width?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LogoInverse({ width = 40 }: Props) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={width}
|
||||||
|
viewBox="0 0 40 40"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<g clip-path="url(#clip0_274_4287)">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M19.7001 32.7386C19.7705 32.7792 19.8502 32.8001 19.9306 32.8001C20.0111 32.8001 20.188 32.7225 20.188 32.7225L30.85 26.581C30.8555 26.5783 30.8632 26.5744 30.8706 26.5701C30.942 26.5295 31.0002 26.4707 31.0406 26.4008C31.0819 26.3302 31.1036 26.25 31.1036 26.1685C31.1036 26.1588 31.1032 26.1499 31.1028 26.1432V13.8597C31.1036 13.8486 31.1036 13.8387 31.1036 13.8337L31.1036 13.8324C31.1036 13.7507 31.0818 13.671 31.0413 13.6009C31.0008 13.5302 30.9421 13.4718 30.872 13.4313C30.8644 13.4268 30.8564 13.4225 30.8484 13.4186L20.1868 7.27756C20.179 7.27228 20.1714 7.26767 20.165 7.26389L20.1644 7.26354C20.0937 7.22255 20.0136 7.20151 19.9325 7.20151H19.9306C19.8495 7.20151 19.77 7.22295 19.701 7.26233C19.692 7.26733 19.6837 7.27257 19.6762 7.27763L9.01204 13.4202C9.00655 13.4229 8.99886 13.4269 8.99144 13.4311C8.92049 13.4717 8.86173 13.531 8.82123 13.6011C8.78067 13.6712 8.75879 13.7509 8.75879 13.8327C8.75879 13.8425 8.75919 13.8513 8.75956 13.858V26.1419C8.75876 26.153 8.75878 26.1629 8.75879 26.1679L8.75879 26.1692C8.75879 26.2508 8.78058 26.3312 8.82193 26.4018C8.86238 26.4712 8.92065 26.5296 8.98909 26.5693L8.99047 26.5701L8.99187 26.5708C9.00023 26.5754 9.00781 26.5793 9.01357 26.5822L19.6768 32.7241C19.6818 32.7275 19.6906 32.7332 19.7001 32.7386ZM30.8023 26.4503C30.7969 26.4533 30.7908 26.4564 30.7847 26.4594C30.788 26.4578 30.7915 26.4561 30.7946 26.4544C30.7973 26.4531 30.7999 26.4517 30.8023 26.4503ZM30.9649 26.1472C30.9651 26.1495 30.9652 26.152 30.9653 26.1545C30.9655 26.1589 30.9657 26.1636 30.9657 26.1685C30.9657 26.167 30.9657 26.1655 30.9656 26.164C30.9655 26.158 30.9652 26.1523 30.9649 26.1472ZM20.3983 26.9771L24.3592 29.2549L20.3951 31.538L20.3983 26.9771ZM15.5034 29.2548L19.4645 26.9769L19.4676 31.538L15.5034 29.2548ZM25.7528 23.8924L29.7137 26.1701L25.7497 28.4532L25.7528 23.8924ZM20.3984 25.3628V20.8084L24.3514 23.0856L20.3984 25.3628ZM15.0439 22.2784V17.724L18.9969 20.0012L15.0439 22.2784ZM20.3984 19.1932V14.6388L24.3514 16.916L20.3984 19.1932ZM19.4649 13.0247L15.5038 10.7468L19.468 8.4636L19.4649 13.0247ZM10.1491 26.1701L14.1131 28.4532L14.11 23.8921L10.1491 26.1701ZM24.8194 23.8927L24.8225 28.4527L20.8658 26.1705L24.8194 23.8927ZM15.0438 23.8927L15.0406 28.4527L18.9974 26.1705L15.0438 23.8927ZM26.2198 23.0856L30.178 20.8025V25.3687L26.2198 23.0856ZM9.68483 20.8025V25.3687L13.643 23.0856L9.68483 20.8025ZM15.5111 23.085L19.4644 25.3624V20.8076L15.5111 23.085ZM29.7125 20.0004L25.7529 17.7227V22.2781L29.7125 20.0004ZM10.1503 20.0008L14.1099 22.2785V17.7231L10.1503 20.0008ZM24.8189 17.7232V22.278L20.8656 20.0006L24.8189 17.7232ZM30.178 14.6329V19.1991L26.2198 16.916L30.178 14.6329ZM13.643 16.916L9.68483 19.1991V14.6329L13.643 16.916ZM19.4644 14.6384V19.1928L15.5114 16.9156L19.4644 14.6384ZM29.7141 13.8315L25.7497 11.5482L25.7528 16.1095L29.7141 13.8315ZM14.1135 11.5484L14.1104 16.1095L10.1491 13.8315L14.1135 11.5484ZM24.8225 11.5489L24.8194 16.1089L20.8658 13.8311L24.8225 11.5489ZM18.9974 13.8311L15.0438 16.1089L15.0406 11.5489L18.9974 13.8311ZM24.3596 10.7467L20.3951 8.46359L20.3983 13.0247L24.3596 10.7467Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_274_4287">
|
||||||
|
<rect width="40" height="40" fill="black" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
import { attributes } from "../../utils/attributes";
|
||||||
|
import "./menu.css";
|
||||||
|
import { LogoInverse } from "../Logo/LogoInverse";
|
||||||
|
import { ComponentType, useEffect } from "react";
|
||||||
|
import { Backdrop } from "../Backdrop/Backdrop";
|
||||||
|
|
||||||
|
export type MenuItemComponentProps = {
|
||||||
|
onClick: () => void;
|
||||||
|
className: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MenuItem =
|
||||||
|
| {
|
||||||
|
type: "separator";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "menu-item";
|
||||||
|
Component: ComponentType<MenuItemComponentProps>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "menu-title";
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
expanded: boolean;
|
||||||
|
|
||||||
|
onClose: () => void;
|
||||||
|
|
||||||
|
onOpen?: () => void;
|
||||||
|
|
||||||
|
items: MenuItem[];
|
||||||
|
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Menu({ expanded, onClose, onOpen, items, className }: Props) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (expanded && onOpen) {
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
}, [expanded, onOpen]);
|
||||||
|
|
||||||
|
const renderItem = (i: MenuItem, index: number) => {
|
||||||
|
switch (i.type) {
|
||||||
|
case "separator":
|
||||||
|
return <hr className="menu-item-separator" key={index}></hr>;
|
||||||
|
case "menu-title":
|
||||||
|
return (
|
||||||
|
<small className="menu-title" key={i.title}>
|
||||||
|
{i.title}
|
||||||
|
</small>
|
||||||
|
);
|
||||||
|
case "menu-item":
|
||||||
|
return (
|
||||||
|
<i.Component onClick={onClose} className="menu-item" key={index} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Backdrop onClose={onClose} open={expanded} />
|
||||||
|
|
||||||
|
<aside
|
||||||
|
className={`menu ${className}`}
|
||||||
|
{...attributes({ "aria-expanded": expanded })}
|
||||||
|
>
|
||||||
|
<div className="menu-container">
|
||||||
|
<div className="menu-header">
|
||||||
|
<LogoInverse width={50} />
|
||||||
|
<span className="menu-separator">|</span>
|
||||||
|
<span className="menu-name">Codex</span>
|
||||||
|
</div>
|
||||||
|
{items.map((item, index) => renderItem(item, index))}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
.menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--codex-background-secondary);
|
||||||
|
border-radius: var(--codex-border-radius);
|
||||||
|
transform: translatex(-500px);
|
||||||
|
transition: transform 0.25s;
|
||||||
|
position: fixed;
|
||||||
|
min-width: 200px;
|
||||||
|
z-index: 1;
|
||||||
|
view-transition-name: main-menu;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-container {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-backdrop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu[aria-expanded] {
|
||||||
|
transform: translatex(0);
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item,
|
||||||
|
.menu-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
padding: 0.75rem;
|
||||||
|
color: var(--codex-color);
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:hover,
|
||||||
|
.menu-item.active {
|
||||||
|
background-color: var(--codex-background-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item-separator {
|
||||||
|
border: 0.1px solid var(--codex-border-color);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
.menu {
|
||||||
|
transform: translatex(0px);
|
||||||
|
position: inherit;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { classnames } from "../../utils/classnames";
|
||||||
|
import "./networkIndicator.css";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
online: boolean;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function NetworkIndicator({ online, text }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="networkIndicator">
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
["networkIndicator-point"],
|
||||||
|
["networkIndicator-point--online", online],
|
||||||
|
["networkIndicator-point--offline", !online]
|
||||||
|
)}
|
||||||
|
></div>
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
.networkIndicator {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.networkIndicator-point {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation-duration: 3s;
|
||||||
|
animation-name: flash;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.networkIndicator-point--online {
|
||||||
|
background-color: var(--codex-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.networkIndicator-point--offline {
|
||||||
|
background-color: rgb(217, 53, 38);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flash {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { ReactNode, useState } from "react";
|
||||||
|
import { Menu, MenuItem, MenuItemComponentProps } from "../Menu/Menu";
|
||||||
|
import { AppBar } from "../AppBar/AppBar";
|
||||||
|
import { NetworkIndicator } from "../NetworkIndicator/NetworkIndicator";
|
||||||
|
import "./page.css";
|
||||||
|
import {
|
||||||
|
Home,
|
||||||
|
Star,
|
||||||
|
ShoppingBag,
|
||||||
|
Server,
|
||||||
|
Settings,
|
||||||
|
HelpCircle,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Page({ children }: Props) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onExpand = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Right = <NetworkIndicator online={true} text="Online" />;
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Home size={"1.25rem"} /> Dashboard
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Star size={"1.25rem"} /> Favorties
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-title",
|
||||||
|
title: "rent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<ShoppingBag size={"1.25rem"} /> Purchases
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-title",
|
||||||
|
title: "host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Server size={"1.25rem"} /> Availabilities
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Settings size={"1.25rem"} /> Settings
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<HelpCircle size={"1.25rem"} /> Help
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] satisfies MenuItem[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page">
|
||||||
|
<Menu expanded={open} onClose={onClose} items={items}></Menu>
|
||||||
|
|
||||||
|
<main className="page-main">
|
||||||
|
<AppBar onExpand={onExpand} Right={Right} />
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
.page {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-main {
|
||||||
|
flex: 1;
|
||||||
|
}
|
|
@ -118,14 +118,3 @@ export function Stepper({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <div className="stepper-success">
|
|
||||||
// <video src="/animations/success.webm" autoPlay />
|
|
||||||
// <p>
|
|
||||||
// <b>Success ! </b>
|
|
||||||
// </p>
|
|
||||||
// <p className="text-center">
|
|
||||||
// Your request has been submitted. Check your purchases list to get
|
|
||||||
// more information about the status.
|
|
||||||
// </p>
|
|
||||||
// </div>
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ export function Toast({
|
||||||
if (message) {
|
if (message) {
|
||||||
timeout.current = window.setTimeout(() => setMsg(""), duration);
|
timeout.current = window.setTimeout(() => setMsg(""), duration);
|
||||||
}
|
}
|
||||||
}, [message, time]);
|
}, [message, time, duration]);
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
if (timeout.current) {
|
if (timeout.current) {
|
||||||
|
|
|
@ -89,7 +89,7 @@ const defaultProvider = () =>
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
abort: () => {},
|
abort: () => {},
|
||||||
result: Promise.resolve({
|
result: Promise.resolve({
|
||||||
error: false as false,
|
error: false,
|
||||||
data: Date.now().toString(),
|
data: Date.now().toString(),
|
||||||
}),
|
}),
|
||||||
} satisfies UploadResponse);
|
} satisfies UploadResponse);
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
import { useRef, useState, useReducer, Reducer, useEffect } from "react";
|
import {
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useReducer,
|
||||||
|
Reducer,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
} from "react";
|
||||||
import { attributes } from "../utils/attributes";
|
import { attributes } from "../utils/attributes";
|
||||||
import { PrettyBytes } from "../utils/bytes";
|
import { PrettyBytes } from "../utils/bytes";
|
||||||
import { Toast } from "../Toast/Toast";
|
import { Toast } from "../Toast/Toast";
|
||||||
|
@ -161,20 +168,23 @@ export function UploadFile({
|
||||||
});
|
});
|
||||||
const init = useRef(false);
|
const init = useRef(false);
|
||||||
|
|
||||||
const onInternalSuccess = (cid: string) => {
|
const onInternalSuccess = useCallback(
|
||||||
worker.current?.terminate();
|
(cid: string) => {
|
||||||
|
worker.current?.terminate();
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ["cids"],
|
queryKey: ["cids"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (onSuccess) {
|
if (onSuccess) {
|
||||||
onSuccess(cid);
|
onSuccess(cid);
|
||||||
dispatch({ type: "reset" });
|
dispatch({ type: "reset" });
|
||||||
} else {
|
} else {
|
||||||
dispatch({ type: "completed", cid });
|
dispatch({ type: "completed", cid });
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[onSuccess, dispatch, queryClient]
|
||||||
|
);
|
||||||
|
|
||||||
const onProgress = (loaded: number, total: number) => {
|
const onProgress = (loaded: number, total: number) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -238,7 +248,7 @@ export function UploadFile({
|
||||||
console.info("running file !!");
|
console.info("running file !!");
|
||||||
mutateAsync(file);
|
mutateAsync(file);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [file, mutateAsync, onInternalSuccess, useWorker, provider]);
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
if (worker.current) {
|
if (worker.current) {
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { AppBar } from "../src/components/AppBar/AppBar";
|
||||||
|
import { fn } from "@storybook/test";
|
||||||
|
import { NetworkIndicator } from "../src/components/NetworkIndicator/NetworkIndicator";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/AppBar",
|
||||||
|
component: AppBar,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullwidth",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof AppBar>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
onExpand: fn(),
|
||||||
|
Right: <NetworkIndicator online={true} text="Online" />,
|
||||||
|
},
|
||||||
|
};
|
|
@ -18,14 +18,14 @@ type Story = StoryObj<typeof meta>;
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Hello",
|
title: "Hello",
|
||||||
children: React.createElement("p", {}, "Hello World !"),
|
children: React.createElement("div", {}, "Hello World !"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomStyle: Story = {
|
export const CustomStyle: Story = {
|
||||||
args: {
|
args: {
|
||||||
title: "Hello",
|
title: "Hello",
|
||||||
children: React.createElement("p", {}, "Hello World !"),
|
children: React.createElement("div", {}, "Hello World !"),
|
||||||
style: { "--codex-border-radius": "0px" },
|
style: { "--codex-border-radius": "0px" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
.menu-story {
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-noSticky {
|
||||||
|
transform: translatex(-1000px) !important;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-noSticky[aria-expanded] {
|
||||||
|
transform: translatex(0) !important;
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
import type { Meta } from "@storybook/react";
|
||||||
|
import {
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuItemComponentProps,
|
||||||
|
} from "../src/components/Menu/Menu";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { fn } from "@storybook/test";
|
||||||
|
import {
|
||||||
|
HelpCircle,
|
||||||
|
Home,
|
||||||
|
Server,
|
||||||
|
Settings,
|
||||||
|
ShoppingBag,
|
||||||
|
Star,
|
||||||
|
} from "lucide-react";
|
||||||
|
import "./Menu.stories.css";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Overlays/Menu",
|
||||||
|
component: Menu,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {},
|
||||||
|
args: {
|
||||||
|
onClose: fn(),
|
||||||
|
onOpen: fn(),
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Menu>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void;
|
||||||
|
onOpen: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (p: Props) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
p.onClose();
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onOpen = () => {
|
||||||
|
setOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Home size={"1.25rem"} /> Dashboard
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Star size={"1.25rem"} /> Favorties
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-title",
|
||||||
|
title: "rent",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<ShoppingBag size={"1.25rem"} /> Purchases
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-title",
|
||||||
|
title: "host",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Server size={"1.25rem"} /> Availabilities
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<Settings size={"1.25rem"} /> Settings
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "menu-item",
|
||||||
|
Component: (p: MenuItemComponentProps) => (
|
||||||
|
<a {...p}>
|
||||||
|
<HelpCircle size={"1.25rem"} /> Help
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] satisfies MenuItem[];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="menu-story">
|
||||||
|
<button onClick={onOpen}>Open menu</button>
|
||||||
|
<Menu
|
||||||
|
expanded={open}
|
||||||
|
onClose={onClose}
|
||||||
|
onOpen={p.onOpen}
|
||||||
|
items={items}
|
||||||
|
className="menu-noSticky"
|
||||||
|
></Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { NetworkIndicator } from "../src/components/NetworkIndicator/NetworkIndicator";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/NetworkIndicator",
|
||||||
|
component: NetworkIndicator,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof NetworkIndicator>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Online: Story = {
|
||||||
|
args: {
|
||||||
|
online: true,
|
||||||
|
text: "Online",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Offline: Story = {
|
||||||
|
args: {
|
||||||
|
online: false,
|
||||||
|
text: "Offline",
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { Page } from "../src/components/Page/Page";
|
||||||
|
import "./Page.stories.css";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Layouts/Page",
|
||||||
|
component: Page,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullwidth",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {},
|
||||||
|
} satisfies Meta<typeof Page>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
children: (
|
||||||
|
<span style={{ padding: "1rem", display: "block" }}>Hello World</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta } from "@storybook/react";
|
||||||
import { Stepper } from "../src/components/Stepper/Stepper";
|
import { Stepper } from "../src/components/Stepper/Stepper";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { fn } from "@storybook/test";
|
import { fn } from "@storybook/test";
|
||||||
|
@ -41,7 +41,7 @@ const Template = (p: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stepper
|
<Stepper
|
||||||
Body={() => React.createElement("p", {}, title)}
|
Body={() => React.createElement("div", {}, title)}
|
||||||
titles={titles}
|
titles={titles}
|
||||||
onChangeStep={onChangeStep}
|
onChangeStep={onChangeStep}
|
||||||
progress={progress}
|
progress={progress}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
import type { Meta } from "@storybook/react";
|
||||||
import { CircleCheck } from "lucide-react";
|
import { CircleCheck } from "lucide-react";
|
||||||
import { Toast } from "../src/components/Toast/Toast";
|
import { Toast } from "../src/components/Toast/Toast";
|
||||||
import { MouseEvent, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Overlays/Toast",
|
title: "Overlays/Toast",
|
||||||
|
@ -17,12 +17,11 @@ const meta = {
|
||||||
} satisfies Meta<typeof Toast>;
|
} satisfies Meta<typeof Toast>;
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof meta>;
|
|
||||||
|
|
||||||
const Template = () => {
|
const Template = () => {
|
||||||
const [time, setTime] = useState(0);
|
const [time, setTime] = useState(0);
|
||||||
|
|
||||||
const onClick = (_: MouseEvent) => setTime(Date.now());
|
const onClick = () => setTime(Date.now());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "2rem" }}>
|
<div style={{ padding: "2rem" }}>
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.upload {
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { UploadResponse } from "@codex/sdk-js";
|
import { UploadResponse } from "@codex/sdk-js";
|
||||||
import { Upload } from "../src/components/Upload/Upload";
|
import { Upload } from "../src/components/Upload/Upload";
|
||||||
import { fn } from "@storybook/test";
|
import { fn } from "@storybook/test";
|
||||||
|
import "./Upload.stories.css";
|
||||||
|
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "Advanced/Upload",
|
title: "Advanced/Upload",
|
||||||
|
@ -69,7 +70,7 @@ const slowProvider = () =>
|
||||||
window.clearInterval(timeout);
|
window.clearInterval(timeout);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
error: false as false,
|
error: false,
|
||||||
data: Date.now().toString(),
|
data: Date.now().toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ const { glob } = pkg;
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
worker: {
|
worker: {
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
external: ["@codex/sdk-js"],
|
external: ["@codex/sdk-js", "@tanstack/react-query"],
|
||||||
output: {
|
output: {
|
||||||
globals: {
|
globals: {
|
||||||
"@codex/sdk-js": "codex-sdk-js",
|
"@codex/sdk-js": "codex-sdk-js",
|
||||||
|
|
Loading…
Reference in New Issue