This commit is contained in:
Rizky 2024-02-05 20:11:29 +07:00
commit 25976636fa
35 changed files with 1956 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.vscode

114
lib/comps/custom/Card.tsx Executable file
View File

@ -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;
};

191
lib/comps/custom/Detail.tsx Executable file
View File

@ -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>
);
};

116
lib/comps/custom/Tab.tsx Executable file
View File

@ -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>
);
};

View File

@ -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>
);
};

16
lib/comps/form/Date/index.tsx Executable file
View File

@ -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>
);
};

View File

@ -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);
}}
/>
);
};

241
lib/comps/form/Field.tsx Executable file
View File

@ -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;
};

37
lib/comps/form/Form.tsx Executable file
View File

@ -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>
);
};

View File

@ -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 "";
}
};

View File

@ -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>
);
};

View File

@ -0,0 +1,5 @@
export type SliderOptions = {
step: number;
min: { value: number; label: string };
max: { value: number; label: string };
};

58
lib/comps/icon.tsx Executable file
View File

@ -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`} />,
};

115
lib/comps/list/List.tsx Executable file
View File

@ -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>
);
};

103
lib/comps/list/ListNew.tsx Executable file
View File

@ -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>
);
};

56
lib/comps/ui/button.tsx Executable file
View File

@ -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 }

64
lib/comps/ui/calendar.tsx Executable file
View File

@ -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 }

79
lib/comps/ui/card.tsx Executable file
View File

@ -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 }

176
lib/comps/ui/form.tsx Executable file
View File

@ -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,
}

25
lib/comps/ui/input.tsx Executable file
View File

@ -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 }

24
lib/comps/ui/label.tsx Executable file
View File

@ -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 }

29
lib/comps/ui/popover.tsx Executable file
View File

@ -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 }

15
lib/comps/ui/skeleton.tsx Executable file
View File

@ -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 }

26
lib/comps/ui/slider.tsx Normal file
View File

@ -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 }

53
lib/comps/ui/tabs.tsx Executable file
View File

@ -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 }

24
lib/comps/ui/textarea.tsx Executable file
View File

@ -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 }

1
lib/css/build.css Executable file

File diff suppressed because one or more lines are too long

76
lib/css/globals.css Executable file
View File

@ -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;
}
}

6
lib/utils.ts Executable file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

15
lib/utils/date.ts Normal file
View File

@ -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 "-";
};

8
lib/utils/globals.d.ts vendored Executable file
View File

@ -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;

6
lib/utils/index.ts Executable file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

13
lib/utils/init.ts Executable file
View File

@ -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;

8
lib/utils/pathname.ts Executable file
View File

@ -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
};

69
lib/utils/use-local.ts Executable file
View File

@ -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;
};