wip add side style

This commit is contained in:
Rizky 2024-01-06 22:33:56 +07:00
parent 27c1b3e5d0
commit 872c41ef19
21 changed files with 1919 additions and 0 deletions

View File

@ -3,6 +3,7 @@ import { EDGlobal, IMeta, active } from "../../logic/ed-global";
import { useGlobal } from "web-utils";
import { IItem } from "../../../../utils/types/item";
import { EdSidePropComp } from "./prop-master";
import { EdStyleAll } from "./style/side-all";
export const EdSideStyle: FC<{ meta: IMeta }> = ({ meta }) => {
const p = useGlobal(EDGlobal, "EDITOR");
@ -32,6 +33,7 @@ export const EdSideStyle: FC<{ meta: IMeta }> = ({ meta }) => {
</div>
)}
</div>
<EdStyleAll />
</div>
);
};

View File

@ -0,0 +1,654 @@
import { FC } from "react";
import { useLocal } from "web-utils";
import { IItem } from "../../../../../../utils/types/item";
import { FNLayout } from "../../../../../../utils/types/meta-fn";
import { ISection } from "../../../../../../utils/types/section";
import { IText } from "../../../../../../utils/types/text";
import { Popover } from "../../../../../../utils/ui/popover";
import { Tooltip } from "../../../../../../utils/ui/tooltip";
import { BoxSep } from "../ui/BoxSep";
import { Button } from "../ui/Button";
import { FieldBtnRadio } from "../ui/FieldBtnRadio";
import { FieldNumUnit } from "../ui/FieldNumUnit";
import { LayoutPacked } from "../ui/LayoutPacked";
import { LayoutSpaced } from "../ui/LayoutSpaced";
import { responsiveVal } from "../tools/responsive-val";
type AutoLayoutUpdate = {
layout: FNLayout;
};
export const PanelAutoLayout: FC<{
value: ISection | IItem | IText;
mode: "desktop" | "mobile";
update: <T extends keyof AutoLayoutUpdate>(
key: T,
val: AutoLayoutUpdate[T]
) => void;
}> = ({ value, update, mode }) => {
const local = useLocal({ lastGap: 0, open: false });
const layout = responsiveVal<FNLayout>(value, "layout", mode, {
dir: "col",
align: "top-left",
gap: 0,
wrap: "flex-nowrap",
});
return (
<>
<div className="flex items-stretch justify-between">
<div className="flex flex-col items-stretch justify-around w-[125px] space-y-[5px]">
<div
className={cx(
"flex flex-row space-x-1 items-center"
// css`
// .fg:hover .other {
// opacity: 1 !important;
// }
// `
)}
>
<div
className={cx(
`flex flex-row space-x-1 border ${
false
? "border-transparent hover:border-slate-300"
: "border-slate-300"
} fg`,
css`
padding-left: 1px;
`
)}
>
<BoxSep
className={cx(
"justify-between my-0.5",
css`
padding: 0px;
& > button {
min-width: 0px;
flex: 1;
padding: 2px 4px;
}
`
)}
>
<FieldBtnRadio
items={{
col: (
<Tooltip content="Direction: Column">
<div>
<TapDown />
</div>
</Tooltip>
),
row: (
<Tooltip content="Direction: Row">
<div>
<TapRight />
</div>
</Tooltip>
),
// wrap: (
// <Tooltip content="Wrap">
// <div>
// <Wrap />
// </div>
// </Tooltip>
// ),
}}
value={layout.dir}
disabled={false}
update={(dir) => {
let align = layout.align;
if (layout.gap === "auto") {
if (dir.startsWith("col") && align === "top")
align = "left";
if (dir.startsWith("col") && align === "bottom")
align = "right";
if (dir.startsWith("row") && align === "left")
align = "top";
if (dir.startsWith("row") && align === "right")
align = "bottom";
}
update("layout", { ...layout, align, dir });
local.render();
}}
/>
</BoxSep>
<Popover
open={local.open}
onOpenChange={(open) => {
local.open = open;
local.render();
}}
backdrop={false}
autoFocus={false}
popoverClassName="rounded-md p-2 text-sm bg-white shadow-2xl border border-slate-300"
content={
<div className="flex flex-col">
<p>Direction</p>
<BoxSep
className={cx(
"justify-between",
css`
padding: 0px;
& > button {
min-width: 0px;
flex: 1;
padding: 2px 4px;
}
`
)}
>
<FieldBtnRadio
items={{
col: (
<Tooltip content="Direction: Column">
<div>
<TapDown />
</div>
</Tooltip>
),
"col-reverse": (
<Tooltip content="Direction: Column Reverse">
<div className="rotate-180">
<TapDown />
</div>
</Tooltip>
),
row: (
<Tooltip content="Direction: Row">
<div>
<TapRight />
</div>
</Tooltip>
),
"row-reverse": (
<Tooltip content="Direction: Row Reverse">
<div className="rotate-180">
<TapRight />
</div>
</Tooltip>
),
}}
value={layout.dir}
disabled={false}
update={(dir) => {
let align = layout.align;
if (layout.gap === "auto") {
if (dir.startsWith("col") && align === "top")
align = "left";
if (dir.startsWith("col") && align === "bottom")
align = "right";
if (dir.startsWith("row") && align === "left")
align = "top";
if (dir.startsWith("row") && align === "right")
align = "bottom";
}
update("layout", { ...layout, align, dir });
local.render();
}}
/>
</BoxSep>
</div>
}
>
<div
onClick={() => {
local.open = !local.open;
local.render();
}}
className={`${
false && "opacity-0"
} h-full px-1 flex flew-row items-center justify-center border-l border-l-slate-300 hover:bg-blue-100 bg-white other cursor-pointer`}
>
<Down />
</div>
</Popover>
</div>
<Tooltip
content={layout.wrap === "flex-wrap" ? "Flex Wrap" : "No Wrap"}
>
<Button
className={cx(
"flex-1",
css`
width: 28px !important;
min-width: 0px !important;
margin-left: 3px !important;
padding: 0px 5px !important;
height: 28px !important;
`,
layout.dir.startsWith("col") && "rotate-90"
)}
onClick={() => {
update("layout", {
...layout,
wrap:
layout.wrap === "flex-wrap" ? "flex-nowrap" : "flex-wrap",
});
}}
>
{layout.wrap !== "flex-wrap" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="none"
viewBox="0 0 436 128"
>
<path
fill={"currentColor"}
d="M38.4 0A38.4 38.4 0 000 38.4v51.2A38.4 38.4 0 0038.4 128h51.2A38.401 38.401 0 00128 89.6V38.4A38.402 38.402 0 0089.6 0H38.4zM25.6 38.4a12.8 12.8 0 0112.8-12.8h51.2a12.8 12.8 0 0112.8 12.8v51.2a12.802 12.802 0 01-12.8 12.8H38.4a12.802 12.802 0 01-12.8-12.8V38.4zm128 0A38.402 38.402 0 01192 0h51.2a38.4 38.4 0 0138.4 38.4v51.2a38.401 38.401 0 01-38.4 38.4H192a38.402 38.402 0 01-38.4-38.4V38.4zM192 25.6a12.8 12.8 0 00-12.8 12.8v51.2a12.802 12.802 0 0012.8 12.8h51.2A12.8 12.8 0 00256 89.6V38.4a12.802 12.802 0 00-12.8-12.8H192zm115.2 12.8A38.402 38.402 0 01345.6 0h51.2a38.402 38.402 0 0138.4 38.4v51.2a38.401 38.401 0 01-38.4 38.4h-51.2a38.403 38.403 0 01-38.4-38.4V38.4zm38.4-12.8a12.8 12.8 0 00-12.8 12.8v51.2a12.802 12.802 0 0012.8 12.8h51.2a12.8 12.8 0 0012.8-12.8V38.4a12.802 12.802 0 00-12.8-12.8h-51.2z"
></path>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 20 20"
>
<path
fill={"currentColor"}
d="M3 4a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 003 9h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 005 4H3zm-.5 1.5A.5.5 0 013 5h2a.5.5 0 01.5.5v2A.5.5 0 015 8H3a.5.5 0 01-.5-.5v-2zM3 10a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 003 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 005 10H3zm-.5 1.5A.5.5 0 013 11h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5H3a.5.5 0 01-.5-.5v-2zm5-6A1.5 1.5 0 019 4h2a1.5 1.5 0 011.5 1.5v2A1.5 1.5 0 0111 9H9a1.5 1.5 0 01-1.5-1.5v-2zM9 5a.5.5 0 00-.5.5v2A.5.5 0 009 8h2a.5.5 0 00.5-.5v-2A.5.5 0 0011 5H9zm0 5a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 009 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 0011 10H9zm-.5 1.5A.5.5 0 019 11h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5H9a.5.5 0 01-.5-.5v-2zm5-6A1.5 1.5 0 0115 4h2a1.5 1.5 0 011.5 1.5v2A1.5 1.5 0 0117 9h-2a1.5 1.5 0 01-1.5-1.5v-2zM15 5a.5.5 0 00-.5.5v2a.5.5 0 00.5.5h2a.5.5 0 00.5-.5v-2A.5.5 0 0017 5h-2zm0 5a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 0015 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 0017 10h-2zm-.5 1.5a.5.5 0 01.5-.5h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 01-.5-.5v-2z"
></path>
</svg>
)}
</Button>
</Tooltip>
</div>
<div className="flex items-stretch justify-between">
<Tooltip content="Gap Size">
<div className="border border-gray-300 max-w-[56px] h-[25px]">
{layout.gap !== "auto" ? (
<FieldNumUnit
positiveOnly
hideUnit
icon={<GapIcon layout={layout} />}
value={layout.gap + "px"}
update={(val) => {
update("layout", {
...layout,
gap: parseInt(val.replaceAll("px", "")),
});
}}
/>
) : (
<BoxSep className="flex text-xs flex-1 bg-white">
<GapIcon layout={layout} />
<div
className={cx(css`
width: 90px;
flex: 1;
font-size: 12px;
color: #999;
`)}
>
Auto
</div>
</BoxSep>
)}
</div>
</Tooltip>
<Tooltip
content={
<>
Gap Mode:
<br /> Space Between / Packed
</>
}
>
<Button
className={cx(
"flex-1",
css`
width: 30px;
min-width: 0px !important;
margin-left: 5px !important;
padding: 0 5px !important;
background: ${layout.gap === "auto"
? "#3c82f6"
: "#fff"} !important;
border-color: ${layout.gap === "auto"
? "#7baeff"
: "#d1d1d1"} !important;
`
)}
onClick={() => {
if (layout.gap !== "auto") {
local.lastGap = layout.gap;
}
const gap = layout.gap !== "auto" ? "auto" : local.lastGap;
let align = layout.align;
if (gap === "auto") {
if (align.includes("-")) {
align = "center";
}
} else {
if (align === "top" || align === "bottom") {
align = "top-left";
}
}
update("layout", {
...layout,
align,
gap,
});
}}
>
{layout.dir.startsWith("row") && (
<svg
width={14}
height={6}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.5 3a.375.375 0 0 1-.375.375H2.283l1.23 1.237a.36.36 0 0 1 0 .526.36.36 0 0 1-.526 0L1.112 3.263a.36.36 0 0 1 0-.526L2.987.863a.36.36 0 0 1 .526 0 .36.36 0 0 1 0 .524l-1.23 1.238h2.842A.375.375 0 0 1 5.5 3Zm7.387-.263L11.012.863a.36.36 0 0 0-.524 0 .359.359 0 0 0 0 .524l1.23 1.238H8.874a.375.375 0 0 0 0 .75h2.842l-1.23 1.237a.359.359 0 0 0 0 .526.36.36 0 0 0 .525 0l1.875-1.875a.359.359 0 0 0 0-.526Z"
fill={layout.gap === "auto" ? "#fff" : "#000"}
/>
</svg>
)}
{layout.dir.startsWith("col") && (
<svg
width={6}
height={14}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3 8.5a.375.375 0 0 1 .375.375v2.842l1.237-1.23a.359.359 0 0 1 .526 0 .36.36 0 0 1 0 .525l-1.875 1.875a.359.359 0 0 1-.526 0L.863 11.012a.36.36 0 0 1 0-.524.359.359 0 0 1 .524 0l1.238 1.23V8.874A.375.375 0 0 1 3 8.5Zm-.263-7.387L.863 2.988a.36.36 0 0 0 0 .525.36.36 0 0 0 .524 0l1.238-1.23v2.842a.375.375 0 0 0 .75 0V2.283l1.237 1.23a.36.36 0 0 0 .526 0 .36.36 0 0 0 0-.525L3.263 1.113a.36.36 0 0 0-.526 0Z"
fill={layout.gap === "auto" ? "#fff" : "#000"}
/>
</svg>
)}
</Button>
</Tooltip>
<Tooltip
content={
<>
Align Items:
<br /> Stretch / Normal
</>
}
>
<Button
className={cx(
"flex-1",
css`
width: 30px;
min-width: 0px !important;
margin-left: 5px !important;
padding: 0 5px !important;
background: ${layout.align === "stretch"
? "#3c82f6"
: "#fff"} !important;
border-color: ${layout.align === "stretch"
? "#7baeff"
: "#d1d1d1"} !important;
color: ${layout.align === "stretch"
? "white"
: "black"} !important;
`
)}
onClick={() => {
let align = layout.align;
if (layout.align !== "stretch") {
align = "stretch";
} else {
align = "center";
}
update("layout", {
...layout,
align,
});
}}
>
{layout.align === "stretch" && (
<>
{layout.dir.startsWith("row") ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M1 .5a.5.5 0 01.5-.5h12a.5.5 0 010 1h-12A.5.5 0 011 .5zM9 14V1H6v13H1.5a.5.5 0 000 1h12a.5.5 0 000-1H9z"
clipRule="evenodd"
></path>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M14.5 1a.5.5 0 00-.5.5V6H1V1.5a.5.5 0 10-1 0v12a.5.5 0 001 0V9h13v4.5a.5.5 0 101 0v-12a.5.5 0 00-.5-.5z"
clipRule="evenodd"
></path>
</svg>
)}
</>
)}
{layout.align !== "stretch" && (
<>
{layout.dir.startsWith("row") ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M7 1a1 1 0 00-1 1v5H1.5a.5.5 0 000 1H6v5a1 1 0 001 1h1a1 1 0 001-1V8h4.5a.5.5 0 000-1H9V2a1 1 0 00-1-1H7z"
clipRule="evenodd"
></path>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
fill="none"
viewBox="0 0 15 15"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M2 6a1 1 0 00-1 1v1a1 1 0 001 1h5v4.5a.5.5 0 001 0V9h5a1 1 0 001-1V7a1 1 0 00-1-1H8V1.5a.5.5 0 00-1 0V6H2z"
clipRule="evenodd"
></path>
</svg>
)}
</>
)}
</Button>
</Tooltip>
</div>
{/* <div className={cx("flex flex-row justify-between text-xs ")}>
<BoxSep
className={cx(
"justify-between",
css`
padding: 0px;
& > button {
min-width: 0px;
flex: 1;
padding: 2px 4px;
}
`
)}
>
<FieldBtnRadio
items={{
"flex-wrap": (
<Tooltip content="Flex wrap">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
>
<path d="M3 4a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 003 9h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 005 4H3zm-.5 1.5A.5.5 0 013 5h2a.5.5 0 01.5.5v2A.5.5 0 015 8H3a.5.5 0 01-.5-.5v-2zM3 10a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 003 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 005 10H3zm-.5 1.5A.5.5 0 013 11h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5H3a.5.5 0 01-.5-.5v-2zm5-6A1.5 1.5 0 019 4h2a1.5 1.5 0 011.5 1.5v2A1.5 1.5 0 0111 9H9a1.5 1.5 0 01-1.5-1.5v-2zM9 5a.5.5 0 00-.5.5v2A.5.5 0 009 8h2a.5.5 0 00.5-.5v-2A.5.5 0 0011 5H9zm0 5a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 009 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 0011 10H9zm-.5 1.5A.5.5 0 019 11h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5H9a.5.5 0 01-.5-.5v-2zm5-6A1.5 1.5 0 0115 4h2a1.5 1.5 0 011.5 1.5v2A1.5 1.5 0 0117 9h-2a1.5 1.5 0 01-1.5-1.5v-2zM15 5a.5.5 0 00-.5.5v2a.5.5 0 00.5.5h2a.5.5 0 00.5-.5v-2A.5.5 0 0017 5h-2zm0 5a1.5 1.5 0 00-1.5 1.5v2A1.5 1.5 0 0015 15h2a1.5 1.5 0 001.5-1.5v-2A1.5 1.5 0 0017 10h-2zm-.5 1.5a.5.5 0 01.5-.5h2a.5.5 0 01.5.5v2a.5.5 0 01-.5.5h-2a.5.5 0 01-.5-.5v-2z"></path>
</svg>
</Tooltip>
),
"Flex nowrap": (
<Tooltip content="Direction: Column Reverse">
<div className="text-lg text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
fill="none"
viewBox="0 0 436 128"
>
<path
fill="#000"
d="M38.4 0A38.4 38.4 0 000 38.4v51.2A38.4 38.4 0 0038.4 128h51.2A38.401 38.401 0 00128 89.6V38.4A38.402 38.402 0 0089.6 0H38.4zM25.6 38.4a12.8 12.8 0 0112.8-12.8h51.2a12.8 12.8 0 0112.8 12.8v51.2a12.802 12.802 0 01-12.8 12.8H38.4a12.802 12.802 0 01-12.8-12.8V38.4zm128 0A38.402 38.402 0 01192 0h51.2a38.4 38.4 0 0138.4 38.4v51.2a38.401 38.401 0 01-38.4 38.4H192a38.402 38.402 0 01-38.4-38.4V38.4zM192 25.6a12.8 12.8 0 00-12.8 12.8v51.2a12.802 12.802 0 0012.8 12.8h51.2A12.8 12.8 0 00256 89.6V38.4a12.802 12.802 0 00-12.8-12.8H192zm115.2 12.8A38.402 38.402 0 01345.6 0h51.2a38.402 38.402 0 0138.4 38.4v51.2a38.401 38.401 0 01-38.4 38.4h-51.2a38.403 38.403 0 01-38.4-38.4V38.4zm38.4-12.8a12.8 12.8 0 00-12.8 12.8v51.2a12.802 12.802 0 0012.8 12.8h51.2a12.8 12.8 0 0012.8-12.8V38.4a12.802 12.802 0 00-12.8-12.8h-51.2z"
></path>
</svg>
</div>
</Tooltip>
),
}}
value={layout.wrap}
disabled={false}
update={(dir) => {
update("layout", { ...layout, wrap: dir as any });
}}
/>
</BoxSep>
</div> */}
</div>
{layout.gap === "auto" ? (
<LayoutSpaced
dir={layout.dir}
align={layout.align}
onChange={(align) => {
update("layout", { ...layout, align });
}}
/>
) : (
<LayoutPacked
dir={layout.dir}
align={layout.align}
onChange={(align) => {
update("layout", { ...layout, align });
}}
/>
)}
</div>
</>
);
};
const Down = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 48 48"
>
<path
fill="none"
stroke="#000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="4"
d="M36 18L24 30 12 18"
></path>
</svg>
);
const Wrap = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 24 24"
>
<path d="M4 20V4h2v16H4zm14 0V4h2v16h-2zm-7.4-2.45L7.05 14l3.55-3.525 1.4 1.4L10.875 13H13q.825 0 1.413-.588T15 11q0-.825-.588-1.413T13 9H7V7h6q1.65 0 2.825 1.175T17 11q0 1.65-1.175 2.825T13 15h-2.125L12 16.125l-1.4 1.425z"></path>
</svg>
);
const TapDown = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="none"
viewBox="0 0 20 20"
>
<path
d="M14 6a4 4 0 0 1-2.5 3.7V8.6a3 3 0 1 0-3 0v1.1A4 4 0 1 1 14 6ZM9.65 17.85c.2.2.5.2.7 0l3-3a.5.5 0 0 0-.7-.7l-2.15 2.14V5.5a.5.5 0 0 0-1 0v10.8l-2.15-2.15a.5.5 0 1 0-.7.7l3 3Z"
fill="currentColor"
></path>
</svg>
);
const TapRight = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="none"
viewBox="0 0 20 20"
>
<path
d="M6 6a4 4 0 0 1 3.7 2.5H8.6a3 3 0 1 0 0 3h1.1A4 4 0 1 1 6 6Zm8.85 7.35 3-3a.5.5 0 0 0 0-.7l-3-3a.5.5 0 1 0-.7.7l2.14 2.15H5.5a.5.5 0 0 0 0 1h10.8l-2.15 2.15a.5.5 0 0 0 .7.7Z"
fill="currentColor"
></path>
</svg>
);
const GapIcon: FC<{ layout: FNLayout }> = ({ layout }) => (
<div
className={cx(
layout.gap !== "auto" ? "pr-2 border-r border-gray-300 mr-1" : "pr-1 pl-1"
)}
>
{layout.dir === "col" ? (
<svg
className="svg"
width={12}
height={13}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M11 13v-2H1v2H0v-3h12v3h-1zm1-10H0V0h1v2h10V0h1v3zM9 7V6H3v1h6z" />
</svg>
) : (
<svg
className="svg"
width={13}
height={12}
xmlns="http://www.w3.org/2000/svg"
>
<path d="M13 1h-2v10h2v1h-3V0h3v1zM3 0v12H0v-1h2V1H0V0h3zm4 3H6v6h1V3z" />
</svg>
)}
</div>
);

