add custom generic input and dropdown selector components for the add link modal
This commit is contained in:
parent
1597cbe837
commit
5644c7108f
@ -3,24 +3,34 @@ import {
|
|||||||
SkillLevel,
|
SkillLevel,
|
||||||
Skill,
|
Skill,
|
||||||
type PlatformLink,
|
type PlatformLink,
|
||||||
|
Platform,
|
||||||
} from "@prisma/client";
|
} from "@prisma/client";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { PencilSquareIcon } from "@heroicons/react/24/solid";
|
import {
|
||||||
import { ChevronDownIcon, TrashIcon } from "@heroicons/react/24/outline";
|
PencilSquareIcon,
|
||||||
|
XCircleIcon as XCircleSolid,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
import {
|
||||||
|
ChevronDownIcon,
|
||||||
|
TrashIcon,
|
||||||
|
XCircleIcon as XCircleOutline,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { PlusIcon } from "@heroicons/react/20/solid";
|
import { PlusIcon } from "@heroicons/react/20/solid";
|
||||||
import {
|
import {
|
||||||
|
DropdownSelector,
|
||||||
MultiSelectorMany,
|
MultiSelectorMany,
|
||||||
MultiSelectorOption,
|
MultiSelectorOption,
|
||||||
SelectedManyContext,
|
SelectedManyContext,
|
||||||
SimpleSelectorManyOption,
|
SimpleSelectorManyOption,
|
||||||
} from "../../forms/selectors";
|
} from "../../forms/selectors";
|
||||||
import { InfoInputLine } from "~/components/forms/textInput";
|
import { GenericInput, InfoInputLine } from "~/components/forms/textInput";
|
||||||
import { PriceIcon } from "~/prices/Icons";
|
import { PriceIcon } from "~/prices/Icons";
|
||||||
import { type Dispatch, type SetStateAction, useState } from "react";
|
import { type Dispatch, type SetStateAction, useState } from "react";
|
||||||
import {
|
import {
|
||||||
type UseFormReturn,
|
type UseFormReturn,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
|
useForm,
|
||||||
} from "react-hook-form";
|
} from "react-hook-form";
|
||||||
import Modal from "react-modal";
|
import Modal from "react-modal";
|
||||||
import { type RouterInputs } from "~/utils/api";
|
import { type RouterInputs } from "~/utils/api";
|
||||||
@ -72,6 +82,26 @@ const LinkModal = ({
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setOpen: Dispatch<SetStateAction<boolean>>;
|
setOpen: Dispatch<SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { register } = useForm<PlatformLink>();
|
||||||
|
const platformTypeOptions = [
|
||||||
|
{
|
||||||
|
label: "Website",
|
||||||
|
value: Platform.WEBSITE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "iOS App",
|
||||||
|
value: Platform.APP_IOS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Android App",
|
||||||
|
value: Platform.APP_ANDROID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "PDF Document",
|
||||||
|
value: Platform.PDF,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
style={{
|
style={{
|
||||||
@ -80,10 +110,10 @@ const LinkModal = ({
|
|||||||
height: "300px",
|
height: "300px",
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
overflow: "visible",
|
overflow: "hidden",
|
||||||
boxShadow: "0 25px 50px -12px rgb(0 0 0 / 0.25)",
|
boxShadow: "0 25px 50px -12px rgb(0 0 0 / 0.25)",
|
||||||
borderRadius: ".8rem",
|
borderRadius: ".8rem",
|
||||||
border: "1px solid #d4d4d4",
|
border: ".1rem solid #d4d4d4",
|
||||||
},
|
},
|
||||||
overlay: {
|
overlay: {
|
||||||
zIndex: 60,
|
zIndex: 60,
|
||||||
@ -95,8 +125,37 @@ const LinkModal = ({
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="h-full bg-neutral-200">
|
||||||
<h1 className="text-lg font-bold">Link Details</h1>
|
<section className="relative bg-gradient-to-t from-neutral-800 to-neutral-600 p-2 drop-shadow-xl">
|
||||||
|
<h1 className="text-center text-lg font-bold text-neutral-200">
|
||||||
|
Platform Details
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
className="group absolute bottom-0 right-0 top-0 h-full px-4"
|
||||||
|
>
|
||||||
|
<XCircleSolid className="hidden h-6 w-6 text-white group-hover:block" />
|
||||||
|
<XCircleOutline className="block h-6 w-6 text-white group-hover:hidden" />
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="space-y-4 p-4">
|
||||||
|
<DropdownSelector
|
||||||
|
options={platformTypeOptions}
|
||||||
|
label="Type"
|
||||||
|
details={register("platform", { required: true })}
|
||||||
|
/>
|
||||||
|
<GenericInput
|
||||||
|
placeholder="platform URL"
|
||||||
|
label="Link"
|
||||||
|
type="text"
|
||||||
|
details={register("link", { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import { createContext, useContext, useState } from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import { useFormContext, type UseFormRegisterReturn } from "react-hook-form";
|
import {
|
||||||
|
type InternalFieldName,
|
||||||
|
useFormContext,
|
||||||
|
type UseFormRegisterReturn,
|
||||||
|
} from "react-hook-form";
|
||||||
|
|
||||||
// generics
|
// generics
|
||||||
interface ToStringable {
|
interface ToStringable {
|
||||||
@ -165,6 +169,45 @@ function SimpleSelectorManyOption<T extends ToStringable>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SelectorOption<
|
||||||
|
T extends string | number | readonly string[] | undefined
|
||||||
|
> {
|
||||||
|
label: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownSelector<
|
||||||
|
TFieldName extends InternalFieldName,
|
||||||
|
T extends string | number | readonly string[] | undefined
|
||||||
|
>({
|
||||||
|
options,
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
details,
|
||||||
|
}: {
|
||||||
|
options: SelectorOption<T>[];
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
details: UseFormRegisterReturn<TFieldName>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="space-y-1">
|
||||||
|
<label className="text-md block px-1 font-semibold text-neutral-600">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="block h-8 w-full rounded-lg border border-neutral-600 px-2 py-1"
|
||||||
|
{...details}
|
||||||
|
placeholder={placeholder}
|
||||||
|
>
|
||||||
|
{options.map((option, key) => {
|
||||||
|
return <option key={key} {...option} />;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SelectedUniqueContext,
|
SelectedUniqueContext,
|
||||||
SelectorContext,
|
SelectorContext,
|
||||||
@ -173,4 +216,5 @@ export {
|
|||||||
MultiSelector,
|
MultiSelector,
|
||||||
MultiSelectorOption,
|
MultiSelectorOption,
|
||||||
SimpleSelectorManyOption,
|
SimpleSelectorManyOption,
|
||||||
|
DropdownSelector,
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { type HTMLInputTypeAttribute, useState } from "react";
|
||||||
import {
|
import {
|
||||||
type UseFormRegisterReturn,
|
type UseFormRegisterReturn,
|
||||||
type InternalFieldName,
|
type InternalFieldName,
|
||||||
@ -8,9 +8,7 @@ import {
|
|||||||
* Single line input for the fields found to the right of the
|
* Single line input for the fields found to the right of the
|
||||||
* resource logo.
|
* resource logo.
|
||||||
*/
|
*/
|
||||||
function InfoInputLine<
|
function InfoInputLine<TFieldName extends InternalFieldName>({
|
||||||
TFieldName extends InternalFieldName = InternalFieldName
|
|
||||||
>({
|
|
||||||
value,
|
value,
|
||||||
placeholder,
|
placeholder,
|
||||||
hint,
|
hint,
|
||||||
@ -46,4 +44,30 @@ function InfoInputLine<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { InfoInputLine };
|
function GenericInput<TFieldName extends InternalFieldName>({
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
type = "text",
|
||||||
|
details,
|
||||||
|
}: {
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
type: HTMLInputTypeAttribute;
|
||||||
|
details: UseFormRegisterReturn<TFieldName>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<section className="w-full space-y-1">
|
||||||
|
<label className="text-md block px-1 font-semibold text-neutral-600">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className="block h-8 w-full rounded-lg border border-neutral-600 px-2 py-1"
|
||||||
|
{...details}
|
||||||
|
placeholder={placeholder}
|
||||||
|
type={type}
|
||||||
|
></input>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { InfoInputLine, GenericInput };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user