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

♻️ Button Refactor #35

Merged
merged 5 commits into from
Oct 22, 2024
Merged
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
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
Loading