View File

@ -0,0 +1,3 @@
export const EdStyleAll = () => {
return <div className="flex flex-1"></div>;
};

View File

@ -0,0 +1,40 @@
function toAbsoluteURL(url: string) {
const a = document.createElement("a");
a.setAttribute("href", url); // <a href="hoge.html">
return (a.cloneNode(false) as any).href; // -> "http://example.com/hoge.html"
}
export function importModule(url: string) {
if (!url) return "";
return new Promise((resolve, reject) => {
const vector = "$importModule$" + Math.random().toString(32).slice(2);
const script = document.createElement("script");
const destructor = () => {
delete (window as any)[vector];
script.onerror = null;
script.onload = null;
script.remove();
URL.revokeObjectURL(script.src);
script.src = "";
};
script.defer = true;
script.type = "module";
script.onerror = () => {
reject(new Error(`Failed to import: ${url}`));
destructor();
};
script.onload = () => {
resolve((window as any)[vector]);
destructor();
};
const absURL = toAbsoluteURL(url);
const loader = `import * as m from "${absURL}"; window.${vector} = m;`; // export Module
const blob = new Blob([loader], { type: "text/javascript" });
script.src = URL.createObjectURL(blob);
document.head.appendChild(script);
});
}
export default importModule;

View File

@ -0,0 +1,41 @@
import { createId as cuid } from "@paralleldrive/cuid2";
import { IContent } from "../../../utils/types/general";
export const fillID = (
object: IContent,
modify?: (obj: IContent) => boolean,
currentDepth?: number
) => {
const _depth = (currentDepth || 0) + 1;
if (modify) {
if (modify(object)) {
object.id = cuid();
}
} else {
object.id = cuid();
}
if (
object.type === "item" &&
object.component &&
object.component.id &&
object.component.props
) {
for (const p of Object.values(object.component.props)) {
if (p.meta?.type === "content-element" && p.content) {
fillID(p.content, modify, _depth);
}
}
}
if (object.type !== "text") {
if (object.childs && Array.isArray(object.childs)) {
for (const child of object.childs) {
fillID(child, modify, _depth);
}
}
}
return object;
};

