This commit is contained in:
rizrmd 2024-03-22 19:08:51 -07:00
parent bff254f467
commit cdf8c5c837
9 changed files with 333 additions and 170 deletions

View File

@ -7,6 +7,7 @@ import {
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/comps/ui/navigation-menu";
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
import { FC, forwardRef } from "react";

View File

@ -175,15 +175,13 @@ export const usePopoverContext = () => {
export function Popover({
children,
content,
className,
modal = false,
popoverClassName,
className,
arrow,
...restOptions
}: {
className?: string;
root?: HTMLElement;
popoverClassName?: string;
className?: string;
children: React.ReactNode;
content?: React.ReactNode;
arrow?: boolean;
@ -196,7 +194,7 @@ export function Popover({
return (
<PopoverContext.Provider value={popover}>
<PopoverTrigger
className={className}
asChild
onClick={
typeof restOptions.open !== "undefined"
? () => {
@ -209,13 +207,12 @@ export function Popover({
</PopoverTrigger>
<PopoverContent
className={cx(
popoverClassName
? popoverClassName
: css`
background: white;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.4);
user-select: none;
`
className,
css`
background: white;
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.2);
user-select: none;
`
)}
>
{_content}
@ -297,10 +294,7 @@ export const PopoverContent = React.forwardRef<
return (
<FloatingPortal root={context.root}>
{context.backdrop ? (
<FloatingOverlay
className={"c-z-50"}
lockScroll
>
<FloatingOverlay className={"c-z-50"} lockScroll>
{content}
</FloatingOverlay>
) : (

61
comps/form/Dropdown/index.tsx Executable file
View File

@ -0,0 +1,61 @@
import { Popover } from "@/comps/custom/Popover";
import { Input } from "@/comps/ui/input";
import { useLocal } from "@/utils/use-local";
import { ChevronDown } from "lucide-react";
import { FC } from "react";
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
export const Dropdown: FC<ControllerRenderProps<FieldValues, string>> = ({
value,
}) => {
const local = useLocal({
open: false,
ref: { input: null as null | HTMLInputElement },
});
return (
<Popover
open={local.open}
onOpenChange={() => {
local.open = false;
local.render();
}}
arrow={false}
className={cx("c-rounded-sm c-bg-white")}
content={
<div
className={cx(
"c-px-3 c-py-2",
css`
width: ${local.ref.input?.clientWidth || 100}px;
`
)}
></div>
}
>
<div
className={cx(
"c-relative",
css`
cursor: pointer !important;
`
)}
>
<div className="c-absolute c-pointer-events-none c-inset-0 c-left-auto c-flex c-items-center c-pr-4">
<ChevronDown size={14} />
</div>
<Input
spellCheck={false}
onFocus={() => {
local.open = true;
local.render();
}}
className="cursor-pointer"
ref={(el) => {
local.ref.input = el;
}}
type="text"
/>
</div>
</Popover>
);
};

View File

@ -9,16 +9,15 @@ import {
import { useLocal } from "@/utils/use-local";
import autosize from "autosize";
import { FC, ReactNode, useEffect, useRef } from "react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import { Date } from "./Date";
import { Datetime } from "./Datetime";
import { InputMoney } from "./InputMoney";
import { PopUpDropdown } from "./PopUpDropdown";
import { Radio } from "./Radio";
import { SliderOptions } from "./Slider/types";
import { FormHook, modify } from "./utils/utils";
import { Dropdown } from "./Dropdown";
export const Field: FC<{
name: string;
@ -70,9 +69,6 @@ export const Field: FC<{
}) => {
const value = form?.hook.getValues()[name];
const local = useLocal({
dropdown: {
popup: false,
},
date: {
// label: "",
popup: false,
@ -120,19 +116,6 @@ export const Field: FC<{
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 || ({} as any)}
name={name}
@ -235,17 +218,7 @@ export const Field: FC<{
<Textarea {...field} ref={textAreaRef} />
)}
{type === "dropdown" && (
<Button
onClick={() => {
local.dropdown.popup = true;
local.render();
}}
variant={"outline"}
>
{field.value}
</Button>
)}
{type === "dropdown" && <Dropdown {...field} />}
{type === "date" && (
<Date

View File

@ -1,68 +0,0 @@
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

@ -10,7 +10,7 @@ export const Radio: FC<{
data: any;
current_name: string;
}) => Promise<(string | { value: string; label: string })[]>;
value: string;
value: string | string[];
PassProp: any;
custom: "y" | "n";
child: any;
@ -99,14 +99,16 @@ export const Radio: FC<{
option_item={item}
current_name={name}
item_click={() => {
console.log(selection);
console.log(value, "====single", name);
if (selection === "single") {
console.log(form.hook.get);
form.hook.setValue(name, [...value]);
local.mod(name, { value: item.value });
local.render();
} else if (selection === "multi") {
form.hook
const val = []
val.push()
local.mod(name, { value: item.value });
local.render();
console.log(value, "====multi", name);
} else {
null;
}

198
comps/ui/dropdown-menu.tsx Executable file
View File

@ -0,0 +1,198 @@
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"c-flex c-cursor-default c-select-none c-items-center c-rounded-sm c-px-2 c-py-1.5 c-text-sm c-outline-none focus:c-bg-accent data-[state=open]:c-bg-accent",
inset && "c-pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="c-ml-auto c-h-4 c-w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"c-z-50 c-min-w-[8rem] c-overflow-hidden c-rounded-md c-border c-bg-popover c-p-1 c-text-popover-foreground c-shadow-lg 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-[side=bottom]:c-slide-in-from-top-2 data-[side=left]:c-slide-in-from-right-2 data-[side=right]:c-slide-in-from-left-2 data-[side=top]:c-slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"c-z-50 c-min-w-[8rem] c-overflow-hidden c-rounded-md c-border c-bg-popover c-p-1 c-text-popover-foreground c-shadow-md 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-[side=bottom]:c-slide-in-from-top-2 data-[side=left]:c-slide-in-from-right-2 data-[side=right]:c-slide-in-from-left-2 data-[side=top]:c-slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"c-relative c-flex c-cursor-default c-select-none c-items-center c-rounded-sm c-px-2 c-py-1.5 c-text-sm c-outline-none c-transition-colors focus:c-bg-accent focus:c-text-accent-foreground data-[disabled]:c-pointer-events-none data-[disabled]:c-opacity-50",
inset && "c-pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"c-relative c-flex c-cursor-default c-select-none c-items-center c-rounded-sm c-py-1.5 c-pl-8 c-pr-2 c-text-sm c-outline-none c-transition-colors focus:c-bg-accent focus:c-text-accent-foreground data-[disabled]:c-pointer-events-none data-[disabled]:c-opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="c-absolute c-left-2 c-flex c-h-3.5 c-w-3.5 c-items-center c-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="c-h-4 c-w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"c-relative c-flex c-cursor-default c-select-none c-items-center c-rounded-sm c-py-1.5 c-pl-8 c-pr-2 c-text-sm c-outline-none c-transition-colors focus:c-bg-accent focus:c-text-accent-foreground data-[disabled]:c-pointer-events-none data-[disabled]:c-opacity-50",
className
)}
{...props}
>
<span className="c-absolute c-left-2 c-flex c-h-3.5 c-w-3.5 c-items-center c-justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="c-h-2 c-w-2 c-fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"c-px-2 c-py-1.5 c-text-sm c-font-semibold",
inset && "c-pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("c--mx-1 c-my-1 c-h-px c-bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("c-ml-auto c-text-xs c-tracking-widest c-opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
@ -8,23 +8,23 @@ import {
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
} from "react-hook-form";
import { cn } from "@/utils"
import { Label } from "@/comps/ui/label"
import { cn } from "@/utils";
import { Label } from "@/comps/ui/label";
const Form = FormProvider
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
@ -36,21 +36,21 @@ const FormField = <
<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 fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState)
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext
const { id } = itemContext;
return {
id,
@ -59,36 +59,36 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
};
};
type FormItemContextValue = {
id: string
}
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
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"
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
const { error, formItemId } = useFormField();
return (
<Label
@ -97,15 +97,16 @@ const FormLabel = React.forwardRef<
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
@ -119,15 +120,15 @@ const FormControl = React.forwardRef<
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
const { formDescriptionId } = useFormField();
return (
<p
@ -136,19 +137,19 @@ const FormDescription = React.forwardRef<
className={cn("c-text-sm c-text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
);
});
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
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null
return null;
}
return (
@ -160,9 +161,9 @@ const FormMessage = React.forwardRef<
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
@ -173,4 +174,4 @@ export {
FormDescription,
FormMessage,
FormField,
}
};

View File

@ -8,11 +8,11 @@ export const useLocal = <T extends object>(
deps?: any[]
): {
[K in keyof T]: T[K] extends Promise<any> ? null | Awaited<T[K]> : T[K];
} & { render: () => void } => {
} & { render: (force?: boolean) => void } => {
const [, _render] = useState({});
const _ = useRef({
data: data as unknown as T & {
render: () => void;
render: (force?: boolean) => void;
},
deps: (deps || []) as any[],
promisedKeys: new Set<string>(),
@ -45,8 +45,9 @@ export const useLocal = <T extends object>(
}
}
local.data.render = () => {
if (local.ready) _render({});
local.data.render = (force) => {
if (force) _render({})
else if (local.ready) _render({});
};
} else {
if (local.deps.length > 0 && deps) {