Skip to content

Commit

Permalink
register page almost done
Browse files Browse the repository at this point in the history
  • Loading branch information
acrantel committed Aug 18, 2023
1 parent 1c8348d commit 5e98add
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 385 deletions.
4 changes: 3 additions & 1 deletion frontend2/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{}
{
"plugins": ["prettier-plugin-tailwindcss"]
}
85 changes: 79 additions & 6 deletions frontend2/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"js-cookie": "^3.0.5",
"prettier": "2.8.8",
"prettier": "3.0.2",
"prettier-plugin-tailwindcss": "^0.5.3",
"react-scripts": "5.0.1",
"tailwindcss": "^3.3.2",
"typescript": "^4.9.5"
Expand Down
15 changes: 10 additions & 5 deletions frontend2/src/components/elements/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
variant?: string;
label?: string;
iconName?: IconName;
fullWidth?: boolean;
className?: string;
}

const variants: Record<string, string> = {
Expand All @@ -13,21 +15,24 @@ const variants: Record<string, string> = {
};

const Button: React.FC<ButtonProps> = ({
variant,
variant = "",
label,
iconName,
fullWidth = false,
className = "",
...rest
}) => {
variant = variant ?? "";
const variantStyle = variants[variant];
const variantStyle = `${variants[variant]} ${
fullWidth ? "w-full" : ""
} ${className}`;
return (
<button
// default button type
type="button"
className={`flex flex-row gap-1.5 items-center justify-center rounded h-9 px-2.5 py-1.5 font-medium shadow-sm ${variantStyle}`}
className={`flex h-9 flex-row items-center justify-center gap-1.5 rounded px-2.5 py-1.5 font-medium shadow-sm ${variantStyle}`}
{...rest}
>
{iconName !== undefined && <Icon name={iconName} size="sm"/>}
{iconName !== undefined && <Icon name={iconName} size="sm" />}
{label}
</button>
);
Expand Down
17 changes: 17 additions & 0 deletions frontend2/src/components/elements/FormError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

const FormError: React.FC<{ message?: string; className?: string }> = ({
message,
className,
}) => {
return (
<span
role="alert"
className={`absolute mt-0.5 text-xs text-red-700 ${className ?? ""}`}
>
{message ?? "This field is invalid."}
</span>
);
};

export default FormError;
20 changes: 20 additions & 0 deletions frontend2/src/components/elements/FormLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";

const FormLabel: React.FC<{
label?: string;
required?: boolean;
className?: string;
}> = ({ label, required = false, className }) => {
return (
<span
className={`text-sm font-medium leading-6 text-gray-700 ${
className ?? ""
}`}
>
{label}
{required && <span className="text-red-700"> *</span>}
</span>
);
};

export default FormLabel;
45 changes: 23 additions & 22 deletions frontend2/src/components/elements/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
import React, { forwardRef } from "react";
import FormError from "./FormError";
import FormLabel from "./FormLabel";

interface InputProps extends React.ComponentPropsWithoutRef<"input"> {
label?: string;
required?: boolean;
className?: string;
errorMessage?: string;
}

const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
{ label, required = false, ...rest },
ref
{ label, required = false, className = "", errorMessage, ...rest },
ref,
) {
const invalid = errorMessage !== undefined;
return (
<div className="w-full">
{label !== undefined && (
<label
htmlFor={label}
className="flex flex-col w-full gap-1 text-sm font-medium leading-6 text-gray-900"
>
<span>
{label}
{required && <span className="text-red-700"> *</span>}
</span>
</label>
)}
<div className="relative rounded-md shadow-sm">
<input
id={label}
ref={ref}
className="block focus:outline-none w-full rounded-md border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-cyan-600 sm:text-sm sm:leading-6"
{...rest}
/>
</div>
<div className={`relative ${invalid ? "mb-1" : ""} ${className}`}>
<label>
<FormLabel label={label} required={required} />
<div className="relative rounded-md shadow-sm">
<input
ref={ref}
aria-invalid={errorMessage !== undefined ? "true" : "false"}
className={`block w-full rounded-md border-0 px-2 py-1.5 text-gray-900 ring-1 ring-inset
ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-1 focus:ring-inset
focus:ring-cyan-600 sm:text-sm sm:leading-6
${invalid ? "ring-red-500" : ""}`}
{...rest}
/>
</div>
{invalid && <FormError message={errorMessage} />}
</label>
</div>
);
});
Expand Down
92 changes: 59 additions & 33 deletions frontend2/src/components/elements/SelectMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import React, { useMemo, useState } from "react";
import { Listbox } from "@headlessui/react";
import Icon, { type IconName } from "./Icon";
import React, { Fragment, useMemo, useState } from "react";

