chore: space ui component revamp and bug fixes (#2980)

* chore: replace space ui component with plane ui component

* fix: space project icon and user pic bug

* chore: code refactor

* fix: profile section navbar fix
This commit is contained in:
Anmol Singh Bhatia 2023-12-05 16:07:25 +05:30 committed by GitHub
parent 9649f42ff3
commit 8b2d78ef92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 62 additions and 262 deletions

View File

@ -11,7 +11,7 @@ import useToast from "hooks/use-toast";
import useTimer from "hooks/use-timer"; import useTimer from "hooks/use-timer";
// ui // ui
import { Input, PrimaryButton } from "components/ui"; import { Button, Input } from "@plane/ui";
// types // types
type EmailCodeFormValues = { type EmailCodeFormValues = {
@ -133,7 +133,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
id="email" id="email"
type="email" type="email"
placeholder="Enter your email address..." placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
{...register("email", { {...register("email", {
required: "Email address is required", required: "Email address is required",
validate: (value) => validate: (value) =>
@ -154,7 +154,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
required: "Code is required", required: "Code is required",
})} })}
placeholder="Enter code..." placeholder="Enter code..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
{errors.token && <div className="text-sm text-red-500">{errors.token.message}</div>} {errors.token && <div className="text-sm text-red-500">{errors.token.message}</div>}
<button <button
@ -185,20 +185,22 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
</> </>
)} )}
{codeSent ? ( {codeSent ? (
<PrimaryButton <Button
variant="primary"
type="submit" type="submit"
className="w-full text-center h-[46px]" className="w-full"
size="md" size="xl"
onClick={handleSubmit(handleSignin)} onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty} disabled={!isValid && isDirty}
loading={isLoading} loading={isLoading}
> >
{isLoading ? "Signing in..." : "Sign in"} {isLoading ? "Signing in..." : "Sign in"}
</PrimaryButton> </Button>
) : ( ) : (
<PrimaryButton <Button
className="w-full text-center h-[46px]" variant="primary"
size="md" className="w-full"
size="xl"
onClick={() => { onClick={() => {
handleSubmit(onSubmit)().then(() => { handleSubmit(onSubmit)().then(() => {
setResendCodeTimer(30); setResendCodeTimer(30);
@ -208,7 +210,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
loading={isSubmitting} loading={isSubmitting}
> >
{isSubmitting ? "Sending code..." : "Send sign in code"} {isSubmitting ? "Sending code..." : "Send sign in code"}
</PrimaryButton> </Button>
)} )}
</form> </form>
</> </>

View File

@ -5,7 +5,8 @@ import { useForm } from "react-hook-form";
// components // components
import { EmailResetPasswordForm } from "./email-reset-password-form"; import { EmailResetPasswordForm } from "./email-reset-password-form";
// ui // ui
import { Input, PrimaryButton } from "components/ui"; import { Button, Input } from "@plane/ui";
// types // types
type EmailPasswordFormValues = { type EmailPasswordFormValues = {
email: string; email: string;
@ -58,7 +59,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
) || "Email address is not valid", ) || "Email address is not valid",
})} })}
placeholder="Enter your email address..." placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
{errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>} {errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>}
</div> </div>
@ -70,7 +71,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
required: "Password is required", required: "Password is required",
})} })}
placeholder="Enter your password..." placeholder="Enter your password..."
className="border-custom-border-300 h-[46px]" className="border-custom-border-300 h-[46px] w-full"
/> />
{errors.password && <div className="text-sm text-red-500">{errors.password.message}</div>} {errors.password && <div className="text-sm text-red-500">{errors.password.message}</div>}
</div> </div>
@ -92,14 +93,16 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
)} )}
</div> </div>
<div> <div>
<PrimaryButton <Button
variant="primary"
type="submit" type="submit"
className="w-full text-center h-[46px]" size="xl"
className="w-full"
disabled={!isValid && isDirty} disabled={!isValid && isDirty}
loading={isSubmitting} loading={isSubmitting}
> >
{isSignUpPage ? (isSubmitting ? "Signing up..." : "Sign up") : isSubmitting ? "Signing in..." : "Sign in"} {isSignUpPage ? (isSubmitting ? "Signing up..." : "Sign up") : isSubmitting ? "Signing in..." : "Sign in"}
</PrimaryButton> </Button>
{!isSignUpPage && ( {!isSignUpPage && (
<Link href="/sign-up"> <Link href="/sign-up">
<span className="block text-custom-text-200 hover:text-custom-primary-100 text-xs mt-4"> <span className="block text-custom-text-200 hover:text-custom-primary-100 text-xs mt-4">

View File

@ -1,8 +1,7 @@
import React from "react"; import React from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
// ui // ui
import { Input } from "components/ui"; import { Button, Input } from "@plane/ui";
import { Button } from "@plane/ui";
// types // types
type Props = { type Props = {
setIsResettingPassword: React.Dispatch<React.SetStateAction<boolean>>; setIsResettingPassword: React.Dispatch<React.SetStateAction<boolean>>;
@ -66,15 +65,15 @@ export const EmailResetPasswordForm: React.FC<Props> = ({ setIsResettingPassword
) || "Email address is not valid", ) || "Email address is not valid",
})} })}
placeholder="Enter registered email address.." placeholder="Enter registered email address.."
className="h-[46px] border-custom-border-300" className="h-[46px] border-custom-border-300 w-full"
/> />
{errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>} {errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>}
</div> </div>
<div className="mt-5 flex flex-col-reverse items-center gap-2 sm:flex-row"> <div className="mt-5 flex flex-col-reverse items-center gap-2 sm:flex-row">
<Button variant="neutral-primary" className="w-full" onClick={() => setIsResettingPassword(false)}> <Button variant="neutral-primary" className="w-full" size="xl" onClick={() => setIsResettingPassword(false)}>
Go Back Go Back
</Button> </Button>
<Button variant="primary" className="w-full" type="submit" loading={isSubmitting}> <Button variant="primary" className="w-full" size="xl" type="submit" loading={isSubmitting}>
{isSubmitting ? "Sending link..." : "Send reset link"} {isSubmitting ? "Sending link..." : "Send reset link"}
</Button> </Button>
</div> </div>

