Working on rough upload csv ui - added float menu for headless-ui

This commit is contained in:
Khalim Conn-Kowlessar 2023-07-13 10:56:28 +01:00
parent c89060650d
commit d1ae0c8470
4 changed files with 255 additions and 19 deletions

99
package-lock.json generated
View file

@ -8,12 +8,14 @@
"name": "assessment-model",
"version": "0.1.0",
"dependencies": {
"@headlessui-float/react": "^0.11.2",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^4.29.12",
"@types/node": "20.2.3",
@ -1066,6 +1068,20 @@
"@floating-ui/core": "^1.2.6"
}
},
"node_modules/@floating-ui/react": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.2.tgz",
"integrity": "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==",
"dependencies": {
"@floating-ui/react-dom": "^1.3.0",
"aria-hidden": "^1.1.3",
"tabbable": "^6.0.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.0.tgz",
@ -1078,6 +1094,18 @@
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/react/node_modules/@floating-ui/react-dom": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz",
"integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==",
"dependencies": {
"@floating-ui/dom": "^1.2.1"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@ -1093,6 +1121,21 @@
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@headlessui-float/react": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/@headlessui-float/react/-/react-0.11.2.tgz",
"integrity": "sha512-ghnA7wOgrSDcLl9O+TTsW7VC7RjivVopt03hHPpKRMAMzV5TLnq8UW0ok6C6xnKb78LRdhCvlaZsvJXcXqb5Yg==",
"dependencies": {
"@floating-ui/core": "^1.0.0",
"@floating-ui/dom": "^1.0.0",
"@floating-ui/react": "^0.19.0"
},
"peerDependencies": {
"@headlessui/react": "^1.0.0",
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/@headlessui/react": {
"version": "1.7.14",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.14.tgz",
@ -1448,6 +1491,14 @@
"url": "https://opencollective.com/unts"
}
},
"node_modules/@radix-ui/number": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
"integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
@ -1871,6 +1922,49 @@
}
}
},
"node_modules/@radix-ui/react-select": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.2.tgz",
"integrity": "sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/number": "1.0.1",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-collection": "1.0.3",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-direction": "1.0.1",
"@radix-ui/react-dismissable-layer": "1.0.4",
"@radix-ui/react-focus-guards": "1.0.1",
"@radix-ui/react-focus-scope": "1.0.3",
"@radix-ui/react-id": "1.0.1",
"@radix-ui/react-popper": "1.1.2",
"@radix-ui/react-portal": "1.0.3",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-slot": "1.0.2",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1",
"@radix-ui/react-use-previous": "1.0.1",
"@radix-ui/react-visually-hidden": "1.0.3",
"aria-hidden": "^1.1.1",
"react-remove-scroll": "2.5.5"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
@ -8447,6 +8541,11 @@
"url": "https://opencollective.com/unts"
}
},
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew=="
},
"node_modules/tailwind-merge": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.13.2.tgz",

View file

