forked from github/plane
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:
parent
9649f42ff3
commit
8b2d78ef92
@ -11,7 +11,7 @@ import useToast from "hooks/use-toast";
|
||||
import useTimer from "hooks/use-timer";
|
||||
|
||||
// ui
|
||||
import { Input, PrimaryButton } from "components/ui";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
|
||||
// types
|
||||
type EmailCodeFormValues = {
|
||||
@ -133,7 +133,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="Enter your email address..."
|
||||
className="border-custom-border-300 h-[46px]"
|
||||
className="border-custom-border-300 h-[46px] w-full"
|
||||
{...register("email", {
|
||||
required: "Email address is required",
|
||||
validate: (value) =>
|
||||
@ -154,7 +154,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
|
||||
required: "Code is required",
|
||||
})}
|
||||
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>}
|
||||
<button
|
||||
@ -185,20 +185,22 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
|
||||
</>
|
||||
)}
|
||||
{codeSent ? (
|
||||
<PrimaryButton
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="w-full text-center h-[46px]"
|
||||
size="md"
|
||||
className="w-full"
|
||||
size="xl"
|
||||
onClick={handleSubmit(handleSignin)}
|
||||
disabled={!isValid && isDirty}
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? "Signing in..." : "Sign in"}
|
||||
</PrimaryButton>
|
||||
</Button>
|
||||
) : (
|
||||
<PrimaryButton
|
||||
className="w-full text-center h-[46px]"
|
||||
size="md"
|
||||
<Button
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
size="xl"
|
||||
onClick={() => {
|
||||
handleSubmit(onSubmit)().then(() => {
|
||||
setResendCodeTimer(30);
|
||||
@ -208,7 +210,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Sending code..." : "Send sign in code"}
|
||||
</PrimaryButton>
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
</>
|
||||
|
@ -5,7 +5,8 @@ import { useForm } from "react-hook-form";
|
||||
// components
|
||||
import { EmailResetPasswordForm } from "./email-reset-password-form";
|
||||
// ui
|
||||
import { Input, PrimaryButton } from "components/ui";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
|
||||
// types
|
||||
type EmailPasswordFormValues = {
|
||||
email: string;
|
||||
@ -58,7 +59,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
|
||||
) || "Email address is not valid",
|
||||
})}
|
||||
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>}
|
||||
</div>
|
||||
@ -70,7 +71,7 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
|
||||
required: "Password is required",
|
||||
})}
|
||||
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>}
|
||||
</div>
|
||||
@ -92,14 +93,16 @@ export const EmailPasswordForm: React.FC<Props> = ({ onSubmit }) => {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<PrimaryButton
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
className="w-full text-center h-[46px]"
|
||||
size="xl"
|
||||
className="w-full"
|
||||
disabled={!isValid && isDirty}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSignUpPage ? (isSubmitting ? "Signing up..." : "Sign up") : isSubmitting ? "Signing in..." : "Sign in"}
|
||||
</PrimaryButton>
|
||||
</Button>
|
||||
{!isSignUpPage && (
|
||||
<Link href="/sign-up">
|
||||
<span className="block text-custom-text-200 hover:text-custom-primary-100 text-xs mt-4">
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
// ui
|
||||
import { Input } from "components/ui";
|
||||
import { Button } from "@plane/ui";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
type Props = {
|
||||
setIsResettingPassword: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@ -66,15 +65,15 @@ export const EmailResetPasswordForm: React.FC<Props> = ({ setIsResettingPassword
|
||||
) || "Email address is not valid",
|
||||
})}
|
||||
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>}
|
||||
</div>
|
||||
<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
|
||||
</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"}
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import UserService from "services/user.service";
|
||||
// ui
|
||||
import { Input, PrimaryButton } from "components/ui";
|
||||
import { Button, Input } from "@plane/ui";
|
||||
|
||||
const defaultValues = {
|
||||
first_name: "",
|
||||
@ -173,9 +173,9 @@ export const OnBoardingForm: React.FC<Props> = observer(({ user }) => {
|
||||
</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"}
|
||||
</PrimaryButton>
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// mobx
|
||||
@ -11,7 +10,8 @@ import { observer } from "mobx-react-lite";
|
||||
import { NavbarIssueBoardView } from "./issue-board-view";
|
||||
import { NavbarTheme } from "./theme";
|
||||
// ui
|
||||
import { PrimaryButton } from "components/ui";
|
||||
import { Avatar, Button } from "@plane/ui";
|
||||
import { Briefcase } from "lucide-react";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// store
|
||||
@ -87,10 +87,24 @@ const IssueNavbar = observer(() => {
|
||||
{/* project detail */}
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<div className="flex h-4 w-4 items-center justify-center">
|
||||
{projectStore?.project && projectStore?.project?.emoji ? (
|
||||
renderEmoji(projectStore?.project?.emoji)
|
||||
{projectStore.project ? (
|
||||
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>
|
||||
) : (
|
||||
<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 bg-gray-700 uppercase text-white">
|
||||
{projectStore.project?.name.charAt(0)}
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase">
|
||||
<Briefcase className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="line-clamp-1 max-w-[300px] overflow-hidden text-lg font-medium">
|
||||
@ -113,26 +127,13 @@ const IssueNavbar = observer(() => {
|
||||
|
||||
{user ? (
|
||||
<div className="flex items-center gap-2 rounded border border-custom-border-200 p-2">
|
||||
{user.avatar && user.avatar !== "" ? (
|
||||
<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>
|
||||
)}
|
||||
<Avatar name={user?.display_name} src={user?.avatar} size={24} shape="square" className="!text-base" />
|
||||
<h6 className="text-xs font-medium">{user.display_name}</h6>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-shrink-0">
|
||||
<Link href={`/login/?next_path=${router.asPath}`}>
|
||||
<span>
|
||||
<PrimaryButton className="flex-shrink-0" outline>
|
||||
Sign in
|
||||
</PrimaryButton>
|
||||
</span>
|
||||
<Button variant="outline-primary">Sign in</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
@ -6,7 +6,8 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { ReactionSelector, Tooltip } from "components/ui";
|
||||
import { ReactionSelector } from "components/ui";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { groupReactions, renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
PeekOverviewIssueProperties,
|
||||
} from "components/issues/peek-overview";
|
||||
// types
|
||||
import { Loader } from "components/ui/loader";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { IIssue } from "types/issue";
|
||||
|
||||
type Props = {
|
||||
|
@ -10,7 +10,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CommentCard, AddComment } from "components/issues/peek-overview";
|
||||
// ui
|
||||
import { Icon, PrimaryButton } from "components/ui";
|
||||
import { Icon } from "components/ui";
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue } from "types/issue";
|
||||
|
||||
@ -55,9 +56,7 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer(() => {
|
||||
Sign in to add your comment
|
||||
</p>
|
||||
<Link href={`/?next_path=${router.asPath}`}>
|
||||
<span>
|
||||
<PrimaryButton className="flex-shrink-0 !px-7">Sign in</PrimaryButton>
|
||||
</span>
|
||||
<Button variant="primary">Sign in</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
@ -6,7 +6,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// helpers
|
||||
import { groupReactions, renderEmoji } from "helpers/emoji.helper";
|
||||
// components
|
||||
import { ReactionSelector, Tooltip } from "components/ui";
|
||||
import { ReactionSelector } from "components/ui";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
export const IssueEmojiReactions: React.FC = observer(() => {
|
||||
// router
|
||||
|
@ -6,7 +6,8 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { Tooltip } from "components/ui";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
export const IssueVotes: React.FC = observer(() => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
PeekOverviewIssueProperties,
|
||||
} from "components/issues/peek-overview";
|
||||
|
||||
import { Loader } from "components/ui/loader";
|
||||
import { Loader } from "@plane/ui";
|
||||
import { IIssue } from "types/issue";
|
||||
|
||||
type Props = {
|
||||
|
@ -1,8 +1,3 @@
|
||||
export * from "./dropdown";
|
||||
export * from "./input";
|
||||
export * from "./loader";
|
||||
export * from "./primary-button";
|
||||
export * from "./secondary-button";
|
||||
export * from "./icon";
|
||||
export * from "./reaction-selector";
|
||||
export * from "./tooltip";
|
||||
|
@ -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;
|
@ -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 };
|
@ -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>
|
||||
);
|
@ -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>
|
||||
);
|
@ -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 })
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
@ -51,7 +51,7 @@ export const ProfileNavbar: React.FC<Props> = (props) => {
|
||||
{tabsList.map((tab) => (
|
||||
<Link key={tab.route} href={`/${workspaceSlug}/profile/${userId}/${tab.route}`}>
|
||||
<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
|
||||
? "border-custom-primary-100 text-custom-primary-100"
|
||||
: "border-transparent"
|
||||
|
Loading…
Reference in New Issue
Block a user