Check warning on line 1 in frontend2/src/components/elements/SelectMenu.tsx

View workflow job for this annotation

GitHub Actions / Frontend linting / unit tests

'useState' is defined but never used
import { Listbox, Transition } from "@headlessui/react";
import Icon from "./Icon";
import FormError from "./FormError";
import FormLabel from "./FormLabel";

interface SelectMenuProps<T extends React.Key | null | undefined> {
options: Array<{ value: T; label: string }>;
label?: string;
required?: boolean;
value?: T;
placeholder?: string;
className?: string;
errorMessage?: string;
onChange?: (value: T) => void;
}

Expand All @@ -17,46 +21,68 @@ function SelectMenu<T extends React.Key | null | undefined>({
options,
value,
placeholder,
className = "",
errorMessage,
onChange,
}: SelectMenuProps<T>): JSX.Element {
const valueToLabel = useMemo(
() => new Map(options.map((option) => [option.value, option.label])),
[options]
[options],
);
const invalid = errorMessage !== undefined;
return (
<div className="flex flex-col gap-1">
<div className={`relative ${invalid ? "mb-2" : ""} ${className}`}>
<Listbox value={value} onChange={onChange}>
{label !== undefined && (
<Listbox.Label className="flex flex-col gap-1 text-sm font-medium leading-6 text-gray-900">
<span>
{label}
{required && <span className="text-red-700"> *</span>}
<div className="relative">
{label !== undefined && (
<Listbox.Label>
<FormLabel label={label} required={required} />
</Listbox.Label>
)}
<Listbox.Button
className={`relative h-9 w-full truncate rounded-md bg-white py-1.5
pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300
focus:outline-none focus:ring-1 focus:ring-cyan-600 ui-open:ring-cyan-600
sm:text-sm sm:leading-6 ${invalid ? "ring-red-500" : ""}`}
>
<span className={`${value === undefined ? "text-gray-400" : ""}`}>
{value === undefined ? placeholder : valueToLabel.get(value)}
</span>
</Listbox.Label>
)}
<Listbox.Button className="h-9 relative w-full rounded-md truncate bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-1 focus:ring-cyan-600 ui-open:ring-cyan-600 sm:text-sm sm:leading-6">
<span className={`${value === undefined ? "text-gray-400" : ""}`}>
{value === undefined ? placeholder : valueToLabel.get(value)}
</span>
<div className="transition transform duration-300 ui-open:rotate-180 absolute inset-y-0 right-0 flex items-center mr-2">
<Icon name="chevron_down" size="sm" />
</div>
</Listbox.Button>
<Listbox.Options className="mt-0 max-h-56 w-full rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((option) => (
<Listbox.Option
className="px-4 relative py-1.5 cursor-default truncate ui-active:bg-cyan-100"
key={option.value}
value={option.value}
<div
className="absolute inset-y-0 right-0 mr-2 flex transform items-center
transition duration-300 ui-open:rotate-180"
>
{option.label}
<span className="hidden ui-selected:flex text-cyan-900 absolute inset-y-0 right-0 items-center pr-2">
<Icon name="check" size="sm" />
</span>
</Listbox.Option>
))}
</Listbox.Options>
<Icon name="chevron_down" size="sm" />
</div>
</Listbox.Button>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options
className="absolute z-10 mt-1 max-h-48 w-full overflow-auto rounded-md
bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none
sm:max-h-60 sm:text-sm"
>
{options.map((option) => (
<Listbox.Option
className="flex cursor-default flex-row justify-between py-1.5 pl-4 pr-2 ui-active:bg-cyan-100"
key={option.value}
value={option.value}
>
<div className="overflow-x-auto pr-2">{option.label}</div>
<span className=" hidden items-center text-cyan-900 ui-selected:flex">
<Icon name="check" size="sm" />
</span>
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</Listbox>
{invalid && <FormError message={errorMessage} />}
</div>
);
}
Expand Down
Loading

0 comments on commit 5e98add

Please sign in to comment.