View File

@ -0,0 +1,32 @@
import find from "lodash.find";
import get from "lodash.get";
import set from "lodash.set";
import { deepClone } from "web-utils";
import { IContent } from "../../../utils/types/general";
export const flatTree = (item: Array<IContent>) => {
const children = item as Array<IContent>;
let ls = deepClone(item);
let sitem: any = ls.map((v: IContent) => {
if (v.type !== "text") {
v.childs = [];
}
return { ...v };
});
let result = [] as any;
sitem.forEach((v: IContent) => {
let parent = children.filter((x: IContent) =>
find(get(x, "childs"), (x: IContent) => x.id === v.id)
);
if (get(parent, "length")) {
let s = sitem.find((e: any) => e.id === get(parent, "[0].id"));
let childs: any = s.childs || [];
childs = childs.filter((e: any) => get(e, "id")) || [];
let now = [v];
set(s, "childs", childs.concat(now));
} else {
result.push(v);
}
});
return result;
};

View File

@ -0,0 +1,23 @@
export const responsiveVal = <T>(
item: any,
key: string,
mode: "desktop" | "mobile" | undefined,
defaultVal: T
): T => {
let value = item[key];
if (mode === "desktop" || !mode) {
if (!value && item.mobile && item.mobile[key]) {
value = item.mobile[key];
}
} else {
if (item.mobile && item.mobile[key]) {
value = item.mobile[key];
}
}
if (!value) {
value = defaultVal;
}
return value as T;
};

