mirror of
https://github.com/khairul169/code-share.git
synced 2026-10-24 11:53:58 +07:00
feat: add personal project list, add project settings, etc
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import React, { forwardRef, useId } from "react";
|
||||
import FormField, { FormFieldProps } from "./form-field";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Controller, FieldValues, Path } from "react-hook-form";
|
||||
import { useFormReturn } from "~/hooks/useForm";
|
||||
|
||||
export type CheckboxItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type BaseCheckboxProps = React.ComponentPropsWithoutRef<"input"> &
|
||||
FormFieldProps & {
|
||||
inputClassName?: string;
|
||||
items?: CheckboxItem[] | null;
|
||||
};
|
||||
|
||||
const BaseCheckbox = forwardRef<HTMLInputElement, BaseCheckboxProps>(
|
||||
(props, ref) => {
|
||||
const { className, label, error, inputClassName, items, ...restProps } =
|
||||
props;
|
||||
const id = useId();
|
||||
|
||||
const input = (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 cursor-pointer",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<input
|
||||
ref={ref}
|
||||
id={id}
|
||||
type="checkbox"
|
||||
{...restProps}
|
||||
className={inputClassName}
|
||||
/>
|
||||
{label ? <span>{label}</span> : null}
|
||||
</label>
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <FormField id={id} error={error} children={input} />;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
);
|
||||
|
||||
type CheckboxProps<T extends FieldValues> = Omit<
|
||||
BaseCheckboxProps,
|
||||
"form" | "name"
|
||||
> & {
|
||||
form?: useFormReturn<T>;
|
||||
name?: Path<T>;
|
||||
};
|
||||
|
||||
const Checkbox = <T extends FieldValues>(props: CheckboxProps<T>) => {
|
||||
const { form, ...restProps } = props;
|
||||
|
||||
if (form && props.name) {
|
||||
return (
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field, fieldState }) => (
|
||||
<BaseCheckbox
|
||||
{...restProps}
|
||||
{...field}
|
||||
checked={field.value}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <BaseCheckbox {...restProps} />;
|
||||
};
|
||||
|
||||
export default Checkbox;
|
||||
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 md:p-8 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:rounded-xl dark:border-slate-800 dark:bg-slate-950",
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg max-h-dvh overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 md:p-8 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:rounded-xl dark:border-slate-600 dark:bg-slate-800",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ForwardedRef, forwardRef, useId } from "react";
|
||||
import { forwardRef, useId } from "react";
|
||||
import { Controller, FieldValues, Path } from "react-hook-form";
|
||||
import { useFormReturn } from "~/hooks/useForm";
|
||||
import { cn } from "~/lib/utils";
|
||||
@@ -17,7 +17,7 @@ const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/40 dark:bg-background dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
|
||||
"flex h-10 w-full rounded-md border border-slate-200 px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/40 bg-transparent dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
|
||||
inputClassName
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import React, { forwardRef, useId } from "react";
|
||||
import FormField, { FormFieldProps } from "./form-field";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Controller, FieldValues, Path } from "react-hook-form";
|
||||
import { useFormReturn } from "~/hooks/useForm";
|
||||
|
||||
export type SelectItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type BaseSelectProps = React.ComponentPropsWithoutRef<"select"> &
|
||||
FormFieldProps & {
|
||||
inputClassName?: string;
|
||||
items?: SelectItem[] | null;
|
||||
};
|
||||
|
||||
const BaseSelect = forwardRef<HTMLSelectElement, BaseSelectProps>(
|
||||
(props, ref) => {
|
||||
const { className, label, error, inputClassName, items, ...restProps } =
|
||||
props;
|
||||
const id = useId();
|
||||
|
||||
const input = (
|
||||
<select
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 w-full rounded-md border border-slate-200 px-3 py-2 text-sm ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/40 bg-transparent dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
|
||||
inputClassName
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{items?.map((item) => (
|
||||
<option key={item.value} value={item.value} className="text-gray-900">
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
|
||||
if (label || error) {
|
||||
return (
|
||||
<FormField
|
||||
id={id}
|
||||
label={label}
|
||||
error={error}
|
||||
className={className}
|
||||
children={input}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
);
|
||||
|
||||
type SelectProps<T extends FieldValues> = Omit<
|
||||
BaseSelectProps,
|
||||
"form" | "name"
|
||||
> & {
|
||||
form?: useFormReturn<T>;
|
||||
name?: Path<T>;
|
||||
};
|
||||
|
||||
const Select = <T extends FieldValues>(props: SelectProps<T>) => {
|
||||
const { form, ...restProps } = props;
|
||||
|
||||
if (form && props.name) {
|
||||
return (
|
||||
<Controller
|
||||
control={form.control}
|
||||
name={props.name}
|
||||
render={({ field, fieldState }) => (
|
||||
<BaseSelect
|
||||
{...restProps}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <BaseSelect {...restProps} />;
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-slate-100 dark:bg-slate-800", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
+25
-11
@@ -2,7 +2,6 @@ import { cn } from "~/lib/utils";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import ActionButton from "./action-button";
|
||||
import { FiX } from "react-icons/fi";
|
||||
import FileIcon from "./file-icon";
|
||||
|
||||
export type Tab = {
|
||||
title: string;
|
||||
@@ -16,9 +15,18 @@ type Props = {
|
||||
current?: number;
|
||||
onChange?: (idx: number) => void;
|
||||
onClose?: (idx: number) => void;
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
};
|
||||
|
||||
const Tabs = ({ tabs, current = 0, onChange, onClose }: Props) => {
|
||||
const Tabs = ({
|
||||
tabs,
|
||||
current = 0,
|
||||
onChange,
|
||||
onClose,
|
||||
className,
|
||||
containerClassName,
|
||||
}: Props) => {
|
||||
const tabContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onWheel = (e: WheelEvent) => {
|
||||
@@ -63,7 +71,12 @@ const Tabs = ({ tabs, current = 0, onChange, onClose }: Props) => {
|
||||
}, [tabs, current]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col items-stretch bg-slate-800">
|
||||
<div
|
||||
className={cn(
|
||||
"w-full flex flex-col items-stretch bg-slate-800",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{tabs.length > 0 ? (
|
||||
<nav
|
||||
ref={tabContainerRef}
|
||||
@@ -77,13 +90,15 @@ const Tabs = ({ tabs, current = 0, onChange, onClose }: Props) => {
|
||||
icon={tab.icon}
|
||||
isActive={idx === current}
|
||||
onSelect={() => onChange && onChange(idx)}
|
||||
onClose={!tab.locked ? () => onClose && onClose(idx) : null}
|
||||
onClose={!tab.locked && onClose ? () => onClose(idx) : null}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
) : null}
|
||||
|
||||
<main className="flex-1 overflow-hidden">{tabView}</main>
|
||||
<main className={cn("flex-1 overflow-hidden", containerClassName)}>
|
||||
{tabView}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -119,17 +134,16 @@ const TabItem = ({
|
||||
onClick={onSelect}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"pl-4 pr-4 truncate flex items-center self-stretch",
|
||||
onClose ? "pr-0" : ""
|
||||
)}
|
||||
>
|
||||
{icon != null ? (
|
||||
icon
|
||||
) : (
|
||||
<FileIcon file={{ isDirectory: false, filename: title }} />
|
||||
)}
|
||||
<span className="inline-block ml-2 truncate">{filename}</span>
|
||||
{icon}
|
||||
<span className={cn("inline-block truncate", icon ? "ml-2" : "")}>
|
||||
{filename}
|
||||
</span>
|
||||
<span>{ext}</span>
|
||||
</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user