Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serena/login #666

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]
}
213 changes: 108 additions & 105 deletions frontend2/package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion frontend2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@headlessui/react": "^1.7.15",
"@headlessui/tailwindcss": "^0.2.0",
"@heroicons/react": "^2.0.18",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
Expand Down Expand Up @@ -60,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
1 change: 1 addition & 0 deletions frontend2/src/components/EpisodeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Sidebar from "./sidebar";
import { Outlet, useParams } from "react-router-dom";
import { EpisodeContext } from "../contexts/EpisodeContext";
import { useCurrentUser } from "../contexts/CurrentUserContext";

Check warning on line 6 in frontend2/src/components/EpisodeLayout.tsx

View workflow job for this annotation

GitHub Actions / Frontend linting / unit tests

'useCurrentUser' is defined but never used

// This component contains the NavBar and SideBar.
// Child route components are rendered with <Outlet />
Expand Down
25 changes: 19 additions & 6 deletions frontend2/src/components/elements/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
import React from "react";
import Icon, { type IconName } from "./Icon";

interface ButtonProps extends React.ComponentPropsWithoutRef<"button"> {
variant?: string;
label?: string;
iconName?: IconName;
fullWidth?: boolean;
className?: string;
}

const variants: Record<string, string> = {
"": "bg-gray-50 text-gray-900 hover:bg-gray-100 ring-gray-300 ring-1 ring-inset",
dark: "bg-gray-700 text-gray-50 hover:bg-gray-800",
"": "bg-gray-50 text-gray-800 hover:bg-gray-100 hover:ring-gray-900 hover:text-black ring-gray-500 ring-1 ring-inset",
dark: "bg-cyan-700 text-white hover:bg-cyan-800",
};