View File

@ -0,0 +1,51 @@
import { syncronize } from "y-pojo";
import * as Y from "yjs";
import { TypedArray, TypedMap } from "yjs-types";
export const getArray = <T extends any>(map: TypedMap<any>, key: string) => {
let item = map.get(key);
if (!item) {
map.set(key, new Y.Array());
item = map.get(key);
}
return item as TypedArray<T> | undefined;
};
export const newMap = (item: any) => {
const map = new Y.Map();
syncronize(map, item as any);
return map;
};
export const getMap = <T extends any>(
map: TypedMap<any>,
key: string,
fill?: any
) => {
let item = map.get(key);
if (!item) {
map.set(key, new Y.Map() as any);
item = map.get(key);
if (fill && item) {
syncronize(item as any, fill);
}
}
return item as T;
};
export const getMText = (map: TypedMap<any>, key: string) => {
let item = map.get(key);
if (typeof item === "string") {
map.set(key, new Y.Text(item));
item = map.get(key);
} else if (typeof item === "object" && item instanceof Y.Text) {
} else {
item = new Y.Text();
map.set(key, item);
}
return item as Y.Text;
};

View File

@ -0,0 +1,16 @@
import { FC, ReactElement } from "react";
export const BoxSep: FC<{
children: string | ReactElement | (ReactElement | string)[];
className?: string;
}> = ({ children, className = "border-l" }) => {
return (
<div
className={`box-sep flex items-center p-[3px] space-x-[2px] ${
className ? className : ""
} border-slate-100`}
>
{children}
</div>
);
};