@ -14,12 +14,14 @@
"create_user": "node -r esbuild-register src/app/db/create_user.ts"
},
"dependencies": {
"@headlessui-float/react": "^0.11.2",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^4.29.12",
"@types/node": "20.2.3",

View file

@ -1,6 +1,6 @@
"use client";
import { Cog6ToothIcon } from "@heroicons/react/24/outline";
import { Cog6ToothIcon, CalculatorIcon } from "@heroicons/react/24/outline";
import {
NavigationMenu,
NavigationMenuItem,
@ -24,11 +24,23 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
console.log("Settings were clicked, implement me");
}
function handleClickPortfolioPlan() {
console.log("Opt Plan was clicked, implement me");
}
const [modalIsOpen, setModalIsOpen] = useState(false);
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
onClick={handleClickPortfolioPlan}
>
<CalculatorIcon className="h-4 w-4 mr-2" />
Portfolio Plan
</NavigationMenuItem>
<NavigationMenuItem
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
onClick={handleClickSettings}
@ -36,6 +48,7 @@ export function Toolbar({ portfolioId }: ToolbarProps) {
<Cog6ToothIcon className="h-4 w-4 mr-2" />
Settings
</NavigationMenuItem>
<AddNewDropDown
portfolioId={portfolioId}
isUploadCsvOpen={modalIsOpen}

View file

@ -1,10 +1,21 @@
import { Dialog, Transition } from "@headlessui/react";
import { Menu, Dialog, Transition } from "@headlessui/react";
import { Fragment, useState } from "react";
import { ChevronDownIcon } from "@heroicons/react/20/solid";
import { Float } from "@headlessui-float/react";
import ModalSubmit from "@/app/components/home/ModalSubmit";
import { Input } from "@/app/shadcn_components/ui/input";
import { Label } from "@/app/shadcn_components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/app/shadcn_components/ui/select";
export function InputFile() {
return (
@ -15,9 +26,64 @@ export function InputFile() {
);
}
interface SelectDropdownProps {
options: string[];
placeholder: string;
onValueChange: (value: string) => void;
}
export function SelectDropdown({
options,
placeholder,
onValueChange,
}: SelectDropdownProps) {
return (
<Menu as="div" className="relative inline-block text-left w-full">
<Float>
<Menu.Button className="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-gray-900 bg-gray-200 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-opacity-75">
{placeholder}
<ChevronDownIcon
className="ml-2 -mr-1 h-5 w-5 text-gray-500 hover:text-gray-400"
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items className="origin-top right-0 w-full rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
{options.map((option, idx) => (
<Menu.Item key={idx}>
{({ active }) => (
<button
className={`${
active ? "bg-blue-500 text-white" : "text-gray-900"
} group flex items-center w-full px-4 py-2 text-sm`}
onClick={() => onValueChange(option)}
>
{option}
</button>
)}
</Menu.Item>
))}
</Menu.Items>
</Transition>
</Float>
</Menu>
);
}
const hiddenInputArrows =
"[-moz-appearance:_textfield] [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none";
const selectFundingSchemeOptions = ["None", "SHDF", "ECO4"];
const selectGoalOptions = ["None", "Increase EPC", "Reduce energy consumption"];
export default function UploadCsvModal({
isOpen = false,
setIsOpen,
@ -25,23 +91,41 @@ export default function UploadCsvModal({
isOpen?: boolean;
setIsOpen: (isOpen: boolean) => void;
}) {
// There is a lingering issue with selecting a dropdown inside of a dialog
// https://github.com/radix-ui/primitives/issues/1658
// This issue will affect shadcn since it's built on top of radix
// The problem comes strictly from the select component propagating clicks to the dialog being, causing the
// dialog to close
const [portfolioName, setPortfolioName] = useState("");
const [budget, setBudget] = useState<undefined | number>(undefined);
const [selectedOutcome, setSelectedOutcome] =
useState<string>("Nothing Specific");
const [buttonDisabled, setButtonDisabled] = useState(true);
const [selectedGoal, setSelectedGoal] = useState<string>("");
const [selectedEPC, setSelectedEPC] = useState<string>("");
function closeModal() {
setIsOpen(false);
}
function handlePortfolioNameChange(e: React.ChangeEvent<HTMLInputElement>) {
if (e.target.value.length > 0) {
setButtonDisabled(false);
} else {
setButtonDisabled(true);
}
setPortfolioName(e.target.value);
// function handlePortfolioNameChange(e: React.ChangeEvent<HTMLInputElement>) {
// if (e.target.value.length > 0) {
// setButtonDisabled(false);
// } else {
// setButtonDisabled(true);
// }
// setPortfolioName(e.target.value);
// }
function handleGoalChange(value: string) {
// e.stopPropagation();
console.log(value);
setSelectedGoal(value);
}
function handleFundingSchemeChange(e: React.ChangeEvent<HTMLSelectElement>) {
setSelectedEPC(e.target.value);
}
return (
@ -87,13 +171,17 @@ export default function UploadCsvModal({
Budget
<span className="text-red-500">*</span>
</label>
<p className="text-sm text-gray-500 mb-1">
If you don't set a budget, we will aim to minimise cost
anyway
</p>
<input
id="csv-upload-budget"
type="number"
placeholder="Set a budget"
required
value={portfolioName}
onChange={(e) => handlePortfolioNameChange(e)}
// value={portfolioName}
// onChange={(e) => handlePortfolioNameChange(e)}
className="p-2 border border-gray-200 rounded-md focus:outline-none bg-gray-100"
/>
</div>
@ -106,17 +194,51 @@ export default function UploadCsvModal({
Funding Scheme
<span className="text-red-500">*</span>
</label>
<input
id="csv-upload-funding-scheme"
type="text"
placeholder="Choose Funding Scheme"
required
// value={portfolioName}
// onChange={(e) => handlePortfolioNameChange(e)}
className="p-2 border border-gray-200 rounded-md focus:outline-none bg-gray-100"
<SelectDropdown
options={selectFundingSchemeOptions}
placeholder="Select a value "
onValueChange={handleFundingSchemeChange}
/>
</div>
<div className="flex flex-col">
<label
htmlFor="portfolio-name"
className="text-sm font-semibold text-gray-600 mb-1 relative"
>
Select your goal
<span className="text-red-500">*</span>
</label>
<SelectDropdown
options={selectGoalOptions}
placeholder="Select a value "
onValueChange={handleGoalChange}
/>
{selectedGoal && (
<div className="mt-4">
<label
htmlFor="csv-upload-epc"
className="text-sm font-semibold text-gray-600 mb-1 relative"
>
Choose a target EPC value
<span className="text-red-500">*</span>
</label>
<Select
// id="csv-upload-epc"
// placeholder="Select a target EPC value"
required
value={selectedEPC}
// onChange={handleEPCChange}
// className="p-2 border border-gray-200 rounded-md focus:outline-none bg-gray-100"
>
<option value="C">C</option>
<option value="B">B</option>
<option value="A">A</option>
</Select>
</div>
)}
</div>
<div className="flex flex-col space-y-2">
<div className="flex space-x-2">
<InputFile />