chore: plane ui library component and code refactor (#2406)

* chore: swap input component with plane/ui package

* chore: swap textarea component with plane/ui package

* chore: swap button component with plane/ui package

* chore: button component revamp

* fix: button type fix

* chore: secondary button revamp

* chore: button props updated

* chore: swap loader component with plane/ui package

* fix: build error fix

* chore: button component refactor

* chore: code refactor

* chore: swap toggle switch component with plane/ui package

* chore: swap spinner component with plane/ui package

* chore: swap progress bar componenet with plan/ui package

* chore: code refactor
This commit is contained in:
Anmol Singh Bhatia 2023-10-11 16:48:58 +05:30 committed by GitHub
parent 00b40fbde4
commit 35a7d10b8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
211 changed files with 3325 additions and 2347 deletions

View File

@ -7,7 +7,7 @@ module.exports = {
"./constants/**/*.{js,ts,jsx,tsx}",
"./layouts/**/*.tsx",
"./pages/**/*.tsx",
"./ui/**/*.tsx",
"../packages/ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {

View File

@ -1,32 +1,39 @@
import * as React from 'react';
import { FC } from 'react';
import React__default, { FC } from 'react';
declare const Button: () => JSX.Element;
declare type TButtonVariant = "primary" | "accent-primary" | "outline-primary" | "neutral-primary" | "link-primary" | "danger" | "accent-danger" | "outline-danger" | "link-danger" | "tertiary-danger";
interface InputProps {
type: string;
id: string;
value: string;
name: string;
onChange: () => void;
className?: string;
mode?: "primary" | "transparent" | "true-transparent";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: TButtonVariant;
size?: "sm" | "md" | "lg";
hasError?: boolean;
placeholder?: string;
className?: string;
loading?: boolean;
disabled?: boolean;
appendIcon?: any;
prependIcon?: any;
children: React.ReactNode;
}
declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
interface IToggleSwitchProps {
value: boolean;
onChange: (value: boolean) => void;
label?: string;
size?: "sm" | "md" | "lg";
disabled?: boolean;
className?: string;
}
declare const ToggleSwitch: React.FC<IToggleSwitchProps>;
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
mode?: "primary" | "transparent" | "true-transparent";
inputSize?: "sm" | "md";
hasError?: boolean;
className?: string;
}
declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
interface TextAreaProps {
id: string;
name: string;
placeholder?: string;
value?: string;
rows?: number;
cols?: number;
disabled?: boolean;
onChange: () => void;
interface TextAreaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
mode?: "primary" | "transparent";
hasError?: boolean;
className?: string;
@ -38,6 +45,30 @@ interface IRadialProgressBar {
}
declare const RadialProgressBar: FC<IRadialProgressBar>;
declare type Props$1 = {
maxValue?: number;
value?: number;
radius?: number;
strokeWidth?: number;
activeStrokeColor?: string;
inactiveStrokeColor?: string;
};
declare const ProgressBar: React__default.FC<Props$1>;
declare const Spinner: React.FC;
export { Button, Input, InputProps, RadialProgressBar, Spinner, TextArea, TextAreaProps };
declare type Props = {
children: React__default.ReactNode;
className?: string;
};
declare const Loader: {
({ children, className }: Props): JSX.Element;
Item: React__default.FC<ItemProps>;
displayName: string;
};
declare type ItemProps = {
height?: string;
width?: string;
};
export { Button, ButtonProps, Input, InputProps, Loader, ProgressBar, RadialProgressBar, Spinner, TextArea, TextAreaProps, ToggleSwitch };

View File

@ -28,52 +28,184 @@ var src_exports = {};
__export(src_exports, {
Button: () => Button,
Input: () => Input,
Loader: () => Loader,
ProgressBar: () => ProgressBar,
RadialProgressBar: () => RadialProgressBar,
Spinner: () => Spinner,
TextArea: () => TextArea
TextArea: () => TextArea,
ToggleSwitch: () => ToggleSwitch
});
module.exports = __toCommonJS(src_exports);
// src/buttons/index.tsx
// src/button/button.tsx
var React = __toESM(require("react"));
var Button = () => {
return /* @__PURE__ */ React.createElement("button", null, "button");
// src/button/helper.tsx
var buttonStyling = {
primary: {
default: `text-white bg-custom-primary-100`,
hover: `hover:bg-custom-primary-200`,
pressed: `focus:text-custom-brand-40 focus:bg-custom-primary-200`,
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`
},
"accent-primary": {
default: `bg-custom-primary-10 text-custom-primary-100`,
hover: `hover:bg-custom-primary-20 hover:text-custom-primary-200`,
pressed: `focus:bg-custom-primary-20`,
disabled: `cursor-not-allowed !text-custom-primary-60`
},
"outline-primary": {
default: `text-custom-primary-100 bg-custom-background-100 border border-custom-primary-100`,
hover: `hover:border-custom-primary-80 hover:bg-custom-primary-10`,
pressed: `focus:text-custom-primary-80 focus:bg-custom-primary-10 focus:border-custom-primary-80`,
disabled: `cursor-not-allowed !text-custom-primary-60 !border-custom-primary-60 `
},
"neutral-primary": {
default: `text-custom-text-200 bg-custom-background-100 border border-custom-border-200`,
hover: `hover:bg-custom-background-90`,
pressed: `focus:text-custom-text-300 focus:bg-custom-background-90`,
disabled: `cursor-not-allowed !text-custom-text-400`
},
"link-primary": {
default: `text-custom-primary-100 bg-custom-background-100`,
hover: `hover:text-custom-primary-200`,
pressed: `focus:text-custom-primary-80 `,
disabled: `cursor-not-allowed !text-custom-primary-60`
},
danger: {
default: `text-white bg-red-500`,
hover: ` hover:bg-red-600`,
pressed: `focus:text-red-200 focus:bg-red-600`,
disabled: `cursor-not-allowed !bg-red-300`
},
"accent-danger": {
default: `text-red-500 bg-red-50`,
hover: `hover:text-red-600 hover:bg-red-100`,
pressed: `focus:text-red-500 focus:bg-red-100`,
disabled: `cursor-not-allowed !text-red-300`
},
"outline-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-500`,
hover: `hover:text-red-400 hover:border-red-400`,
pressed: `focus:text-red-400 focus:border-red-400`,
disabled: `cursor-not-allowed !text-red-300 !border-red-300`
},
"link-danger": {
default: `text-red-500 bg-custom-background-100`,
hover: `hover:text-red-400`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`
},
"tertiary-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-200`,
hover: `hover:bg-red-50 hover:border-red-300`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`
}
};
var getButtonStyling = (variant, size, disabled = false) => {
let _variant = ``;
const currentVariant = buttonStyling[variant];
_variant = `${currentVariant.default} ${disabled ? currentVariant.disabled : currentVariant.hover} ${currentVariant.pressed}`;
let _size = ``;
if (size === "sm")
_size = "px-3 py-1.5 font-medium text-xs rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* sm */;
if (size === "md")
_size = "px-4 py-1.5 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* md */;
if (size === "lg")
_size = "px-5 py-2 font-medium text-base rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* lg */;
return `${_variant} ${_size}`;
};
var getIconStyling = (size) => {
let icon = ``;
if (size === "sm")
icon = "h-3 w-3 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* sm */;
if (size === "md")
icon = "h-3.5 w-3.5 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* md */;
if (size === "lg")
icon = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* lg */;
return icon;
};
// src/form-fields/input.tsx
// src/button/button.tsx
var Button = React.forwardRef(
(props, ref) => {
const {
variant = "primary",
size = "md",
className = "",
type = "button",
loading = false,
disabled = false,
prependIcon = null,
appendIcon = null,
children,
...rest
} = props;
const buttonStyle = getButtonStyling(variant, size, disabled || loading);
const buttonIconStyle = getIconStyling(size);
return /* @__PURE__ */ React.createElement("button", {
ref,
type,
className: `${buttonStyle} ${className}`,
disabled: disabled || loading,
...rest
}, prependIcon && /* @__PURE__ */ React.createElement("div", {
className: buttonIconStyle
}, React.cloneElement(prependIcon, { "stroke-width": 2 })), children, appendIcon && /* @__PURE__ */ React.createElement("div", {
className: buttonIconStyle
}, React.cloneElement(appendIcon, { "stroke-width": 2 })));
}
);
Button.displayName = "plane-ui-button";
// src/button/toggle-switch.tsx
var React2 = __toESM(require("react"));
var Input = React2.forwardRef((props, ref) => {
var import_react = require("@headlessui/react");
var ToggleSwitch = (props) => {
const { value, onChange, label, size = "sm", disabled, className } = props;
return /* @__PURE__ */ React2.createElement(import_react.Switch, {
checked: value,
disabled,
onChange,
className: `relative flex-shrink-0 inline-flex ${size === "sm" ? "h-4 w-6" : size === "md" ? "h-5 w-8" : "h-6 w-10"} flex-shrink-0 cursor-pointer rounded-full border border-custom-border-200 transition-colors duration-200 ease-in-out focus:outline-none ${value ? "bg-custom-primary-100" : "bg-gray-700"} ${className || ""} ${disabled ? "cursor-not-allowed" : ""}`
}, /* @__PURE__ */ React2.createElement("span", {
className: "sr-only"
}, label), /* @__PURE__ */ React2.createElement("span", {
"aria-hidden": "true",
className: `self-center inline-block ${size === "sm" ? "h-2 w-2" : size === "md" ? "h-3 w-3" : "h-4 w-4"} transform rounded-full shadow ring-0 transition duration-200 ease-in-out ${value ? (size === "sm" ? "translate-x-3" : size === "md" ? "translate-x-4" : "translate-x-5") + " bg-white" : "translate-x-0.5 bg-custom-background-90"} ${disabled ? "cursor-not-allowed" : ""}`
}));
};
ToggleSwitch.displayName = "plane-ui-toggle-switch";
// src/form-fields/input.tsx
var React3 = __toESM(require("react"));
var Input = React3.forwardRef((props, ref) => {
const {
id,
type,
value,
name,
onChange,
className = "",
mode = "primary",
size = "md",
inputSize = "sm",
hasError = false,
placeholder = "",
disabled = false
className = "",
...rest
} = props;
return /* @__PURE__ */ React2.createElement("input", {
return /* @__PURE__ */ React3.createElement("input", {
id,
ref,
type,
value,
name,
onChange,
placeholder,
disabled,
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 === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`
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 === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${inputSize === "sm" ? "px-3 py-2" : inputSize === "md" ? "p-3" : ""} ${className}`,
...rest
});
});
Input.displayName = "form-input-field";
// src/form-fields/textarea.tsx
var React3 = __toESM(require("react"));
var React4 = __toESM(require("react"));
var useAutoSizeTextArea = (textAreaRef, value) => {
React3.useEffect(() => {
React4.useEffect(() => {
if (textAreaRef) {
textAreaRef.style.height = "0px";
const scrollHeight = textAreaRef.scrollHeight;
@ -81,55 +213,51 @@ var useAutoSizeTextArea = (textAreaRef, value) => {
}
}, [textAreaRef, value]);
};
var TextArea = React3.forwardRef(
var TextArea = React4.forwardRef(
(props, ref) => {
const {
id,
name,
placeholder = "",
value = "",
rows = 1,
cols = 1,
disabled,
onChange,
mode = "primary",
hasError = false,
className = ""
className = "",
...rest
} = props;
const textAreaRef = React3.useRef(ref);
const textAreaRef = React4.useRef(ref);
ref && useAutoSizeTextArea(textAreaRef == null ? void 0 : textAreaRef.current, value);
return /* @__PURE__ */ React3.createElement("textarea", {
return /* @__PURE__ */ React4.createElement("textarea", {
id,
name,
ref: textAreaRef,
placeholder,
value,
rows,
cols,
disabled,
onChange,
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${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-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${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-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`,
...rest
});
}
);
// src/progress/radial-progress.tsx
var import_react = __toESM(require("react"));
var import_react2 = __toESM(require("react"));
var RadialProgressBar = (props) => {
const { progress } = props;
const [circumference, setCircumference] = (0, import_react.useState)(0);
(0, import_react.useEffect)(() => {
const [circumference, setCircumference] = (0, import_react2.useState)(0);
(0, import_react2.useEffect)(() => {
const radius = 40;
const circumference2 = 2 * Math.PI * radius;
setCircumference(circumference2);
}, []);
const progressOffset = (100 - progress) / 100 * circumference;
return /* @__PURE__ */ import_react.default.createElement("div", {
return /* @__PURE__ */ import_react2.default.createElement("div", {
className: "relative h-4 w-4"
}, /* @__PURE__ */ import_react.default.createElement("svg", {
}, /* @__PURE__ */ import_react2.default.createElement("svg", {
className: "absolute top-0 left-0",
viewBox: "0 0 100 100"
}, /* @__PURE__ */ import_react.default.createElement("circle", {
}, /* @__PURE__ */ import_react2.default.createElement("circle", {
className: "stroke-current opacity-10",
cx: "50",
cy: "50",
@ -137,7 +265,7 @@ var RadialProgressBar = (props) => {
strokeWidth: "12",
fill: "none",
strokeDasharray: `${circumference} ${circumference}`
}), /* @__PURE__ */ import_react.default.createElement("circle", {
}), /* @__PURE__ */ import_react2.default.createElement("circle", {
className: `stroke-current`,
cx: "50",
cy: "50",
@ -150,30 +278,96 @@ var RadialProgressBar = (props) => {
})));
};
// src/progress/progress-bar.tsx
var import_react3 = __toESM(require("react"));
var ProgressBar = ({
maxValue = 0,
value = 0,
radius = 8,
strokeWidth = 2,
activeStrokeColor = "#3e98c7",
inactiveStrokeColor = "#ddd"
}) => {
const generatePie = (value2) => {
const x = radius - Math.cos(2 * Math.PI / (100 / value2)) * radius;
const y = radius + Math.sin(2 * Math.PI / (100 / value2)) * radius;
const long = value2 <= 50 ? 0 : 1;
const d = `M${radius} ${radius} L${radius} ${0} A${radius} ${radius} 0 ${long} 1 ${y} ${x} Z`;
return d;
};
const calculatePieValue = (numberOfBars) => {
const angle = 360 / numberOfBars;
const pieValue = Math.floor(angle / 4);
return pieValue < 1 ? 1 : Math.floor(angle / 4);
};
const renderPie = (i) => {
const DIRECTION = -1;
const primaryRotationAngle = (maxValue - 1) * (360 / maxValue);
const rotationAngle = -1 * DIRECTION * primaryRotationAngle + i * DIRECTION * primaryRotationAngle;
const rotationTransformation = `rotate(${rotationAngle}, ${radius}, ${radius})`;
const pieValue = calculatePieValue(maxValue);
const dValue = generatePie(pieValue);
const fillColor = value > 0 && i <= value ? activeStrokeColor : inactiveStrokeColor;
return /* @__PURE__ */ import_react3.default.createElement("path", {
style: { opacity: i === 0 ? 0 : 1 },
key: i,
d: dValue,
fill: fillColor,
transform: rotationTransformation
});
};
const renderOuterCircle = () => [...Array(maxValue + 1)].map((e, i) => renderPie(i));
return /* @__PURE__ */ import_react3.default.createElement("svg", {
width: radius * 2,
height: radius * 2
}, renderOuterCircle(), /* @__PURE__ */ import_react3.default.createElement("circle", {
r: radius - strokeWidth,
cx: radius,
cy: radius,
className: "progress-bar"
}));
};
// src/spinners/circular-spinner.tsx
var React5 = __toESM(require("react"));
var Spinner = () => /* @__PURE__ */ React5.createElement("div", {
var React7 = __toESM(require("react"));
var Spinner = () => /* @__PURE__ */ React7.createElement("div", {
role: "status"
}, /* @__PURE__ */ React5.createElement("svg", {
}, /* @__PURE__ */ React7.createElement("svg", {
"aria-hidden": "true",
className: "mr-2 h-8 w-8 animate-spin fill-blue-600 text-custom-text-200",
viewBox: "0 0 100 101",
fill: "none",
xmlns: "http://www.w3.org/2000/svg"
}, /* @__PURE__ */ React5.createElement("path", {
}, /* @__PURE__ */ React7.createElement("path", {
d: "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z",
fill: "currentColor"
}), /* @__PURE__ */ React5.createElement("path", {
}), /* @__PURE__ */ React7.createElement("path", {
d: "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z",
fill: "currentFill"
})), /* @__PURE__ */ React5.createElement("span", {
})), /* @__PURE__ */ React7.createElement("span", {
className: "sr-only"
}, "Loading..."));
// src/loader.tsx
var import_react4 = __toESM(require("react"));
var Loader = ({ children, className = "" }) => /* @__PURE__ */ import_react4.default.createElement("div", {
className: `${className} animate-pulse`,
role: "status"
}, children);
var Item = ({ height = "auto", width = "auto" }) => /* @__PURE__ */ import_react4.default.createElement("div", {
className: "rounded-md bg-custom-background-80",
style: { height, width }
});
Loader.Item = Item;
Loader.displayName = "plane-ui-loader";
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Button,
Input,
Loader,
ProgressBar,
RadialProgressBar,
Spinner,
TextArea
TextArea,
ToggleSwitch
});

View File

@ -1,43 +1,172 @@
// src/buttons/index.tsx
// src/button/button.tsx
import * as React from "react";
var Button = () => {
return /* @__PURE__ */ React.createElement("button", null, "button");
// src/button/helper.tsx
var buttonStyling = {
primary: {
default: `text-white bg-custom-primary-100`,
hover: `hover:bg-custom-primary-200`,
pressed: `focus:text-custom-brand-40 focus:bg-custom-primary-200`,
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`
},
"accent-primary": {
default: `bg-custom-primary-10 text-custom-primary-100`,
hover: `hover:bg-custom-primary-20 hover:text-custom-primary-200`,
pressed: `focus:bg-custom-primary-20`,
disabled: `cursor-not-allowed !text-custom-primary-60`
},
"outline-primary": {
default: `text-custom-primary-100 bg-custom-background-100 border border-custom-primary-100`,
hover: `hover:border-custom-primary-80 hover:bg-custom-primary-10`,
pressed: `focus:text-custom-primary-80 focus:bg-custom-primary-10 focus:border-custom-primary-80`,
disabled: `cursor-not-allowed !text-custom-primary-60 !border-custom-primary-60 `
},
"neutral-primary": {
default: `text-custom-text-200 bg-custom-background-100 border border-custom-border-200`,
hover: `hover:bg-custom-background-90`,
pressed: `focus:text-custom-text-300 focus:bg-custom-background-90`,
disabled: `cursor-not-allowed !text-custom-text-400`
},
"link-primary": {
default: `text-custom-primary-100 bg-custom-background-100`,
hover: `hover:text-custom-primary-200`,
pressed: `focus:text-custom-primary-80 `,
disabled: `cursor-not-allowed !text-custom-primary-60`
},
danger: {
default: `text-white bg-red-500`,
hover: ` hover:bg-red-600`,
pressed: `focus:text-red-200 focus:bg-red-600`,
disabled: `cursor-not-allowed !bg-red-300`
},
"accent-danger": {
default: `text-red-500 bg-red-50`,
hover: `hover:text-red-600 hover:bg-red-100`,
pressed: `focus:text-red-500 focus:bg-red-100`,
disabled: `cursor-not-allowed !text-red-300`
},
"outline-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-500`,
hover: `hover:text-red-400 hover:border-red-400`,
pressed: `focus:text-red-400 focus:border-red-400`,
disabled: `cursor-not-allowed !text-red-300 !border-red-300`
},
"link-danger": {
default: `text-red-500 bg-custom-background-100`,
hover: `hover:text-red-400`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`
},
"tertiary-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-200`,
hover: `hover:bg-red-50 hover:border-red-300`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`
}
};
var getButtonStyling = (variant, size, disabled = false) => {
let _variant = ``;
const currentVariant = buttonStyling[variant];
_variant = `${currentVariant.default} ${disabled ? currentVariant.disabled : currentVariant.hover} ${currentVariant.pressed}`;
let _size = ``;
if (size === "sm")
_size = "px-3 py-1.5 font-medium text-xs rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* sm */;
if (size === "md")
_size = "px-4 py-1.5 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* md */;
if (size === "lg")
_size = "px-5 py-2 font-medium text-base rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline" /* lg */;
return `${_variant} ${_size}`;
};
var getIconStyling = (size) => {
let icon = ``;
if (size === "sm")
icon = "h-3 w-3 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* sm */;
if (size === "md")
icon = "h-3.5 w-3.5 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* md */;
if (size === "lg")
icon = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0" /* lg */;
return icon;
};
// src/form-fields/input.tsx
// src/button/button.tsx
var Button = React.forwardRef(
(props, ref) => {
const {
variant = "primary",
size = "md",
className = "",
type = "button",
loading = false,
disabled = false,
prependIcon = null,
appendIcon = null,
children,
...rest
} = props;
const buttonStyle = getButtonStyling(variant, size, disabled || loading);
const buttonIconStyle = getIconStyling(size);
return /* @__PURE__ */ React.createElement("button", {
ref,
type,
className: `${buttonStyle} ${className}`,
disabled: disabled || loading,
...rest
}, prependIcon && /* @__PURE__ */ React.createElement("div", {
className: buttonIconStyle
}, React.cloneElement(prependIcon, { "stroke-width": 2 })), children, appendIcon && /* @__PURE__ */ React.createElement("div", {
className: buttonIconStyle
}, React.cloneElement(appendIcon, { "stroke-width": 2 })));
}
);
Button.displayName = "plane-ui-button";
// src/button/toggle-switch.tsx
import * as React2 from "react";
var Input = React2.forwardRef((props, ref) => {
import { Switch } from "@headlessui/react";
var ToggleSwitch = (props) => {
const { value, onChange, label, size = "sm", disabled, className } = props;
return /* @__PURE__ */ React2.createElement(Switch, {
checked: value,
disabled,
onChange,
className: `relative flex-shrink-0 inline-flex ${size === "sm" ? "h-4 w-6" : size === "md" ? "h-5 w-8" : "h-6 w-10"} flex-shrink-0 cursor-pointer rounded-full border border-custom-border-200 transition-colors duration-200 ease-in-out focus:outline-none ${value ? "bg-custom-primary-100" : "bg-gray-700"} ${className || ""} ${disabled ? "cursor-not-allowed" : ""}`
}, /* @__PURE__ */ React2.createElement("span", {
className: "sr-only"
}, label), /* @__PURE__ */ React2.createElement("span", {
"aria-hidden": "true",
className: `self-center inline-block ${size === "sm" ? "h-2 w-2" : size === "md" ? "h-3 w-3" : "h-4 w-4"} transform rounded-full shadow ring-0 transition duration-200 ease-in-out ${value ? (size === "sm" ? "translate-x-3" : size === "md" ? "translate-x-4" : "translate-x-5") + " bg-white" : "translate-x-0.5 bg-custom-background-90"} ${disabled ? "cursor-not-allowed" : ""}`
}));
};
ToggleSwitch.displayName = "plane-ui-toggle-switch";
// src/form-fields/input.tsx
import * as React3 from "react";
var Input = React3.forwardRef((props, ref) => {
const {
id,
type,
value,
name,
onChange,
className = "",
mode = "primary",
size = "md",
inputSize = "sm",
hasError = false,
placeholder = "",
disabled = false
className = "",
...rest
} = props;
return /* @__PURE__ */ React2.createElement("input", {
return /* @__PURE__ */ React3.createElement("input", {
id,
ref,
type,
value,
name,
onChange,
placeholder,
disabled,
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 === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""} ${className}`
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 === "true-transparent" ? "rounded border-none bg-transparent ring-0" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-500/20" : ""} ${inputSize === "sm" ? "px-3 py-2" : inputSize === "md" ? "p-3" : ""} ${className}`,
...rest
});
});
Input.displayName = "form-input-field";
// src/form-fields/textarea.tsx
import * as React3 from "react";
import * as React4 from "react";
var useAutoSizeTextArea = (textAreaRef, value) => {
React3.useEffect(() => {
React4.useEffect(() => {
if (textAreaRef) {
textAreaRef.style.height = "0px";
const scrollHeight = textAreaRef.scrollHeight;
@ -45,40 +174,36 @@ var useAutoSizeTextArea = (textAreaRef, value) => {
}
}, [textAreaRef, value]);
};
var TextArea = React3.forwardRef(
var TextArea = React4.forwardRef(
(props, ref) => {
const {
id,
name,
placeholder = "",
value = "",
rows = 1,
cols = 1,
disabled,
onChange,
mode = "primary",
hasError = false,
className = ""
className = "",
...rest
} = props;
const textAreaRef = React3.useRef(ref);
const textAreaRef = React4.useRef(ref);
ref && useAutoSizeTextArea(textAreaRef == null ? void 0 : textAreaRef.current, value);
return /* @__PURE__ */ React3.createElement("textarea", {
return /* @__PURE__ */ React4.createElement("textarea", {
id,
name,
ref: textAreaRef,
placeholder,
value,
rows,
cols,
disabled,
onChange,
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${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-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`
className: `no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${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-theme" : ""} ${hasError ? "border-red-500" : ""} ${hasError && mode === "primary" ? "bg-red-100" : ""} ${className}`,
...rest
});
}
);
// src/progress/radial-progress.tsx
import React4, { useState, useEffect as useEffect2 } from "react";
import React5, { useState, useEffect as useEffect2 } from "react";
var RadialProgressBar = (props) => {
const { progress } = props;
const [circumference, setCircumference] = useState(0);
@ -88,12 +213,12 @@ var RadialProgressBar = (props) => {
setCircumference(circumference2);
}, []);
const progressOffset = (100 - progress) / 100 * circumference;
return /* @__PURE__ */ React4.createElement("div", {
return /* @__PURE__ */ React5.createElement("div", {
className: "relative h-4 w-4"
}, /* @__PURE__ */ React4.createElement("svg", {
}, /* @__PURE__ */ React5.createElement("svg", {
className: "absolute top-0 left-0",
viewBox: "0 0 100 100"
}, /* @__PURE__ */ React4.createElement("circle", {
}, /* @__PURE__ */ React5.createElement("circle", {
className: "stroke-current opacity-10",
cx: "50",
cy: "50",
@ -101,7 +226,7 @@ var RadialProgressBar = (props) => {
strokeWidth: "12",
fill: "none",
strokeDasharray: `${circumference} ${circumference}`
}), /* @__PURE__ */ React4.createElement("circle", {
}), /* @__PURE__ */ React5.createElement("circle", {
className: `stroke-current`,
cx: "50",
cy: "50",
@ -114,29 +239,95 @@ var RadialProgressBar = (props) => {
})));
};
// src/progress/progress-bar.tsx
import React6 from "react";
var ProgressBar = ({
maxValue = 0,
value = 0,
radius = 8,
strokeWidth = 2,
activeStrokeColor = "#3e98c7",
inactiveStrokeColor = "#ddd"
}) => {
const generatePie = (value2) => {
const x = radius - Math.cos(2 * Math.PI / (100 / value2)) * radius;
const y = radius + Math.sin(2 * Math.PI / (100 / value2)) * radius;
const long = value2 <= 50 ? 0 : 1;
const d = `M${radius} ${radius} L${radius} ${0} A${radius} ${radius} 0 ${long} 1 ${y} ${x} Z`;
return d;
};
const calculatePieValue = (numberOfBars) => {
const angle = 360 / numberOfBars;
const pieValue = Math.floor(angle / 4);
return pieValue < 1 ? 1 : Math.floor(angle / 4);
};
const renderPie = (i) => {
const DIRECTION = -1;
const primaryRotationAngle = (maxValue - 1) * (360 / maxValue);
const rotationAngle = -1 * DIRECTION * primaryRotationAngle + i * DIRECTION * primaryRotationAngle;
const rotationTransformation = `rotate(${rotationAngle}, ${radius}, ${radius})`;
const pieValue = calculatePieValue(maxValue);
const dValue = generatePie(pieValue);
const fillColor = value > 0 && i <= value ? activeStrokeColor : inactiveStrokeColor;
return /* @__PURE__ */ React6.createElement("path", {
style: { opacity: i === 0 ? 0 : 1 },
key: i,
d: dValue,
fill: fillColor,
transform: rotationTransformation
});
};
const renderOuterCircle = () => [...Array(maxValue + 1)].map((e, i) => renderPie(i));
return /* @__PURE__ */ React6.createElement("svg", {
width: radius * 2,
height: radius * 2
}, renderOuterCircle(), /* @__PURE__ */ React6.createElement("circle", {
r: radius - strokeWidth,
cx: radius,
cy: radius,
className: "progress-bar"
}));
};
// src/spinners/circular-spinner.tsx
import * as React5 from "react";
var Spinner = () => /* @__PURE__ */ React5.createElement("div", {
import * as React7 from "react";
var Spinner = () => /* @__PURE__ */ React7.createElement("div", {
role: "status"
}, /* @__PURE__ */ React5.createElement("svg", {
}, /* @__PURE__ */ React7.createElement("svg", {
"aria-hidden": "true",
className: "mr-2 h-8 w-8 animate-spin fill-blue-600 text-custom-text-200",
viewBox: "0 0 100 101",
fill: "none",
xmlns: "http://www.w3.org/2000/svg"
}, /* @__PURE__ */ React5.createElement("path", {
}, /* @__PURE__ */ React7.createElement("path", {
d: "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z",
fill: "currentColor"
}), /* @__PURE__ */ React5.createElement("path", {
}), /* @__PURE__ */ React7.createElement("path", {
d: "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z",
fill: "currentFill"
})), /* @__PURE__ */ React5.createElement("span", {
})), /* @__PURE__ */ React7.createElement("span", {
className: "sr-only"
}, "Loading..."));
// src/loader.tsx
import React8 from "react";
var Loader = ({ children, className = "" }) => /* @__PURE__ */ React8.createElement("div", {
className: `${className} animate-pulse`,
role: "status"
}, children);
var Item = ({ height = "auto", width = "auto" }) => /* @__PURE__ */ React8.createElement("div", {
className: "rounded-md bg-custom-background-80",
style: { height, width }
});
Loader.Item = Item;
Loader.displayName = "plane-ui-loader";
export {
Button,
Input,
Loader,
ProgressBar,
RadialProgressBar,
Spinner,
TextArea
TextArea,
ToggleSwitch
};

View File

@ -29,5 +29,9 @@
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"@headlessui/react": "^1.7.17",
"clsx": "^2.0.0"
}
}

View File

@ -0,0 +1,61 @@
import * as React from "react";
import { getIconStyling, getButtonStyling, TButtonVariant } from "./helper";
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: TButtonVariant;
size?: "sm" | "md" | "lg";
className?: string;
loading?: boolean;
disabled?: boolean;
appendIcon?: any;
prependIcon?: any;
children: React.ReactNode;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const {
variant = "primary",
size = "md",
className = "",
type = "button",
loading = false,
disabled = false,
prependIcon = null,
appendIcon = null,
children,
...rest
} = props;
const buttonStyle = getButtonStyling(variant, size, disabled || loading);
const buttonIconStyle = getIconStyling(size);
return (
<button
ref={ref}
type={type}
className={`${buttonStyle} ${className}`}
disabled={disabled || loading}
{...rest}
>
{prependIcon && (
<div className={buttonIconStyle}>
{React.cloneElement(prependIcon, { "stroke-width": 2 })}
</div>
)}
{children}
{appendIcon && (
<div className={buttonIconStyle}>
{React.cloneElement(appendIcon, { "stroke-width": 2 })}
</div>
)}
</button>
);
}
);
Button.displayName = "plane-ui-button";
export { Button };

View File

@ -0,0 +1,125 @@
export type TButtonVariant =
| "primary"
| "accent-primary"
| "outline-primary"
| "neutral-primary"
| "link-primary"
| "danger"
| "accent-danger"
| "outline-danger"
| "link-danger"
| "tertiary-danger";
export type TButtonSizes = "sm" | "md" | "lg";
export interface IButtonStyling {
[key: string]: {
default: string;
hover: string;
pressed: string;
disabled: string;
};
}
enum buttonSizeStyling {
sm = `px-3 py-1.5 font-medium text-xs rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline`,
md = `px-4 py-1.5 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline`,
lg = `px-5 py-2 font-medium text-base rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center inline`,
}
enum buttonIconStyling {
sm = "h-3 w-3 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
md = "h-3.5 w-3.5 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
lg = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
}
export const buttonStyling: IButtonStyling = {
primary: {
default: `text-white bg-custom-primary-100`,
hover: `hover:bg-custom-primary-200`,
pressed: `focus:text-custom-brand-40 focus:bg-custom-primary-200`,
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`,
},
"accent-primary": {
default: `bg-custom-primary-10 text-custom-primary-100`,
hover: `hover:bg-custom-primary-20 hover:text-custom-primary-200`,
pressed: `focus:bg-custom-primary-20`,
disabled: `cursor-not-allowed !text-custom-primary-60`,
},
"outline-primary": {
default: `text-custom-primary-100 bg-custom-background-100 border border-custom-primary-100`,
hover: `hover:border-custom-primary-80 hover:bg-custom-primary-10`,
pressed: `focus:text-custom-primary-80 focus:bg-custom-primary-10 focus:border-custom-primary-80`,
disabled: `cursor-not-allowed !text-custom-primary-60 !border-custom-primary-60 `,
},
"neutral-primary": {
default: `text-custom-text-200 bg-custom-background-100 border border-custom-border-200`,
hover: `hover:bg-custom-background-90`,
pressed: `focus:text-custom-text-300 focus:bg-custom-background-90`,
disabled: `cursor-not-allowed !text-custom-text-400`,
},
"link-primary": {
default: `text-custom-primary-100 bg-custom-background-100`,
hover: `hover:text-custom-primary-200`,
pressed: `focus:text-custom-primary-80 `,
disabled: `cursor-not-allowed !text-custom-primary-60`,
},
danger: {
default: `text-white bg-red-500`,
hover: ` hover:bg-red-600`,
pressed: `focus:text-red-200 focus:bg-red-600`,
disabled: `cursor-not-allowed !bg-red-300`,
},
"accent-danger": {
default: `text-red-500 bg-red-50`,
hover: `hover:text-red-600 hover:bg-red-100`,
pressed: `focus:text-red-500 focus:bg-red-100`,
disabled: `cursor-not-allowed !text-red-300`,
},
"outline-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-500`,
hover: `hover:text-red-400 hover:border-red-400`,
pressed: `focus:text-red-400 focus:border-red-400`,
disabled: `cursor-not-allowed !text-red-300 !border-red-300`,
},
"link-danger": {
default: `text-red-500 bg-custom-background-100`,
hover: `hover:text-red-400`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`,
},
"tertiary-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-200`,
hover: `hover:bg-red-50 hover:border-red-300`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`,
},
};
export const getButtonStyling = (
variant: TButtonVariant,
size: TButtonSizes,
disabled: boolean = false
): string => {
let _variant: string = ``;
const currentVariant = buttonStyling[variant];
_variant = `${currentVariant.default} ${
disabled ? currentVariant.disabled : currentVariant.hover
} ${currentVariant.pressed}`;
let _size: string = ``;
if (size === "sm") _size = buttonSizeStyling["sm"];
if (size === "md") _size = buttonSizeStyling["md"];
if (size === "lg") _size = buttonSizeStyling["lg"];
return `${_variant} ${_size}`;
};
export const getIconStyling = (size: TButtonSizes): string => {
let icon: string = ``;
if (size === "sm") icon = buttonIconStyling["sm"];
if (size === "md") icon = buttonIconStyling["md"];
if (size === "lg") icon = buttonIconStyling["lg"];
return icon;
};

View File

@ -0,0 +1,2 @@
export * from "./button";
export * from "./toggle-switch";

View File

@ -0,0 +1,49 @@
import * as React from "react";
import { Switch } from "@headlessui/react";
interface IToggleSwitchProps {
value: boolean;
onChange: (value: boolean) => void;
label?: string;
size?: "sm" | "md" | "lg";
disabled?: boolean;
className?: string;
}
const ToggleSwitch: React.FC<IToggleSwitchProps> = (props) => {
const { value, onChange, label, size = "sm", disabled, className } = props;
return (
<Switch
checked={value}
disabled={disabled}
onChange={onChange}
className={`relative flex-shrink-0 inline-flex ${
size === "sm" ? "h-4 w-6" : size === "md" ? "h-5 w-8" : "h-6 w-10"
} flex-shrink-0 cursor-pointer rounded-full border border-custom-border-200 transition-colors duration-200 ease-in-out focus:outline-none ${
value ? "bg-custom-primary-100" : "bg-gray-700"
} ${className || ""} ${disabled ? "cursor-not-allowed" : ""}`}
>
<span className="sr-only">{label}</span>
<span
aria-hidden="true"
className={`self-center inline-block ${
size === "sm" ? "h-2 w-2" : size === "md" ? "h-3 w-3" : "h-4 w-4"
} transform rounded-full shadow ring-0 transition duration-200 ease-in-out ${
value
? (size === "sm"
? "translate-x-3"
: size === "md"
? "translate-x-4"
: "translate-x-5") + " bg-white"
: "translate-x-0.5 bg-custom-background-90"
} ${disabled ? "cursor-not-allowed" : ""}`}
/>
</Switch>
);
};
ToggleSwitch.displayName = "plane-ui-toggle-switch";
export { ToggleSwitch };

View File

@ -1,5 +0,0 @@
import * as React from "react";
export const Button = () => {
return <button>button</button>;
};

View File

@ -1,32 +1,23 @@
import * as React from "react";
export interface InputProps {
type: string;
id: string;
value: string;
name: string;
onChange: () => void;
className?: string;
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
mode?: "primary" | "transparent" | "true-transparent";
size?: "sm" | "md" | "lg";
inputSize?: "sm" | "md";
hasError?: boolean;
placeholder?: string;
disabled?: boolean;
className?: string;
}
const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const {
id,
type,
value,
name,
onChange,
className = "",
mode = "primary",
size = "md",
inputSize = "sm",
hasError = false,
placeholder = "",
disabled = false,
className = "",
...rest
} = props;
return (
@ -34,11 +25,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
id={id}
ref={ref}
type={type}
value={value}
name={name}
onChange={onChange}
placeholder={placeholder}
disabled={disabled}
className={`block rounded-md bg-transparent text-sm focus:outline-none placeholder-custom-text-400 ${
mode === "primary"
? "rounded-md border border-custom-border-200"
@ -50,8 +37,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
} ${hasError ? "border-red-500" : ""} ${
hasError && mode === "primary" ? "bg-red-500/20" : ""
} ${
size === "sm" ? "px-3 py-2" : size === "lg" ? "p-3" : ""
inputSize === "sm" ? "px-3 py-2" : inputSize === "md" ? "p-3" : ""
} ${className}`}
{...rest}
/>
);
});

View File

@ -1,14 +1,7 @@
import * as React from "react";
export interface TextAreaProps {
id: string;
name: string;
placeholder?: string;
value?: string;
rows?: number;
cols?: number;
disabled?: boolean;
onChange: () => void;
export interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
mode?: "primary" | "transparent";
hasError?: boolean;
className?: string;
@ -37,15 +30,13 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
const {
id,
name,
placeholder = "",
value = "",
rows = 1,
cols = 1,
disabled,
onChange,
mode = "primary",
hasError = false,
className = "",
...rest
} = props;
const textAreaRef = React.useRef<any>(ref);
@ -57,12 +48,9 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
id={id}
name={name}
ref={textAreaRef}
placeholder={placeholder}
value={value}
rows={rows}
cols={cols}
disabled={disabled}
onChange={onChange}
className={`no-scrollbar w-full bg-transparent placeholder-custom-text-400 px-3 py-2 outline-none ${
mode === "primary"
? "rounded-md border border-custom-border-200"
@ -72,6 +60,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
} ${hasError ? "border-red-500" : ""} ${
hasError && mode === "primary" ? "bg-red-100" : ""
} ${className}`}
{...rest}
/>
);
}

View File

@ -1,4 +1,5 @@
export * from "./buttons";
export * from "./button";
export * from "./form-fields";
export * from "./progress";
export * from "./spinners";
export * from "./loader";

View File

@ -0,0 +1,30 @@
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;
Loader.displayName = "plane-ui-loader";
export { Loader };

View File

@ -1 +1,2 @@
export * from "./radial-progress";
export * from "./progress-bar";

View File

@ -0,0 +1,77 @@
import React from "react";
type Props = {
maxValue?: number;
value?: number;
radius?: number;
strokeWidth?: number;
activeStrokeColor?: string;
inactiveStrokeColor?: string;
};
export const ProgressBar: React.FC<Props> = ({
maxValue = 0,
value = 0,
radius = 8,
strokeWidth = 2,
activeStrokeColor = "#3e98c7",
inactiveStrokeColor = "#ddd",
}) => {
// PIE Calc Fn
const generatePie = (value: any) => {
const x = radius - Math.cos((2 * Math.PI) / (100 / value)) * radius;
const y = radius + Math.sin((2 * Math.PI) / (100 / value)) * radius;
const long = value <= 50 ? 0 : 1;
const d = `M${radius} ${radius} L${radius} ${0} A${radius} ${radius} 0 ${long} 1 ${y} ${x} Z`;
return d;
};
// ---- PIE Area Calc --------
const calculatePieValue = (numberOfBars: any) => {
const angle = 360 / numberOfBars;
const pieValue = Math.floor(angle / 4);
return pieValue < 1 ? 1 : Math.floor(angle / 4);
};
// ---- PIE Render Fn --------
const renderPie = (i: any) => {
const DIRECTION = -1;
// Rotation Calc
const primaryRotationAngle = (maxValue - 1) * (360 / maxValue);
const rotationAngle =
-1 * DIRECTION * primaryRotationAngle +
i * DIRECTION * primaryRotationAngle;
const rotationTransformation = `rotate(${rotationAngle}, ${radius}, ${radius})`;
const pieValue = calculatePieValue(maxValue);
const dValue = generatePie(pieValue);
const fillColor =
value > 0 && i <= value ? activeStrokeColor : inactiveStrokeColor;
return (
<path
style={{ opacity: i === 0 ? 0 : 1 }}
key={i}
d={dValue}
fill={fillColor}
transform={rotationTransformation}
/>
);
};
// combining the Pies
const renderOuterCircle = () =>
[...Array(maxValue + 1)].map((e, i) => renderPie(i));
return (
<svg width={radius * 2} height={radius * 2}>
{renderOuterCircle()}
<circle
r={radius - strokeWidth}
cx={radius}
cy={radius}
className="progress-bar"
/>
</svg>
);
};

View File

@ -7,7 +7,8 @@ import userService from "services/user.service";
// hooks
// import useToast from "hooks/use-toast";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
import { Input } from "components/ui";
import { Button } from "@plane/ui";
// types
type Props = {
setIsResettingPassword: React.Dispatch<React.SetStateAction<boolean>>;
@ -77,12 +78,12 @@ export const EmailResetPasswordForm: React.FC<Props> = ({ setIsResettingPassword
{errors.email && <div className="text-sm text-red-500">{errors.email.message}</div>}
</div>
<div className="mt-5 flex flex-col-reverse sm:flex-row items-center gap-2">
<SecondaryButton className="w-full text-center h-[46px]" onClick={() => setIsResettingPassword(false)}>
<Button variant="neutral-primary" className="w-full" onClick={() => setIsResettingPassword(false)}>
Go Back
</SecondaryButton>
<PrimaryButton type="submit" className="w-full text-center h-[46px]" loading={isSubmitting}>
</Button>
<Button variant="primary" className="w-full" type="submit" loading={isSubmitting}>
{isSubmitting ? "Sending link..." : "Send reset link"}
</PrimaryButton>
</Button>
</div>
</form>
);

View File

@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// ui
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
import { Button, Input } from "@plane/ui";
// services
import authenticationService from "services/authentication.service";
import useToast from "hooks/use-toast";
@ -29,6 +28,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
const {
register,
handleSubmit,
control,
setError,
setValue,
getValues,
@ -44,8 +44,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
reValidateMode: "onChange",
});
const isResendDisabled =
resendCodeTimer > 0 || isCodeResending || isSubmitting || errorResendingCode;
const isResendDisabled = resendCodeTimer > 0 || isCodeResending || isSubmitting || errorResendingCode;
const onSubmit = async ({ email }: EmailCodeFormValues) => {
setErrorResendingCode(false);
@ -122,44 +121,58 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
)}
<form className="space-y-4 mt-10 sm:w-[360px] mx-auto">
<div className="space-y-1">
<Input
id="email"
type="email"
<Controller
control={control}
name="email"
register={register}
validations={{
rules={{
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || "Email address is not valid",
}}
error={errors.email}
render={({ field: { value, onChange, ref } }) => (
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.email)}
placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]"
className="border-custom-border-300 h-[46px] w-full"
/>
)}
/>
</div>
{codeSent && (
<>
<Input
id="token"
type="token"
<Controller
control={control}
name="token"
register={register}
validations={{
rules={{
required: "Code is required",
}}
error={errors.token}
render={({ field: { value, onChange, ref } }) => (
<Input
id="token"
name="token"
type="token"
value={value ?? ""}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.token)}
placeholder="Enter code..."
className="border-custom-border-300 h-[46px]"
className="border-custom-border-300 h-[46px] w-full"
/>
)}
/>
<button
type="button"
className={`flex w-full justify-end text-xs outline-none ${
isResendDisabled
? "cursor-default text-custom-text-200"
: "cursor-pointer text-custom-primary-100"
isResendDisabled ? "cursor-default text-custom-text-200" : "cursor-pointer text-custom-primary-100"
} `}
onClick={() => {
setIsCodeResending(true);
@ -184,19 +197,21 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
</>
)}
{codeSent ? (
<PrimaryButton
<Button
variant="primary"
type="submit"
className="w-full text-center h-[46px]"
className="w-full"
size="md"
onClick={handleSubmit(handleSignin)}
disabled={!isValid && isDirty}
loading={isLoading}
>
{isLoading ? "Signing in..." : "Sign in"}
</PrimaryButton>
</Button>
) : (
<PrimaryButton
className="w-full text-center h-[46px]"
<Button
variant="primary"
className="w-full"
size="md"
onClick={() => {
handleSubmit(onSubmit)().then(() => {
@ -207,7 +222,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => {
loading={isSubmitting}
>
{isSubmitting ? "Sending code..." : "Send sign in code"}
</PrimaryButton>
</Button>
)}
</form>
</>

View File

@ -1,8 +1,8 @@
import React from "react";
import Link from "next/link";
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// ui
import { Input, PrimaryButton } from "components/ui";
import { Button, Input } from "@plane/ui";
// types
type EmailPasswordFormValues = {
email: string;
@ -21,6 +21,7 @@ export const EmailSignUpForm: React.FC<Props> = (props) => {
const {
register,
handleSubmit,
control,
watch,
formState: { errors, isSubmitting, isValid, isDirty },
} = useForm<EmailPasswordFormValues>({
@ -36,49 +37,60 @@ export const EmailSignUpForm: React.FC<Props> = (props) => {
return (
<>
<form
className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto"
onSubmit={handleSubmit(onSubmit)}
>
<form className="space-y-4 mt-10 w-full sm:w-[360px] mx-auto" onSubmit={handleSubmit(onSubmit)}>
<div className="space-y-1">
<Input
id="email"
type="email"
<Controller
control={control}
name="email"
register={register}
validations={{
rules={{
required: "Email address is required",
validate: (value) =>
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(
value
) || "Email address is not valid",
}}
error={errors.email}
render={({ field: { value, onChange, ref } }) => (
<Input
id="email"
name="email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.email)}
placeholder="Enter your email address..."
className="border-custom-border-300 h-[46px]"
className="border-custom-border-300 h-[46px] w-full"
/>
)}
/>
</div>
<div className="space-y-1">
<Input
id="password"
type="password"
<Controller
control={control}
name="password"
register={register}
validations={{
rules={{
required: "Password is required",
}}
error={errors.password}
render={({ field: { value, onChange, ref } }) => (
<Input
id="password"
name="password"
type="password"
value={value ?? ""}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.password)}
placeholder="Enter your password..."
className="border-custom-border-300 h-[46px]"
className="border-custom-border-300 h-[46px] w-full"
/>
)}
/>
</div>
<div className="space-y-1">
<Input
id="confirm_password"
type="password"
<Controller
control={control}
name="confirm_password"
register={register}
validations={{
rules={{
required: "Password is required",
validate: (val: string) => {
if (watch("password") != val) {
@ -86,27 +98,36 @@ export const EmailSignUpForm: React.FC<Props> = (props) => {
}
},
}}
error={errors.confirm_password}
render={({ field: { value, onChange, ref } }) => (
<Input
id="confirm_password"
name="confirm_password"
type="password"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.confirm_password)}
placeholder="Confirm your password..."
className="border-custom-border-300 h-[46px]"
className="border-custom-border-300 h-[46px] w-full"
/>
)}
/>
</div>
<div className="text-right text-xs">
<Link href="/">
<a className="text-custom-text-200 hover:text-custom-primary-100">
Already have an account? Sign in.
</a>
<a className="text-custom-text-200 hover:text-custom-primary-100">Already have an account? Sign in.</a>
</Link>
</div>
<div>
<PrimaryButton
<Button
variant="primary"
type="submit"
className="w-full text-center h-[46px]"
className="w-full"
disabled={!isValid && isDirty}
loading={isSubmitting}
>
{isSubmitting ? "Signing up..." : "Sign up"}
</PrimaryButton>
</Button>
</div>
</form>
</>

View File

@ -3,7 +3,7 @@ import React from "react";
import { useRouter } from "next/router";
// react-hook-form
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
@ -11,7 +11,7 @@ import analyticsService from "services/analytics.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
import { Button, Input, TextArea } from "@plane/ui";
// types
import { IAnalyticsParams, ISaveAnalyticsFormData } from "types";
@ -39,9 +39,9 @@ export const CreateUpdateAnalyticsModal: React.FC<Props> = ({ isOpen, handleClos
const { setToastAlert } = useToast();
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<FormValues>({
defaultValues,
@ -114,41 +114,54 @@ export const CreateUpdateAnalyticsModal: React.FC<Props> = ({ isOpen, handleClos
<Dialog.Panel className="relative transform rounded-lg border border-custom-border-200 bg-custom-background-100 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Save Analytics
</Dialog.Title>
<div className="mt-5">
<Input
type="text"
id="name"
<Controller
control={control}
name="name"
placeholder="Title"
autoComplete="off"
error={errors.name}
register={register}
width="full"
validations={{
rules={{
required: "Title is required",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
className="w-full"
/>
)}
/>
<Controller
name="description"
control={control}
render={({ field: { value, onChange } }) => (
<TextArea
id="description"
name="description"
placeholder="Description"
className="mt-3 h-32 resize-none text-sm"
error={errors.description}
register={register}
hasError={Boolean(errors?.description)}
value={value}
onChange={onChange}
/>
)}
/>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{isSubmitting ? "Saving..." : "Save Analytics"}
</PrimaryButton>
</Button>
</div>
</form>
</Dialog.Panel>

View File

@ -6,7 +6,7 @@ import useProjects from "hooks/use-projects";
// components
import { AnalyticsGraph, AnalyticsSelectBar, AnalyticsSidebar, AnalyticsTable } from "components/analytics";
// ui
import { Loader, PrimaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
// helpers
import { convertResponseToBarGraphData } from "helpers/analytics.helper";
// types
@ -100,7 +100,8 @@ export const CustomAnalytics: React.FC<Props> = ({ fullScreen, user }) => {
<div className="space-y-4 text-custom-text-200">
<p className="text-sm">There was some error in fetching the data.</p>
<div className="flex items-center justify-center gap-2">
<PrimaryButton
<Button
variant="primary"
onClick={() => {
if (!workspaceSlug) return;
@ -108,7 +109,7 @@ export const CustomAnalytics: React.FC<Props> = ({ fullScreen, user }) => {
}}
>
Refresh
</PrimaryButton>
</Button>
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@ import trackEventServices from "services/track_event.service";
import useProjects from "hooks/use-projects";
import useToast from "hooks/use-toast";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { ArrowDownTrayIcon, ArrowPathIcon, CalendarDaysIcon, UserGroupIcon } from "@heroicons/react/24/outline";
import { ContrastIcon, LayerDiagonalIcon } from "components/icons";
@ -328,24 +328,19 @@ export const AnalyticsSidebar: React.FC<Props> = ({ analytics, params, fullScree
) : null}
</div>
<div className="flex items-center gap-2 flex-wrap justify-self-end">
<SecondaryButton
<Button
variant="neutral-primary"
prependIcon={<ArrowPathIcon className="h-3.5 w-3.5" />}
onClick={() => {
if (!workspaceSlug) return;
mutate(ANALYTICS(workspaceSlug.toString(), params));
}}
>
<div className="flex items-center gap-2 -my-1">
<ArrowPathIcon className="h-3.5 w-3.5" />
Refresh
</div>
</SecondaryButton>
<PrimaryButton onClick={exportAnalytics}>
<div className="flex items-center gap-2 -my-1">
<ArrowDownTrayIcon className="h-3.5 w-3.5" />
</Button>
<Button variant="primary" prependIcon={<ArrowDownTrayIcon />} onClick={exportAnalytics}>
Export as CSV
</div>
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -5,14 +5,9 @@ import useSWR from "swr";
// services
import analyticsService from "services/analytics.service";
// components
import {
AnalyticsDemand,
AnalyticsLeaderboard,
AnalyticsScope,
AnalyticsYearWiseIssues,
} from "components/analytics";
import { AnalyticsDemand, AnalyticsLeaderboard, AnalyticsScope, AnalyticsYearWiseIssues } from "components/analytics";
// ui
import { Loader, PrimaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
// fetch-keys
import { DEFAULT_ANALYTICS } from "constants/fetch-keys";
@ -40,9 +35,7 @@ export const ScopeAndDemand: React.FC<Props> = ({ fullScreen = true }) => {
mutate: mutateDefaultAnalytics,
} = useSWR(
workspaceSlug ? DEFAULT_ANALYTICS(workspaceSlug.toString(), params) : null,
workspaceSlug
? () => analyticsService.getDefaultAnalytics(workspaceSlug.toString(), params)
: null
workspaceSlug ? () => analyticsService.getDefaultAnalytics(workspaceSlug.toString(), params) : null
);
return (
@ -97,7 +90,9 @@ export const ScopeAndDemand: React.FC<Props> = ({ fullScreen = true }) => {
<div className="space-y-4 text-custom-text-200">
<p className="text-sm">There was some error in fetching the data.</p>
<div className="flex items-center justify-center gap-2">
<PrimaryButton onClick={() => mutateDefaultAnalytics()}>Refresh</PrimaryButton>
<Button variant="primary" onClick={() => mutateDefaultAnalytics()}>
Refresh
</Button>
</div>
</div>
</div>

View File

@ -8,7 +8,7 @@ import { mutate } from "swr";
// services
import projectService from "services/project.service";
// ui
import { PrimaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { AssignmentClipboardIcon } from "components/icons";
// images
@ -45,25 +45,22 @@ export const JoinProject: React.FC = () => {
<div className="h-44 w-72">
<Image src={JoinProjectImg} height="176" width="288" alt="JoinProject" />
</div>
<h1 className="text-xl font-medium text-custom-text-100">
You are not a member of this project
</h1>
<h1 className="text-xl font-medium text-custom-text-100">You are not a member of this project</h1>
<div className="w-full max-w-md text-base text-custom-text-200">
<p className="mx-auto w-full text-sm md:w-3/4">
You are not a member of this project, but you can join this project by clicking the button
below.
You are not a member of this project, but you can join this project by clicking the button below.
</p>
</div>
<div>
<PrimaryButton
className="flex items-center gap-1"
<Button
variant="primary"
prependIcon={<AssignmentClipboardIcon color="white" />}
loading={isJoiningProject}
onClick={handleJoin}
>
<AssignmentClipboardIcon height={16} width={16} color="white" />
{isJoiningProject ? "Joining..." : "Click to join"}
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -3,7 +3,7 @@ import Link from "next/link";
// layouts
import DefaultLayout from "layouts/default-layout";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
export const NotAWorkspaceMember = () => (
<DefaultLayout>
@ -12,19 +12,19 @@ export const NotAWorkspaceMember = () => (
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an
invitation or check your pending invitations.
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an invitation or check
your pending invitations.
</p>
</div>
<div className="flex items-center justify-center gap-2">
<Link href="/invitations">
<a>
<SecondaryButton>Check pending invites</SecondaryButton>
<Button variant="neutral-primary">Check pending invites</Button>
</a>
</Link>
<Link href="/create-workspace">
<a>
<PrimaryButton>Create new workspace</PrimaryButton>
<Button variant="primary">Create new workspace</Button>
</a>
</Link>
</div>

View File

@ -1,7 +1,8 @@
import React, { useState } from "react";
// component
import { CustomSelect, ToggleSwitch } from "components/ui";
import { CustomSelect } from "components/ui";
import { ToggleSwitch } from "@plane/ui";
import { SelectMonthModal } from "components/automation";
// icon
import { ArchiveRestore } from "lucide-react";
@ -16,11 +17,7 @@ type Props = {
disabled?: boolean;
};
export const AutoArchiveAutomation: React.FC<Props> = ({
projectDetails,
handleChange,
disabled = false,
}) => {
export const AutoArchiveAutomation: React.FC<Props> = ({ projectDetails, handleChange, disabled = false }) => {
const [monthModal, setmonthModal] = useState(false);
const initialValues: Partial<IProject> = { archive_in: 1 };
@ -49,9 +46,7 @@ export const AutoArchiveAutomation: React.FC<Props> = ({
<ToggleSwitch
value={projectDetails?.archive_in !== 0}
onChange={() =>
projectDetails?.archive_in === 0
? handleChange({ archive_in: 1 })
: handleChange({ archive_in: 0 })
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
}
size="sm"
disabled={disabled}
@ -61,15 +56,11 @@ export const AutoArchiveAutomation: React.FC<Props> = ({
{projectDetails?.archive_in !== 0 && (
<div className="ml-12">
<div className="flex items-center justify-between rounded px-5 py-4 bg-custom-background-90 border border-custom-border-200 gap-2 w-full">
<div className="w-1/2 text-sm font-medium">
Auto-archive issues that are closed for
</div>
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
<div className="w-1/2">
<CustomSelect
value={projectDetails?.archive_in}
label={`${projectDetails?.archive_in} ${
projectDetails?.archive_in === 1 ? "Month" : "Months"
}`}
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
onChange={(val: number) => {
handleChange({ archive_in: val });
}}

View File

@ -5,8 +5,9 @@ import useSWR from "swr";
import { useRouter } from "next/router";
// component
import { CustomSearchSelect, CustomSelect, Icon, ToggleSwitch } from "components/ui";
import { CustomSearchSelect, CustomSelect, Icon } from "components/ui";
import { SelectMonthModal } from "components/automation";
import { ToggleSwitch } from "@plane/ui";
// icons
import { Squares2X2Icon } from "@heroicons/react/24/outline";
import { StateGroupIcon } from "components/icons";
@ -27,11 +28,7 @@ type Props = {
disabled?: boolean;
};
export const AutoCloseAutomation: React.FC<Props> = ({
projectDetails,
handleChange,
disabled = false,
}) => {
export const AutoCloseAutomation: React.FC<Props> = ({ projectDetails, handleChange, disabled = false }) => {
const [monthModal, setmonthModal] = useState(false);
const router = useRouter();

View File

@ -3,11 +3,11 @@ import React from "react";
import { useRouter } from "next/router";
// react-hook-form
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
import { Button, Input } from "@plane/ui";
// types
import type { IProject } from "types";
@ -20,13 +20,7 @@ type Props = {
handleChange: (formData: Partial<IProject>) => Promise<void>;
};
export const SelectMonthModal: React.FC<Props> = ({
type,
initialValues,
isOpen,
handleClose,
handleChange,
}) => {
export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen, handleClose, handleChange }) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
@ -34,6 +28,7 @@ export const SelectMonthModal: React.FC<Props> = ({
register,
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<IProject>({
defaultValues: initialValues,
@ -50,27 +45,6 @@ export const SelectMonthModal: React.FC<Props> = ({
onClose();
};
const inputSection = (name: string) => (
<div className="relative flex flex-col gap-1 justify-center w-full">
<Input
type="number"
id={name}
name={name}
placeholder="Enter Months"
autoComplete="off"
register={register}
width="full"
validations={{
required: "Select a month between 1 and 12.",
min: 1,
max: 12,
}}
className="border-custom-border-200"
/>
<span className="absolute text-sm text-custom-text-200 top-2.5 right-8">Months</span>
</div>
);
return (
<Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-30" onClose={onClose}>
@ -100,30 +74,72 @@ export const SelectMonthModal: React.FC<Props> = ({
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-90 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-2xl sm:p-6">
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Customise Time Range
</Dialog.Title>
<div className="mt-8 flex items-center gap-2">
<div className="flex w-full flex-col gap-1 justify-center">
{type === "auto-close" ? (
<>
{inputSection("close_in")}
<Controller
control={control}
name="close_in"
rules={{
required: "Select a month between 1 and 12.",
min: 1,
max: 12,
}}
render={({ field: { value, onChange, ref } }) => (
<div className="relative flex flex-col gap-1 justify-center w-full">
<Input
id="close_in"
name="close_in"
type="number"
value={value.toString()}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.close_in)}
placeholder="Enter Months"
className="border-custom-border-200 w-full"
/>
<span className="absolute text-sm text-custom-text-200 top-2.5 right-8">Months</span>
</div>
)}
/>
{errors.close_in && (
<span className="text-sm px-1 text-red-500">
Select a month between 1 and 12.
</span>
<span className="text-sm px-1 text-red-500">Select a month between 1 and 12.</span>
)}
</>
) : (
<>
{inputSection("archive_in")}
<Controller
control={control}
name="archive_in"
rules={{
required: "Select a month between 1 and 12.",
min: 1,
max: 12,
}}
render={({ field: { value, onChange, ref } }) => (
<div className="relative flex flex-col gap-1 justify-center w-full">
<Input
id="archive_in"
name="archive_in"
type="number"
value={value.toString()}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.archive_in)}
placeholder="Enter Months"
className="border-custom-border-200 w-full"
/>
<span className="absolute text-sm text-custom-text-200 top-2.5 right-8">Months</span>
</div>
)}
/>
{errors.archive_in && (
<span className="text-sm px-1 text-red-500">
Select a month between 1 and 12.
</span>
<span className="text-sm px-1 text-red-500">Select a month between 1 and 12.</span>
)}
</>
)}
@ -131,10 +147,12 @@ export const SelectMonthModal: React.FC<Props> = ({
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{isSubmitting ? "Submitting..." : "Submit"}
</PrimaryButton>
</Button>
</div>
</form>
</Dialog.Panel>

View File

@ -26,7 +26,8 @@ import {
commandGroups,
} from "components/command-palette";
// ui
import { Icon, Loader, ToggleSwitch, Tooltip } from "components/ui";
import { Icon, Tooltip } from "components/ui";
import { Loader, ToggleSwitch } from "@plane/ui";
// icons
import { DiscordIcon, GithubIcon, SettingIcon } from "components/icons";
import { InboxIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline";

View File

@ -10,7 +10,7 @@ import { Command } from "cmdk";
import issuesService from "services/issue.service";
import stateService from "services/project_state.service";
// ui
import { Spinner } from "components/ui";
import { Spinner } from "@plane/ui";
// icons
import { CheckIcon, StateGroupIcon } from "components/icons";
// helpers

View File

@ -6,7 +6,7 @@ import { XMarkIcon } from "@heroicons/react/20/solid";
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { CommandIcon } from "components/icons";
// ui
import { Input } from "components/ui";
import { Input } from "@plane/ui";
type Props = {
isOpen: boolean;
@ -48,9 +48,7 @@ const allShortcuts = shortcuts.map((i) => i.shortcuts).flat(1);
export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
const [query, setQuery] = useState("");
const filteredShortcuts = allShortcuts.filter((shortcut) =>
shortcut.description.toLowerCase().includes(query.trim().toLowerCase()) || query === ""
? true
: false
shortcut.description.toLowerCase().includes(query.trim().toLowerCase()) || query === "" ? true : false
);
useEffect(() => {
@ -105,12 +103,13 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<div className="flex w-full items-center justify-start gap-1 rounded border-[0.6px] border-custom-border-200 bg-custom-background-90 px-3 py-2">
<MagnifyingGlassIcon className="h-3.5 w-3.5 text-custom-text-200" />
<Input
className="w-full border-none bg-transparent py-1 px-2 text-xs text-custom-text-200 focus:outline-none"
id="search"
name="search"
type="text"
placeholder="Search for shortcuts"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search for shortcuts"
className="w-full border-none bg-transparent py-1 px-2 text-xs text-custom-text-200 focus:outline-none"
/>
</div>
</div>
@ -121,9 +120,7 @@ export const ShortcutsModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<div key={shortcut.keys} className="flex w-full flex-col">
<div className="flex flex-col gap-y-3">
<div className="flex items-center justify-between">
<p className="text-sm text-custom-text-200">
{shortcut.description}
</p>
<p className="text-sm text-custom-text-200">{shortcut.description}</p>
<div className="flex items-center gap-x-2.5">
{shortcut.keys.split(",").map((key, index) => (
<span key={index} className="flex items-center gap-1">

View File

@ -7,7 +7,8 @@ import { Dialog, Transition } from "@headlessui/react";
// services
import workspaceService from "services/workspace.service";
// components
import { Loader, MarkdownRenderer } from "components/ui";
import { MarkdownRenderer } from "components/ui";
import { Loader } from "@plane/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// helpers
@ -48,10 +49,7 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
>
<Dialog.Panel className="relative overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-100 shadow-custom-shadow-rg] min-w-[100%] sm:min-w-[50%] sm:max-w-[50%]">
<div className="flex flex-col p-4 max-h-[90vh] w-full">
<Dialog.Title
as="h3"
className="flex items-center justify-between text-lg font-semibold"
>
<Dialog.Title as="h3" className="flex items-center justify-between text-lg font-semibold">
<span>Product Updates</span>
<span>
<button type="button" onClick={() => setIsOpen(false)}>

View File

@ -6,7 +6,7 @@ import { Dialog, Transition } from "@headlessui/react";
// components
import { DateFilterSelect } from "./date-filter-select";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// helpers
@ -127,12 +127,12 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
</h6>
)}
<div className="flex justify-end gap-4">
<SecondaryButton className="flex items-center gap-2" onClick={handleClose}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</SecondaryButton>
<PrimaryButton type="submit" className="flex items-center gap-2" disabled={isInvalid}>
</Button>
<Button variant="primary" type="submit" disabled={isInvalid}>
Apply
</PrimaryButton>
</Button>
</div>
</form>
</Dialog.Panel>

View File

@ -11,7 +11,8 @@ import useEstimateOption from "hooks/use-estimate-option";
// components
import { SelectFilters } from "components/views";
// ui
import { CustomMenu, ToggleSwitch, Tooltip } from "components/ui";
import { CustomMenu, Tooltip } from "components/ui";
import { ToggleSwitch } from "@plane/ui";
// icons
import { ChevronDownIcon } from "@heroicons/react/24/outline";
import {

View File

@ -5,13 +5,15 @@ import useSWR from "swr";
import { useDropzone } from "react-dropzone";
import { Tab, Transition, Popover } from "@headlessui/react";
import { Control, Controller } from "react-hook-form";
// services
import fileService from "services/file.service";
// hooks
import useWorkspaceDetails from "hooks/use-workspace-details";
import useOutsideClickDetector from "hooks/use-outside-click-detector";
// components
import { Input, PrimaryButton, SecondaryButton, Loader } from "components/ui";
import { Button, Input, Loader } from "@plane/ui";
const tabOptions = [
{
@ -31,16 +33,12 @@ const tabOptions = [
type Props = {
label: string | React.ReactNode;
value: string | null;
control: Control<any>;
onChange: (data: string) => void;
disabled?: boolean;
};
export const ImagePickerPopover: React.FC<Props> = ({
label,
value,
onChange,
disabled = false,
}) => {
export const ImagePickerPopover: React.FC<Props> = ({ label, value, control, onChange, disabled = false }) => {
const ref = useRef<HTMLDivElement>(null);
const router = useRouter();
@ -64,14 +62,10 @@ export const ImagePickerPopover: React.FC<Props> = ({
}
);
const { data: projectCoverImages } = useSWR(
`PROJECT_COVER_IMAGES`,
() => fileService.getProjectCoverImages(),
{
const { data: projectCoverImages } = useSWR(`PROJECT_COVER_IMAGES`, () => fileService.getProjectCoverImages(), {
revalidateOnFocus: false,
revalidateOnReconnect: false,
}
);
});
const imagePickerRef = useRef<HTMLDivElement>(null);
@ -154,8 +148,7 @@ export const ImagePickerPopover: React.FC<Props> = ({
<Tab.List as="span" className="inline-block rounded bg-custom-background-80 p-1">
{tabOptions.map((tab) => {
if (!unsplashImages && unsplashError && tab.key === "unsplash") return null;
if (projectCoverImages && projectCoverImages.length === 0 && tab.key === "images")
return null;
if (projectCoverImages && projectCoverImages.length === 0 && tab.key === "images") return null;
return (
<Tab
@ -175,17 +168,25 @@ export const ImagePickerPopover: React.FC<Props> = ({
{(unsplashImages || !unsplashError) && (
<Tab.Panel className="h-full w-full space-y-4 mt-4">
<div className="flex gap-x-2">
<Input
<Controller
control={control}
name="search"
className="text-sm"
render={({ field: { value, ref } }) => (
<Input
id="search"
value={formData.search}
name="search"
type="text"
value={value}
onChange={(e) => setFormData({ ...formData, search: e.target.value })}
ref={ref}
placeholder="Search for images"
className="text-sm w-full"
/>
<PrimaryButton onClick={() => setSearchParams(formData.search)} size="sm">
)}
/>
<Button variant="primary" onClick={() => setSearchParams(formData.search)} size="sm">
Search
</PrimaryButton>
</Button>
</div>
{unsplashImages ? (
unsplashImages.length > 0 ? (
@ -208,9 +209,7 @@ export const ImagePickerPopover: React.FC<Props> = ({
))}
</div>
) : (
<p className="text-center text-custom-text-300 text-xs pt-7">
No images found.
</p>
<p className="text-center text-custom-text-300 text-xs pt-7">No images found.</p>
)
) : (
<Loader className="grid grid-cols-4 gap-4">
@ -249,9 +248,7 @@ export const ImagePickerPopover: React.FC<Props> = ({
))}
</div>
) : (
<p className="text-center text-custom-text-300 text-xs pt-7">
No images found.
</p>
<p className="text-center text-custom-text-300 text-xs pt-7">No images found.</p>
)
) : (
<Loader className="grid grid-cols-4 gap-4 pt-4">
@ -297,9 +294,7 @@ export const ImagePickerPopover: React.FC<Props> = ({
) : (
<div>
<span className="mt-2 block text-sm font-medium text-custom-text-200">
{isDragActive
? "Drop image here to upload"
: "Drag & drop image here"}
{isDragActive ? "Drop image here to upload" : "Drag & drop image here"}
</span>
</div>
)}
@ -320,23 +315,24 @@ export const ImagePickerPopover: React.FC<Props> = ({
</p>
<div className="flex items-center justify-end gap-2">
<SecondaryButton
className="w-full"
<Button
variant="neutral-primary"
onClick={() => {
setIsOpen(false);
setImage(null);
}}
>
Cancel
</SecondaryButton>
<PrimaryButton
</Button>
<Button
variant="primary"
className="w-full"
onClick={handleSubmit}
disabled={!image}
loading={isImageUploading}
>
{isImageUploading ? "Uploading..." : "Upload & Save"}
</PrimaryButton>
</Button>
</div>
</div>
</Tab.Panel>

View File

@ -14,7 +14,7 @@ import issuesServices from "services/issue.service";
import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
// ui
import { DangerButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { LayerDiagonalIcon } from "components/icons";
@ -240,10 +240,12 @@ export const BulkDeleteIssuesModal: React.FC<Props> = ({ isOpen, setIsOpen, user
{filteredIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleSubmit(handleDelete)} loading={isSubmitting}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleSubmit(handleDelete)} loading={isSubmitting}>
{isSubmitting ? "Deleting..." : "Delete selected issues"}
</DangerButton>
</Button>
</div>
)}
</form>

View File

@ -13,7 +13,8 @@ import useToast from "hooks/use-toast";
import useIssuesView from "hooks/use-issues-view";
import useDebounce from "hooks/use-debounce";
// ui
import { Loader, PrimaryButton, SecondaryButton, ToggleSwitch, Tooltip } from "components/ui";
import { Button, Loader, ToggleSwitch } from "@plane/ui";
import { Tooltip } from "components/ui";
// icons
import { LaunchOutlined } from "@mui/icons-material";
import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline";
@ -117,12 +118,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
return (
<>
<Transition.Root
show={isOpen}
as={React.Fragment}
afterLeave={() => setSearchTerm("")}
appear
>
<Transition.Root show={isOpen} as={React.Fragment} afterLeave={() => setSearchTerm("")} appear>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Transition.Child
as={React.Fragment}
@ -180,11 +176,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
<button
type="button"
className="group p-1"
onClick={() =>
setSelectedIssues((prevData) =>
prevData.filter((i) => i.id !== issue.id)
)
}
onClick={() => setSelectedIssues((prevData) => prevData.filter((i) => i.id !== issue.id))}
>
<XMarkIcon className="h-3 w-3 text-custom-text-200 group-hover:text-custom-text-100" />
</button>
@ -232,18 +224,12 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
</h5>
)}
{!isSearching &&
issues.length === 0 &&
searchTerm !== "" &&
debouncedSearchTerm !== "" && (
{!isSearching && issues.length === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && (
<div className="flex flex-col items-center justify-center gap-4 px-3 py-8 text-center">
<LayerDiagonalIcon height="52" width="52" />
<h3 className="text-custom-text-200">
No issues found. Create a new issue with{" "}
<pre className="inline rounded bg-custom-background-80 px-2 py-1 text-sm">
C
</pre>
.
<pre className="inline rounded bg-custom-background-80 px-2 py-1 text-sm">C</pre>.
</h3>
</div>
)}
@ -256,9 +242,7 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
<Loader.Item height="40px" />
</Loader>
) : (
<ul
className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}
>
<ul className={`text-sm text-custom-text-100 ${issues.length > 0 ? "p-2" : ""}`}>
{issues.map((issue) => {
const selected = selectedIssues.some((i) => i.id === issue.id);
@ -309,10 +293,12 @@ export const ExistingIssuesListModal: React.FC<Props> = ({
</Combobox>
{selectedIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton onClick={onSubmit} loading={isSubmitting}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={onSubmit} loading={isSubmitting}>
{isSubmitting ? "Adding..." : "Add selected issues"}
</PrimaryButton>
</Button>
</div>
)}
</Dialog.Panel>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, forwardRef, useRef } from "react";
import { useRouter } from "next/router";
// react-hook-form
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// services
import aiService from "services/ai.service";
import trackEventServices from "services/track_event.service";
@ -9,8 +9,8 @@ import trackEventServices from "services/track_event.service";
import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
import { TipTapEditor } from "components/tiptap";
import { Button, Input } from "@plane/ui";
// types
import { IIssue, IPageBlock } from "types";
@ -56,6 +56,7 @@ export const GptAssistantModal: React.FC<Props> = ({
const {
handleSubmit,
control,
register,
reset,
setFocus,
@ -167,18 +168,28 @@ export const GptAssistantModal: React.FC<Props> = ({
No response could be generated. This may be due to insufficient content or task information. Please try again.
</div>
)}
<Input
type="text"
<Controller
control={control}
name="task"
register={register}
render={({ field: { value, onChange, ref } }) => (
<Input
id="task"
name="task"
type="text"
value={value}
onChange={onChange}
ref={ref}
placeholder={`${
content && content !== "" ? "Tell AI what action to perform on this content..." : "Ask AI anything..."
}`}
autoComplete="off"
className="w-full"
/>
)}
/>
<div className={`flex gap-2 ${response === "" ? "justify-end" : "justify-between"}`}>
{response !== "" && (
<PrimaryButton
<Button
variant="primary"
onClick={() => {
onResponse(response);
onClose();
@ -187,13 +198,15 @@ export const GptAssistantModal: React.FC<Props> = ({
}}
>
Use this response
</PrimaryButton>
</Button>
)}
<div className="flex items-center gap-2">
<SecondaryButton onClick={onClose}>Close</SecondaryButton>
<PrimaryButton type="button" onClick={handleSubmit(handleResponse)} loading={isSubmitting}>
<Button variant="neutral-primary" onClick={onClose}>
Close
</Button>
<Button variant="primary" onClick={handleSubmit(handleResponse)} loading={isSubmitting}>
{isSubmitting ? "Generating response..." : response === "" ? "Generate response" : "Generate again"}
</PrimaryButton>
</Button>
</div>
</div>
</div>

View File

@ -11,7 +11,7 @@ import fileServices from "services/file.service";
// hooks
import useWorkspaceDetails from "hooks/use-workspace-details";
// ui
import { DangerButton, PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { UserCircleIcon } from "components/icons";
@ -127,10 +127,7 @@ export const ImageUploadModal: React.FC<Props> = ({
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg border border-custom-border-200 bg-custom-background-100 px-5 py-8 text-left shadow-xl transition-all sm:w-full sm:max-w-xl sm:p-6">
<div className="space-y-5">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Upload Image
</Dialog.Title>
<div className="space-y-3">
@ -161,9 +158,7 @@ export const ImageUploadModal: React.FC<Props> = ({
<div>
<UserCircleIcon className="mx-auto h-16 w-16 text-custom-text-200" />
<span className="mt-2 block text-sm font-medium text-custom-text-200">
{isDragActive
? "Drop image here to upload"
: "Drag & drop image here"}
{isDragActive ? "Drop image here to upload" : "Drag & drop image here"}
</span>
</div>
)}
@ -185,19 +180,17 @@ export const ImageUploadModal: React.FC<Props> = ({
</p>
<div className="flex items-center justify-between">
<div className="flex items-center">
<DangerButton onClick={handleDelete} outline disabled={!value}>
<Button variant="danger" onClick={handleDelete} disabled={!value}>
{isRemoving ? "Removing..." : "Remove"}
</DangerButton>
</Button>
</div>
<div className="flex items-center gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton
onClick={handleSubmit}
disabled={!image}
loading={isImageUploading}
>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit} disabled={!image} loading={isImageUploading}>
{isImageUploading ? "Uploading..." : "Upload & Save"}
</PrimaryButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -1,11 +1,11 @@
import React, { useEffect } from "react";
// react-hook-form
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// ui
import { Input, PrimaryButton, SecondaryButton } from "components/ui";
import { Button, Input } from "@plane/ui";
// types
import type { IIssueLink, linkDetails, ModuleLink } from "types";
@ -23,18 +23,12 @@ const defaultValues: IIssueLink | ModuleLink = {
url: "",
};
export const LinkModal: React.FC<Props> = ({
isOpen,
handleClose,
createIssueLink,
updateIssueLink,
status,
data,
}) => {
export const LinkModal: React.FC<Props> = ({ isOpen, handleClose, createIssueLink, updateIssueLink, status, data }) => {
const {
register,
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<ModuleLink>({
defaultValues,
@ -99,46 +93,65 @@ export const LinkModal: React.FC<Props> = ({
<form onSubmit={handleSubmit(handleCreateUpdatePage)}>
<div>
<div className="space-y-5">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
{status ? "Update Link" : "Add Link"}
</Dialog.Title>
<div className="mt-2 space-y-3">
<div>
<Input
id="url"
label="URL"
<label htmlFor="url" className="text-custom-text-200 mb-2">
URL
</label>
<Controller
control={control}
name="url"
type="url"
placeholder="https://..."
autoComplete="off"
error={errors.url}
register={register}
validations={{
rules={{
required: "URL is required",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="url"
name="url"
type="url"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.url)}
placeholder="https://..."
className="w-full"
/>
)}
/>
</div>
<div>
<label htmlFor="title" className="text-custom-text-200 mb-2">
{`Title (optional)`}
</label>
<Controller
control={control}
name="title"
render={({ field: { value, onChange, ref } }) => (
<Input
id="title"
label="Title (optional)"
name="title"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.title)}
placeholder="Enter title"
autoComplete="off"
error={errors.title}
register={register}
className="w-full"
/>
)}
/>
</div>
</div>
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{status
? isSubmitting
? "Updating Link..."
@ -146,7 +159,7 @@ export const LinkModal: React.FC<Props> = ({
: isSubmitting
? "Adding Link..."
: "Add Link"}
</PrimaryButton>
</Button>
</div>
</form>
</Dialog.Panel>

View File

@ -1,6 +1,6 @@
import React from "react";
import { ProgressBar } from "components/ui";
import { ProgressBar } from "@plane/ui";
type TSingleProgressStatsProps = {
title: any;
@ -30,10 +30,7 @@ export const SingleProgressStats: React.FC<TSingleProgressStatsProps> = ({
<ProgressBar value={completed} maxValue={total} />
</span>
<span className="w-8 text-right">
{isNaN(Math.floor((completed / total) * 100))
? "0"
: Math.floor((completed / total) * 100)}
%
{isNaN(Math.floor((completed / total) * 100)) ? "0" : Math.floor((completed / total) * 100)}%
</span>
</div>
<span>of {total}</span>

View File

@ -2,6 +2,8 @@ import React from "react";
// react-form
import {
Control,
Controller,
FieldError,
FieldErrorsImpl,
Merge,
@ -13,7 +15,7 @@ import {
import { ColorResult, SketchPicker } from "react-color";
// component
import { Popover, Transition } from "@headlessui/react";
import { Input } from "components/ui";
import { Input } from "@plane/ui";
// icons
import { ColorPickerIcon } from "components/icons";
// types
@ -24,6 +26,7 @@ type Props = {
position?: "left" | "right";
watch: UseFormWatch<any>;
setValue: UseFormSetValue<any>;
control: Control<ICustomTheme, any>;
error: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined;
register: UseFormRegister<any>;
};
@ -34,6 +37,7 @@ export const ColorPickerInput: React.FC<Props> = ({
watch,
setValue,
error,
control,
register,
}) => {
const handleColorChange = (newColor: ColorResult) => {
@ -60,22 +64,28 @@ export const ColorPickerInput: React.FC<Props> = ({
return (
<div className="relative">
<Input
id={name}
<Controller
control={control}
name={name}
type="name"
placeholder="#FFFFFF"
autoComplete="off"
error={error}
value={watch(name)}
register={register}
validations={{
rules={{
required: `${getColorText(name)} color is required`,
pattern: {
value: /^#(?:[0-9a-fA-F]{3}){1,2}$/g,
message: `${getColorText(name)} color should be hex format`,
},
}}
render={({ field: { onChange, ref } }) => (
<Input
id={name}
name={name}
type="text"
value={watch("name")}
onChange={onChange}
ref={ref}
hasError={Boolean(error)}
placeholder="#FFFFFF"
/>
)}
/>
<div className="absolute right-4 top-2.5">
<Popover className="relative grid place-items-center">
@ -95,11 +105,7 @@ export const ColorPickerInput: React.FC<Props> = ({
}}
/>
) : (
<ColorPickerIcon
height={14}
width={14}
className="fill-current text-custom-text-100"
/>
<ColorPickerIcon height={14} width={14} className="fill-current text-custom-text-100" />
)}
</Popover.Button>

View File

@ -5,8 +5,8 @@ import { useTheme } from "next-themes";
import { useForm } from "react-hook-form";
// ui
import { PrimaryButton } from "components/ui";
import { ColorPickerInput } from "components/core";
import { Button } from "@plane/ui";
// types
import { ICustomTheme } from "types";
// mobx react lite
@ -38,6 +38,7 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
register,
formState: { errors, isSubmitting },
handleSubmit,
control,
watch,
setValue,
reset,
@ -78,12 +79,11 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
<div className="space-y-4">
<div className="grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2 md:grid-cols-3">
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Background color
</h3>
<h3 className="text-left text-sm font-medium text-custom-text-200">Background color</h3>
<ColorPickerInput
name="background"
position="right"
control={control}
error={errors.background}
watch={watch}
setValue={setValue}
@ -95,6 +95,7 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
<h3 className="text-left text-sm font-medium text-custom-text-200">Text color</h3>
<ColorPickerInput
name="text"
control={control}
error={errors.text}
watch={watch}
setValue={setValue}
@ -103,12 +104,11 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Primary(Theme) color
</h3>
<h3 className="text-left text-sm font-medium text-custom-text-200">Primary(Theme) color</h3>
<ColorPickerInput
name="primary"
error={errors.primary}
control={control}
watch={watch}
setValue={setValue}
register={register}
@ -116,12 +116,11 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Sidebar background color
</h3>
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar background color</h3>
<ColorPickerInput
name="sidebarBackground"
position="right"
control={control}
error={errors.sidebarBackground}
watch={watch}
setValue={setValue}
@ -130,11 +129,10 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
</div>
<div className="flex flex-col items-start gap-2">
<h3 className="text-left text-sm font-medium text-custom-text-200">
Sidebar text color
</h3>
<h3 className="text-left text-sm font-medium text-custom-text-200">Sidebar text color</h3>
<ColorPickerInput
name="sidebarText"
control={control}
error={errors.sidebarText}
watch={watch}
setValue={setValue}
@ -145,9 +143,9 @@ export const CustomThemeSelector: React.FC<Props> = observer(({ preLoadedData })
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="primary" type="submit" loading={isSubmitting}>
{isSubmitting ? "Creating Theme..." : "Set Theme"}
</PrimaryButton>
</Button>
</div>
</form>
);

View File

@ -3,17 +3,12 @@ import React from "react";
// headless ui
import { Popover, Transition } from "@headlessui/react";
// ui
import { CustomMenu, ToggleSwitch } from "components/ui";
import { CustomMenu } from "components/ui";
import { ToggleSwitch } from "@plane/ui";
// icons
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
// helpers
import {
formatDate,
isSameMonth,
isSameYear,
updateDateWithMonth,
updateDateWithYear,
} from "helpers/calendar.helper";
import { formatDate, isSameMonth, isSameYear, updateDateWithMonth, updateDateWithYear } from "helpers/calendar.helper";
// constants
import { MONTHS_LIST, YEARS_LIST } from "constants/calendar";
@ -24,12 +19,7 @@ type Props = {
setShowWeekEnds: React.Dispatch<React.SetStateAction<boolean>>;
};
export const CalendarHeader: React.FC<Props> = ({
currentDate,
setCurrentDate,
showWeekEnds,
setShowWeekEnds,
}) => (
export const CalendarHeader: React.FC<Props> = ({ currentDate, setCurrentDate, showWeekEnds, setShowWeekEnds }) => (
<div className="mb-4 flex items-center justify-between">
<div className="relative flex h-full w-full items-center justify-start gap-2 text-sm ">
<Popover className="flex h-full items-center justify-start rounded-lg">
@ -37,8 +27,7 @@ export const CalendarHeader: React.FC<Props> = ({
<>
<Popover.Button>
<div className="flex items-center justify-center gap-2 text-2xl font-semibold text-custom-text-100">
<span>{formatDate(currentDate, "Month")}</span>{" "}
<span>{formatDate(currentDate, "yyyy")}</span>
<span>{formatDate(currentDate, "Month")}</span> <span>{formatDate(currentDate, "yyyy")}</span>
</div>
</Popover.Button>
@ -69,13 +58,9 @@ export const CalendarHeader: React.FC<Props> = ({
<div className="grid grid-cols-4 border-t border-custom-border-200 px-2">
{MONTHS_LIST.map((month) => (
<button
onClick={() =>
setCurrentDate(updateDateWithMonth(`${month.value}`, currentDate))
}
onClick={() => setCurrentDate(updateDateWithMonth(`${month.value}`, currentDate))}
className={`px-2 py-2 text-xs text-custom-text-200 hover:font-medium hover:text-custom-text-100 ${
isSameMonth(`${month.value}`, currentDate)
? "font-medium text-custom-text-100"
: ""
isSameMonth(`${month.value}`, currentDate) ? "font-medium text-custom-text-100" : ""
}`}
>
{month.label}
@ -93,11 +78,8 @@ export const CalendarHeader: React.FC<Props> = ({
className="cursor-pointer"
onClick={() => {
const previousMonthYear =
currentDate.getMonth() === 0
? currentDate.getFullYear() - 1
: currentDate.getFullYear();
const previousMonthMonth =
currentDate.getMonth() === 0 ? 11 : currentDate.getMonth() - 1;
currentDate.getMonth() === 0 ? currentDate.getFullYear() - 1 : currentDate.getFullYear();
const previousMonthMonth = currentDate.getMonth() === 0 ? 11 : currentDate.getMonth() - 1;
const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1);
@ -110,9 +92,7 @@ export const CalendarHeader: React.FC<Props> = ({
className="cursor-pointer"
onClick={() => {
const nextMonthYear =
currentDate.getMonth() === 11
? currentDate.getFullYear() + 1
: currentDate.getFullYear();
currentDate.getMonth() === 11 ? currentDate.getFullYear() + 1 : currentDate.getFullYear();
const nextMonthMonth = (currentDate.getMonth() + 1) % 12;
const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1);

View File

@ -8,7 +8,7 @@ import issuesService from "services/issue.service";
import { SingleCalendarDate, CalendarHeader } from "components/core";
import { IssuePeekOverview } from "components/issues";
// ui
import { Spinner } from "components/ui";
import { Spinner } from "@plane/ui";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
import { startOfWeek, lastDayOfWeek, eachDayOfInterval, weekDayInterval, formatDate } from "helpers/calendar.helper";

View File

@ -27,7 +27,7 @@ import {
} from "components/issues";
import { CreateUpdateViewModal } from "components/views";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { PlusIcon } from "@heroicons/react/24/outline";
// helpers
@ -498,7 +498,9 @@ export const IssuesView: React.FC<Props> = ({ openIssuesListModal, disableUserAc
})
}
/>
<PrimaryButton
<Button
variant="primary"
prependIcon={!viewId && <PlusIcon />}
onClick={() => {
if (viewId) {
setFilters({}, true);
@ -512,11 +514,9 @@ export const IssuesView: React.FC<Props> = ({ openIssuesListModal, disableUserAc
query: filters,
});
}}
className="flex items-center gap-2 text-sm"
>
{!viewId && <PlusIcon className="h-4 w-4" />}
{viewId ? "Update" : "Save"} view
</PrimaryButton>
</Button>
</div>
{<div className="mt-3 border-t border-custom-border-200" />}
</>
@ -556,10 +556,9 @@ export const IssuesView: React.FC<Props> = ({ openIssuesListModal, disableUserAc
: undefined,
secondaryButton:
cycleId || moduleId ? (
<SecondaryButton className="flex items-center gap-1.5" onClick={openIssuesListModal ?? (() => {})}>
<PlusIcon className="h-4 w-4" />
<Button variant="neutral-primary" prependIcon={<PlusIcon />} onClick={openIssuesListModal ?? (() => {})}>
Add an existing issue
</SecondaryButton>
</Button>
) : null,
}}
handleOnDragEnd={handleOnDragEnd}

View File

@ -18,8 +18,9 @@ import {
SpreadsheetStateColumn,
SpreadsheetUpdatedOnColumn,
} from "components/core";
import { CustomMenu, Icon, Spinner } from "components/ui";
import { CustomMenu, Icon } from "components/ui";
import { IssuePeekOverview } from "components/issues";
import { Spinner } from "@plane/ui";
// types
import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, TIssueOrderByOptions } from "types";
// icon

View File

@ -10,9 +10,10 @@ import cyclesService from "services/cycles.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { LinearProgressIndicator, Loader, Tooltip } from "components/ui";
import { LinearProgressIndicator, Tooltip } from "components/ui";
import { AssigneesList } from "components/ui/avatar";
import { SingleProgressStats } from "components/core";
import { Loader } from "@plane/ui";
// components
import ProgressChart from "components/core/sidebar/progress-chart";
import { ActiveCycleProgressStats } from "components/cycles";
@ -34,11 +35,7 @@ import { StarIcon } from "@heroicons/react/24/outline";
// components
import { ViewIssueLabel } from "components/issues";
// helpers
import {
getDateRangeStatus,
renderShortDateWithYearFormat,
findHowManyDaysLeft,
} from "helpers/date-time.helper";
import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper";
import { truncateText } from "helpers/string.helper";
// types
import { ICycle, IIssue } from "types";
@ -82,24 +79,18 @@ export const ActiveCycleDetails: React.FC = () => {
const { data: currentCycle } = useSWR(
workspaceSlug && projectId ? CURRENT_CYCLE_LIST(projectId as string) : null,
workspaceSlug && projectId
? () =>
cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, "current")
? () => cyclesService.getCyclesWithParams(workspaceSlug as string, projectId as string, "current")
: null
);
const cycle = currentCycle ? currentCycle[0] : null;
const { data: issues } = useSWR(
workspaceSlug && projectId && cycle?.id
? CYCLE_ISSUES_WITH_PARAMS(cycle?.id, { priority: "urgent,high" })
: null,
workspaceSlug && projectId && cycle?.id ? CYCLE_ISSUES_WITH_PARAMS(cycle?.id, { priority: "urgent,high" }) : null,
workspaceSlug && projectId && cycle?.id
? () =>
cyclesService.getCycleIssuesWithParams(
workspaceSlug as string,
projectId as string,
cycle.id,
{ priority: "urgent,high" }
)
cyclesService.getCycleIssuesWithParams(workspaceSlug as string, projectId as string, cycle.id, {
priority: "urgent,high",
})
: null
) as { data: IIssue[] | undefined };
@ -115,20 +106,8 @@ export const ActiveCycleDetails: React.FC = () => {
<div className="h-full grid place-items-center text-center">
<div className="space-y-2">
<div className="mx-auto flex justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="66"
height="66"
viewBox="0 0 66 66"
fill="none"
>
<circle
cx="34.375"
cy="34.375"
r="22"
stroke="rgb(var(--color-text-400))"
strokeLinecap="round"
/>
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="66" viewBox="0 0 66 66" fill="none">
<circle cx="34.375" cy="34.375" r="22" stroke="rgb(var(--color-text-400))" strokeLinecap="round" />
<path
d="M36.4375 20.9919C36.4375 19.2528 37.6796 17.8127 39.1709 18.1419C40.125 18.3526 41.0604 18.6735 41.9625 19.1014C43.7141 19.9322 45.3057 21.1499 46.6464 22.685C47.987 24.2202 49.0505 26.0426 49.776 28.0484C50.5016 30.0541 50.875 32.2038 50.875 34.3748C50.875 36.5458 50.5016 38.6956 49.776 40.7013C49.0505 42.7071 47.987 44.5295 46.6464 46.0647C45.3057 47.5998 43.7141 48.8175 41.9625 49.6483C41.0604 50.0762 40.125 50.3971 39.1709 50.6077C37.6796 50.937 36.4375 49.4969 36.4375 47.7578L36.4375 20.9919Z"
fill="rgb(var(--color-text-400))"
@ -224,9 +203,7 @@ export const ActiveCycleDetails: React.FC = () => {
false
);
cyclesService
.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id)
.catch(() => {
cyclesService.removeCycleFromFavorites(workspaceSlug as string, projectId as string, cycle.id).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -238,10 +215,7 @@ export const ActiveCycleDetails: React.FC = () => {
const progressIndicatorData = stateGroups.map((group, index) => ({
id: index,
name: group.title,
value:
cycle.total_issues > 0
? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100
: 0,
value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0,
color: group.color,
}));
@ -270,9 +244,7 @@ export const ActiveCycleDetails: React.FC = () => {
/>
</span>
<Tooltip tooltipContent={cycle.name} position="top-left">
<h3 className="break-words text-lg font-semibold">
{truncateText(cycle.name, 70)}
</h3>
<h3 className="break-words text-lg font-semibold">{truncateText(cycle.name, 70)}</h3>
</Tooltip>
</span>
<span className="flex items-center gap-1 capitalize">
@ -304,9 +276,7 @@ export const ActiveCycleDetails: React.FC = () => {
<span className="flex gap-1 whitespace-nowrap">
{cycle.total_issues - cycle.completed_issues > 0 && (
<Tooltip
tooltipContent={`${
cycle.total_issues - cycle.completed_issues
} more pending ${
tooltipContent={`${cycle.total_issues - cycle.completed_issues} more pending ${
cycle.total_issues - cycle.completed_issues === 1 ? "issue" : "issues"
}`}
>
@ -443,9 +413,7 @@ export const ActiveCycleDetails: React.FC = () => {
issues.map((issue) => (
<div
key={issue.id}
onClick={() =>
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)
}
onClick={() => router.push(`/${workspaceSlug}/projects/${projectId}/issues/${issue.id}`)}
className="flex flex-wrap cursor-pointer rounded-md items-center justify-between gap-2 border border-custom-border-200 bg-custom-background-90 px-3 py-1.5"
>
<div className="flex flex-col gap-1">
@ -459,14 +427,8 @@ export const ActiveCycleDetails: React.FC = () => {
</span>
</Tooltip>
</div>
<Tooltip
position="top-left"
tooltipHeading="Title"
tooltipContent={issue.name}
>
<span className="text-[0.825rem] text-custom-text-100">
{truncateText(issue.name, 30)}
</span>
<Tooltip position="top-left" tooltipHeading="Title" tooltipContent={issue.name}>
<span className="text-[0.825rem] text-custom-text-100">{truncateText(issue.name, 30)}</span>
</Tooltip>
</div>
<div className="flex items-center gap-1.5">
@ -481,15 +443,9 @@ export const ActiveCycleDetails: React.FC = () => {
</div>
<ViewIssueLabel labelDetails={issue.label_details} maxRender={2} />
<div className={`flex items-center gap-2 text-custom-text-200`}>
{issue.assignees &&
issue.assignees.length > 0 &&
Array.isArray(issue.assignees) ? (
{issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? (
<div className="-my-0.5 flex items-center justify-center gap-2">
<AssigneesList
users={issue.assignee_details}
length={3}
showLength={false}
/>
<AssigneesList users={issue.assignee_details} length={3} showLength={false} />
</div>
) : (
""
@ -522,17 +478,14 @@ export const ActiveCycleDetails: React.FC = () => {
width:
issues &&
`${
(issues.filter((issue) => issue?.state_detail?.group === "completed")
?.length /
issues.length) *
(issues.filter((issue) => issue?.state_detail?.group === "completed")?.length / issues.length) *
100 ?? 0
}%`,
}}
/>
</div>
<div className="w-16 text-end text-xs text-custom-text-200">
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "}
{issues?.length}
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of {issues?.length}
</div>
</div>
)}
@ -553,10 +506,7 @@ export const ActiveCycleDetails: React.FC = () => {
<span>
<LayerDiagonalIcon className="h-5 w-5 flex-shrink-0 text-custom-text-200" />
</span>
<span>
Pending Issues -{" "}
{cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}
</span>
<span>Pending Issues - {cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}</span>
</div>
</div>
<div className="relative h-64">

View File

@ -1,6 +1,6 @@
import { FC } from "react";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// types
import { ICycle } from "types";
import { CyclesListItem } from "./cycles-list-item";

View File

@ -16,7 +16,7 @@ import {
SingleCycleList,
} from "components/cycles";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// helpers
import { getDateRangeStatus } from "helpers/date-time.helper";
// types

View File

@ -5,7 +5,7 @@ import { observer } from "mobx-react-lite";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CyclesBoard, CyclesList } from "components/cycles";
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
export interface ICyclesView {
filter: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete";

View File

@ -10,7 +10,7 @@ import cycleService from "services/cycles.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { DangerButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// types
@ -32,12 +32,7 @@ import {
} from "constants/fetch-keys";
import { getDateRangeStatus } from "helpers/date-time.helper";
export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({
isOpen,
setIsOpen,
data,
user,
}) => {
export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({ isOpen, setIsOpen, data, user }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const router = useRouter();
@ -144,36 +139,29 @@ export const DeleteCycleModal: React.FC<TConfirmCycleDeletionProps> = ({
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-500/20 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Delete Cycle
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Are you sure you want to delete cycle-{" "}
<span className="break-words font-medium text-custom-text-100">
{data?.name}
</span>
? All of the data related to the cycle will be permanently removed. This
action cannot be undone.
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? All of the
data related to the cycle will be permanently removed. This action cannot be undone.
</p>
</div>
</div>
</div>
</div>
<div className="flex justify-end gap-2 p-4 sm:px-6">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDeletion} loading={isDeleteLoading}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete"}
</DangerButton>
</Button>
</div>
</Dialog.Panel>
</Transition.Child>

View File

@ -1,7 +1,7 @@
import { Controller, useForm } from "react-hook-form";
// ui
import { Input, TextArea } from "@plane/ui";
import { DateSelect, PrimaryButton, SecondaryButton } from "components/ui";
import { Button, Input, TextArea } from "@plane/ui";
import { DateSelect } from "components/ui";
// types
import { ICycle } from "types";
@ -114,8 +114,10 @@ export const CycleForm: React.FC<Props> = (props) => {
</div>
</div>
<div className="-mx-5 mt-5 flex justify-end gap-2 border-t border-custom-border-200 px-5 pt-5">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{data
? isSubmitting
? "Updating Cycle..."
@ -123,7 +125,7 @@ export const CycleForm: React.FC<Props> = (props) => {
: isSubmitting
? "Creating Cycle..."
: "Create Cycle"}
</PrimaryButton>
</Button>
</div>
</form>
);

View File

@ -17,7 +17,8 @@ import { SidebarProgressStats } from "components/core";
import ProgressChart from "components/core/sidebar/progress-chart";
import { DeleteCycleModal } from "components/cycles";
// ui
import { CustomMenu, CustomRangeDatePicker, Loader, ProgressBar } from "components/ui";
import { CustomMenu, CustomRangeDatePicker } from "components/ui";
import { Loader, ProgressBar } from "@plane/ui";
// icons
import {
CalendarDaysIcon,

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import useSWR from "swr";
// component
import { PrimaryButton, Tooltip } from "components/ui";
import { Button } from "@plane/ui";
// icon
import { ExclamationIcon, TransferIcon } from "components/icons";
// services
@ -24,12 +24,7 @@ export const TransferIssues: React.FC<Props> = ({ handleClick }) => {
const { data: cycleDetails } = useSWR(
cycleId ? CYCLE_DETAILS(cycleId as string) : null,
workspaceSlug && projectId && cycleId
? () =>
cycleServices.getCycleDetails(
workspaceSlug as string,
projectId as string,
cycleId as string
)
? () => cycleServices.getCycleDetails(workspaceSlug as string, projectId as string, cycleId as string)
: null
);
@ -45,10 +40,9 @@ export const TransferIssues: React.FC<Props> = ({ handleClick }) => {
{transferableIssuesCount > 0 && (
<div>
<PrimaryButton onClick={handleClick} className="flex items-center gap-3 rounded-lg">
<TransferIcon className="h-4 w-4" color="white" />
<span className="text-white">Transfer Issues</span>
</PrimaryButton>
<Button variant="primary" prependIcon={<TransferIcon color="white" />} onClick={handleClick}>
Transfer Issues
</Button>
</div>
)}
</div>

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { mutate } from "swr";
// react-hook-form
import { useForm } from "react-hook-form";
import { Controller, useForm } from "react-hook-form";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// services
@ -13,7 +13,7 @@ import estimatesService from "services/project_estimates.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { Input, PrimaryButton, SecondaryButton, TextArea } from "components/ui";
import { Button, Input, TextArea } from "@plane/ui";
// helpers
import { checkDuplicates } from "helpers/array.helper";
// types
@ -52,9 +52,9 @@ const defaultValues: Partial<FormValues> = {
export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data, isOpen, user }) => {
const {
register,
formState: { isSubmitting },
formState: { errors, isSubmitting },
handleSubmit,
control,
reset,
} = useForm<FormValues>({
defaultValues,
@ -253,23 +253,39 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<div className="space-y-3">
<div className="text-lg font-medium leading-6">{data ? "Update" : "Create"} Estimate</div>
<div>
<Controller
control={control}
name="name"
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="name"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
autoComplete="off"
className="resize-none text-xl"
register={register}
className="resize-none text-xl w-full"
/>
)}
/>
</div>
<div>
<Controller
name="description"
control={control}
render={({ field: { value, onChange } }) => (
<TextArea
id="description"
name="description"
value={value}
placeholder="Description"
className="h-32 resize-none text-sm"
register={register}
onChange={onChange}
className="mt-3 h-32 resize-none text-sm"
hasError={Boolean(errors?.description)}
/>
)}
/>
</div>
<div className="grid grid-cols-3 gap-3">
@ -277,14 +293,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">1</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value1"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value1"
name="value1"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value1)}
placeholder="Point 1"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -293,14 +317,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">2</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value2"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value2"
name="value2"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value2)}
placeholder="Point 2"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -309,14 +341,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">3</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value3"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value3"
name="value3"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value3)}
placeholder="Point 3"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -325,14 +365,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">4</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value4"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value4"
name="value4"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value4)}
placeholder="Point 4"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -341,14 +389,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">5</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value5"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value5"
name="value5"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value5)}
placeholder="Point 5"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -357,14 +413,22 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
<span className="flex h-full items-center rounded-lg bg-custom-background-80">
<span className="rounded-lg px-2 text-sm text-custom-text-200">6</span>
<span className="rounded-r-lg bg-custom-background-100">
<Input
id="name"
<Controller
control={control}
name="value6"
type="name"
className="rounded-l-none"
render={({ field: { value, onChange, ref } }) => (
<Input
id="value6"
name="value6"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.value6)}
placeholder="Point 6"
autoComplete="off"
register={register}
className="rounded-l-none w-full"
/>
)}
/>
</span>
</span>
@ -372,8 +436,10 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
</div>
</div>
<div className="mt-5 flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{data
? isSubmitting
? "Updating Estimate..."
@ -381,7 +447,7 @@ export const CreateUpdateEstimateModal: React.FC<Props> = ({ handleClose, data,
: isSubmitting
? "Creating Estimate..."
: "Create Estimate"}
</PrimaryButton>
</Button>
</div>
</form>
</Dialog.Panel>

View File

@ -8,7 +8,7 @@ import { IEstimate } from "types";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
type Props = {
isOpen: boolean;
@ -17,12 +17,7 @@ type Props = {
handleDelete: () => void;
};
export const DeleteEstimateModal: React.FC<Props> = ({
isOpen,
handleClose,
data,
handleDelete,
}) => {
export const DeleteEstimateModal: React.FC<Props> = ({ isOpen, handleClose, data, handleDelete }) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
useEffect(() => {
@ -64,10 +59,7 @@ export const DeleteEstimateModal: React.FC<Props> = ({
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Estimate</h3>
@ -76,16 +68,17 @@ export const DeleteEstimateModal: React.FC<Props> = ({
<span>
<p className="break-words text-sm leading-7 text-custom-text-200">
Are you sure you want to delete estimate-{" "}
<span className="break-words font-medium text-custom-text-100">
{data.name}
</span>
{""}? All of the data related to the estiamte will be permanently removed.
This action cannot be undone.
<span className="break-words font-medium text-custom-text-100">{data.name}</span>
{""}? All of the data related to the estiamte will be permanently removed. This action cannot be
undone.
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<DangerButton
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button
variant="danger"
onClick={() => {
setIsDeleteLoading(true);
handleDelete();
@ -93,7 +86,7 @@ export const DeleteEstimateModal: React.FC<Props> = ({
loading={isDeleteLoading}
>
{isDeleteLoading ? "Deleting..." : "Delete Estimate"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -10,7 +10,8 @@ import useProjectDetails from "hooks/use-project-details";
// components
import { DeleteEstimateModal } from "components/estimates";
// ui
import { CustomMenu, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
import { CustomMenu } from "components/ui";
//icons
import { PencilIcon, TrashIcon } from "@heroicons/react/24/outline";
// helpers
@ -25,12 +26,7 @@ type Props = {
handleEstimateDelete: (estimateId: string) => void;
};
export const SingleEstimate: React.FC<Props> = ({
user,
estimate,
editEstimate,
handleEstimateDelete,
}) => {
export const SingleEstimate: React.FC<Props> = ({ user, estimate, editEstimate, handleEstimateDelete }) => {
const [isDeleteEstimateModalOpen, setIsDeleteEstimateModalOpen] = useState(false);
const router = useRouter();
@ -53,9 +49,7 @@ export const SingleEstimate: React.FC<Props> = ({
return { ...prevData, estimate: estimate.id };
}, false);
await projectService
.updateProject(workspaceSlug as string, projectId as string, payload, user)
.catch(() => {
await projectService.updateProject(workspaceSlug as string, projectId as string, payload, user).catch(() => {
setToastAlert({
type: "error",
title: "Error!",
@ -72,9 +66,7 @@ export const SingleEstimate: React.FC<Props> = ({
<h6 className="flex w-[40vw] items-center gap-2 truncate text-sm font-medium">
{estimate.name}
{projectDetails?.estimate && projectDetails?.estimate === estimate.id && (
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs text-green-500">
In use
</span>
<span className="rounded bg-green-500/20 px-2 py-0.5 text-xs text-green-500">In use</span>
)}
</h6>
<p className="font-sm w-[40vw] truncate text-[14px] font-normal text-custom-text-200">
@ -83,12 +75,9 @@ export const SingleEstimate: React.FC<Props> = ({
</div>
<div className="flex items-center gap-2">
{projectDetails?.estimate !== estimate.id && estimate.points.length > 0 && (
<SecondaryButton
onClick={handleUseEstimate}
className="!py-1 text-custom-text-200 hover:text-custom-text-100"
>
<Button variant="neutral-primary" onClick={handleUseEstimate}>
Use
</SecondaryButton>
</Button>
)}
<CustomMenu ellipsis>
<CustomMenu.MenuItem

View File

@ -7,7 +7,8 @@ import { CSVIntegrationService } from "services/csv.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { SecondaryButton, PrimaryButton, CustomSearchSelect } from "components/ui";
import { Button } from "@plane/ui";
import { CustomSearchSelect } from "components/ui";
// types
import { ICurrentUserResponse, IImporterService } from "types";
// fetch-keys
@ -143,10 +144,17 @@ export const Exporter: React.FC<Props> = ({ isOpen, handleClose, user, provider,
<div className="text-sm whitespace-nowrap">Export the data into separate files</div>
</div>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton onClick={ExportCSVToMail} disabled={exportLoading} loading={exportLoading}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button
variant="primary"
onClick={ExportCSVToMail}
disabled={exportLoading}
loading={exportLoading}
>
{exportLoading ? "Exporting..." : "Export"}
</PrimaryButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -13,7 +13,8 @@ import IntegrationService from "services/integration.service";
// components
import { Exporter, SingleExport } from "components/exporter";
// ui
import { Icon, Loader, PrimaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
import { Icon } from "components/ui";
// icons
import { ArrowPathIcon } from "@heroicons/react/24/outline";
// fetch-keys
@ -65,9 +66,7 @@ const IntegrationGuide = () => {
<div className="flex-shrink-0">
<Link href={`/${workspaceSlug}/settings/exports?provider=${service.provider}`}>
<a>
<PrimaryButton>
<span className="capitalize">{service.type}</span>
</PrimaryButton>
<Button variant="primary">{service.type}</Button>
</a>
</Link>
</div>

View File

@ -1,6 +1,6 @@
import React from "react";
// ui
import { PrimaryButton } from "components/ui"; // icons
import { Button } from "@plane/ui";
// helpers
import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types
@ -29,13 +29,7 @@ export const SingleExport: React.FC<Props> = ({ service, refreshing }) => {
<span>
Export to{" "}
<span className="font-medium">
{provider === "csv"
? "CSV"
: provider === "xlsx"
? "Excel"
: provider === "json"
? "JSON"
: ""}
{provider === "csv" ? "CSV" : provider === "xlsx" ? "Excel" : provider === "json" ? "JSON" : ""}
</span>{" "}
</span>
<span
@ -64,9 +58,9 @@ export const SingleExport: React.FC<Props> = ({ service, refreshing }) => {
{service.status == "completed" && (
<div>
<a target="_blank" href={service?.url} rel="noopener noreferrer">
<PrimaryButton className="w-full text-center">
<Button variant="primary" className="w-full">
{isLoading ? "Downloading..." : "Download"}
</PrimaryButton>
</Button>
</a>
</div>
)}

View File

@ -5,7 +5,7 @@ import StrictModeDroppable from "components/dnd/StrictModeDroppable";
// hooks
import { useChart } from "./hooks";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// icons
import { EllipsisVerticalIcon } from "@heroicons/react/24/outline";
// helpers
@ -55,8 +55,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
// update the sort order to the lowest if dropped at the top
if (destination.index === 0) updatedSortOrder = blocks[0].sort_order - 1000;
// update the sort order to the highest if dropped at the bottom
else if (destination.index === blocks.length - 1)
updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
else if (destination.index === blocks.length - 1) updatedSortOrder = blocks[blocks.length - 1].sort_order + 1000;
// update the sort order to the average of the two adjacent blocks if dropped in between
else {
const destinationSortingOrder = blocks[destination.index].sort_order;
@ -95,11 +94,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
const duration = findTotalDaysInRange(
block.start_date ?? "",
block.target_date ?? "",
true
);
const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "", true);
return (
<Draggable
@ -110,9 +105,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
>
{(provided, snapshot) => (
<div
className={`h-11 ${
snapshot.isDragging ? "bg-custom-background-80 rounded" : ""
}`}
className={`h-11 ${snapshot.isDragging ? "bg-custom-background-80 rounded" : ""}`}
onMouseEnter={() => updateActiveBlock(block)}
onMouseLeave={() => updateActiveBlock(null)}
ref={provided.innerRef}

View File

@ -10,7 +10,8 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection } from "components/issues";
import { CreateUpdateWorkspaceViewModal } from "components/workspace";
// ui
import { PrimaryButton, Tooltip } from "components/ui";
import { Tooltip } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { List, PlusIcon, Sheet } from "lucide-react";
// types
@ -145,10 +146,9 @@ export const GlobalIssuesHeader: React.FC<Props> = observer((props) => {
</FiltersDropdown>
</>
)}
<PrimaryButton className="flex items-center gap-2" onClick={() => setCreateViewModal(true)}>
<PlusIcon className="h-4 w-4" />
<Button variant="primary" prependIcon={<PlusIcon />} onClick={() => setCreateViewModal(true)}>
New View
</PrimaryButton>
</Button>
</div>
</>
);

View File

@ -5,7 +5,7 @@ import { Dialog, Transition } from "@headlessui/react";
// icons
import { CheckCircleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, PrimaryButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IInboxIssue } from "types";
@ -76,10 +76,12 @@ export const AcceptIssueModal: React.FC<Props> = ({ isOpen, handleClose, data, o
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton onClick={handleAccept} loading={isAccepting}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleAccept} loading={isAccepting}>
{isAccepting ? "Accepting..." : "Accept Issue"}
</PrimaryButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -5,7 +5,7 @@ import { Dialog, Transition } from "@headlessui/react";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IInboxIssue } from "types";
@ -60,10 +60,7 @@ export const DeclineIssueModal: React.FC<Props> = ({ isOpen, handleClose, data,
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Decline Issue</h3>
@ -79,10 +76,12 @@ export const DeclineIssueModal: React.FC<Props> = ({ isOpen, handleClose, data,
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDecline} loading={isDeclining}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDecline} loading={isDeclining}>
{isDeclining ? "Declining..." : "Decline Issue"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -15,7 +15,7 @@ import useUser from "hooks/use-user";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IInboxIssue } from "types";
// fetch-keys
@ -115,10 +115,7 @@ export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data })
<div className="flex flex-col gap-6 p-6">
<div className="flex w-full items-center justify-start gap-6">
<span className="place-items-center rounded-full bg-red-500/20 p-4">
<ExclamationTriangleIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
<ExclamationTriangleIcon className="h-6 w-6 text-red-600" aria-hidden="true" />
</span>
<span className="flex items-center justify-start">
<h3 className="text-xl font-medium 2xl:text-2xl">Delete Issue</h3>
@ -130,15 +127,16 @@ export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data })
<span className="break-words font-medium text-custom-text-100">
{data?.project_detail?.identifier}-{data?.sequence_id}
</span>
{""}? The issue will only be deleted from the inbox and this action cannot be
undone.
{""}? The issue will only be deleted from the inbox and this action cannot be undone.
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDelete} loading={isDeleting}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDelete} loading={isDeleting}>
{isDeleting ? "Deleting..." : "Delete Issue"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -25,7 +25,7 @@ import {
SelectDuplicateInboxIssueModal,
} from "components/inbox";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { InboxIcon, StackedLayersHorizontalIcon } from "components/icons";
import {
@ -74,9 +74,7 @@ export const InboxActionHeader = () => {
mutateInboxIssues(
(prevData: any) =>
(prevData ?? []).map((i: any) =>
i.bridge_id === inboxIssueId
? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] }
: i
i.bridge_id === inboxIssueId ? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] } : i
),
false
);
@ -104,8 +102,7 @@ export const InboxActionHeader = () => {
};
const issue = inboxIssues?.find((issue) => issue.bridge_id === inboxIssueId);
const currentIssueIndex =
inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0;
const currentIssueIndex = inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0;
useEffect(() => {
if (!issue?.issue_inbox[0].snoozed_till) return;
@ -126,10 +123,7 @@ export const InboxActionHeader = () => {
<SelectDuplicateInboxIssueModal
isOpen={selectDuplicateIssue}
onClose={() => setSelectDuplicateIssue(false)}
value={
inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.issue_inbox[0]
.duplicate_to
}
value={inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.issue_inbox[0].duplicate_to}
onSubmit={(dupIssueId: string) => {
markInboxStatus({
status: 2,
@ -202,10 +196,13 @@ export const InboxActionHeader = () => {
<div className="flex-shrink-0">
<Popover className="relative">
<Popover.Button as="button" type="button">
<SecondaryButton className="flex gap-x-1 items-center" size="sm">
<ClockIcon className="h-4 w-4 text-custom-text-200" />
<span>Snooze</span>
</SecondaryButton>
<Button
variant="neutral-primary"
prependIcon={<ClockIcon className="text-custom-text-200" />}
size="sm"
>
Snooze
</Button>
</Popover.Button>
<Popover.Panel className="w-80 p-2 absolute right-0 z-10 mt-2 rounded-md border border-custom-border-200 bg-custom-background-80 shadow-lg">
{({ close }) => (
@ -220,8 +217,8 @@ export const InboxActionHeader = () => {
minDate={tomorrow}
inline
/>
<PrimaryButton
className="ml-auto"
<Button
variant="primary"
onClick={() => {
close();
markInboxStatus({
@ -231,7 +228,7 @@ export const InboxActionHeader = () => {
}}
>
Snooze
</PrimaryButton>
</Button>
</div>
)}
</Popover.Panel>
@ -240,50 +237,50 @@ export const InboxActionHeader = () => {
)}
{isAllowed && issueStatus === -2 && (
<div className="flex-shrink-0">
<SecondaryButton
<Button
variant="neutral-primary"
size="sm"
className="flex gap-2 items-center"
prependIcon={<StackedLayersHorizontalIcon className="text-custom-text-200" />}
onClick={() => setSelectDuplicateIssue(true)}
>
<StackedLayersHorizontalIcon className="h-4 w-4 text-custom-text-200" />
<span>Mark as duplicate</span>
</SecondaryButton>
Mark as duplicate
</Button>
</div>
)}
{isAllowed && (issueStatus === 0 || issueStatus === -2) && (
<div className="flex-shrink-0">
<SecondaryButton
<Button
variant="neutral-primary"
size="sm"
className="flex gap-2 items-center"
prependIcon={<CheckCircleIcon className="text-green-500" />}
onClick={() => setAcceptIssueModal(true)}
>
<CheckCircleIcon className="h-4 w-4 text-green-500" />
<span>Accept</span>
</SecondaryButton>
Accept
</Button>
</div>
)}
{isAllowed && issueStatus === -2 && (
<div className="flex-shrink-0">
<SecondaryButton
<Button
variant="neutral-primary"
size="sm"
className="flex gap-2 items-center"
prependIcon={<XCircleIcon className="text-red-500" />}
onClick={() => setDeclineIssueModal(true)}
>
<XCircleIcon className="h-4 w-4 text-red-500" />
<span>Decline</span>
</SecondaryButton>
Decline
</Button>
</div>
)}
{(isAllowed || user?.id === issue?.created_by) && (
<div className="flex-shrink-0">
<SecondaryButton
<Button
variant="neutral-primary"
size="sm"
className="flex gap-2 items-center"
prependIcon={<TrashIcon className="text-red-500" />}
onClick={() => setDeleteIssueModal(true)}
>
<TrashIcon className="h-4 w-4 text-red-500" />
<span>Delete</span>
</SecondaryButton>
Delete
</Button>
</div>
)}
</div>

View File

@ -17,7 +17,7 @@ import useUserAuth from "hooks/use-user-auth";
import { IssueDescriptionForm, IssueDetailsSidebar, IssueReaction } from "components/issues";
import { InboxIssueActivity } from "components/inbox";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// icons
import {
ArrowTopRightOnSquareIcon,
@ -117,16 +117,7 @@ export const InboxMainContent: React.FC = () => {
mutate(PROJECT_ISSUES_ACTIVITY(issueDetails.id));
});
},
[
workspaceSlug,
inboxIssueId,
projectId,
mutateIssueDetails,
inboxId,
user,
issueDetails,
params,
]
[workspaceSlug, inboxIssueId, projectId, mutateIssueDetails, inboxId, user, issueDetails, params]
);
const onKeyDown = useCallback(
@ -178,8 +169,7 @@ export const InboxMainContent: React.FC = () => {
reset({
...issueDetails,
assignees_list:
issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id),
assignees_list: issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id),
labels_list: issueDetails.labels_list ?? issueDetails.labels,
});
}, [issueDetails, reset, inboxIssueId]);
@ -194,13 +184,11 @@ export const InboxMainContent: React.FC = () => {
<InboxIcon height={60} width={60} />
{inboxIssues && inboxIssues.length > 0 ? (
<span className="text-custom-text-200">
{inboxIssues?.length} issues found. Select an issue from the sidebar to view its
details.
{inboxIssues?.length} issues found. Select an issue from the sidebar to view its details.
</span>
) : (
<span className="text-custom-text-200">
No issues found. Use{" "}
<pre className="inline rounded bg-custom-background-80 px-2 py-1">C</pre> shortcut
No issues found. Use <pre className="inline rounded bg-custom-background-80 px-2 py-1">C</pre> shortcut
to create a new issue
</span>
)}
@ -247,18 +235,12 @@ export const InboxMainContent: React.FC = () => {
{new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date() ? (
<p>
This issue was snoozed till{" "}
{renderShortDateWithYearFormat(
issueDetails.issue_inbox[0].snoozed_till ?? ""
)}
.
{renderShortDateWithYearFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
</p>
) : (
<p>
This issue has been snoozed till{" "}
{renderShortDateWithYearFormat(
issueDetails.issue_inbox[0].snoozed_till ?? ""
)}
.
{renderShortDateWithYearFormat(issueDetails.issue_inbox[0].snoozed_till ?? "")}.
</p>
)}
</>
@ -293,17 +275,11 @@ export const InboxMainContent: React.FC = () => {
description_html: issueDetails.description_html,
}}
handleFormSubmit={submitChanges}
isAllowed={
memberRole.isMember || memberRole.isOwner || user?.id === issueDetails.created_by
}
isAllowed={memberRole.isMember || memberRole.isOwner || user?.id === issueDetails.created_by}
/>
</div>
<IssueReaction
projectId={projectId}
workspaceSlug={workspaceSlug}
issueId={issueDetails.id}
/>
<IssueReaction projectId={projectId} workspaceSlug={workspaceSlug} issueId={issueDetails.id} />
<InboxIssueActivity issueDetails={issueDetails} />
</div>

View File

@ -5,7 +5,7 @@ import useInboxView from "hooks/use-inbox-view";
// components
import { InboxIssueCard, InboxFiltersList } from "components/inbox";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
export const IssuesListSidebar = () => {
const router = useRouter();
@ -20,17 +20,12 @@ export const IssuesListSidebar = () => {
inboxIssues.length > 0 ? (
<div className="divide-y divide-custom-border-200 overflow-auto h-full">
{inboxIssues.map((issue) => (
<InboxIssueCard
key={issue.id}
active={issue.bridge_id === inboxIssueId}
issue={issue}
/>
<InboxIssueCard key={issue.id} active={issue.bridge_id === inboxIssueId} issue={issue} />
))}
</div>
) : (
<div className="h-full p-4 grid place-items-center text-center text-sm text-custom-text-200">
{filtersLength > 0 &&
"No issues found for the selected filters. Try changing the filters."}
{filtersLength > 0 && "No issues found for the selected filters. Try changing the filters."}
</div>
)
) : (

View File

@ -13,7 +13,7 @@ import useToast from "hooks/use-toast";
// services
import issuesServices from "services/issue.service";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
import { LayerDiagonalIcon } from "components/icons";
@ -170,8 +170,12 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
{filteredIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<PrimaryButton onClick={handleSubmit}>Mark as original</PrimaryButton>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleSubmit}>
Mark as original
</Button>
</div>
)}
</Dialog.Panel>

View File

@ -11,7 +11,7 @@ import IntegrationService from "services/integration.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { DangerButton, Input, SecondaryButton } from "components/ui";
import { Button, Input } from "@plane/ui";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// types
@ -110,21 +110,30 @@ export const DeleteImportModal: React.FC<Props> = ({ isOpen, handleClose, data,
To confirm, type <span className="font-medium text-custom-text-100">delete import</span> below:
</p>
<Input
id="typeDelete"
type="text"
name="typeDelete"
className="mt-2"
value=""
onChange={(e) => {
if (e.target.value === "delete import") setConfirmDeleteImport(true);
else setConfirmDeleteImport(false);
}}
placeholder="Enter 'delete import'"
className="mt-2 w-full"
/>
</div>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDeletion} disabled={!confirmDeleteImport} loading={deleteLoading}>
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button
variant="danger"
onClick={handleDeletion}
disabled={!confirmDeleteImport}
loading={deleteLoading}
>
{deleteLoading ? "Deleting..." : "Delete Project"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -1,7 +1,7 @@
// hooks
import useIntegrationPopup from "hooks/use-integration-popup";
// ui
import { PrimaryButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import { IWorkspaceIntegration } from "types";
@ -16,11 +16,13 @@ export const GithubAuth: React.FC<Props> = ({ workspaceIntegration, provider })
return (
<div>
{workspaceIntegration && workspaceIntegration?.id ? (
<PrimaryButton disabled>Successfully Connected</PrimaryButton>
<Button variant="primary" disabled>
Successfully Connected
</Button>
) : (
<PrimaryButton onClick={startAuth} loading={isConnecting}>
<Button variant="primary" onClick={startAuth} loading={isConnecting}>
{isConnecting ? "Connecting..." : "Connect"}
</PrimaryButton>
</Button>
)}
</div>
);

View File

@ -1,7 +1,6 @@
// components
import { Button } from "@plane/ui";
import { GithubAuth, TIntegrationSteps } from "components/integration";
// ui
import { PrimaryButton } from "components/ui";
// types
import { IAppIntegration, IWorkspaceIntegration } from "types";
@ -20,9 +19,7 @@ export const GithubImportConfigure: React.FC<Props> = ({
}) => {
// current integration from all the integrations available
const integration =
appIntegrations &&
appIntegrations.length > 0 &&
appIntegrations.find((i) => i.provider === provider);
appIntegrations && appIntegrations.length > 0 && appIntegrations.find((i) => i.provider === provider);
// current integration from workspace integrations
const workspaceIntegration =
@ -44,12 +41,13 @@ export const GithubImportConfigure: React.FC<Props> = ({
</div>
<div className="flex items-center justify-end">
<PrimaryButton
<Button
variant="primary"
onClick={() => handleStepChange("import-data")}
disabled={workspaceIntegration && workspaceIntegration?.id ? false : true}
>
Next
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -3,7 +3,7 @@ import { FC } from "react";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import { TFormValues, TIntegrationSteps } from "components/integration";
@ -15,13 +15,16 @@ type Props = {
export const GithubImportConfirm: FC<Props> = ({ handleStepChange, watch }) => (
<div className="mt-6">
<h4 className="font-medium text-custom-text-200">
You are about to import issues from {watch("github").full_name}. Click on {'"'}Confirm &
Import{'" '}
You are about to import issues from {watch("github").full_name}. Click on {'"'}Confirm & Import{'" '}
to complete the process.
</h4>
<div className="mt-6 flex items-center justify-between">
<SecondaryButton onClick={() => handleStepChange("import-users")}>Back</SecondaryButton>
<PrimaryButton type="submit">Confirm & Import</PrimaryButton>
<Button variant="neutral-primary" onClick={() => handleStepChange("import-users")}>
Back
</Button>
<Button variant="primary" type="submit">
Confirm & Import
</Button>
</div>
</div>
);

View File

@ -7,7 +7,8 @@ import useProjects from "hooks/use-projects";
// components
import { SelectRepository, TFormValues, TIntegrationSteps } from "components/integration";
// ui
import { CustomSearchSelect, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
import { Button, ToggleSwitch } from "@plane/ui";
import { CustomSearchSelect } from "components/ui";
// helpers
import { truncateText } from "helpers/string.helper";
// types
@ -51,11 +52,7 @@ export const GithubImportData: FC<Props> = ({ handleStepChange, integration, con
integration={integration}
value={value ? value.id : null}
label={
value ? (
`${value.full_name}`
) : (
<span className="text-custom-text-200">Select Repository</span>
)
value ? `${value.full_name}` : <span className="text-custom-text-200">Select Repository</span>
}
onChange={onChange}
characterLimit={50}
@ -68,9 +65,7 @@ export const GithubImportData: FC<Props> = ({ handleStepChange, integration, con
<div className="grid grid-cols-12 gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-8">
<h4 className="font-semibold">Select Project</h4>
<p className="text-xs text-custom-text-200">
Select the project to import the issues to.
</p>
<p className="text-xs text-custom-text-200">Select the project to import the issues to.</p>
</div>
<div className="col-span-12 sm:col-span-4">
{projects && (
@ -99,9 +94,7 @@ export const GithubImportData: FC<Props> = ({ handleStepChange, integration, con
<div className="grid grid-cols-12 gap-4 sm:gap-16">
<div className="col-span-12 sm:col-span-8">
<h4 className="font-semibold">Sync Issues</h4>
<p className="text-xs text-custom-text-200">
Set whether you want to sync the issues or not.
</p>
<p className="text-xs text-custom-text-200">Set whether you want to sync the issues or not.</p>
</div>
<div className="col-span-12 sm:col-span-4">
<Controller
@ -115,13 +108,16 @@ export const GithubImportData: FC<Props> = ({ handleStepChange, integration, con
</div>
</div>
<div className="mt-6 flex items-center justify-end gap-2">
<SecondaryButton onClick={() => handleStepChange("import-configure")}>Back</SecondaryButton>
<PrimaryButton
<Button variant="neutral-primary" onClick={() => handleStepChange("import-configure")}>
Back
</Button>
<Button
variant="primary"
onClick={() => handleStepChange("repo-details")}
disabled={!watch("github") || !watch("project")}
>
Next
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -3,14 +3,9 @@ import { FC } from "react";
// react-hook-form
import { UseFormWatch } from "react-hook-form";
// ui
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import {
IUserDetails,
SingleUserSelect,
TFormValues,
TIntegrationSteps,
} from "components/integration";
import { IUserDetails, SingleUserSelect, TFormValues, TIntegrationSteps } from "components/integration";
type Props = {
handleStepChange: (value: TIntegrationSteps) => void;
@ -28,9 +23,7 @@ export const GithubImportUsers: FC<Props> = ({ handleStepChange, users, setUsers
<div className="mb-2 grid grid-cols-3 gap-2 text-sm font-medium">
<div className="text-custom-text-200">Name</div>
<div className="text-custom-text-200">Import as...</div>
<div className="text-right">
{users.filter((u) => u.import !== false).length} users selected
</div>
<div className="text-right">{users.filter((u) => u.import !== false).length} users selected</div>
</div>
<div className="space-y-2">
{watch("collaborators").map((collaborator, index) => (
@ -45,10 +38,12 @@ export const GithubImportUsers: FC<Props> = ({ handleStepChange, users, setUsers
</div>
</div>
<div className="mt-6 flex items-center justify-end gap-2">
<SecondaryButton onClick={() => handleStepChange("repo-details")}>Back</SecondaryButton>
<PrimaryButton onClick={() => handleStepChange("import-confirm")} disabled={isInvalid}>
<Button variant="neutral-primary" onClick={() => handleStepChange("repo-details")}>
Back
</Button>
<Button variant="primary" onClick={() => handleStepChange("import-confirm")} disabled={isInvalid}>
Next
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -9,7 +9,7 @@ import { UseFormSetValue } from "react-hook-form";
// services
import GithubIntegrationService from "services/github.service";
// ui
import { Loader, PrimaryButton, SecondaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
// types
import { IUserDetails, TFormValues, TIntegrationSteps } from "components/integration";
// fetch-keys
@ -85,13 +85,16 @@ export const GithubRepoDetails: FC<Props> = ({ selectedRepo, handleStepChange, s
</Loader>
)}
<div className="mt-6 flex items-center justify-end gap-2">
<SecondaryButton onClick={() => handleStepChange("import-data")}>Back</SecondaryButton>
<PrimaryButton
<Button variant="neutral-primary" onClick={() => handleStepChange("import-data")}>
Back
</Button>
<Button
variant="primary"
onClick={() => handleStepChange("import-users")}
disabled={!repoInfo || repoInfo.issue_count === 0}
>
Next
</PrimaryButton>
</Button>
</div>
</div>
);

View File

@ -5,7 +5,8 @@ import useSWR from "swr";
// services
import workspaceService from "services/workspace.service";
// ui
import { Avatar, CustomSearchSelect, CustomSelect, Input } from "components/ui";
import { Avatar, CustomSearchSelect, CustomSelect } from "components/ui";
import { Input } from "@plane/ui";
// types
import { IGithubRepoCollaborator } from "types";
import { IUserDetails } from "./root";
@ -69,11 +70,7 @@ export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users,
<div>
<CustomSelect
value={users[index].import}
label={
<div className="text-xs">
{importOptions.find((o) => o.key === users[index].import)?.label}
</div>
}
label={<div className="text-xs">{importOptions.find((o) => o.key === users[index].import)?.label}</div>}
onChange={(val: any) => {
const newUsers = [...users];
newUsers[index].import = val;
@ -92,6 +89,7 @@ export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users,
</div>
{users[index].import === "invite" && (
<Input
id="email"
type="email"
name={`userEmail${index}`}
value={users[index].email}
@ -101,7 +99,7 @@ export const SingleUserSelect: React.FC<Props> = ({ collaborator, index, users,
setUsers(newUsers);
}}
placeholder="Enter email of the user"
className="py-1 text-xs"
className="py-1 text-xs w-full"
/>
)}
{users[index].import === "map" && members && (

View File

@ -13,7 +13,7 @@ import IntegrationService from "services/integration.service";
// components
import { DeleteImportModal, GithubImporterRoot, JiraImporterRoot, SingleImport } from "components/integration";
// ui
import { Loader, PrimaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
// icons
import { ArrowPathIcon } from "@heroicons/react/24/outline";
// types
@ -90,9 +90,7 @@ const IntegrationGuide = () => {
<div className="flex-shrink-0">
<Link href={`/${workspaceSlug}/settings/imports?provider=${service.provider}`}>
<a>
<PrimaryButton>
<span className="capitalize">{service.type}</span>
</PrimaryButton>
<Button variant="primary">{service.type}</Button>
</a>
</Link>
</div>

View File

@ -13,8 +13,10 @@ import { PlusIcon } from "@heroicons/react/20/solid";
import useProjects from "hooks/use-projects";
// components
import { Input, CustomSelect } from "components/ui";
import { CustomSelect } from "components/ui";
import { Input } from "@plane/ui";
// types
import { IJiraImporterForm } from "types";
export const JiraGetImportDetail: React.FC = () => {
@ -42,15 +44,24 @@ export const JiraGetImportDetail: React.FC = () => {
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.api_token"
rules={{
required: "Please enter your personal access token.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.api_token"
name="metadata.api_token"
type="text"
value={value}
onChange={onChange}
ref={ref}
placeholder="XXXXXXXX"
validations={{
required: "Please enter your personal access token.",
}}
register={register}
error={errors.metadata?.api_token}
className="w-full"
/>
)}
/>
</div>
</div>
@ -61,15 +72,25 @@ export const JiraGetImportDetail: React.FC = () => {
<p className="text-sm text-custom-text-200">If XXX-123 is your issue, then enter XXX</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.project_key"
rules={{
required: "Please enter your project key.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.project_key"
name="metadata.project_key"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.project_key)}
placeholder="LIN"
register={register}
validations={{
required: "Please enter your project key.",
}}
error={errors.metadata?.project_key}
className="w-full"
/>
)}
/>
</div>
</div>
@ -77,21 +98,28 @@ export const JiraGetImportDetail: React.FC = () => {
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Jira Email Address</h3>
<p className="text-sm text-custom-text-200">
Enter the Gmail account that you use in Jira account
</p>
<p className="text-sm text-custom-text-200">Enter the Gmail account that you use in Jira account</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.email"
rules={{
required: "Please enter email address.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.email"
name="metadata.email"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.email)}
placeholder="name@company.com"
register={register}
validations={{
required: "Please enter email address.",
}}
error={errors.metadata?.email}
className="w-full"
/>
)}
/>
</div>
</div>
@ -102,16 +130,25 @@ export const JiraGetImportDetail: React.FC = () => {
<p className="text-sm text-custom-text-200">Enter your companies cloud host name</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="metadata.cloud_hostname"
rules={{
required: "Please enter your cloud host name.",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="metadata.cloud_hostname"
name="metadata.cloud_hostname"
type="email"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.metadata?.cloud_hostname)}
placeholder="my-company.atlassian.net"
register={register}
validations={{
required: "Please enter your cloud host name.",
}}
error={errors.metadata?.cloud_hostname}
className="w-full"
/>
)}
/>
</div>
</div>
@ -119,9 +156,7 @@ export const JiraGetImportDetail: React.FC = () => {
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Import to project</h3>
<p className="text-sm text-custom-text-200">
Select which project you want to import to.
</p>
<p className="text-sm text-custom-text-200">Select which project you want to import to.</p>
</div>
<div className="col-span-1">
<Controller

View File

@ -16,7 +16,8 @@ import { WORKSPACE_MEMBERS_WITH_EMAIL } from "constants/fetch-keys";
import workspaceService from "services/workspace.service";
// components
import { ToggleSwitch, Input, CustomSelect, CustomSearchSelect, Avatar } from "components/ui";
import { CustomSelect, CustomSearchSelect, Avatar } from "components/ui";
import { Input, ToggleSwitch } from "@plane/ui";
import { IJiraImporterForm } from "types";
@ -38,9 +39,7 @@ export const JiraImportUsers: FC = () => {
const { data: members } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_WITH_EMAIL(workspaceSlug?.toString() ?? "") : null,
workspaceSlug
? () => workspaceService.workspaceMembersWithEmail(workspaceSlug?.toString() ?? "")
: null
workspaceSlug ? () => workspaceService.workspaceMembersWithEmail(workspaceSlug?.toString() ?? "") : null
);
const options = members?.map((member) => ({
@ -59,17 +58,13 @@ export const JiraImportUsers: FC = () => {
<div className="grid grid-cols-1 gap-10 md:grid-cols-2">
<div className="col-span-1">
<h3 className="font-semibold">Users</h3>
<p className="text-sm text-custom-text-200">
Update, invite or choose not to invite assignee
</p>
<p className="text-sm text-custom-text-200">Update, invite or choose not to invite assignee</p>
</div>
<div className="col-span-1">
<Controller
control={control}
name="data.invite_users"
render={({ field: { value, onChange } }) => (
<ToggleSwitch onChange={onChange} value={value} />
)}
render={({ field: { value, onChange } }) => <ToggleSwitch onChange={onChange} value={value} />}
/>
</div>
</div>
@ -97,11 +92,7 @@ export const JiraImportUsers: FC = () => {
value={value}
onChange={onChange}
width="w-full"
label={
<span className="capitalize">
{Boolean(value) ? value : ("Ignore" as any)}
</span>
}
label={<span className="capitalize">{Boolean(value) ? value : ("Ignore" as any)}</span>}
>
<CustomSelect.Option value="invite">Invite by email</CustomSelect.Option>
<CustomSelect.Option value="map">Map to existing</CustomSelect.Option>
@ -112,15 +103,24 @@ export const JiraImportUsers: FC = () => {
</div>
<div className="col-span-1">
{watch(`data.users.${index}.import`) === "invite" && (
<Controller
control={control}
name={`data.users.${index}.email`}
rules={{
required: "This field is required",
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id={`data.users.${index}.email`}
name={`data.users.${index}.email`}
type="text"
register={register}
validations={{
required: "This field is required",
}}
error={errors?.data?.users?.[index]?.email}
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.data?.users?.[index]?.email)}
className="w-full"
/>
)}
/>
)}
{watch(`data.users.${index}.import`) === "map" && (

View File

@ -18,7 +18,7 @@ import { JIRA_IMPORTER_DETAIL } from "constants/fetch-keys";
import { IJiraImporterForm, IJiraMetadata } from "types";
// components
import { Spinner, ToggleSwitch } from "components/ui";
import { ToggleSwitch, Spinner } from "@plane/ui";
import type { IJiraIntegrationData, TJiraIntegrationSteps } from ".";

View File

@ -22,7 +22,7 @@ import jiraImporterService from "services/jira.service";
import { IMPORTER_SERVICES_LIST } from "constants/fetch-keys";
// components
import { PrimaryButton, SecondaryButton } from "components/ui";
import { Button } from "@plane/ui";
import {
JiraGetImportDetail,
JiraProjectDetail,
@ -178,7 +178,8 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
<div className="-mx-4 mt-4 flex justify-end gap-4 border-t border-custom-border-200 p-4 pb-0">
{currentStep?.state !== "import-configure" && (
<SecondaryButton
<Button
variant="neutral-primary"
onClick={() => {
const currentElementIndex = integrationWorkflowData.findIndex(
(i) => i?.key === currentStep?.state
@ -189,9 +190,10 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
}}
>
Back
</SecondaryButton>
</Button>
)}
<PrimaryButton
<Button
variant="primary"
disabled={disableTopBarAfter === currentStep?.state || !isValid || methods.formState.isSubmitting}
onClick={() => {
const currentElementIndex = integrationWorkflowData.findIndex((i) => i?.key === currentStep?.state);
@ -206,7 +208,7 @@ export const JiraImporterRoot: React.FC<Props> = ({ user }) => {
}}
>
{currentStep?.state === "import-confirmation" ? "Confirm & Import" : "Next"}
</PrimaryButton>
</Button>
</div>
</form>
</FormProvider>

View File

@ -11,7 +11,7 @@ import IntegrationService from "services/integration.service";
import useToast from "hooks/use-toast";
import useIntegrationPopup from "hooks/use-integration-popup";
// ui
import { DangerButton, Loader, PrimaryButton } from "components/ui";
import { Button, Loader } from "@plane/ui";
// icons
import GithubLogo from "public/services/github.png";
import SlackLogo from "public/services/slack.png";
@ -113,13 +113,13 @@ export const SingleIntegrationCard: React.FC<Props> = ({ integration }) => {
{workspaceIntegrations ? (
isInstalled ? (
<DangerButton onClick={handleRemoveIntegration} loading={deletingIntegration} outline>
<Button variant="danger" onClick={handleRemoveIntegration} loading={deletingIntegration}>
{deletingIntegration ? "Uninstalling..." : "Uninstall"}
</DangerButton>
</Button>
) : (
<PrimaryButton onClick={startAuth} loading={isInstalling}>
<Button variant="primary" onClick={startAuth} loading={isInstalling}>
{isInstalling ? "Installing..." : "Install"}
</PrimaryButton>
</Button>
)
) : (
<Loader>

View File

@ -6,7 +6,7 @@ import useSWR, { mutate } from "swr";
// services
import appinstallationsService from "services/app_installation.service";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// hooks
import useToast from "hooks/use-toast";
import useIntegrationPopup from "hooks/use-integration-popup";

View File

@ -7,7 +7,8 @@ import { useRouter } from "next/router";
import { ActivityIcon, ActivityMessage } from "components/core";
import { CommentCard } from "components/issues/comment";
// ui
import { Icon, Loader, Tooltip } from "components/ui";
import { Icon, Tooltip } from "components/ui";
import { Loader } from "@plane/ui";
// helpers
import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper";
// types
@ -52,11 +53,7 @@ export const IssueActivitySection: React.FC<Props> = ({
<ul role="list" className="-mb-4">
{activity.map((activityItem, index) => {
// determines what type of action is performed
const message = activityItem.field ? (
<ActivityMessage activity={activityItem} />
) : (
"created the issue."
);
const message = activityItem.field ? <ActivityMessage activity={activityItem} /> : "created the issue.";
if ("field" in activityItem && activityItem.field !== "updated_by") {
return (
@ -79,8 +76,7 @@ export const IssueActivitySection: React.FC<Props> = ({
) : (
<ActivityIcon activity={activityItem} />
)
) : activityItem.actor_detail.avatar &&
activityItem.actor_detail.avatar !== "" ? (
) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? (
<img
src={activityItem.actor_detail.avatar}
alt={activityItem.actor_detail.display_name}
@ -103,13 +99,10 @@ export const IssueActivitySection: React.FC<Props> = ({
</div>
<div className="min-w-0 flex-1 py-3">
<div className="text-xs text-custom-text-200 break-words">
{activityItem.field === "archived_at" &&
activityItem.new_value !== "restore" ? (
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
<span className="text-gray font-medium">Plane</span>
) : activityItem.actor_detail.is_bot ? (
<span className="text-gray font-medium">
{activityItem.actor_detail.first_name} Bot
</span>
<span className="text-gray font-medium">{activityItem.actor_detail.first_name} Bot</span>
) : (
<Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
<a className="text-gray font-medium">
@ -121,13 +114,11 @@ export const IssueActivitySection: React.FC<Props> = ({
)}{" "}
{message}{" "}
<Tooltip
tooltipContent={`${renderLongDateFormat(
tooltipContent={`${renderLongDateFormat(activityItem.created_at)}, ${render24HourFormatTime(
activityItem.created_at
)}, ${render24HourFormatTime(activityItem.created_at)}`}
)}`}
>
<span className="whitespace-nowrap">
{timeAgo(activityItem.created_at)}
</span>
<span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
</Tooltip>
</div>
</div>

View File

@ -11,7 +11,7 @@ import issuesService from "services/issue.service";
// hooks
import useToast from "hooks/use-toast";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// helper
@ -106,15 +106,18 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
</div>
</div>
<div className="flex justify-end gap-2 bg-custom-background-90 p-4 sm:px-6">
<SecondaryButton onClick={handleClose}>Cancel</SecondaryButton>
<DangerButton
<Button variant="neutral-primary" onClick={handleClose}>
Cancel
</Button>
<Button
variant="danger"
onClick={() => {
handleDeletion(data.id);
handleClose();
}}
>
Delete
</DangerButton>
</Button>
</div>
</Dialog.Panel>
</Transition.Child>

View File

@ -5,7 +5,8 @@ import { useForm, Controller } from "react-hook-form";
// components
import { TipTapEditor } from "components/tiptap";
// ui
import { Icon, SecondaryButton, Tooltip } from "components/ui";
import { Icon, Tooltip } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IIssueComment } from "types";
@ -33,11 +34,7 @@ const commentAccess = [
},
];
export const AddComment: React.FC<Props> = ({
disabled = false,
onSubmit,
showAccessSpecifier = false,
}) => {
export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAccessSpecifier = false }) => {
const editorRef = React.useRef<any>(null);
const router = useRouter();
@ -83,9 +80,7 @@ export const AddComment: React.FC<Props> = ({
<Icon
iconName={access.icon}
className={`w-4 h-4 -mt-1 ${
value === access.key
? "!text-custom-text-100"
: "!text-custom-text-400"
value === access.key ? "!text-custom-text-100" : "!text-custom-text-400"
}`}
/>
</button>
@ -112,9 +107,9 @@ export const AddComment: React.FC<Props> = ({
/>
</div>
<SecondaryButton type="submit" disabled={isSubmitting || disabled} className="mt-2">
<Button variant="neutral-primary" type="submit" disabled={isSubmitting || disabled}>
{isSubmitting ? "Adding..." : "Comment"}
</SecondaryButton>
</Button>
</div>
</form>
</div>

View File

@ -3,7 +3,7 @@ import React, { useState } from "react";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// ui
import { SecondaryButton, PrimaryButton } from "components/ui";
import { Button } from "@plane/ui";
type Props = {
isOpen: boolean;
@ -58,29 +58,28 @@ export const ConfirmIssueDiscard: React.FC<Props> = (props) => {
<div className="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-custom-text-100"
>
<Dialog.Title as="h3" className="text-lg font-medium leading-6 text-custom-text-100">
Draft Issue
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-custom-text-200">
Would you like to save this issue in drafts?
</p>
<p className="text-sm text-custom-text-200">Would you like to save this issue in drafts?</p>
</div>
</div>
</div>
</div>
<div className="flex justify-between gap-2 p-4 sm:px-6">
<div>
<SecondaryButton onClick={onDiscard}>Discard</SecondaryButton>
<Button variant="neutral-primary" onClick={onDiscard}>
Discard
</Button>
</div>
<div className="space-x-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<PrimaryButton onClick={handleDeletion} loading={isLoading}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="primary" onClick={handleDeletion} loading={isLoading}>
{isLoading ? "Saving..." : "Save Draft"}
</PrimaryButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -16,7 +16,7 @@ import useToast from "hooks/use-toast";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IIssue } from "types";
// fetch-keys
@ -129,10 +129,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleDeletion} loading={isDeleteLoading}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleDeletion} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -14,7 +14,7 @@ import useToast from "hooks/use-toast";
// icons
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
// ui
import { SecondaryButton, DangerButton } from "components/ui";
import { Button } from "@plane/ui";
// types
import type { IIssue, ICurrentUserResponse, ISubIssueResponse } from "types";
// fetch-keys
@ -182,10 +182,12 @@ export const DeleteIssueModal: React.FC<Props> = ({
</p>
</span>
<div className="flex justify-end gap-2">
<SecondaryButton onClick={onClose}>Cancel</SecondaryButton>
<DangerButton onClick={handleIssueDelete} loading={isDeleteLoading}>
<Button variant="neutral-primary" onClick={onClose}>
Cancel
</Button>
<Button variant="danger" onClick={handleIssueDelete} loading={isDeleteLoading}>
{isDeleteLoading ? "Deleting..." : "Delete Issue"}
</DangerButton>
</Button>
</div>
</div>
</Dialog.Panel>

View File

@ -6,7 +6,7 @@ import { Controller, useForm } from "react-hook-form";
import useReloadConfirmations from "hooks/use-reload-confirmation";
import { useDebouncedCallback } from "use-debounce";
// components
import { TextArea } from "components/ui";
import { TextArea } from "@plane/ui";
import { TipTapEditor } from "components/tiptap";
// types
import { IIssue } from "types";
@ -26,12 +26,7 @@ export interface IssueDetailsProps {
isAllowed: boolean;
}
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
issue,
handleFormSubmit,
workspaceSlug,
isAllowed,
}) => {
export const IssueDescriptionForm: FC<IssueDetailsProps> = ({ issue, handleFormSubmit, workspaceSlug, isAllowed }) => {
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false);
@ -93,32 +88,36 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
<div className="relative">
<div className="relative">
{isAllowed ? (
<Controller
name="name"
control={control}
render={({ field: { value, onChange } }) => (
<TextArea
id="name"
name="name"
value={value}
placeholder="Enter issue name"
register={register}
onFocus={() => setCharacterLimit(true)}
onChange={(e) => {
setCharacterLimit(false);
setIsSubmitting("submitting");
debouncedTitleSave();
onChange(e.target.value);
}}
required={true}
className="min-h-10 block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-xl outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
hasError={Boolean(errors?.description)}
role="textbox"
disabled={!isAllowed}
/>
)}
/>
) : (
<h4 className="break-words text-2xl font-semibold">{issue.name}</h4>
)}
{characterLimit && isAllowed && (
<div className="pointer-events-none absolute bottom-1 right-1 z-[2] rounded bg-custom-background-100 text-custom-text-200 p-0.5 text-xs">
<span
className={`${
watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""
}`}
>
<span className={`${watch("name").length === 0 || watch("name").length > 255 ? "text-red-500" : ""}`}>
{watch("name").length}
</span>
/255
@ -136,9 +135,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
return (
<TipTapEditor
value={
!value ||
value === "" ||
(typeof value === "object" && Object.keys(value).length === 0)
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
? "<p></p>"
: value
}
@ -146,17 +143,13 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = ({
debouncedUpdatesEnabled={true}
setShouldShowAlert={setShowAlert}
setIsSubmitting={setIsSubmitting}
customClassName={
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"
}
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"}
noBorder={!isAllowed}
onChange={(description: Object, description_html: string) => {
setShowAlert(true);
setIsSubmitting("submitting");
onChange(description_html);
handleSubmit(handleDescriptionFormSubmit)().finally(() =>
setIsSubmitting("submitted")
);
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
}}
editable={isAllowed}
/>

View File

@ -24,7 +24,8 @@ import {
import { CreateStateModal } from "components/states";
import { CreateLabelModal } from "components/labels";
// ui
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
import { CustomMenu } from "components/ui";
import { Button, Input, ToggleSwitch } from "@plane/ui";
import { TipTapEditor } from "components/tiptap";
// icons
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
@ -356,21 +357,29 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
<div className="mt-2 space-y-3">
{(fieldsToShow.includes("all") || fieldsToShow.includes("name")) && (
<div>
<Input
id="name"
<Controller
control={control}
name="name"
className="resize-none text-xl"
placeholder="Title"
autoComplete="off"
error={errors.name}
register={register}
validations={{
rules={{
required: "Title is required",
maxLength: {
value: 255,
message: "Title should be less than 255 characters",
},
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
className="resize-none text-xl w-full"
/>
)}
/>
</div>
)}
@ -584,23 +593,27 @@ export const DraftIssueForm: FC<IssueFormProps> = (props) => {
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div>
<div className="flex items-center gap-2">
<SecondaryButton onClick={handleDiscard}>Discard</SecondaryButton>
<SecondaryButton
<Button variant="neutral-primary" onClick={handleDiscard}>
Discard
</Button>
<Button
variant="neutral-primary"
loading={isSubmitting}
onClick={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, data?.id ? "updateDraft" : "createDraft")
)}
>
{isSubmitting ? "Saving..." : "Save Draft"}
</SecondaryButton>
<PrimaryButton
</Button>
<Button
loading={isSubmitting}
variant="primary"
onClick={handleSubmit((formData) =>
handleCreateUpdateIssue(formData, data ? "convertToNewIssue" : "createNewIssue")
)}
>
{isSubmitting ? "Saving..." : "Add Issue"}
</PrimaryButton>
</Button>
</div>
</div>
</form>

View File

@ -23,7 +23,8 @@ import {
import { CreateStateModal } from "components/states";
import { CreateLabelModal } from "components/labels";
// ui
import { CustomMenu, Input, PrimaryButton, SecondaryButton, ToggleSwitch } from "components/ui";
import { CustomMenu } from "components/ui";
import { Button, Input, ToggleSwitch } from "@plane/ui";
import { TipTapEditor } from "components/tiptap";
// icons
import { SparklesIcon, XMarkIcon } from "@heroicons/react/24/outline";
@ -321,21 +322,29 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
<div className="mt-2 space-y-3">
{(fieldsToShow.includes("all") || fieldsToShow.includes("name")) && (
<div>
<Input
id="name"
<Controller
control={control}
name="name"
className="resize-none text-xl"
placeholder="Title"
autoComplete="off"
error={errors.name}
register={register}
validations={{
rules={{
required: "Title is required",
maxLength: {
value: 255,
message: "Title should be less than 255 characters",
},
}}
render={({ field: { value, onChange, ref } }) => (
<Input
id="name"
name="name"
type="text"
value={value}
onChange={onChange}
ref={ref}
hasError={Boolean(errors.name)}
placeholder="Title"
className="resize-none text-xl w-full"
/>
)}
/>
</div>
)}
@ -549,14 +558,15 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div>
<div className="flex items-center gap-2">
<SecondaryButton
<Button
variant="neutral-primary"
onClick={() => {
handleDiscardClose();
}}
>
Discard
</SecondaryButton>
<PrimaryButton type="submit" loading={isSubmitting}>
</Button>
<Button variant="primary" type="submit" loading={isSubmitting}>
{status
? isSubmitting
? "Updating Issue..."
@ -564,7 +574,7 @@ export const IssueForm: FC<IssueFormProps> = (props) => {
: isSubmitting
? "Adding Issue..."
: "Add Issue"}
</PrimaryButton>
</Button>
</div>
</div>
</form>

View File

@ -5,7 +5,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues";
// ui
import { Spinner } from "components/ui";
import { Spinner } from "@plane/ui";
// types
import { ICalendarWeek } from "./types";
import { IIssueGroupedStructure } from "store/issue";

View File

@ -6,7 +6,7 @@ import { observer } from "mobx-react-lite";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
// ui
import { ToggleSwitch } from "components/ui";
import { ToggleSwitch } from "@plane/ui";
// icons
import { Check, ChevronUp } from "lucide-react";
// types

View File

@ -7,7 +7,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
// components
import { AppliedFiltersList } from "components/issues";
// ui
import { PrimaryButton } from "components/ui";
import { Button } from "@plane/ui";
// helpers
import { areFiltersDifferent } from "helpers/filter.helper";
// types
@ -93,9 +93,9 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
projects={workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined}
/>
{storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data.filters ?? {}) && (
<PrimaryButton className="whitespace-nowrap" onClick={handleUpdateView}>
<Button variant="primary" onClick={handleUpdateView}>
Update view
</PrimaryButton>
</Button>
)}
</div>
);

View File

@ -3,7 +3,8 @@ import React, { useState } from "react";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Avatar, Loader } from "components/ui";
import { Avatar } from "components/ui";
import { Loader } from "@plane/ui";
// types
import { IUserLite } from "types";

View File

@ -3,7 +3,8 @@ import React, { useState } from "react";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Avatar, Loader } from "components/ui";
import { Avatar } from "components/ui";
import { Loader } from "@plane/ui";
// types
import { IUserLite } from "types";

View File

@ -3,7 +3,7 @@ import React, { useState } from "react";
// components
import { FilterHeader, FilterOption } from "components/issues";
// ui
import { Loader } from "components/ui";
import { Loader } from "@plane/ui";
// types
import { IIssueLabels } from "types";

Some files were not shown because too many files have changed in this diff Show More