View File

@ -0,0 +1,34 @@
import { FC, ReactNode } from "react";
type ButtonProp = {
disabled?: boolean;
className?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
appearance?: "secondary" | "subtle";
children?: ReactNode;
};
export const Button: FC<ButtonProp> = ({
children,
appearance,
className,
onClick,
}) => {
return (
<button
className={cx(
"transition-all flex items-center justify-center border select-none outline-none prasi-btn",
css`
height: 25px;
width: 28px;
`,
className,
appearance !== "subtle"
? "bg-white border-[#d1d5db] hover:border-[#ccc] active:bg-[#d1d1d1] focus:border-[#ccc]"
: "active:bg-[#d1d1d1] hover:bg-white hover:bg-opacity-50 cursor-pointer border-transparent hover:border-blue-100 focus:border-[#ccc]"
)}
onClick={onClick}
>
{children}
</button>
);
};

View File

@ -0,0 +1,39 @@
import { FC, ReactElement } from "react";
import { Button } from "./Button";
export const FieldBtnRadio: FC<{
value: any;
update: (value: any) => void;
disabled?: boolean;
items: Record<string, ReactElement | String>;
}> = ({ items, update, value, disabled }) => {
return (
<>
{Object.entries(items).map(([name, content], idx) => {
return (
<Button
disabled={disabled}
key={idx}
className={cx(
"btn-hover",
value === name &&
name.toUpperCase() === "ON" &&
css`
color: white !important;
font-weight: bold !important;
background-color: green !important;
border: 0px !important;
`
)}
onClick={() => {
update(name);
}}
appearance={value === name ? "secondary" : "subtle"}
>
{content}
</Button>
);
})}
</>
);
};

View File

@ -0,0 +1,73 @@
import { FC, useEffect } from "react";
import { useLocal } from "web-utils";
import { FieldColorPicker } from "./FieldColorPopover";
import { w } from "../../../../../../utils/types/general";
export const FieldColor: FC<{
popupID: string;
value?: string;
update: (value: string) => void;
showHistory?: boolean;
}> = ({ value, update, showHistory = true, popupID }) => {
if (!w.openedPopupID) w.openedPopupID = {};
const local = useLocal({
val: w.lastColorPicked || "",
});
useEffect(() => {
if (value) {
w.lastColorPicked = value;
}
local.val = value || "";
local.render();
}, [value]);
const onOpen = () => {
w.openedPopupID[popupID] = true;
local.render();
};
const onClose = () => {
delete w.openedPopupID[popupID];
w.lastColorPicked = "";
local.render();
};
if (typeof local.val === "string" && local.val.length > 10) {
update("");
return null;
}
return (
<FieldColorPicker
value={local.val}
update={(val) => update(val)}
onOpen={onOpen}
onClose={onClose}
open={w.openedPopupID[popupID]}
showHistory={showHistory}
>
<div
className={cx(
css`
background-image: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill-opacity=".05"><path d="M8 0h8v8H8zM0 8h8v8H0z"/></svg>');
`,
"cursor-pointer"
)}
>
<div
className={cx(
css`
background: ${local.val};
width: 30px;
height: 20px;
`,
"color-box"
)}
></div>
</div>
</FieldColorPicker>
);
};

View File

