Skip to content

Commit

Permalink
Merge pull request #35 from UofA-Blueprint/button-refactor
Browse files Browse the repository at this point in the history
♻️ Button Refactor
  • Loading branch information
PaulHo0501 authored Oct 22, 2024
2 parents 1347f36 + 359bb5b commit 53be90c
Show file tree
Hide file tree
Showing 16 changed files with 538 additions and 269 deletions.
17 changes: 16 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import ModalTestRoute from "@/routes/test/ModalTestRoute";
import GalleryTestRoute from "./routes/test/GalleryTestRoute";
import { MemberInformation } from "./components/MemberInformation";
import MemberProfilePictureTest from "@/routes/test/MemberProfilePictureTest";
import AddMemberTestRoute from "@/routes/test/AddMemberTestRoute";
import EditMemberTestRoute from "@/routes/test/EditMemberTestRoute";
import MemberPageBackgroundTestRoute from "./routes/test/MemberPageBackgroundTestRoute";

// routes
import AdminLogin from "./routes/admin/AdminLogin";
Expand All @@ -41,7 +44,7 @@ const router = createBrowserRouter([
element: <SearchBarTest />,
},
{
path: "/testButton",
path: "/button-test",
element: <ButtonTestRoute />,
},
{
Expand Down Expand Up @@ -128,6 +131,18 @@ const router = createBrowserRouter([
path: "/media-card-test",
element: <MediaCardTestRoute />,
},
{
path: "/add-member",
element: <AddMemberTestRoute />,
},
{
path: "/edit-member",
element: <EditMemberTestRoute />,
},
{
path: "/member-background-test",
element: <MemberPageBackgroundTestRoute />,
},

// Admin Routes
{
Expand Down
70 changes: 70 additions & 0 deletions src/components/AddMember.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useState } from "react";
import clsx from "clsx";
import Modal from "@/components/Modal";
import InputField from "@/components/InputField";
import MemberProfilePicture from "@/components/MemberProfilePicture";
import Button from "@/components/Button";
import { WarningCircle } from "@phosphor-icons/react";

interface AddMemberProps {
/* Toggles the modal's visibility */
isOpen: boolean;

/* Callback to close the modal */
onClose: () => void;
}
function AddMember({ isOpen, onClose }: AddMemberProps) {
const [nameError, setNameError] = useState<boolean>(false);
const [idError, setIdError] = useState<boolean>(false);

// Content style
const content = "flex flex-col gap-6";

return (
<Modal
isOpen={isOpen}
onClose={onClose}
title="Add Member"
content={
<div className={clsx(content)}>
<InputField
type="text"
label="Name"
error={false}
required={true}
placeholder={"John Doe"}
setError={setNameError}
setInput={() => {}}
/>
<InputField
type="text"
label="Unique ID"
error={false}
required={true}
placeholder="12345678"
setError={setIdError}
setInput={() => {}}
/>
<MemberProfilePicture />
</div>
}
actions={
<div className="flex flex-col gap-1 w-full">
<Button
shape="medium"
text="Add Member"
onClick={onClose}
/>
{(idError || nameError) && (
<div className="flex flex-row justify-start items-center gap-1 text-red-500 text-body-sm">
<WarningCircle size={16} />
<span> Error</span>
</div>
)}
</div>
}
></Modal>
);
}

export default AddMember;
117 changes: 67 additions & 50 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,85 @@
import { useRef } from "react";
import { ReactNode, useRef } from "react";
import { twMerge } from "tailwind-merge";
import clsx from "clsx";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
onClick?: React.MouseEventHandler<HTMLButtonElement>;
icon?: React.ElementType;
/* Height of the button */
shape: "small" | "medium" | "large" | "round" | "square";

/* The text to display on the button */
text?: string;
rounded?: boolean;
fill?: boolean;

/* 32x32 Phosphor icon to display on the button */
icon?: React.ReactNode;

/* Whether the button is disabled */
disabled?: boolean;
fontSize?: string;
color?: string;

/* The severity of the button */
severity?: "primary" | "secondary" | "danger";

/* The callback to run when the button is clicked */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
}

const Button: React.FC<ButtonProps> = ({
text = null,
icon: Icon = null,
function Button({
shape,
text,
icon,
disabled = false,
severity = "primary",
onClick,
rounded = false,
fill = true,
disabled,
fontSize = "1em",
className = null,
}) => {
}: ButtonProps) {
const buttonRef = useRef<HTMLButtonElement>(null);
const buttonClassName = twMerge(
`flex flex-row items-center justify-center p-4 ${
disabled === true
? "bg-neutrals-light-200 text-gray-400 cursor-default"
: fill
? "relative overflow-hidden bg-primary-main text-white z-10 active:bg-primary-main active:text-white transition-colors hover:bg-neutrals-light-100 hover:text-primary-main border-2 border-primary-main hover:border-primary-main rounded-full w-full h-full before:absolute before:right-0 before:top-0 before:w-full before:h-full before:z-0 before:bg-primary-main before:content[''] before:transition-transform before:duration-300 hover:before:transform hover:before:translate-x-full"
: "relative overflow-hidden text-primary-dark border-primary-dark border-2 active:bg-white active:text-primary-dark transition-colors before:absolute before:left-0 before:top-0 before:-z-10 before:h-full before:w-full before:origin-top-left before:scale-x-0 before:bg-primary-dark before:transition-transform before:duration-300 before:content-[''] hover:text-white before:hover:scale-x-100"
} ${rounded ? "rounded-full" : "rounded-lg"} w-full h-full`,
className,
// change color --> change text-{color}, border-{color} and active:bg-{color}, active:text-{color}, before:bg-{color}
);

// Button styles
const base =
"flex flex-row items-center justify-center cursor-pointer w-full rounded-lg gap-2";

const small = "h-8 py-1 px-6 gap-1 text-sm";

const medium = "h-12 py-3 px-6 text-base";

const large = "h-16 py-4 px-6 text-2xl";

const round = " h-[60px] w-[60px] rounded-full p-4";

const square = "h-[60px] w-[60px] p-4";

const primary = "bg-primary-main text-neutrals-light-100";

const secondary =
"bg-transparent text-primary-dark border-2 border-primary-dark ";

const danger =
"bg-transparent text-status-red-main border-2 border-status-red-main";

const disabledButton =
"bg-neutrals-light-500 text-neutrals-dark-200 border-transparent cursor-not-allowed";

return (
<button
ref={buttonRef}
style={{ fontSize: fontSize }}
onClick={onClick}
className={buttonClassName}
disabled={disabled}
>
{Icon && !text && (
<div className="relative z-20">
<Icon size={20} />
</div>
)}
{Icon && text && (
<div className="flex items-center">
<div className="relative z-20">
<Icon
className="mt-0.2"
size={20}
/>
</div>
<span className="relative z-20 pl-1 whitespace-nowrap hidden lg:block">
{text}
</span>
</div>
className={twMerge(
clsx(base, {
[small]: shape === "small",
[medium]: shape === "medium",
[large]: shape === "large",
[round]: shape === "round",
[square]: shape === "square",
[primary]: severity === "primary",
[secondary]: severity === "secondary",
[danger]: severity === "danger",
[disabledButton]: disabled,
}),
)}
{!Icon && text && <span className="relative z-20">{text}</span>}
onClick={onClick}
>
{icon}
{text}
</button>
);
};
}

export default Button;
2 changes: 1 addition & 1 deletion src/components/ColorPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import clsx from "clsx";
import { useState, useEffect, useRef, useCallback } from "react";
import ProfileColor from "@/types/ProfileColor";
import { ProfileColor } from "@/types/ProfileColor";

interface ColorCircleProps {
/** The color of the circle */
Expand Down
74 changes: 74 additions & 0 deletions src/components/EditMember.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import clsx from "clsx";
import Modal from "@/components/Modal";
import InputField from "@/components/InputField";
import MemberProfilePicture from "@/components/MemberProfilePicture";
import MemberPageBackground from "@/components/MemberPageBackground";
import Button from "@/components/Button";
import { WarningCircle, PencilSimple } from "@phosphor-icons/react";

interface EditMemberProps {
/* Toggles the modal's visibility */
isOpen: boolean;

/* Callback to close the modal */
onClose: () => void;
}
function EditMember({ isOpen, onClose }: EditMemberProps) {
// Content style
const content = "flex flex-col gap-6";

return (
<Modal
isOpen={isOpen}
onClose={onClose}
icon={<PencilSimple size={24} />}
title="Edit Member"
content={
<div className={clsx(content)}>
<InputField
type="text"
label="Name"
error={false}
required={false}
placeholder={"John Doe"}
setError={() => {}}
setInput={() => {}}
/>
<MemberProfilePicture />
<MemberPageBackground />
</div>
}
actions={
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-col gap-4">
<div>
<Button
shape="medium"
text="Save Changes"
onClick={onClose}
/>
</div>
<div className="flex flex-row gap-4">
<Button
shape="medium"
text="Delete Member Page"
severity="danger"
/>
<Button
shape="medium"
text="Reset Member Page"
severity="secondary"
/>
</div>
</div>
<div className="flex flex-row justify-start items-center gap-1 text-red-500 text-body-sm">
<WarningCircle size={16} />
<span> Error</span>
</div>
</div>
}
></Modal>
);
}

export default EditMember;
16 changes: 12 additions & 4 deletions src/components/IconOption.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from "react";
import { twMerge } from "tailwind-merge";
import clsx from "clsx";
import ProfileColor from "@/types/ProfileColor";
import { ProfileColor } from "@/types/ProfileColor";

interface IconOptionProps {
/** The phosphor icon to display in the option component */
icon: React.ReactNode;
icon?: React.ReactNode;

/** The background color of the option component */
color: ProfileColor;
Expand All @@ -25,11 +26,18 @@ const IconOption = ({
const body =
"flex items-center justify-center w-16 h-16 rounded cursor-pointer";

const border = "outline outline-2 outline-primary-dark outline-offset-2";
const border = "border border-[1px] border-neutrals-dark-200";

const transition = "transition transition-all duration-75 ease-in-out";

const outline =
"border-none outline outline-2 outline-primary-dark outline-offset-2";

return (
<div
className={clsx(body, color, { [border]: selected })}
className={twMerge(
clsx(body, color, border, transition, { [outline]: selected }),
)}
onClick={setSelectedIcon}
>
{icon}
Expand Down
2 changes: 2 additions & 0 deletions src/components/LoginModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ const LoginModal = ({
{/* Login Button */}
<Button
text="Login"
shape="medium"
severity="primary"
disabled={invalidUsername || invalidPassword}
onClick={onClick}
/>
Expand Down
Loading

0 comments on commit 53be90c

Please sign in to comment.