add multiselector form

This commit is contained in:
Brandon Egger 2023-06-04 22:30:07 -05:00
parent 026e7917ed
commit 5170d883cc
5 changed files with 211 additions and 68 deletions

View File

@ -1,15 +1,10 @@
import {
type PlatformLink,
type PaymentType,
type AuditoryResource,
type Skill,
type SkillLevel,
type Manufacturer,
} from "@prisma/client";
import {
CurrencyDollarIcon,
ArrowPathRoundedSquareIcon,
} from "@heroicons/react/24/solid";
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
import Image from "next/image";
import Link from "next/link";
@ -18,6 +13,7 @@ import { type ChangeEvent } from "react";
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { type ParsedUrlQuery, type ParsedUrlQueryInput } from "querystring";
import { useRouter } from "next/router";
import { PriceIcon } from "~/prices/Icons";
export const ResourceInfo = ({
resource,
@ -26,34 +22,6 @@ export const ResourceInfo = ({
resource: AuditoryResource;
showMoreInfo?: boolean;
}) => {
const PriceIcons = ({ type }: { type: PaymentType }) => {
switch (type) {
case "FREE": {
return (
<div className="space-x-1 pt-2" title="Free">
<span className="rounded-lg border border-neutral-900 bg-amber-100 px-2 py-[1px] italic text-black">
free
</span>
</div>
);
}
case "SUBSCRIPTION_MONTHLY": {
<div className="space-x-1" title="Monthly recurring subscription">
<ArrowPathRoundedSquareIcon className="inline h-6 w-6" />
<CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
</div>;
}
case "SUBSCRIPTION_WEEKLY": {
return (
<div className="space-x-1" title="Weekly recurring subscription">
<ArrowPathRoundedSquareIcon className="inline h-6 w-6" />
<CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
</div>
);
}
}
};
const PlatformInfo = ({
platformLinks,
}: {
@ -105,7 +73,7 @@ export const ResourceInfo = ({
</h2>
<h1 className="text-xl font-bold">{resource.name}</h1>
<PlatformInfo platformLinks={resource.platform_links} />
<PriceIcons type={resource?.payment_options[0] ?? "FREE"} />
<PriceIcon type={resource?.payment_options[0] ?? "FREE"} />
</div>
</div>
</div>

View File

@ -1,7 +1,13 @@
import { type AuditoryResource } from "@prisma/client";
import { PaymentType, type AuditoryResource } from "@prisma/client";
import Image from "next/image";
import { PencilSquareIcon } from "@heroicons/react/24/solid";
import { useState } from "react";
import {
MultiSelector,
MultiSelectorContext,
MultiSelectorOption,
} from "../../forms/selectors";
import { InfoInputLine } from "~/components/forms/textInput";
import { PriceIcon } from "~/prices/Icons";
/**
* Renders the image selector for resource form.
@ -56,29 +62,38 @@ const ResourceLinkSubForm = ({}) => {
);
};
/**
* Single line input for the fields found to the right of the
* resource logo.
*/
const InfoInputLine = ({
value,
placeholder,
const PaymentTypeOption = ({
type,
label,
}: {
value: string;
placeholder: string;
type: PaymentType;
label: string;
}) => {
const [currentValue, setCurrentValue] = useState<string>(value);
return (
<input
onChange={(event) => {
setCurrentValue(event.target.value);
}}
placeholder={placeholder}
value={currentValue}
type="text"
className="w-full border-b border-neutral-300 px-2"
/>
<MultiSelectorOption value={type}>
<MultiSelectorContext.Consumer>
{({ selected }) => (
<div
className={
(selected === type ? "bg-stone-800" : "bg-white") +
" flex flex-row space-x-2 whitespace-nowrap rounded-xl border border-neutral-400 py-[1px] pl-[1px] pr-2"
}
>
<span className="rounded-[10px] bg-white p-1">
<PriceIcon type={type} />
</span>
<span
className={
(selected === type ? "text-white" : "text black") +
" my-auto inline-block whitespace-nowrap text-sm"
}
>
{label}
</span>
</div>
)}
</MultiSelectorContext.Consumer>
</MultiSelectorOption>
);
};
@ -91,41 +106,67 @@ const ResourceSummarySubForm = ({
resource?: AuditoryResource;
}) => {
return (
<>
<div className="flex flex-row space-x-4 p-4">
<div className="space-y-4 px-4">
<div className="flex flex-row space-x-4 pt-4">
<div className="flex w-20 flex-col justify-center space-y-2 sm:w-28">
<SelectImageInput file={resource?.icon} />
</div>
<div className="w-full overflow-hidden rounded-xl border border-neutral-400 bg-white drop-shadow-lg">
<span className="text-sm">
<span className="text-md">
<InfoInputLine
placeholder="manufacturer"
value={resource?.manufacturer?.name ?? ""}
hint="manufacturer"
/>
</span>
<InfoInputLine placeholder="name" value={resource?.name ?? ""} />
<span className="my-2 block w-full text-center text-sm italic text-neutral-400">
<InfoInputLine
placeholder="name"
value={resource?.name ?? ""}
hint="name"
/>
<span className="my-1 block w-full text-center text-xs italic text-neutral-400">
Edit the fields above
</span>
</div>
</div>
<div className="mx-4 overflow-hidden rounded-xl border border-neutral-400 bg-neutral-200 text-left shadow"></div>
<div className="ml-4 mr-auto mt-4 rounded-lg border-2 border-neutral-900 bg-neutral-600">
<span className="px-2 py-2 text-sm text-neutral-200">
Ages {/** Age range here */}
</span>
<div>
<MultiSelector
label="Price Category"
defaultValue={
resource?.payment_options.toString() ?? PaymentType.FREE.toString()
}
>
<PaymentTypeOption type={PaymentType.FREE} label="Free" />
<PaymentTypeOption
type={PaymentType.SUBSCRIPTION_MONTHLY}
label="Monthly Subscription"
/>
<PaymentTypeOption
type={PaymentType.SUBSCRIPTION_WEEKLY}
label="Weekly Subscription"
/>
</MultiSelector>
</div>
</>
</div>
);
};
// TODO:
// const ResourceDescriptionSubForm = ({
// resource,
// }: {
// resource?: AuditoryResource;
// }) => {
// return <div></div>;
// };
const ResourceForm = ({ resource }: { resource?: AuditoryResource }) => {
return (
<div className="mx-auto flex max-w-2xl flex-col flex-col-reverse divide-x py-4 sm:flex-row">
<div className="my-5 mr-4 flex flex-col justify-end text-lg font-bold">
<ResourceLinkSubForm /> {/** //resource={resource} /> */}
</div>
<div className="justify-left flex flex-col pb-5">
<div className="justify-left flex max-w-lg flex-col pb-5">
<ResourceSummarySubForm resource={resource} />
</div>
</div>

View File

@ -0,0 +1,61 @@
import { createContext, useContext, useState } from "react";
// Define contexts
const MultiSelectorContext = createContext({
selected: "",
updateCallback: (_value: string) => {
return;
},
});
function MultiSelector<T extends { toString: () => string }>({
label,
defaultValue,
children,
}: {
label: string;
defaultValue: T;
children: undefined | JSX.Element | JSX.Element[];
}) {
const [selected, setSelected] = useState<string>(defaultValue.toString());
return (
<MultiSelectorContext.Provider
value={{ selected, updateCallback: setSelected }}
>
<div className="flex flex-col">
<label className="text-md block font-semibold">{label}</label>
<span className="block text-sm italic text-neutral-400">
Select one from below
</span>
<input readOnly type="text" className="hidden" value={selected ?? ""} />
<div className="mt-2 flex flex-row space-x-2 overflow-x-auto">
{children}
</div>
</div>
</MultiSelectorContext.Provider>
);
}
function MultiSelectorOption<T extends { toString: () => string }>({
value,
children,
}: {
value: T;
children: undefined | JSX.Element | JSX.Element[];
}) {
const { updateCallback } = useContext(MultiSelectorContext);
return (
<button
type="button"
onClick={() => {
updateCallback?.(value.toString());
}}
>
{children}
</button>
);
}
export { MultiSelectorContext, MultiSelector, MultiSelectorOption };

View File

@ -0,0 +1,38 @@
import { useState } from "react";
/**
* Single line input for the fields found to the right of the
* resource logo.
*/
const InfoInputLine = ({
value,
placeholder,
hint,
}: {
value: string;
placeholder: string;
hint?: string;
}) => {
const [currentValue, setCurrentValue] = useState<string>(value);
return (
<div className="relative">
<input
onChange={(event) => {
setCurrentValue(event.target.value);
}}
placeholder={placeholder}
value={currentValue}
type="text"
className="w-full border-b border-neutral-300 px-2"
/>
<label className="absolute bottom-0 right-2 top-0 text-right text-sm">
{currentValue !== "" ? (
<span className="italic text-neutral-400">{hint}</span>
) : undefined}
</label>
</div>
);
};
export { InfoInputLine };

35
src/prices/Icons.tsx Normal file
View File

@ -0,0 +1,35 @@
import { type PaymentType } from "@prisma/client";
import {
CurrencyDollarIcon,
ArrowPathRoundedSquareIcon,
} from "@heroicons/react/24/solid";
const PriceIcon = ({ type }: { type: PaymentType }) => {
switch (type) {
case "FREE": {
return (
<div className="space-x-1" title="Free">
<span className="rounded-lg border border-neutral-900 bg-amber-100 px-2 py-[1px] italic text-black">
free
</span>
</div>
);
}
case "SUBSCRIPTION_MONTHLY": {
<div className="space-x-1" title="Monthly recurring subscription">
<ArrowPathRoundedSquareIcon className="inline-block h-6 w-6" />
<CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
</div>;
}
case "SUBSCRIPTION_WEEKLY": {
return (
<div className="space-x-1" title="Weekly recurring subscription">
<ArrowPathRoundedSquareIcon className="inline-block h-6 w-6" />
<CurrencyDollarIcon className="inline h-6 w-6 text-lime-800" />
</div>
);
}
}
};
export { PriceIcon };