@ -0,0 +1,200 @@
import { createId as cuid } from "@paralleldrive/cuid2";
import { FC, Suspense, lazy, useEffect } from "react";
import tinycolor from "tinycolor2";
import { useLocal } from "web-utils";
const HexAlphaColorPicker = lazy(async () => {
return { default: (await import("react-colorful")).HexAlphaColorPicker };
});
export const FieldPickColor: FC<{
value?: string;
onChangePicker: (value: string) => void;
onClose?: () => void;
showHistory?: boolean;
}> = ({ value, onChangePicker, onClose, showHistory }) => {
const meta = useLocal({
originalValue: "",
inputValue: value,
rgbValue: "",
selectedEd: "" as string,
});
useEffect(() => {
meta.inputValue = value || "";
const convertColor = tinycolor(meta.inputValue);
meta.rgbValue = convertColor.toRgbString();
meta.render();
}, [value]);
const colors: { id: string; value: string }[] = [];
const tin = tinycolor(meta.inputValue);
return (
<div className="flex p-3 space-x-4 items-start">
<div
className={cx(
"flex flex-col items-center",
css`
.react-colorful__pointer {
border-radius: 4px;
width: 20px;
height: 20px;
}
`
)}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
>
<Suspense>
<HexAlphaColorPicker
color={meta.inputValue}
onChange={(color) => {
if (color) {
meta.inputValue = color;
onChangePicker(color);
const convertColor = tinycolor(meta.inputValue);
meta.rgbValue = convertColor.toRgbString();
}
}}
/>
</Suspense>
</div>
<div
className={cx(
"grid grid-cols-1 gap-y-0.5",
css`
width: 78px;
`
)}
>
<div
className="p-[1px] border rounded flex items-center justify-center"
style={{
marginBottom: "4px",
}}
>
<input
value={meta.inputValue || "#FFFFFFFF"}
className={cx(
`rounded cursor-text bg-[${meta.inputValue}] min-w-[0px] text-[13px] px-[8px] py-[1px] uppercase`,
tin.isValid() &&
css`
color: ${!tin.isLight() ? "#FFF" : "#000"};
background-color: ${meta.inputValue};
`
)}
onClick={() => {
// height: "18px",
// minWidth: "0px",
// fontSize: "13px",
// meta.selectedEd = -1;
// meta.render();
}}
spellCheck={false}
onChange={(e) => {
const color = e.currentTarget.value;
meta.inputValue = color;
// if (meta.selectedEd >= 0) {
// ed.colors[meta.selectedEd] = color;
// }
onChangePicker(color);
}}
/>
</div>
{showHistory &&
colors.map((e, key) => (
<div
key={key}
className={cx(
"flex space-x-1 items-center border p-0.5 rounded",
meta.selectedEd === e.id && "border-black"
)}
>
<div
className={cx(
`w-12 h-4 rounded cursor-pointer border bg-[${e}]`,
css`
background-color: ${e.value};
`
)}
style={{
backgroundColor: e.value,
}}
onClick={() => {
meta.inputValue = e.value;
meta.selectedEd = e.id;
onChangePicker(e.value);
const convertColor = tinycolor(meta.inputValue);
meta.rgbValue = convertColor.toRgbString();
}}
/>
{/* <Delete16Regular
className={`cursor-pointer hover:text-[${e}]`}
css={css`
:hover {
color: ${e.value};
}
`}
onClick={() => {
meta.selectedEd = "";
const index = colors.indexOf(e);
const color = colors.find((_, i) => i === index);
if (color) onChangePicker(color?.value);
if (index > -1) {
colors.splice(index, 1);
ed.render();
}
}}
/> */}
</div>
))}
<div className="">
{meta.inputValue !== "" && (
<>
{/* <div
className="cursor-pointer text-center border border-gray-200 hover:bg-gray-100 rounded-t"
onClick={() => {
if (meta.inputValue) {
const id = cuid();
const color = { id, value: meta.inputValue };
colors.push(color);
meta.selectedEd = id;
meta.render();
onChangePicker(meta.inputValue);
}
}}
>
+ Add
</div> */}
<div
className="cursor-pointer text-center border border-gray-200 rounded hover:bg-gray-100"
onClick={() => {
meta.inputValue = "";
onChangePicker("");
}}
>
Reset
</div>
</>
)}
{onClose && (
<div
className="cursor-pointer text-center border border-gray-200 rounded hover:bg-gray-100 mt-[4px]"
onClick={onClose}
>
Close
</div>
)}
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,68 @@
import { FC, ReactElement, useEffect, useTransition } from "react";
import { useLocal } from "web-utils";
import { FieldPickColor } from "./FieldColorPicker";
import { Popover } from "../../../../../utils/ui/popover";
export const FieldColorPicker: FC<{
children: ReactElement;
value?: string;
update: (value: string) => void;
open: boolean;
onOpen?: () => void;
onClose?: () => void;
showHistory?: boolean;
}> = ({ children, value, update, open, onClose, onOpen, showHistory }) => {
const local = useLocal({ show: open || false });
useEffect(() => {
if (value) {
local.show = open || false;
local.render();
}
}, [value, open]);
const [_, tx] = useTransition();
return (
<Popover
open={local.show}
onOpenChange={(open) => {
local.show = open;
if (open && onOpen) {
onOpen();
} else if (onClose) {
onClose();
}
local.render();
}}
backdrop={false}
popoverClassName="rounded-md p-2 text-sm bg-white shadow-2xl border border-slate-300"
content={
<FieldPickColor
value={value}
showHistory={showHistory}
onClose={() => {
local.show = false;
local.render();
if (onClose) onClose();
}}
onChangePicker={(color) => {
tx(() => {
if (color.indexOf("NaN") < 0) {
update(color);
}
});
}}
/>
}
>
<div
onClick={() => {
local.show = true;
local.render();
if (onOpen) onOpen();
}}
>
{children}
</div>
</Popover>
);
};

View File

@ -0,0 +1,182 @@
import React, {
FC,
ReactElement,
useCallback,
useEffect,
useTransition,
} from "react";
import { useLocal } from "web-utils";
export const FieldNumUnit: FC<{
label?: string;
icon?: ReactElement;
value: string;
unit?: string;
hideUnit?: boolean;
update: (value: string, setDragVal?: (val: number) => void) => void;
width?: string;
positiveOnly?: boolean;
disabled?: boolean | string;
enableWhenDrag?: boolean;
dashIfEmpty?: boolean;
}> = ({
icon,
value,
label,
update,
unit,
hideUnit,
width,
disabled,
positiveOnly,
enableWhenDrag,
}) => {
const local = useLocal({
val: 0,
unit: "",
drag: { clientX: 0, old: 0 },
dragging: false,
});
const parseVal = useCallback(() => {
let val = "";
let unt = "";
if (value.length >= 1) {
let fillMode = "val" as "val" | "unit";
for (let idx = 0; idx < value.length; idx++) {
const c = value[idx];
if (idx > 0 && isNaN(parseInt(c))) {
fillMode = "unit";
}
if (fillMode === "val") {
val += c;
} else {
unt += c || "";
}
}
if (!parseInt(val)) unt = "";
}
local.val = parseInt(val) || 0;
if (positiveOnly && local.val < 0) {
local.val = Math.max(0, local.val);
}
local.unit = unit || unt || "px";
local.render();
}, [value, unit]);
useEffect(() => {
parseVal();
local.render();
}, [value, unit]);
const [txPending, tx] = useTransition();
useEffect(() => {
// Only change the value if the drag was actually started.
const onUpdate = (event: any) => {
if (local.drag.clientX) {
local.val = Math.round(
local.drag.old + (event.clientX - local.drag.clientX)
);
if (positiveOnly && local.val < 0) {
local.val = Math.max(0, local.val);
}
local.render();
tx(() => {
update(local.val + local.unit);
});
}
};
// Stop the drag operation now.
const onEnd = () => {
local.drag.clientX = 0;
local.dragging = false;
local.render();
};
document.addEventListener("pointermove", onUpdate);
document.addEventListener("pointerup", onEnd);
return () => {
document.removeEventListener("pointermove", onUpdate);
document.removeEventListener("pointerup", onEnd);
};
}, [local.drag.clientX, local.drag.old, local.val]);
const onStart = useCallback(
(event: React.MouseEvent) => {
let _disabled = disabled;
if (enableWhenDrag && _disabled) {
update(local.val + local.unit, (val) => {
local.val = val;
});
_disabled = false;
}
if (!_disabled) {
local.dragging = true;
local.render();
local.drag.clientX = event.clientX;
local.drag.old = local.val;
}
},
[local.val, disabled]
);
return (
<>
<div className="field-num flex flex-row items-stretch justify-between bg-white border border-transparent btn-hover h-full">
<div className="flex cursor-ew-resize" onPointerDown={onStart}>
{icon && (
<div
className="flex items-center justify-center opacity-50 ml-1"
onPointerDown={onStart}
>
{icon}
</div>
)}
{label && (
<div className="flex items-center justify-center text-[11px] opacity-50 w-[14px] ml-1">
{label}
</div>
)}
</div>
<div className="flex justify-between flex-1 items-center flex-grow overflow-hidden">
<input
type="text"
className={cx(
css`
width: ${width ? width : "23px"};
background: transparent;
outline: none;
font-size: 11px;
`,
!!disabled && "text-center text-gray-400"
)}
disabled={!!disabled}
value={typeof disabled === "string" ? disabled : local.val}
onChange={(e) => {
local.val = parseInt(e.currentTarget.value) || 0;
update(local.val + local.unit);
}}
/>
{hideUnit !== true && (
<div
className="text-[11px] mx-1 flex cursor-ew-resize"
onPointerDown={onStart}
>
{local.unit}
</div>
)}
</div>
</div>
{local.dragging && (
<div className="fixed z-50 inset-0 cursor-ew-resize"></div>
)}
</>
);
};

View File

@ -0,0 +1,40 @@
import { FC } from "react";
import { FNLayout } from "../../../../../utils/types/meta-fn";
export const AlignIcon: FC<{
dir: FNLayout["dir"];
pos: "start" | "center" | "end";
className?: string;
}> = ({ dir, pos, className }) => {
return (
<div
className={cx(
"flex w-[16px] h-[16px] justify-between",
`flex-${dir}`,
`items-${pos}`,
className
)}
>
<div
className={cx(
"bg-blue-500",
dir.startsWith("col") ? "w-[12px] h-[4px]" : "h-[12px] w-[4px]"
)}
></div>
<div
className={cx(
"bg-blue-500",
dir.startsWith("col") ? "w-[18px] h-[4px]" : "h-[18px] w-[4px]"
)}
></div>
<div
className={cx(
"bg-blue-500",
dir.startsWith("col") ? "w-[8px] h-[4px]" : "h-[8px] w-[4px]"
)}
></div>
</div>
);
};

View File

@ -0,0 +1,111 @@
import { FC } from "react";
import { FNAlign, FNLayout } from "../../../../../../utils/types/meta-fn";
import { AlignIcon } from "./LayoutIcon";
import { Tooltip } from "../../../../../../utils/ui/tooltip";
export const LayoutPacked: FC<{
dir: FNLayout["dir"];
align: FNAlign;
onChange: (align: FNAlign) => void;
}> = ({ dir, align, onChange }) => {
return (
<div className="ml-1 w-[68px] h-[68px] p-[2px] border grid grid-cols-3 bg-white">
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="top-left"
/>
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="top-center"
/>
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="top-right"
/>
<AlignItem dir={dir} active={align} onChange={onChange} align="left" />
<AlignItem dir={dir} active={align} onChange={onChange} align="center" />
<AlignItem dir={dir} active={align} onChange={onChange} align="right" />
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="bottom-left"
/>
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="bottom-center"
/>
<AlignItem
dir={dir}
active={align}
onChange={onChange}
align="bottom-right"
/>
</div>
);
};
const AlignItem: FC<{
dir: FNLayout["dir"];
align: FNAlign;
active: string;
onChange: (align: FNAlign) => void;
}> = ({ dir, align, active, onChange }) => {
let pos = "start";
if (dir.startsWith("col")) {
if (align.endsWith("left")) pos = "start";
if (align.endsWith("center")) pos = "center";
if (align.endsWith("right")) pos = "end";
} else {
if (align.startsWith("top")) pos = "start";
else if (align.startsWith("bottom")) pos = "end";
else pos = "center";
}
return (
<Tooltip content={`Align: ${align}`}>
<div
className={cx(
"w-[21px] h-[21px] flex items-center justify-center cursor-pointer",
active === align &&
css`
.icon {
display: flex;
}
.point {
display: none;
}
`,
css`
&:hover {
.icon {
display: flex;
opacity: 0.5;
}
.point {
display: none;
}
}
`
)}
onClick={() => {
onChange(align);
}}
>
<AlignIcon dir={dir} pos={pos as any} className={"icon hidden"} />
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
</Tooltip>
);
};

View File

@ -0,0 +1,261 @@
import { FC } from "react";
import { useLocal } from "web-utils";
import { FNAlign, FNLayout } from "../../../../../../utils/types/meta-fn";
export const LayoutSpaced: FC<{
dir: FNLayout["dir"];
align: FNAlign;
onChange: (align: FNAlign) => void;
}> = ({ dir, align, onChange }) => {
return (
<div
className={cx(
"w-[68px] h-[68px] border flex bg-white items-stretch p-[2px]",
(
{
col: "flex-row",
row: "flex-col",
"col-reverse": "flex-row-reverse",
"row-reverse": "flex-col-reverse",
} as any
)[dir]
)}
>
{dir === "col" && (
<>
<AlignItemCol active={align} onChange={onChange} align="left" />
<AlignItemCol active={align} onChange={onChange} align="center" />
<AlignItemCol active={align} onChange={onChange} align="right" />
</>
)}
{dir === "col-reverse" && (
<>
<AlignItemCol active={align} onChange={onChange} align="left" />
<AlignItemCol
active={align}
onChange={onChange}
align="center"
reverse
/>
<AlignItemCol active={align} onChange={onChange} align="right" />
</>
)}
{dir === "row" && (
<>
<AlignItemRow active={align} onChange={onChange} align="top" />
<AlignItemRow active={align} onChange={onChange} align="center" />
<AlignItemRow active={align} onChange={onChange} align="bottom" />
</>
)}
{dir === "row-reverse" && (
<>
<AlignItemRow active={align} onChange={onChange} align="bottom" />
<AlignItemRow
active={align}
onChange={onChange}
align="center"
reverse
/>
<AlignItemRow active={align} onChange={onChange} align="top" />
</>
)}
</div>
);
};
const AlignItemRow: FC<{
align: "top" | "center" | "bottom";
active: string;
onChange: (align: FNAlign) => void;
reverse?: boolean;
}> = ({ align, active, onChange, reverse }) => {
const local = useLocal({ hover: false });
let justify = "justify-start";
if (align === "center") justify = `justify-center`;
if (align === "bottom") justify = `justify-end`;
return (
<div
className={cx(
"flex flex-row cursor-pointer justify-between flex-1 items-stretch",
local.hover && "hover",
active === align &&
css`
.icon {
display: flex;
}
.point {
display: none;
}
`,
css`
&.hover {
.icon {
display: flex;
opacity: 0.5;
}
.point {
display: none;
}
}
`
)}
onMouseOver={() => {
local.hover = true;
local.render();
}}
onMouseOut={() => {
local.hover = false;
local.render();
}}
onClick={() => {
onChange(align);
}}
>
{active === align || local.hover ? (
<>
<div
className={cx("icon flex-1 flex flex-col items-center", justify)}
>
<div
className={cx(
"bg-blue-500",
reverse
? "py-[2px] w-[4px] h-[8px]"
: "py-[2px] w-[4px] h-[10px]"
)}
></div>
</div>
<div
className={cx("icon flex-1 flex flex-col items-center", justify)}
>
<div
className={cx("bg-blue-500", "py-[2px] w-[4px] h-[16px]")}
></div>
</div>
<div
className={cx("icon flex-1 flex flex-col items-center", justify)}
>
<div
className={cx(
"bg-blue-500",
!reverse
? "py-[2px] w-[4px] h-[8px]"
: "py-[2px] w-[4px] h-[10px]"
)}
></div>
</div>
</>
) : (
<>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
</>
)}
</div>
);
};
const AlignItemCol: FC<{
align: "left" | "center" | "right";
active: string;
onChange: (align: FNAlign) => void;
reverse?: boolean;
}> = ({ align, active, onChange, reverse }) => {
const local = useLocal({ hover: false });
let justify = "justify-start";
if (align === "center") justify = `justify-center`;
if (align === "right") justify = `justify-end`;
return (
<div
className={cx(
"flex flex-col cursor-pointer justify-between flex-1 items-stretch",
local.hover && "hover",
active === align &&
css`
.icon {
display: flex;
}
.point {
display: none;
}
`,
css`
&.hover {
.icon {
display: flex;
opacity: 0.5;
}
.point {
display: none;
}
}
`
)}
onMouseOver={() => {
local.hover = true;
local.render();
}}
onMouseOut={() => {
local.hover = false;
local.render();
}}
onClick={() => {
onChange(align);
}}
>
{active === align || local.hover ? (
<>
<div className={cx("icon flex-1 flex items-center", justify)}>
<div
className={cx(
"bg-blue-500",
reverse
? "px-[2px] w-[8px] h-[4px]"
: "px-[2px] w-[10px] h-[4px]"
)}
></div>
</div>
<div className={cx("icon flex-1 flex items-center", justify)}>
<div
className={cx("bg-blue-500", "px-[2px] w-[16px] h-[4px]")}
></div>
</div>
<div className={cx("icon flex-1 flex items-center", justify)}>
<div
className={cx(
"bg-blue-500",
!reverse
? "px-[2px] w-[8px] h-[4px]"
: "px-[2px] w-[10px] h-[4px]"
)}
></div>
</div>
</>
) : (
<>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
<div className="flex-1 flex items-center justify-center">
<div className="w-[2px] h-[2px] bg-slate-400 point"></div>
</div>
</>
)}
</div>
);
};

View File

@ -0,0 +1,5 @@
import { FC, ReactNode } from "react";
export const SideBox: FC<{ children: ReactNode }> = ({ children }) => {
return <div className="flex flex-col pb-2 px-2 space-y-2">{children}</div>;
};

View File

@ -0,0 +1,20 @@
import { FC, ReactNode } from "react";
export const SideLabel: FC<{ children: ReactNode; sep?: "top" | "bottom" }> = ({
children,
sep,
}) => {
return (
<div
className={cx(
sep === "bottom"
? "border-b border-b-slate-300 bg-white mb-1"
: "border-t border-t-slate-300"
)}
>
<div className="text-[10px] select-none text-slate-400 pl-2 py-1">
{children}
</div>
</div>
);
};

View File

@ -0,0 +1,24 @@
export const dropdownProp = {
className: cx(
"p-1 border border-gray-300 h-[28px]",
css`
input {
max-width: none;
width: 87px;
flex: 1;
}
`
),
popover: {
className: "border border-gray-300",
itemClassName: cx(
"text-sm cursor-pointer min-w-[150px] p-1 hover:bg-blue-100",
css`
&.active {
background: #3c82f6;
color: white;
}
`
),
},
};