add
This commit is contained in:
commit
25976636fa
|
|
@ -0,0 +1 @@
|
|||
.vscode
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
import { FC, ReactNode, isValidElement, useEffect } from "react";
|
||||
import * as card from "../ui/card";
|
||||
import { cn } from "@/utils";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
export const Card: FC<{
|
||||
title: { left: ReactNode; right: ReactNode };
|
||||
desc: (() => Promise<ReactNode>) | ReactNode;
|
||||
value: (() => Promise<ReactNode>) | ReactNode;
|
||||
}> = ({ title, desc, value }) => {
|
||||
const local = useLocal({
|
||||
value: "" as any,
|
||||
desc: "" as any,
|
||||
status_value: "init" as "init" | "loading" | "ready",
|
||||
status_desc: "init" as "init" | "loading" | "ready",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditor) {
|
||||
if (!!value && typeof value === "function") {
|
||||
local.status_value = "loading";
|
||||
local.render();
|
||||
const result = value();
|
||||
if (typeof result === "object" && result instanceof Promise) {
|
||||
result.then((val) => {
|
||||
local.value = val;
|
||||
local.status_value = "ready";
|
||||
local.render();
|
||||
});
|
||||
} else {
|
||||
local.value = result;
|
||||
local.status_value = "ready";
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
|
||||
if (!!desc && typeof desc === "function") {
|
||||
local.status_desc = "loading";
|
||||
local.render();
|
||||
const result = desc();
|
||||
if (typeof result === "object" && result instanceof Promise) {
|
||||
result.then((val) => {
|
||||
local.desc = val;
|
||||
local.status_desc = "ready";
|
||||
local.render();
|
||||
});
|
||||
} else {
|
||||
local.desc = result;
|
||||
local.status_desc = "ready";
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (typeof desc !== "function") {
|
||||
local.status_desc = "ready";
|
||||
local.desc = desc;
|
||||
} else if (isEditor) {
|
||||
local.desc = typeof desc === "function" ? "..." : desc;
|
||||
local.status_desc = "ready";
|
||||
}
|
||||
|
||||
if (typeof value !== "function") {
|
||||
local.status_value = "ready";
|
||||
local.value = value;
|
||||
} else if (isEditor) {
|
||||
local.value = "...";
|
||||
local.status_value = "ready";
|
||||
}
|
||||
|
||||
return (
|
||||
<card.Card className="c-flex c-flex-1">
|
||||
<div className={cn("c-p-3 c-text-[14px] c-flex-1")}>
|
||||
{!!title && (title.left || title.right) && (
|
||||
<div className="c-tracking-tight c-text-sm c-font-medium c-flex c-justify-between c-space-x-1 mb-1 c-items-center">
|
||||
<div className="c-flex">{title.left}</div>
|
||||
{title.right && (
|
||||
<div className="c-flex c-opacity-70 c-text-xs">{title.right}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<card.CardTitle className="c-whitespace-nowrap pb-1 h-[28px] c-overflow-hidden">
|
||||
{local.status_value === "ready" ? (
|
||||
formatObject(local.value)
|
||||
) : (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<Skeleton className="h-[10px] c-w-[50px]" />
|
||||
<Skeleton className="h-[10px] c-w-[40px]" />
|
||||
</div>
|
||||
)}
|
||||
</card.CardTitle>
|
||||
<card.CardDescription className="c-text-xs c-whitespace-pre-wrap">
|
||||
{local.status_desc === "ready" ? (
|
||||
formatObject(local.desc)
|
||||
) : (
|
||||
<div className="flex flex-col space-y-1">
|
||||
<Skeleton className="h-[10px] c-w-[50px]" />
|
||||
</div>
|
||||
)}
|
||||
</card.CardDescription>
|
||||
</div>
|
||||
</card.Card>
|
||||
);
|
||||
};
|
||||
|
||||
const formatObject = (val: any) => {
|
||||
if (typeof val === "object") {
|
||||
if (isValidElement(val)) return val;
|
||||
else return JSON.stringify(val);
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { cx } from "class-variance-authority";
|
||||
import { FC, useEffect } from "react";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
|
||||
export const Detail: FC<{
|
||||
detail: (item: any) => Record<string, [string, string, string]>;
|
||||
on_load: (arg: { params: any }) => Promise<any>;
|
||||
mode: "standard" | "compact" | "inline";
|
||||
}> = ({ detail, mode, on_load }) => {
|
||||
const local = useLocal({
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
detail: null as any,
|
||||
});
|
||||
|
||||
if (!isEditor) {
|
||||
useEffect(() => {
|
||||
if (local.status === "init" && typeof on_load === "function") {
|
||||
local.status = "loading";
|
||||
local.detail = detail({});
|
||||
local.render();
|
||||
const res = on_load({ params: {} });
|
||||
if (typeof res === "object" && res instanceof Promise) {
|
||||
res.then((item) => {
|
||||
local.detail = detail(item);
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
});
|
||||
} else {
|
||||
local.detail = detail(res);
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}, [on_load]);
|
||||
}
|
||||
let values = {};
|
||||
|
||||
if (!isEditor && typeof on_load === "function") {
|
||||
values = local.detail || {};
|
||||
} else {
|
||||
values = detail(null);
|
||||
local.status = "ready";
|
||||
}
|
||||
|
||||
const entries = Object.entries(values);
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"c-flex c-relative items-stretch",
|
||||
mode === "inline"
|
||||
? "c-flex-row c-my-2"
|
||||
: "c-flex-col c-flex-1 c-w-full c-h-full "
|
||||
)}
|
||||
>
|
||||
{entries.map(([name, data], idx) => {
|
||||
const is_first = idx === 0;
|
||||
const is_last = idx === entries.length - 1;
|
||||
if (
|
||||
typeof data !== "object" ||
|
||||
!data ||
|
||||
(typeof data === "object" && !Array.isArray(data))
|
||||
)
|
||||
return null;
|
||||
const [label, sample, link] = data;
|
||||
|
||||
if (link) {
|
||||
preload(link);
|
||||
}
|
||||
|
||||
if (mode === "standard") {
|
||||
return (
|
||||
<div key={idx} className="c-flex c-flex-col c-items-stretch mb-2">
|
||||
<div className="c-flex c-font-bold">{label}</div>
|
||||
<div className="c-flex">
|
||||
<Linkable sample={sample} link={link} status={local.status} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (mode === "compact") {
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className="c-flex c-flex-row c-items-center mb-1 border-b"
|
||||
>
|
||||
<div className="c-flex c-font-bold c-min-w-[30%] c-overflow-hidden c-text-sm">
|
||||
{label}
|
||||
</div>
|
||||
<div className={cx("c-flex c-flex-1 c-ml-2 items-center")}>
|
||||
<Linkable sample={sample} link={link} status={local.status} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={cx(
|
||||
"c-flex c-flex-col c-items-stretch mr-1",
|
||||
!is_last && `border-r pr-2`,
|
||||
!is_first && `ml-1`
|
||||
)}
|
||||
>
|
||||
<div className={"c-flex c-font-bold"}>{label}</div>
|
||||
<div className="c-flex">
|
||||
<Linkable sample={sample} link={link} status={local.status} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Linkable: FC<{
|
||||
sample?: string;
|
||||
link?: string;
|
||||
status: "init" | "loading" | "ready";
|
||||
}> = ({ sample, link, status }) => {
|
||||
const loading = (
|
||||
<Skeleton
|
||||
className={cx(
|
||||
css`
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
if (!link) {
|
||||
if (status !== "ready") return loading;
|
||||
return sample || "-";
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="c-flex-1 c-px-2 c-my-1 c-rounded-md c-border c-flex c-items-center cursor-pointer"
|
||||
onClick={() => {
|
||||
if (!isEditor) {
|
||||
navigate(link);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{status === "ready" ? (
|
||||
<>
|
||||
<div
|
||||
className={cx(
|
||||
"c-flex-1",
|
||||
css`
|
||||
line-height: 1.1;
|
||||
padding: 5px 0px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{sample || '-'}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={cx(
|
||||
css`
|
||||
flex: 1;
|
||||
padding: 5px;
|
||||
min-width: 30px;
|
||||
min-height: 19px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{loading}
|
||||
</div>
|
||||
)}
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
className="c-ml-2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 2C2.44772 2 2 2.44772 2 3V12C2 12.5523 2.44772 13 3 13H12C12.5523 13 13 12.5523 13 12V8.5C13 8.22386 12.7761 8 12.5 8C12.2239 8 12 8.22386 12 8.5V12H3V3L6.5 3C6.77614 3 7 2.77614 7 2.5C7 2.22386 6.77614 2 6.5 2H3ZM12.8536 2.14645C12.9015 2.19439 12.9377 2.24964 12.9621 2.30861C12.9861 2.36669 12.9996 2.4303 13 2.497L13 2.5V2.50049V5.5C13 5.77614 12.7761 6 12.5 6C12.2239 6 12 5.77614 12 5.5V3.70711L6.85355 8.85355C6.65829 9.04882 6.34171 9.04882 6.14645 8.85355C5.95118 8.65829 5.95118 8.34171 6.14645 8.14645L11.2929 3H9.5C9.22386 3 9 2.77614 9 2.5C9 2.22386 9.22386 2 9.5 2H12.4999H12.5C12.5678 2 12.6324 2.01349 12.6914 2.03794C12.7504 2.06234 12.8056 2.09851 12.8536 2.14645Z"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import { FC } from "react";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
|
||||
export const Tab: FC<{
|
||||
tabs: (arg: { count: (string | number)[] }) => {
|
||||
label: string;
|
||||
navigate: string;
|
||||
count: string;
|
||||
color?: string;
|
||||
onClick?: () => Promise<void> | void;
|
||||
}[];
|
||||
active: string;
|
||||
body: any;
|
||||
on_load?: () => any;
|
||||
PassProp: any;
|
||||
}> = ({ tabs, active, body, PassProp, on_load }) => {
|
||||
const local = useLocal({
|
||||
active,
|
||||
count: [] as (number | string)[],
|
||||
status: "init" as "init" | "load" | "ready",
|
||||
}, () => {
|
||||
if (local.status === "init") {
|
||||
if (typeof on_load === "function" && !isEditor) {
|
||||
local.status = "load";
|
||||
const res = on_load();
|
||||
if (typeof res === "object" && res instanceof Promise) {
|
||||
res.then((value) => {
|
||||
local.count = value;
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
});
|
||||
} else {
|
||||
local.count = res;
|
||||
local.status = "ready";
|
||||
}
|
||||
} else {
|
||||
local.status = "ready";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const all_tabs = tabs({ count: local.count || [] });
|
||||
return (
|
||||
<div className="c-p-1 c-flex c-flex-1 c-w-full c-flex-col c-items-stretch">
|
||||
<Tabs value={local.active} className="">
|
||||
<TabsList
|
||||
className={cx(
|
||||
"c-grid c-w-full ",
|
||||
css`
|
||||
grid-template-columns: repeat(${all_tabs.length}, minmax(0, 1fr));
|
||||
`
|
||||
)}
|
||||
>
|
||||
{all_tabs.map((e, idx) => {
|
||||
if (e.navigate) {
|
||||
preload(e.navigate);
|
||||
}
|
||||
return (
|
||||
<TabsTrigger
|
||||
value={idx + ""}
|
||||
onClick={() => {
|
||||
local.active = idx.toString();
|
||||
local.render();
|
||||
if (e.navigate) {
|
||||
navigate(e.navigate);
|
||||
}
|
||||
}}
|
||||
className={cx(
|
||||
css`
|
||||
padding: 0px !important;
|
||||
margin: 0px 0px 0px ${idx === 0 ? 0 : 5}px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
" c-flex-1 c-p-1",
|
||||
e.count ? "c-flex c-justify-between" : "",
|
||||
local.active === idx.toString()
|
||||
? css`
|
||||
border-bottom: 2px solid
|
||||
${e.color ? e.color : "#3c82f6"};
|
||||
`
|
||||
: "border-b-transparent"
|
||||
)}
|
||||
>
|
||||
<div className="mr-1">{e.label}</div>
|
||||
{e.count && (
|
||||
<div
|
||||
className={cx(
|
||||
"c-rounded-sm c-px-2 c-text-white",
|
||||
e.color
|
||||
? css`
|
||||
background-color: ${e.color};
|
||||
`
|
||||
: `c-bg-blue-500`
|
||||
)}
|
||||
>
|
||||
{local.status != "ready" ? "..." : e.count}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TabsTrigger>
|
||||
);
|
||||
})}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div className="c-flex-1 c-flex c-flex-col">
|
||||
<PassProp activeIndex={parseInt(local.active)}>{body}</PassProp>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, useEffect } from "react";
|
||||
import { Button } from "../../ui/button";
|
||||
|
||||
export const ButtonOptions: FC<{
|
||||
on_select: (val: any) => void;
|
||||
options: () => Promise<{ value: string; label: string }[]>;
|
||||
value: string
|
||||
}> = ({ options, on_select, value }) => {
|
||||
const local = useLocal({
|
||||
list: [] as { value: string; label: string }[],
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (local.status === "init") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
options().then((result) => {
|
||||
local.list = result;
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
});
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!!local.list &&
|
||||
local.list.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => {
|
||||
on_select(item.value);
|
||||
local.render();
|
||||
}}
|
||||
className="c-mr-3"
|
||||
variant={item.value === value ? "default" : "outline"}
|
||||
>
|
||||
<span>{item.label}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { FC } from "react";
|
||||
|
||||
export const Date: FC<{
|
||||
on_select: (val: any) => void;
|
||||
}> = ({ on_select }) => {
|
||||
return (
|
||||
<input
|
||||
id="date"
|
||||
className="c-w-full c-flex c-justify-center c-border c-px-3 c-py-2 c-rounded-lg c-cursor-pointer"
|
||||
type="date"
|
||||
onChange={(event) => {
|
||||
on_select(event.target.value);
|
||||
}}
|
||||
></input>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { FC } from "react";
|
||||
|
||||
export const Datetime: FC<{
|
||||
on_select: (val: any) => void;
|
||||
}> = ({ on_select }) => {
|
||||
return (
|
||||
<input
|
||||
id="datetime"
|
||||
className="c-w-full c-flex c-justify-center c-border c-px-3 c-py-2 c-rounded-lg c-cursor-pointer"
|
||||
type="datetime-local"
|
||||
onChange={(event) => {
|
||||
on_select(event.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/comps/ui/form";
|
||||
import { FC, useEffect, useRef } from "react";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
import { Input } from "../ui/input";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
import { Button } from "../ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/comps/ui/popover";
|
||||
import { format } from "date-fns";
|
||||
import { Calendar } from "@/comps/ui/calendar";
|
||||
import { Calendar as CalendarIcon } from "lucide-react";
|
||||
import { PopUpDropdown } from "./PopUpDropdown";
|
||||
import { ButtonOptions } from "./ButtonOptions";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
import autosize from "autosize";
|
||||
import { InputMoney } from "./InputMoney";
|
||||
import { Date } from "./Date";
|
||||
import { Datetime } from "./Datetime";
|
||||
import { Slider } from "@radix-ui/react-slider";
|
||||
import { SliderOptions } from "./Slider/types";
|
||||
import { cn } from "@/utils";
|
||||
|
||||
export const Field: FC<{
|
||||
name: string;
|
||||
label: string;
|
||||
desc?: string;
|
||||
form: { hook: UseFormReturn<any, any, undefined>; render: () => void };
|
||||
type:
|
||||
| "text"
|
||||
| "textarea"
|
||||
| "dropdown"
|
||||
| "password"
|
||||
| "button-options"
|
||||
| "date"
|
||||
| "datetime"
|
||||
| "money"
|
||||
| "slider"
|
||||
| "master-linkF";
|
||||
required: "y" | "n";
|
||||
options: () => Promise<{ value: string; label: string }[]>;
|
||||
slider_options: () => Promise<SliderOptions>;
|
||||
}> = ({ name, form, desc, label, type, required, options, slider_options }) => {
|
||||
const value = form.hook.getValues()[name];
|
||||
const local = useLocal({
|
||||
dropdown: {
|
||||
popup: false,
|
||||
},
|
||||
date: {
|
||||
// label: "",
|
||||
popup: false,
|
||||
},
|
||||
slider: {
|
||||
value: 0,
|
||||
opt: {
|
||||
step: 1,
|
||||
min: { value: 0, label: "Start" },
|
||||
max: { value: 100, label: "End" },
|
||||
} as SliderOptions,
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
},
|
||||
});
|
||||
|
||||
const textAreaRef = useRef<any>();
|
||||
useEffect(() => {
|
||||
autosize(textAreaRef.current);
|
||||
return () => {
|
||||
autosize.destroy(textAreaRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (type === "slider") {
|
||||
local.slider.value = parseSliderValue(value, local.slider.opt);
|
||||
if (typeof slider_options === "function") {
|
||||
if (local.slider.status === "init") {
|
||||
local.slider.status = "ready";
|
||||
local.render();
|
||||
(async () => {
|
||||
const res = await slider_options();
|
||||
local.slider.opt = res;
|
||||
local.render();
|
||||
})();
|
||||
}
|
||||
} else {
|
||||
local.slider.status = "ready";
|
||||
local.render();
|
||||
}
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{local.dropdown.popup && (
|
||||
<PopUpDropdown
|
||||
on_close={() => {
|
||||
local.dropdown.popup = false;
|
||||
local.render();
|
||||
}}
|
||||
on_select={(value: any) => {
|
||||
form.hook.setValue(name, value);
|
||||
}}
|
||||
title={label}
|
||||
options={options}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.hook.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="c-flex c-flex-1 c-flex-col">
|
||||
<FormLabel className="flex">
|
||||
{label}
|
||||
{required === "y" && <h1 className="c-ml-1 c-text-red-500">*</h1>}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<>
|
||||
{type === "slider" && (
|
||||
<div className="c-flex-1 c-min-h-[40px] c-flex">
|
||||
<div className="c-flex c-flex-col c-items-center">
|
||||
<div>{local.slider.opt.min.value}</div>
|
||||
<div>{local.slider.opt.min.label}</div>
|
||||
</div>
|
||||
<div className="c-flex-1 c-flex-col c-items-stretch">
|
||||
<input
|
||||
type="range"
|
||||
className="c-flex-1 c-w-full"
|
||||
onInput={(e) => {
|
||||
const value = e.currentTarget.value;
|
||||
|
||||
local.slider.value = parseSliderValue(
|
||||
value,
|
||||
local.slider.opt
|
||||
);
|
||||
form.hook.setValue(name, value);
|
||||
local.render();
|
||||
}}
|
||||
value={local.slider.value}
|
||||
min={local.slider.opt.min.value}
|
||||
max={local.slider.opt.max.value}
|
||||
/>
|
||||
<div className="c-w-full c-bg-slate-200 c-mx-auto c-text-center">
|
||||
{local.slider.value}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="c-flex c-flex-col c-items-center">
|
||||
<div>{local.slider.opt.max.value}</div>
|
||||
<div>{local.slider.opt.max.label}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{["text", "password"].includes(type) && (
|
||||
<Input {...field} type={type} />
|
||||
)}
|
||||
|
||||
{type === "textarea" && (
|
||||
<Textarea {...field} ref={textAreaRef} />
|
||||
)}
|
||||
|
||||
{type === "dropdown" && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
local.dropdown.popup = true;
|
||||
local.render();
|
||||
}}
|
||||
variant={"outline"}
|
||||
>
|
||||
{field.value}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{type === "date" && (
|
||||
<Date
|
||||
on_select={(value: any) => {
|
||||
form.hook.setValue(name, value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "datetime" && (
|
||||
<Datetime
|
||||
on_select={(value: any) => {
|
||||
form.hook.setValue(name, value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "button-options" && (
|
||||
<ButtonOptions
|
||||
options={options}
|
||||
value={field.value}
|
||||
on_select={(value: any) => {
|
||||
form.hook.setValue(name, value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "money" && (
|
||||
<InputMoney
|
||||
value={field.value}
|
||||
on_select={(value: any) => {
|
||||
form.hook.setValue(name, value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</FormControl>
|
||||
<FormDescription>{desc}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const parseSliderValue = (value: any, opt: SliderOptions) => {
|
||||
let val = value;
|
||||
if (typeof value !== "number") {
|
||||
try {
|
||||
val = parseInt(val);
|
||||
} catch (e) {
|
||||
val = opt.min.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof val !== "number" || isNaN(val)) {
|
||||
val = opt.min.value;
|
||||
}
|
||||
|
||||
if (val >= opt.max.value) return opt.max.value;
|
||||
else if (val <= opt.min.value) return opt.min.value;
|
||||
return val;
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { Form as FForm } from "@/comps/ui/form";
|
||||
import { FC } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
export const Form: FC<{
|
||||
on_load: () => any;
|
||||
on_submit: (arg: { form: any; error: any }) => any;
|
||||
body: any;
|
||||
form: { hook: any; render: () => void };
|
||||
PassProp: any;
|
||||
}> = ({ on_load, body, form, PassProp, on_submit }) => {
|
||||
const form_hook = useForm<any>({
|
||||
defaultValues: on_load,
|
||||
});
|
||||
|
||||
form.hook = form_hook;
|
||||
|
||||
return (
|
||||
<FForm {...form_hook}>
|
||||
<div
|
||||
className={
|
||||
"flex-1 flex flex-col w-full items-stretch relative overflow-auto"
|
||||
}
|
||||
>
|
||||
<div className="absolute inset-0">
|
||||
<PassProp
|
||||
submit={() => {
|
||||
on_submit({ form: form.hook.getValues(), error: {} });
|
||||
}}
|
||||
>
|
||||
{body}
|
||||
</PassProp>
|
||||
</div>
|
||||
</div>
|
||||
</FForm>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { FC } from "react";
|
||||
import { Input } from "../../ui/input";
|
||||
import { useLocal } from "@/utils/use-local";
|
||||
|
||||
export const InputMoney: FC<{
|
||||
value: string;
|
||||
on_select: (val: any) => void;
|
||||
}> = ({ value, on_select }) => {
|
||||
const local = useLocal({
|
||||
numberWithComma: "",
|
||||
number: 0,
|
||||
});
|
||||
|
||||
const removeNonNumeric = (num: any) => {
|
||||
// replace non numeric
|
||||
return num.replace(/[^0-9]/g, "");
|
||||
};
|
||||
|
||||
const handleChange = (event: any) => {
|
||||
const val = event.target.value;
|
||||
|
||||
local.number = parseInt(val.replace(/\W/g, ""));
|
||||
local.numberWithComma = formatMoney(removeNonNumeric(val));
|
||||
local.render();
|
||||
|
||||
on_select(local.number);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="c-relative">
|
||||
<Input
|
||||
type="text"
|
||||
className="c-pl-10"
|
||||
value={local.numberWithComma}
|
||||
onChange={(event) => {
|
||||
handleChange(event);
|
||||
}}
|
||||
/>
|
||||
<span className="c-absolute c-top-1/2 c-left-4 c-transform -c-translate-y-1/2 absolute top-1/2 left-1/2 c-text-base">
|
||||
Rp.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const formatMoney = (num: any) => {
|
||||
// add comma
|
||||
if (!!num) {
|
||||
let str = num;
|
||||
if (typeof num === "number") str = num.toString();
|
||||
if (typeof str === "string")
|
||||
return str.replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
||||
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, useEffect } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { Button } from "../../ui/button";
|
||||
|
||||
export const PopUpDropdown: FC<{
|
||||
on_select: (val: any) => void;
|
||||
on_close: () => void;
|
||||
title: string;
|
||||
options: () => Promise<{ value: string; label: string }[]>;
|
||||
}> = ({ on_close, title, options, on_select }) => {
|
||||
const local = useLocal({
|
||||
list: [] as { value: string; label: string }[],
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (local.status === "init") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
options().then((result) => {
|
||||
local.list = result;
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
});
|
||||
}
|
||||
}, [options]);
|
||||
|
||||
return (
|
||||
<div className="c-fixed c-inset-0 c-bg-white c-z-50">
|
||||
<div className="c-flex c-flex-col c-mx-3">
|
||||
<div className="c-flex c-justify-between c-items-center">
|
||||
<h1 className="c-font-bold c-text-center c-truncate c-text-ellipsis c-overflow-hidden ...">
|
||||
{title}
|
||||
</h1>
|
||||
<button
|
||||
className="c-my-5 c-mx-3 hover:c-text-black/50"
|
||||
onClick={() => {
|
||||
on_close();
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<X />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="rounded">
|
||||
{!!local.list &&
|
||||
local.list.map((item, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
onClick={() => {
|
||||
on_select(item.value);
|
||||
on_close();
|
||||
local.render();
|
||||
}}
|
||||
className="w-full px-3 py-2 mb-2 cursor-pointer rounded hover:rounded hover:c-text-white"
|
||||
>
|
||||
<p>{item.label}</p>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export type SliderOptions = {
|
||||
step: number;
|
||||
min: { value: number; label: string };
|
||||
max: { value: number; label: string };
|
||||
};
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { Bell, Calendar, ChevronRight, UsersRound, ChevronDown, Contact2, Drill, HelpCircle, Home, LayoutList, ListChecks, ListTodo, PenBoxIcon, QrCode, UserCog, Workflow, Settings, Newspaper, PanelRightOpen, PanelLeftOpen, LayoutDashboard, Building, Box, Package, Blocks, Megaphone, MousePointerSquare, Siren, Ship, HeartHandshake, StickyNote, Volume2, Wallet, FileDown, Menu, X, LogOut, Rocket, BookOpen } from "lucide-react";
|
||||
|
||||
export const icon = {
|
||||
home: <Home />,
|
||||
dashboard: <LayoutDashboard />,
|
||||
dashboardSmall: <LayoutDashboard className={`c-w-4`} fill="currentColor" />,
|
||||
dashboardSmallTransparent: <LayoutDashboard className={`c-w-4`} />,
|
||||
inspect: <ListTodo />,
|
||||
qr: <QrCode />,
|
||||
maintenance: <Drill />,
|
||||
schedule: <Calendar />,
|
||||
transaction: <ListChecks />,
|
||||
master_data: <PenBoxIcon />,
|
||||
manage_user: <UserCog />,
|
||||
notification: <Bell />,
|
||||
profile: <Contact2 />,
|
||||
bell: <Bell />,
|
||||
bellSmall: <Bell className={`c-w-4`} fill="currentColor" />,
|
||||
bellSmallTransparent: <Bell className={`c-w-4`} />,
|
||||
help: <HelpCircle />,
|
||||
user: <UsersRound />,
|
||||
userSmall: <UsersRound className={`c-w-4`} fill="currentColor" />,
|
||||
userSmallTransparent: <UsersRound className={`c-w-4`} />,
|
||||
workflow: <Workflow />,
|
||||
layout: <LayoutList />,
|
||||
right: <ChevronRight />,
|
||||
down: <ChevronDown />,
|
||||
setting: <Settings />,
|
||||
settingSmall: <Settings className={`c-w-4`} fill="currentColor" />,
|
||||
settingSmallTransparent: <Settings className={`c-w-4`} />,
|
||||
article: <Newspaper />,
|
||||
menuOpen: <PanelRightOpen />,
|
||||
menuClose: <PanelLeftOpen />,
|
||||
building: <Building />,
|
||||
box: <Box />,
|
||||
package: <Package />,
|
||||
block: <Blocks />,
|
||||
megaphone: <Megaphone />,
|
||||
pointer: <MousePointerSquare />,
|
||||
policy: <Siren />,
|
||||
ship: <Ship />,
|
||||
heart: <HeartHandshake />,
|
||||
note: <StickyNote />,
|
||||
volume: <Volume2 />,
|
||||
wallet: <Wallet />,
|
||||
download: <FileDown />,
|
||||
menu: <Menu />,
|
||||
close: <X />,
|
||||
logout: <LogOut />,
|
||||
logoutSmall: <LogOut className={`c-w-4`} fill="currentColor" />,
|
||||
logoutSmallTransparent: <LogOut className={`c-w-4`} />,
|
||||
releaseSmall: <Rocket className={`c-w-4`} fill="currentColor" />,
|
||||
releaseSmallTransparent: <Rocket className={`c-w-4`} />,
|
||||
newsSmall: <BookOpen className={`c-w-4`} fill="currentColor" />,
|
||||
newsSmallTransparent: <BookOpen className={`c-w-4`} />,
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, ReactElement, useEffect } from "react";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import get from "lodash.get";
|
||||
|
||||
export const List: FC<{
|
||||
className: string;
|
||||
on_load: (arg: { params: any }) => Promise<any[]>;
|
||||
map_val: (item: any) => any;
|
||||
row: ReactElement;
|
||||
props: any;
|
||||
PassProp: any;
|
||||
}> = ({ className, row, props, on_load, PassProp, map_val }) => {
|
||||
const local = useLocal({
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
params: {
|
||||
skip: 0,
|
||||
take: 3,
|
||||
},
|
||||
list: [] as any[],
|
||||
});
|
||||
|
||||
if (isEditor) {
|
||||
return (
|
||||
<ListDummy
|
||||
row={row}
|
||||
props={props}
|
||||
PassProp={PassProp}
|
||||
map_val={map_val}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (local.status === "init") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
|
||||
local.list = await on_load({ params: local.params });
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
}
|
||||
})();
|
||||
}, [on_load]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="c-flex c-flex-1 c-w-full c-h-full c-relative c-overflow-auto"
|
||||
onPointerDown={props.onPointerDown}
|
||||
onPointerLeave={props.onPointerLeave}
|
||||
onPointerEnter={props.onPointerEnter}
|
||||
>
|
||||
<div className="c-absolute c-inset-0 c-flex c-flex-col c-items-stretch">
|
||||
{local.status !== "ready" ? (
|
||||
<div className="c-p-2 c-flex c-flex-col c-space-y-2 c-flex-1 c-items-start">
|
||||
<Skeleton className="c-h-4 c-w-[80%]" />
|
||||
<Skeleton className="c-h-4 c-w-[70%]" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{local.list === null ? (
|
||||
<ListDummy
|
||||
row={row}
|
||||
props={props}
|
||||
PassProp={PassProp}
|
||||
map_val={map_val}
|
||||
/>
|
||||
) : (
|
||||
local.list.map((item, idx) => {
|
||||
const val = (...arg: any[]) => {
|
||||
const value = get(map_val(item), `${arg.join("")}`);
|
||||
return value;
|
||||
};
|
||||
return (
|
||||
<div key={item} className="c-border-b">
|
||||
<PassProp item={val}>{row}</PassProp>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ListDummy = ({ props, row, PassProp, map_val }: any) => {
|
||||
const item = (...arg: string[]) => {
|
||||
if (map_val) {
|
||||
const value = get(map_val({}), `${arg.join("")}`);
|
||||
if (value) return value;
|
||||
}
|
||||
return `[${arg.join("")}]`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="c-flex c-flex-1 c-w-full c-h-full c-relative c-overflow-auto"
|
||||
onPointerDown={props.onPointerDown}
|
||||
onPointerLeave={props.onPointerLeave}
|
||||
onPointerEnter={props.onPointerEnter}
|
||||
>
|
||||
<div className="c-absolute c-inset-0 c-flex c-flex-col c-items-stretch">
|
||||
<div className="c-border-b ">
|
||||
<PassProp item={item}>{row}</PassProp>
|
||||
</div>
|
||||
<div className="c-border-b px-2"> ...</div>
|
||||
<div className="c-border-b px-2"> ...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
import { useLocal } from "@/utils/use-local";
|
||||
import { FC, ReactElement, useEffect } from "react";
|
||||
import { Skeleton } from "../ui/skeleton";
|
||||
import get from "lodash.get";
|
||||
|
||||
type ListNewProp = {
|
||||
on_load: (arg: { params: any }) => Promise<any[]>;
|
||||
map_val: (item: any) => any;
|
||||
row: ReactElement;
|
||||
props: any;
|
||||
PassProp: any;
|
||||
mode: "responsive" | "list" | "table";
|
||||
};
|
||||
|
||||
export const ListNew: FC<ListNewProp> = (_arg) => {
|
||||
const { row, on_load, PassProp, map_val } = _arg;
|
||||
const local = useLocal({
|
||||
status: "init" as "init" | "loading" | "ready",
|
||||
params: {
|
||||
skip: 0,
|
||||
take: 3,
|
||||
},
|
||||
list: [] as any[],
|
||||
});
|
||||
|
||||
if (isEditor) {
|
||||
return <ListDummy {..._arg} />;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (local.status === "init") {
|
||||
local.status = "loading";
|
||||
local.render();
|
||||
|
||||
local.list = await on_load({ params: local.params });
|
||||
|
||||
local.status = "ready";
|
||||
local.render();
|
||||
}
|
||||
})();
|
||||
}, [on_load]);
|
||||
|
||||
return (
|
||||
<div className="c-flex c-flex-1 c-w-full c-h-full c-relative c-overflow-auto">
|
||||
<div className="c-absolute c-inset-0 c-flex c-flex-col c-items-stretch">
|
||||
{local.status !== "ready" ? (
|
||||
<div className="c-p-2 c-flex c-flex-col c-space-y-2 c-flex-1 c-items-start">
|
||||
<Skeleton className="c-h-4 c-w-[80%]" />
|
||||
<Skeleton className="c-h-4 c-w-[70%]" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{local.list === null ? (
|
||||
<ListDummy {..._arg} />
|
||||
) : (
|
||||
local.list.map((item, idx) => {
|
||||
const val = (...arg: any[]) => {
|
||||
const value = get(map_val(item), `${arg.join("")}`);
|
||||
return value;
|
||||
};
|
||||
return (
|
||||
<div key={item} className="c-border-b">
|
||||
<PassProp item={val}>{row}</PassProp>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ListDummy = ({ props, row, PassProp, map_val, mode }: ListNewProp) => {
|
||||
const item = (...arg: string[]) => {
|
||||
if (map_val) {
|
||||
const value = get(map_val({}), `${arg.join("")}`);
|
||||
if (value) return value;
|
||||
}
|
||||
return `[${arg.join("")}]`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="c-flex c-flex-1 c-w-full c-h-full c-relative c-overflow-auto"
|
||||
onPointerDown={props.onPointerDown}
|
||||
onPointerLeave={props.onPointerLeave}
|
||||
onPointerEnter={props.onPointerEnter}
|
||||
>
|
||||
{isMobile && "mobile"}
|
||||
{isDesktop && "desktop"}
|
||||
<div className="c-absolute c-inset-0 c-flex c-flex-col c-items-stretch">
|
||||
<div className="c-border-b ">
|
||||
<PassProp item={item}>{row}</PassProp>
|
||||
</div>
|
||||
<div className="c-border-b px-2"> ...</div>
|
||||
<div className="c-border-b px-2"> ...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"c-inline-flex c-items-center c-justify-center c-whitespace-nowrap c-rounded-md c-text-sm c-font-medium c-ring-offset-background c-transition-colors focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-pointer-events-none disabled:c-opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "c-bg-primary c-text-primary-foreground hover:c-bg-primary/90",
|
||||
destructive:
|
||||
"c-bg-destructive c-text-destructive-foreground hover:c-bg-destructive/90",
|
||||
outline:
|
||||
"c-border c-border-input c-bg-background hover:c-bg-accent hover:c-text-accent-foreground",
|
||||
secondary:
|
||||
"c-bg-secondary c-text-secondary-foreground hover:c-bg-secondary/80",
|
||||
ghost: "hover:c-bg-accent hover:c-text-accent-foreground",
|
||||
link: "c-text-primary c-underline-offset-4 hover:c-underline",
|
||||
},
|
||||
size: {
|
||||
default: "c-h-10 c-px-4 c-py-2",
|
||||
sm: "c-h-9 c-rounded-md c-px-3",
|
||||
lg: "c-h-11 c-rounded-md c-px-8",
|
||||
icon: "c-h-10 c-w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import * as React from "react"
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react"
|
||||
import { DayPicker } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
import { buttonVariants } from "@/comps//ui/button"
|
||||
|
||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
classNames,
|
||||
showOutsideDays = true,
|
||||
...props
|
||||
}: CalendarProps) {
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("c-p-3", className)}
|
||||
classNames={{
|
||||
months: "c-flex c-flex-col sm:c-flex-row c-space-y-4 sm:c-space-x-4 sm:c-space-y-0",
|
||||
month: "c-space-y-4",
|
||||
caption: "c-flex c-justify-center c-pt-1 c-relative c-items-center",
|
||||
caption_label: "c-text-sm c-font-medium",
|
||||
nav: "c-space-x-1 c-flex c-items-center",
|
||||
nav_button: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"c-h-7 c-w-7 c-bg-transparent c-p-0 c-opacity-50 hover:c-opacity-100"
|
||||
),
|
||||
nav_button_previous: "c-absolute c-left-1",
|
||||
nav_button_next: "c-absolute c-right-1",
|
||||
table: "c-w-full c-border-collapse c-space-y-1",
|
||||
head_row: "c-flex",
|
||||
head_cell:
|
||||
"c-text-muted-foreground c-rounded-md c-w-9 c-font-normal c-text-[0.8rem]",
|
||||
row: "c-flex c-w-full c-mt-2",
|
||||
cell: "c-h-9 c-w-9 c-text-center c-text-sm c-p-0 c-relative [&:has([aria-selected].day-range-end)]:c-rounded-r-md [&:has([aria-selected].day-outside)]:c-bg-accent/50 [&:has([aria-selected])]:c-bg-accent first:[&:has([aria-selected])]:c-rounded-l-md last:[&:has([aria-selected])]:c-rounded-r-md focus-within:c-relative focus-within:c-z-20",
|
||||
day: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"c-h-9 c-w-9 c-p-0 c-font-normal aria-selected:c-opacity-100"
|
||||
),
|
||||
day_range_end: "c-day-range-end",
|
||||
day_selected:
|
||||
"c-bg-primary c-text-primary-foreground hover:c-bg-primary hover:c-text-primary-foreground focus:c-bg-primary focus:c-text-primary-foreground",
|
||||
day_today: "c-bg-accent c-text-accent-foreground",
|
||||
day_outside:
|
||||
"c-day-outside c-text-muted-foreground c-opacity-50 aria-selected:c-bg-accent/50 aria-selected:c-text-muted-foreground aria-selected:c-opacity-30",
|
||||
day_disabled: "c-text-muted-foreground c-opacity-50",
|
||||
day_range_middle:
|
||||
"aria-selected:c-bg-accent aria-selected:c-text-accent-foreground",
|
||||
day_hidden: "c-invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeft className="c-h-4 c-w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRight className="c-h-4 c-w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Calendar.displayName = "Calendar"
|
||||
|
||||
export { Calendar }
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-rounded-lg c-border c-bg-card c-text-card-foreground c-shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("c-flex c-flex-col c-space-y-1.5 c-p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-text-2xl c-font-semibold c-leading-none c-tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("c-text-sm c-text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("c-p-6 c-pt-0", className)} {...props} />
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("c-flex c-items-center c-p-6 c-pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
import { Label } from "@/comps//ui/label"
|
||||
|
||||
const Form = FormProvider
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
> = {
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
)
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("c-space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField()
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "c-text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField()
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("c-text-sm c-text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("c-text-sm c-font-medium c-text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
export { Label }
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { cn } from "@/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("c-animate-pulse c-rounded-md c-bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import * as React from "react"
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const Slider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-relative c-flex c-w-full c-touch-none c-select-none c-items-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SliderPrimitive.Track className="c-relative c-h-2 c-w-full c-grow c-overflow-hidden c-rounded-full c-bg-secondary">
|
||||
<SliderPrimitive.Range className="c-absolute c-h-full c-bg-primary" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="c-block c-h-5 c-w-5 c-rounded-full c-border-2 c-border-primary c-bg-background c-ring-offset-background c-transition-colors focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-pointer-events-none disabled:c-opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
))
|
||||
Slider.displayName = SliderPrimitive.Root.displayName
|
||||
|
||||
export { Slider }
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-inline-flex c-h-10 c-items-center c-justify-center c-rounded-md c-bg-muted c-p-1 c-text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-inline-flex c-items-center c-justify-center c-whitespace-nowrap c-rounded-sm c-px-3 c-py-1.5 c-text-sm c-font-medium c-ring-offset-background c-transition-all focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-pointer-events-none disabled:c-opacity-50 data-[state=active]:c-bg-background data-[state=active]:c-text-foreground data-[state=active]:c-shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"c-mt-2 c-ring-offset-background focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/utils"
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"c-flex c-min-h-[80px] c-w-full c-rounded-md c-border c-border-input c-bg-background c-px-3 c-py-2 c-text-sm c-ring-offset-background placeholder:c-text-muted-foreground focus-visible:c-outline-none focus-visible:c-ring-2 focus-visible:c-ring-ring focus-visible:c-ring-offset-2 disabled:c-cursor-not-allowed disabled:c-opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,76 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply c-border-border;
|
||||
}
|
||||
body {
|
||||
@apply c-bg-background c-text-foreground;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import { format } from "date-fns";
|
||||
|
||||
export const longDate = (date: string | Date) => {
|
||||
if (date instanceof Date || typeof date === "string") {
|
||||
return format(date, "dd MMM yyyy - kk:mm");
|
||||
}
|
||||
return "-";
|
||||
};
|
||||
|
||||
export const shortDate = (date: string | Date) => {
|
||||
if (date instanceof Date || typeof date === "string") {
|
||||
return format(date, "P");
|
||||
}
|
||||
return "-";
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
declare var db: any;
|
||||
declare var isEditor: boolean;
|
||||
declare var isMobile: boolean;
|
||||
declare var isDesktop: boolean;
|
||||
declare var css: any;
|
||||
declare var cx: any;
|
||||
declare var preload: (urls: string[] | string) => any;
|
||||
declare var navigate: (link: string) => void;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
const w = window as any;
|
||||
if (typeof isEditor === "undefined") {
|
||||
if (
|
||||
location.hostname === "prasi.avolut.com" &&
|
||||
location.pathname.startsWith("/ed")
|
||||
) {
|
||||
w.isEditor = true;
|
||||
} else {
|
||||
w.isEditor = false;
|
||||
}
|
||||
}
|
||||
w.isMobile = false;
|
||||
w.isDesktop = false;
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export const getPathname = () => {
|
||||
if (["localhost", "prasi.avolut.com"].includes(location.hostname)) {
|
||||
if (location.pathname.startsWith("/vi")) {
|
||||
return '/' + location.pathname.split("/").slice(3).join('/');
|
||||
}
|
||||
}
|
||||
return location.pathname
|
||||
};
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export const useLocal = <T extends object>(
|
||||
data: T,
|
||||
effect?: (arg: {
|
||||
init: boolean;
|
||||
}) => Promise<void | (() => void)> | void | (() => void),
|
||||
deps?: any[]
|
||||
): {
|
||||
[K in keyof T]: T[K] extends Promise<any> ? null | Awaited<T[K]> : T[K];
|
||||
} & { render: () => void } => {
|
||||
const [, _render] = useState({});
|
||||
const _ = useRef({
|
||||
data: data as unknown as T & {
|
||||
render: () => void;
|
||||
},
|
||||
deps: (deps || []) as any[],
|
||||
promisedKeys: new Set<string>(),
|
||||
ready: false,
|
||||
_loading: {} as any,
|
||||
});
|
||||
const local = _.current;
|
||||
|
||||
useEffect(() => {
|
||||
local.ready = true;
|
||||
if (effect) effect({ init: true });
|
||||
}, []);
|
||||
|
||||
if (local.ready === false) {
|
||||
local._loading = {};
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (!local.promisedKeys.has(k)) {
|
||||
let val = v;
|
||||
if (typeof val === "object" && val instanceof Promise) {
|
||||
local._loading[k] = true;
|
||||
local.promisedKeys.add(k);
|
||||
(local.data as any)[k] = null;
|
||||
val.then((resolved) => {
|
||||
(local.data as any)[k] = resolved;
|
||||
local._loading[k] = false;
|
||||
local.data.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local.data.render = () => {
|
||||
if (local.ready) _render({});
|
||||
};
|
||||
} else {
|
||||
if (local.deps.length > 0 && deps) {
|
||||
for (const [k, dep] of Object.entries(deps) as any) {
|
||||
if (local.deps[k] !== dep) {
|
||||
local.deps[k] = dep;
|
||||
|
||||
if (effect) {
|
||||
setTimeout(() => {
|
||||
effect({ init: false });
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return local.data as any;
|
||||
};
|
||||
Loading…
Reference in New Issue