wip add side style
This commit is contained in:
parent
27c1b3e5d0
commit
872c41ef19
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export const EdStyleAll = () => {
|
||||
return <div className="flex flex-1"></div>;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>;
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
`
|
||||
),
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue