This commit is contained in:
rizky 2024-07-28 23:59:50 -07:00
parent f5f187ff81
commit a20ae163f7
8 changed files with 658 additions and 158 deletions

View File

@ -57,7 +57,7 @@ export const Form: FC<FMProps> = (props) => {
field: null, field: null,
}, },
has_fields_container: null as any, has_fields_container: null as any,
is_newly_created: false is_newly_created: false,
}); });
const form_inner_ref = useRef<HTMLDivElement>(null); const form_inner_ref = useRef<HTMLDivElement>(null);
@ -174,6 +174,11 @@ export const Form: FC<FMProps> = (props) => {
if (fm.status === "resizing") { if (fm.status === "resizing") {
fm.status = "ready"; fm.status = "ready";
} }
// useEffect(() => {
// setTimeout(() => {
// fm.render();
// }, 100);
// }, []);
return ( return (
<form <form
onSubmit={(e) => { onSubmit={(e) => {

View File

@ -168,6 +168,7 @@ export const FieldTypeInput: FC<{
case "upload": case "upload":
return ( return (
<FieldUpload <FieldUpload
arg={arg}
field={field} field={field}
fm={fm} fm={fm}
prop={prop} prop={prop}
@ -177,6 +178,7 @@ export const FieldTypeInput: FC<{
case "import": case "import":
return ( return (
<FieldUpload <FieldUpload
arg={arg}
field={field} field={field}
fm={fm} fm={fm}
prop={prop} prop={prop}

View File

@ -1,19 +1,32 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import get from "lodash.get"; import get from "lodash.get";
import { FC } from "react"; import { FC } from "react";
import { FMLocal, FieldLocal } from "../../typings"; import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { PropTypeInput } from "./TypeInput"; import { PropTypeInput } from "./TypeInput";
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import {
ArrowDownToLine,
ExternalLink,
Loader2,
Paperclip,
SquareArrowOutUpRight,
Trash2,
Upload,
} from "lucide-react";
import { Spinner } from "lib/comps/ui/field-loading";
const w = window as unknown as { const w = window as unknown as {
serverurl: string serverurl: string;
} };
export const FieldUpload: FC<{ export const FieldUpload: FC<{
field: FieldLocal; field: FieldLocal;
fm: FMLocal; fm: FMLocal;
prop: PropTypeInput; prop: PropTypeInput;
styling?: string;
arg: FieldProp;
on_change: (e: any) => void | Promise<void>; on_change: (e: any) => void | Promise<void>;
}> = ({ field, fm, prop, on_change }) => { }> = ({ field, fm, prop, on_change, arg }) => {
const styling = arg.upload_style ? arg.upload_style : "full";
let type_field = prop.sub_type; let type_field = prop.sub_type;
let value: any = fm.data[field.name]; let value: any = fm.data[field.name];
// let type_upload = // let type_upload =
@ -22,12 +35,125 @@ export const FieldUpload: FC<{
display: false as any, display: false as any,
ref: null as any, ref: null as any,
drop: false as boolean, drop: false as boolean,
fase: value ? "preview" : ("start" as "start" | "upload" | "preview"),
style: "inline" as "inline" | "full",
}); });
let display: any = null; let display: any = null;
const disabled = const disabled =
typeof field.disabled === "function" ? field.disabled() : field.disabled; typeof field.disabled === "function" ? field.disabled() : field.disabled;
const on_upload = async (event: any) => {
let file = null;
try {
file = event.target.files[0];
} catch (ex) {}
if (type_field === "import") {
const reader = new FileReader();
reader.onload = (e: any) => {
const binaryStr = e.target.result;
const workbook = XLSX.read(binaryStr, { type: "binary" });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
if (typeof on_change === "function") {
const res = on_change({
value: jsonData,
file: file,
binnary: e.target.result,
});
}
};
reader.readAsBinaryString(file);
} else {
const formData = new FormData();
formData.append("file", file);
let url = siteurl("/_upload");
if (
location.hostname === "prasi.avolut.com" ||
location.host === "localhost:4550"
) {
const newurl = new URL(location.href);
newurl.pathname = `/_proxy/${url}`;
url = newurl.toString();
}
input.fase = "upload";
input.render();
try {
const response = await fetch(url, {
method: "POST",
body: formData,
});
if (response.ok) {
const contentType: any = response.headers.get("content-type");
let result;
if (contentType.includes("application/json")) {
result = await response.json();
} else if (contentType.includes("text/plain")) {
result = await response.text();
} else {
result = await response.blob();
}
if (Array.isArray(result)) {
fm.data[field.name] = `_file${get(result, "[0]")}`;
fm.render();
setTimeout(() => {
input.fase = "preview";
input.render();
}, 1000);
} else {
input.fase = "start";
input.render();
alert("Error upload");
}
} else {
}
} catch (ex) {
input.fase = "start";
input.render();
alert("Error upload");
}
}
if (input.ref) {
input.ref.value = null;
}
};
return ( return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full"> <div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full c-items-center">
{input.fase === "start" ? (
<>
<div className="c-flex c-flex-row c-relative c-flex-grow">
<input
ref={(ref) => (input.ref = ref)}
type="file"
multiple={false}
onChange={on_upload}
className={cx(
"c-absolute c-w-full c-h-full c-cursor-pointer c-top-0 c-left-0 c-hidden"
)}
/>
{styling === "inline" ? (
<>
<div
onClick={() => {
if (input.ref) {
console.log(input.ref)
input.ref.click();
}
}}
className="c-items-center c-flex c-text-base c-px-5 c-py-3 c-outline-none c-rounded c-cursor-pointer "
>
<div className="c-flex c-flex-row c-items-center c-px-2">
<Upload className="c-h-4 c-w-4" />
</div>
<div className="c-flex c-flex-row c-items-center">
Upload Your File
</div>
</div>
</>
) : (
<>
<div <div
onDrop={(e: any) => { onDrop={(e: any) => {
e.preventDefault(); e.preventDefault();
@ -42,7 +168,7 @@ export const FieldUpload: FC<{
}} }}
className={cx( className={cx(
input.drop ? "c-bg-gray-100" : "", input.drop ? "c-bg-gray-100" : "",
"hover:c-bg-gray-100 c-m-1 c-relative c-flex-grow c-p-4 c-items-center c-flex c-flex-row c-text-gray-400 c-border c-border-gray-200 c-border-dashed c-rounded c-cursor-pointer" "hover:c-bg-gray-100 c-flex-grow c-m-1 c-relative c-flex-grow c-p-4 c-items-center c-flex c-flex-row c-text-gray-400 c-border c-border-gray-200 c-border-dashed c-rounded c-cursor-pointer"
)} )}
> >
<div className="c-flex-row c-flex c-flex-grow c-space-x-2"> <div className="c-flex-row c-flex c-flex-grow c-space-x-2">
@ -65,127 +191,145 @@ export const FieldUpload: FC<{
<div className="c-flex c-flex-col"> <div className="c-flex c-flex-col">
<span className="c-font-medium"> <span className="c-font-medium">
Drop Your File or{" "} Drop Your File or{" "}
<span className="c-underline c-text-blue-500">Browse</span> <span className="c-underline c-text-blue-500">
Browse
</span>
</span> </span>
</div> </div>
</div> </div>
<input
ref={(ref) => (input.ref = ref)}
type="file"
multiple
onChange={async (event: any) => {
let file = null;
try {
file = event.target.files[0];
} catch (ex) {}
if (type_field === "import") {
const reader = new FileReader();
reader.onload = (e: any) => {
const binaryStr = e.target.result;
const workbook = XLSX.read(binaryStr, { type: "binary" });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(worksheet);
if (typeof on_change === "function") {
const res = on_change({ value: jsonData });
}
};
reader.readAsBinaryString(file);
} else {
const formData = new FormData();
formData.append("file", file);
let url = siteurl("/_upload");
if (location.hostname === 'prasi.avolut.com' || location.host === 'localhost:4550') {
const newurl = new URL(location.href);
newurl.pathname = `/_proxy/${w.serverurl}/_upload`;
url = newurl.toString();
}
const response = await fetch(url, {
method: "POST",
body: formData,
});
if (response.ok) {
const contentType: any = response.headers.get("content-type");
let result;
if (contentType.includes("application/json")) {
result = await response.json();
} else if (contentType.includes("text/plain")) {
result = await response.text();
} else {
result = await response.blob();
}
if (Array.isArray(result)) {
fm.data[field.name] = get(result, "[0]");
fm.render();
} else {
alert("Error upload");
}
} else {
}
}
}}
className={
"c-absolute c-w-full c-h-full c-cursor-pointer c-top-0 c-left-0 c-hidden"
}
/>
</div> </div>
</div> </>
);
// console.log({ prop });
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full">
<div
className={cx(
input.display ? "c-hidden" : "",
"c-flex-grow c-px-2 c-flex c-flex-row c-items-center"
)} )}
</div>
</>
) : input.fase === "upload" ? (
<>
<div className="c-px-2">
<Loader2 className={cx("c-h-5 c-w-5 c-animate-spin")} />
</div>
<div className="c-px-2">Uploading</div>
</>
) : input.fase === "preview" ? (
<>
<div className="c-flex c-flex-row c-p-2 c-items-center">
<IconFile type={getFileName(siteurl(value)).extension} />
</div>
<div
className="c-line-clamp-1 c-flex-grow c-items-center"
onClick={() => { onClick={() => {
if (input.ref) { let url = siteurl(value);
input.display = !input.display; window.open(url, "_blank");
input.ref.focus();
input.render();
}
}} }}
> >
{formatMoney(Number(value) || 0)} {getFileName(siteurl(value)).fullname}
</div> </div>
<input <div className="c-flex c-flex-row c-items-center">
ref={(el) => (input.ref = el)} <div className="c-flex c-flex-row c-space-x-1 c-px-2">
type={"number"} <SquareArrowOutUpRight
onClick={() => {}} className="c-h-5 c-w-5"
onChange={(ev) => { onClick={() => {
fm.data[field.name] = ev.currentTarget.value; let url = siteurl(value);
window.open(url, "_blank");
}}
/>
<Trash2
className="c-text-red-500 c-h-5 c-w-5"
onClick={() => {
fm.data[field.name] = null;
fm.render(); fm.render();
}} }}
value={value}
disabled={disabled}
className={cx(
!input.display ? "c-hidden" : "",
"c-flex-1 c-bg-transparent c-outline-none c-px-2 c-text-sm c-w-full"
)}
spellCheck={false}
onFocus={() => {
field.focused = true;
field.render();
}}
onBlur={() => {
console.log("blur");
field.focused = false;
input.display = !input.display;
input.render();
field.render();
}}
/> />
</div> </div>
</div>
</>
) : (
<></>
)}
</div>
);
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full c-items-center">
<div className="c-flex c-flex-row c-p-2 c-items-center">
<IconFile
type={
getFileName("https://www.example.com/path/to/your/file.txt")
.extension
}
/>
</div>
<div className="c-line-clamp-1 c-flex-grow c-items-center">
{getFileName("https://www.example.com/path/to/your/file.txt").fullname}
</div>
<div className="c-flex c-flex-row c-items-center">
<div className="c-flex c-flex-row c-space-x-1 c-px-2">
<SquareArrowOutUpRight
className="c-h-5 c-w-5"
onClick={() => {
let url = siteurl(value);
window.open(url, "_blank");
}}
/>
<Trash2
className="c-text-red-500 c-h-5 c-w-5"
onClick={() => {
fm.data[field.name] = null;
fm.render();
}}
/>
</div>
</div>
</div>
); );
}; };
const formatMoney = (res: number) => { const getFileName = (url: string) => {
const formattedAmount = new Intl.NumberFormat("id-ID", { const fileName = url.substring(url.lastIndexOf("/") + 1);
minimumFractionDigits: 0, const dotIndex = fileName.lastIndexOf(".");
}).format(res); const fullname = fileName;
return formattedAmount; if (dotIndex === -1) {
return { name: fileName, extension: "", fullname };
}
const name = fileName.substring(0, dotIndex);
const extension = fileName.substring(dotIndex + 1);
return { name, extension, fullname };
};
const IconFile: FC<{ type: string }> = ({ type }) => {
if (["xlsx"].includes(type)) {
return (
<div className="c-flex c-flex-row c-text-[#2a801d]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
} else {
return (
<div className="c-flex c-flex-row ">
<Paperclip className="c-h-5 c-w-5" />
</div>
);
}
return (
<div className="c-flex c-flex-row c-p-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
}; };

View File

@ -117,6 +117,7 @@ export type FieldProp = {
model_upload?: "upload" | "import"; model_upload?: "upload" | "import";
max_date?: any; max_date?: any;
min_date?: any; min_date?: any;
upload_style?: "inline" | "full"
}; };
export type FMInternal = { export type FMInternal = {

View File

@ -0,0 +1,200 @@
import React, { useState, useEffect, useRef } from 'react';
interface RowData {
id: number;
[key: string]: any;
}
interface VirtualTableProps {
data: RowData[];
columns: string[];
estimatedRowHeight: number;
visibleRows: number;
resizableColumns?: boolean;
pinnedColumns?: string[]; // New prop to specify pinned columns
}
const VirtualTable: React.FC<VirtualTableProps> = ({
data,
columns,
estimatedRowHeight,
visibleRows,
resizableColumns = false,
pinnedColumns = [] // Default to no pinned columns
}) => {
const [start, setStart] = useState(0);
const [rowHeights, setRowHeights] = useState<number[]>([]);
const [columnWidths, setColumnWidths] = useState<{ [key: string]: number }>(
Object.fromEntries(columns.map(column => [column, 100]))
);
const containerRef = useRef<HTMLDivElement>(null);
const rowRefs = useRef<(HTMLTableRowElement | null)[]>([]);
const resizingColumn = useRef<string | null>(null);
const scrollableColumns = columns.filter(col => !pinnedColumns.includes(col));
useEffect(() => {
const handleScroll = () => {
if (containerRef.current) {
const scrollTop = containerRef.current.scrollTop;
const newStart = Math.floor(scrollTop / estimatedRowHeight);
setStart(newStart);
}
};
containerRef.current?.addEventListener('scroll', handleScroll);
return () => containerRef.current?.removeEventListener('scroll', handleScroll);
}, [estimatedRowHeight]);
useEffect(() => {
const measureRowHeights = () => {
const newRowHeights = rowRefs.current.map(
(rowRef) => rowRef?.getBoundingClientRect().height || estimatedRowHeight
);
setRowHeights(newRowHeights);
};
measureRowHeights();
window.addEventListener('resize', measureRowHeights);
return () => window.removeEventListener('resize', measureRowHeights);
}, [data, estimatedRowHeight]);
const getTotalHeight = () => {
return rowHeights.reduce((sum, height) => sum + height, 0) || data.length * estimatedRowHeight;
};
const getOffsetForIndex = (index: number) => {
return rowHeights.slice(0, index).reduce((sum, height) => sum + height, 0);
};
const visibleData = data.slice(start, start + visibleRows);
const handleMouseDown = (column: string) => (e: React.MouseEvent) => {
if (resizableColumns) {
resizingColumn.current = column;
e.preventDefault();
}
};
const handleMouseMove = (e: React.MouseEvent) => {
if (resizableColumns && resizingColumn.current) {
const newWidth = Math.max(50, e.clientX - (e.target as HTMLElement).getBoundingClientRect().left);
setColumnWidths(prev => ({
...prev,
[resizingColumn.current!]: newWidth
}));
}
};
const handleMouseUp = () => {
if (resizableColumns) {
resizingColumn.current = null;
}
};
useEffect(() => {
if (resizableColumns) {
document.addEventListener('mousemove', handleMouseMove as any);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove as any);
document.removeEventListener('mouseup', handleMouseUp);
};
}
}, [resizableColumns]);
const renderTableContent = (columnSet: string[]) => (
<table style={{
transform: `translateY(${getOffsetForIndex(start)}px)`,
width: '100%',
borderCollapse: 'collapse'
}}>
<thead>
<tr>
{columnSet.map((column) => (
<th key={column} style={{
padding: '8px',
textAlign: 'left',
fontWeight: 'bold',
width: columnWidths[column],
position: 'relative'
}}>
{column}
{resizableColumns && (
<div
style={{
position: 'absolute',
right: 0,
top: 0,
bottom: 0,
width: '5px',
cursor: 'col-resize',
background: 'transparent'
}}
onMouseDown={handleMouseDown(column)}
/>
)}
</th>
))}
</tr>
</thead>
<tbody>
{visibleData.map((row, index) => (
<tr
key={row.id}
ref={(el) => (rowRefs.current[start + index] = el)}
>
{columnSet.map((column) => (
<td key={column} style={{
padding: '8px',
width: columnWidths[column],
maxWidth: columnWidths[column],
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>
{row[column]}
</td>
))}
</tr>
))}
</tbody>
</table>
);
return (
<div
ref={containerRef}
style={{
height: `${visibleRows * estimatedRowHeight}px`,
overflowY: 'auto',
display: 'flex'
}}
>
{pinnedColumns.length > 0 && (
<div style={{
position: 'sticky',
left: 0,
zIndex: 1,
backgroundColor: 'white',
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
}}>
<div style={{ height: `${getTotalHeight()}px`, position: 'relative' }}>
{renderTableContent(pinnedColumns)}
</div>
</div>
)}
<div style={{
flexGrow: 1,
overflowX: 'auto',
marginLeft: pinnedColumns.length > 0 ? '5px' : '0'
}}>
<div style={{ height: `${getTotalHeight()}px`, position: 'relative' }}>
{renderTableContent(scrollableColumns)}
</div>
</div>
</div>
);
};
export default VirtualTable;

115
comps/ui/dialog.tsx Executable file
View File

@ -0,0 +1,115 @@
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/utils";
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"c-fixed c-inset-0 c-z-50 c-bg-black/80 data-[state=open]:c-animate-in data-[state=closed]:c-animate-out data-[state=closed]:c-fade-out-0 data-[state=open]:c-fade-in-0",
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogPrimitive.Content
ref={ref}
className={cn(
"c-fixed c-left-[50%] c-top-[50%] c-z-50 c-grid c-w-full c-max-w-lg c-translate-x-[-50%] c-translate-y-[-50%] c-gap-4 c-border c-bg-background c-p-6 c-shadow-lg c-duration-200 data-[state=open]:c-animate-in data-[state=closed]:c-animate-out data-[state=closed]:c-fade-out-0 data-[state=open]:c-fade-in-0 data-[state=closed]:c-zoom-out-95 data-[state=open]:c-zoom-in-95 data-[state=closed]:c-slide-out-to-left-1/2 data-[state=closed]:c-slide-out-to-top-[48%] data-[state=open]:c-slide-in-from-left-1/2 data-[state=open]:c-slide-in-from-top-[48%] sm:c-rounded-lg",
className
)}
{...props}
>
{children}
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"c-flex c-flex-col c-space-y-1.5 c-text-center sm:c-text-left",
className
)}
{...props}
/>
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"c-flex c-flex-col-reverse sm:c-flex-row sm:c-justify-end sm:c-space-x-2",
className
)}
{...props}
/>
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"c-text-lg c-font-semibold c-leading-none c-tracking-tight",
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("c-text-sm c-text-muted-foreground", className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
};

28
comps/ui/progress.tsx Executable file
View File

@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "@/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cx(
"c-relative c-h-4 c-w-full c-overflow-hidden c-rounded-full c-bg-secondary",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="c-h-full c-w-full c-flex-1 c-bg-primary c-transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View File

@ -11,7 +11,12 @@ export const Accordion = lazify(
export const Popover = lazify( export const Popover = lazify(
async () => (await import("@/comps/custom/Popover")).Popover async () => (await import("@/comps/custom/Popover")).Popover
); );
export const Progress = lazify(
async () => (await import("@/comps/ui/progress")).Progress
);
export const Dialog = lazify(
async () => (await import("@/comps/ui/dialog")).Dialog
);
export const Typeahead = lazify( export const Typeahead = lazify(
async () => (await import("@/comps/ui/typeahead")).Typeahead async () => (await import("@/comps/ui/typeahead")).Typeahead
); );