View File

@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
// services // services
import UserService from "services/user.service"; import UserService from "services/user.service";
// ui // ui
import { Input, PrimaryButton } from "components/ui"; import { Button, Input } from "@plane/ui";
const defaultValues = { const defaultValues = {
first_name: "", first_name: "",
@ -173,9 +173,9 @@ export const OnBoardingForm: React.FC<Props> = observer(({ user }) => {
</div> </div>
</div> </div>
<PrimaryButton type="submit" size="md" disabled={!isValid} loading={isSubmitting}> <Button variant="primary" type="submit" className="w-full" size="xl" disabled={!isValid} loading={isSubmitting}>
{isSubmitting ? "Updating..." : "Continue"} {isSubmitting ? "Updating..." : "Continue"}
</PrimaryButton> </Button>
</form> </form>
); );
}); });

View File

@ -1,7 +1,6 @@
import { useEffect } from "react"; import { useEffect } from "react";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
// mobx // mobx
@ -11,7 +10,8 @@ import { observer } from "mobx-react-lite";
import { NavbarIssueBoardView } from "./issue-board-view"; import { NavbarIssueBoardView } from "./issue-board-view";
import { NavbarTheme } from "./theme"; import { NavbarTheme } from "./theme";
// ui // ui
import { PrimaryButton } from "components/ui"; import { Avatar, Button } from "@plane/ui";
import { Briefcase } from "lucide-react";
// lib // lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// store // store
@ -87,10 +87,24 @@ const IssueNavbar = observer(() => {
{/* project detail */} {/* project detail */}
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<div className="flex h-4 w-4 items-center justify-center"> <div className="flex h-4 w-4 items-center justify-center">
{projectStore?.project && projectStore?.project?.emoji ? ( {projectStore.project ? (
renderEmoji(projectStore?.project?.emoji) projectStore.project?.emoji ? (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
{renderEmoji(projectStore.project.emoji)}
</span>
) : projectStore.project?.icon_prop ? (
<div className="h-7 w-7 flex-shrink-0 grid place-items-center">
{renderEmoji(projectStore.project.icon_prop)}
</div>
) : (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
{projectStore.project?.name.charAt(0)}
</span>
)
) : ( ) : (
<Image src="/plane-logo.webp" alt="plane logo" className="h-[24px] w-[24px]" height="24" width="24" /> <span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
<Briefcase className="h-4 w-4" />
</span>
)} )}
</div> </div>
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium"> <div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
@ -113,26 +127,13 @@ const IssueNavbar = observer(() => {
{user ? ( {user ? (
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2"> <div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
{user.avatar && user.avatar !== "" ? ( <Avatar name={user?.display_name} src={user?.avatar} size={24} shape="square" className="!text-base" />
<div className="h-5 w-5 rounded-full">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={user.avatar} alt={user.display_name ?? ""} className="rounded-full" />
</div>
) : (
<div className="grid h-5 w-5 place-items-center rounded-full bg-custom-background-80 text-[10px] capitalize">
{(user.display_name ?? "A")[0]}
</div>
)}
<h6 className="text-xs font-medium">{user.display_name}</h6> <h6 className="text-xs font-medium">{user.display_name}</h6>
</div> </div>
) : ( ) : (
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<Link href={`/login/?next_path=${router.asPath}`}> <Link href={`/login/?next_path=${router.asPath}`}>
<span> <Button variant="outline-primary">Sign in</Button>
<PrimaryButton className="flex-shrink-0" outline>
Sign in
</PrimaryButton>
</span>
</Link> </Link>
</div> </div>
)} )}

View File

@ -6,7 +6,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// ui // ui
import { ReactionSelector, Tooltip } from "components/ui"; import { ReactionSelector } from "components/ui";
import { Tooltip } from "@plane/ui";
// helpers // helpers
import { groupReactions, renderEmoji } from "helpers/emoji.helper"; import { groupReactions, renderEmoji } from "helpers/emoji.helper";

View File

@ -7,7 +7,7 @@ import {
PeekOverviewIssueProperties, PeekOverviewIssueProperties,
} from "components/issues/peek-overview"; } from "components/issues/peek-overview";
// types // types
import { Loader } from "components/ui/loader"; import { Loader } from "@plane/ui";
import { IIssue } from "types/issue"; import { IIssue } from "types/issue";
type Props = { type Props = {

View File

@ -10,7 +10,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { CommentCard, AddComment } from "components/issues/peek-overview"; import { CommentCard, AddComment } from "components/issues/peek-overview";
// ui // ui
import { Icon, PrimaryButton } from "components/ui"; import { Icon } from "components/ui";
import { Button } from "@plane/ui";
// types // types
import { IIssue } from "types/issue"; import { IIssue } from "types/issue";
@ -55,9 +56,7 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer(() => {
Sign in to add your comment Sign in to add your comment
</p> </p>
<Link href={`/?next_path=${router.asPath}`}> <Link href={`/?next_path=${router.asPath}`}>
<span> <Button variant="primary">Sign in</Button>
<PrimaryButton className="flex-shrink-0 !px-7">Sign in</PrimaryButton>
</span>
</Link> </Link>
</div> </div>
)} )}

View File

@ -6,7 +6,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
// helpers // helpers
import { groupReactions, renderEmoji } from "helpers/emoji.helper"; import { groupReactions, renderEmoji } from "helpers/emoji.helper";
// components // components
import { ReactionSelector, Tooltip } from "components/ui"; import { ReactionSelector } from "components/ui";
import { Tooltip } from "@plane/ui";
export const IssueEmojiReactions: React.FC = observer(() => { export const IssueEmojiReactions: React.FC = observer(() => {
// router // router

View File

@ -6,7 +6,8 @@ import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// lib // lib
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { Tooltip } from "components/ui"; // ui
import { Tooltip } from "@plane/ui";
export const IssueVotes: React.FC = observer(() => { export const IssueVotes: React.FC = observer(() => {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);

View File

@ -7,7 +7,7 @@ import {
PeekOverviewIssueProperties, PeekOverviewIssueProperties,
} from "components/issues/peek-overview"; } from "components/issues/peek-overview";
import { Loader } from "components/ui/loader"; import { Loader } from "@plane/ui";
import { IIssue } from "types/issue"; import { IIssue } from "types/issue";
type Props = { type Props = {

View File

@ -1,8 +1,3 @@
export * from "./dropdown"; export * from "./dropdown";
export * from "./input";
export * from "./loader";
export * from "./primary-button";
export * from "./secondary-button";
export * from "./icon"; export * from "./icon";
export * from "./reaction-selector"; export * from "./reaction-selector";
export * from "./tooltip";

View File

@ -1,37 +0,0 @@
import React, { forwardRef, Ref } from "react";
// types
interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
mode?: "primary" | "transparent" | "trueTransparent";
error?: boolean;
inputSize?: "rg" | "lg";
fullWidth?: boolean;
}
export const Input = forwardRef((props: Props, ref: Ref<HTMLInputElement>) => {
const { mode = "primary", error, className = "", type, fullWidth = true, id, inputSize = "rg", ...rest } = props;
return (
<input
id={id}
ref={ref}
type={type}
className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${
mode === "primary"
? "rounded-md border border-custom-border-200"
: mode === "transparent"
? "rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-custom-primary"
: mode === "trueTransparent"
? "rounded border-none bg-transparent ring-0"
: ""
} ${error ? "border-red-500" : ""} ${error && mode === "primary" ? "bg-red-500/20" : ""} ${
fullWidth ? "w-full" : ""
} ${inputSize === "rg" ? "px-3 py-2" : inputSize === "lg" ? "p-3" : ""} ${className}`}
{...rest}
/>
);
});
Input.displayName = "Input";
export default Input;

View File

@ -1,25 +0,0 @@
import React from "react";
type Props = {
children: React.ReactNode;
className?: string;
};
const Loader = ({ children, className = "" }: Props) => (
<div className={`${className} animate-pulse`} role="status">
{children}
</div>
);
type ItemProps = {
height?: string;
width?: string;
};
const Item: React.FC<ItemProps> = ({ height = "auto", width = "auto" }) => (
<div className="rounded-md bg-custom-background-80" style={{ height: height, width: width }} />
);
Loader.Item = Item;
export { Loader };

View File

@ -1,35 +0,0 @@
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
size?: "sm" | "md" | "lg";
outline?: boolean;
loading?: boolean;
}
export const PrimaryButton: React.FC<ButtonProps> = ({
children,
className = "",
onClick,
type = "button",
disabled = false,
loading = false,
size = "sm",
outline = false,
}) => (
<button
type={type}
className={`${className} border border-custom-primary font-medium duration-300 ${
size === "sm"
? "rounded px-3 py-2 text-xs"
: size === "md"
? "rounded-md px-3.5 py-2 text-sm"
: "rounded-lg px-4 py-2 text-base"
} ${disabled ? "cursor-not-allowed opacity-70 hover:opacity-70" : ""} ${
outline
? "bg-transparent text-custom-primary hover:bg-custom-primary hover:text-white"
: "text-white bg-custom-primary hover:border-opacity-90 hover:bg-opacity-90"
} ${loading ? "cursor-wait" : ""}`}
onClick={onClick}
disabled={disabled || loading}
>
{children}
</button>
);

View File

@ -1,35 +0,0 @@
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
size?: "sm" | "md" | "lg";
outline?: boolean;
loading?: boolean;
}
export const SecondaryButton: React.FC<ButtonProps> = ({
children,
className = "",
onClick,
type = "button",
disabled = false,
loading = false,
size = "sm",
outline = false,
}) => (
<button
type={type}
className={`${className} border border-custom-border-200 font-medium duration-300 ${
size === "sm"
? "rounded px-3 py-2 text-xs"
: size === "md"
? "rounded-md px-3.5 py-2 text-sm"
: "rounded-lg px-4 py-2 text-base"
} ${disabled ? "cursor-not-allowed border-custom-border-200 bg-custom-background-90" : ""} ${
outline
? "bg-transparent hover:bg-custom-background-80"
: "bg-custom-background-100 hover:border-opacity-70 hover:bg-opacity-70"
} ${loading ? "cursor-wait" : ""}`}
onClick={onClick}
disabled={disabled || loading}
>
{children}
</button>
);

View File

@ -1,70 +0,0 @@
import React from "react";
// next-themes
import { useTheme } from "next-themes";
// tooltip2
import { Tooltip2 } from "@blueprintjs/popover2";
type Props = {
tooltipHeading?: string;
tooltipContent: string | React.ReactNode;
position?:
| "top"
| "right"
| "bottom"
| "left"
| "auto"
| "auto-end"
| "auto-start"
| "bottom-left"
| "bottom-right"
| "left-bottom"
| "left-top"
| "right-bottom"
| "right-top"
| "top-left"
| "top-right";
children: JSX.Element;
disabled?: boolean;
className?: string;
openDelay?: number;
closeDelay?: number;
};
export const Tooltip: React.FC<Props> = ({
tooltipHeading,
tooltipContent,
position = "top",
children,
disabled = false,
className = "",
openDelay = 200,
closeDelay,
}) => {
const { theme } = useTheme();
return (
<Tooltip2
disabled={disabled}
hoverOpenDelay={openDelay}
hoverCloseDelay={closeDelay}
content={
<div
className={`relative z-50 max-w-xs gap-1 rounded-md border border-custom-border-200 p-2 text-xs shadow-md ${
theme === "custom" ? "bg-custom-background-100 text-custom-text-200" : "bg-black text-gray-400"
} overflow-hidden break-words ${className}`}
>
{tooltipHeading && (
<h5 className={`font-medium ${theme === "custom" ? "text-custom-text-100" : "text-white"}`}>
{tooltipHeading}
</h5>
)}
{tooltipContent}
</div>
}
position={position}
renderTarget={({ isOpen: isTooltipOpen, ref: eleReference, ...tooltipProps }) =>
React.cloneElement(children, { ref: eleReference, ...tooltipProps, ...children.props })
}
/>
);
};

View File

@ -51,7 +51,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
{tabsList.map((tab) => ( {tabsList.map((tab) => (
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}> <Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
<span <span
className={`border-b-2 p-4 text-sm font-medium outline-none whitespace-nowrap ${ className={`flex border-b-2 p-4 text-sm font-medium outline-none whitespace-nowrap ${
router.pathname === tab.selected router.pathname === tab.selected
? "border-custom-primary-100 text-custom-primary-100" ? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent" : "border-transparent"