const Button: React.FC<ButtonProps> = ({ variant, label, ...rest }) => {
variant = variant ?? "";
const variantStyle = variants[variant];
const Button: React.FC<ButtonProps> = ({
variant = "",
label,
iconName,
fullWidth = false,
className = "",
...rest
}) => {
const variantStyle = `${variants[variant]} ${
fullWidth ? "w-full" : ""
} ${className}`;
return (
<button
// default button type
type="button"
className={`rounded-md px-2.5 py-1.5 text-sm font-semibold 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" />}
{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;
40 changes: 40 additions & 0 deletions frontend2/src/components/elements/FormLabel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import Icon from "./Icon";
import Tooltip from "./Tooltip";

const FormLabel: React.FC<{
label?: string;
required?: boolean;
className?: string;
tooltip?: string;
}> = ({ label, required = false, className, tooltip }) => {
return (
<div
className={`flex flex-row items-center gap-1 text-sm font-medium leading-6 text-gray-700 ${
className ?? ""
}`}
>
<Tooltip tooltip="asdflkajdsf">
<Icon
className="ml-0.5 mt-0.5 inline text-gray-400"
size="xs"
name="information_circle"
/>
</Tooltip>

{label}
{required && <span className="text-red-700"> *</span>}
{tooltip !== undefined && (
<Tooltip tooltip={tooltip}>
<Icon
className="ml-0.5 mt-0.5 inline text-gray-400"
size="xs"
name="information_circle"
/>
</Tooltip>
)}
</div>
);
};

export default FormLabel;
85 changes: 85 additions & 0 deletions frontend2/src/components/elements/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from "react";
import {
ClipboardDocumentIcon as ClipboardDocumentIcon24,
HomeIcon as HomeIcon24,
MapIcon as MapIcon24,
TrophyIcon as TrophyIcon24,
ChartBarIcon as ChartBarIcon24,
ClockIcon as ClockIcon24,
UserGroupIcon as UserGroupIcon24,
ArrowUpTrayIcon as ArrowUpTrayIcon24,
PlayCircleIcon as PlayCircleIcon24,
ChevronDownIcon as ChevronDownIcon24,
CheckIcon as CheckIcon24,
InformationCircleIcon as InformationCircleIcon24,
} from "@heroicons/react/24/outline";

import {
ClipboardDocumentIcon as ClipboardDocumentIcon20,
HomeIcon as HomeIcon20,
MapIcon as MapIcon20,
TrophyIcon as TrophyIcon20,
ChartBarIcon as ChartBarIcon20,
ClockIcon as ClockIcon20,
UserGroupIcon as UserGroupIcon20,
ArrowUpTrayIcon as ArrowUpTrayIcon20,
PlayCircleIcon as PlayCircleIcon20,
ChevronDownIcon as ChevronDownIcon20,
CheckIcon as CheckIcon20,
InformationCircleIcon as InformationCircleIcon20,
} from "@heroicons/react/20/solid";

const icons24 = {
clipboard_document: ClipboardDocumentIcon24,
home: HomeIcon24,
map: MapIcon24,
trophy: TrophyIcon24,
chart_bar: ChartBarIcon24,
clock: ClockIcon24,
user_group: UserGroupIcon24,
arrow_up_tray: ArrowUpTrayIcon24,
play_circle: PlayCircleIcon24,
chevron_down: ChevronDownIcon24,
check: CheckIcon24,
information_circle: InformationCircleIcon24,
};

const icons20 = {
clipboard_document: ClipboardDocumentIcon20,
home: HomeIcon20,
map: MapIcon20,
trophy: TrophyIcon20,
chart_bar: ChartBarIcon20,
clock: ClockIcon20,
user_group: UserGroupIcon20,
arrow_up_tray: ArrowUpTrayIcon20,
play_circle: PlayCircleIcon20,
chevron_down: ChevronDownIcon20,
check: CheckIcon20,
information_circle: InformationCircleIcon20,
};

export type IconName = keyof typeof icons24 | keyof typeof icons20;

export interface IconProps {
name: IconName;
size?: "sm" | "md" | "xs";
className?: string;
}

const sizeToClass = {
sm: "h-5 w-5",
md: "h-6 w-6",
xs: "h-4 w-4"
};

const Icon: React.FC<IconProps> = ({
name,
size = "md",
className = "",
}: IconProps) => {
const IconComponent = size === "md" ? icons24[name] : icons20[name];
return <IconComponent className={`${sizeToClass[size]} ${className}`} />;
};

export default Icon;
43 changes: 24 additions & 19 deletions frontend2/src/components/elements/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
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;
tooltip?: string;
}

const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
{ label, required, ...rest },
ref
{ label, required = false, className = "", errorMessage, tooltip, ...rest },
ref,
) {
required = required ?? false;
const invalid = errorMessage !== undefined;
return (
<div className="w-full">
{label !== undefined && (
<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
ref={ref}
className="block 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-gray-700 sm:text-sm sm:leading-6"
{...rest}
/>
</div>
<div className={`relative ${invalid ? "mb-1" : ""} ${className}`}>
<label>
<FormLabel label={label} required={required} tooltip={tooltip} />
<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
90 changes: 90 additions & 0 deletions frontend2/src/components/elements/SelectMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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;
}

function SelectMenu<T extends React.Key | null | undefined>({
label,
required = false,
options,
value,
placeholder,
className = "",
errorMessage,
onChange,
}: SelectMenuProps<T>): JSX.Element {
const valueToLabel = useMemo(
() => new Map(options.map((option) => [option.value, option.label])),
[options],
);
const invalid = errorMessage !== undefined;
return (
<div className={`relative ${invalid ? "mb-2" : ""} ${className}`}>
<Listbox value={value} onChange={onChange}>
<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>
<div
className="absolute inset-y-0 right-0 mr-2 flex transform items-center
transition duration-300 ui-open:rotate-180"
>
<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>
);
}

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

interface TooltipProps {
children?: React.ReactNode;
tooltip: string;
className?: string;
}

const Tooltip: React.FC<TooltipProps> = ({
children,
tooltip,
className = "",
}) => {
return (
<div className={`${className}`}>
{children}
<div
className="absolute -top-5 rounded-md bg-cyan-50 px-1.5 py-1 text-sm text-cyan-700 outline-1
outline-cyan-500 sm:text-xs"
>
{tooltip}
</div>
</div>
);
};

export default Tooltip;
Loading
Loading