forked from github/plane
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:
parent
00b40fbde4
commit
35a7d10b8f
@ -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: {
|
||||
|
75
packages/ui/dist/index.d.ts
vendored
75
packages/ui/dist/index.d.ts
vendored
@ -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 };
|
||||
|
284
packages/ui/dist/index.js
vendored
284
packages/ui/dist/index.js
vendored
@ -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
|
||||
});
|
||||
|
275
packages/ui/dist/index.mjs
vendored
275
packages/ui/dist/index.mjs
vendored
@ -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
|
||||
};
|
||||
|
@ -29,5 +29,9 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.7.17",
|
||||
"clsx": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
61
packages/ui/src/button/button.tsx
Normal file
61
packages/ui/src/button/button.tsx
Normal 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 };
|
125
packages/ui/src/button/helper.tsx
Normal file
125
packages/ui/src/button/helper.tsx
Normal 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;
|
||||
};
|
2
packages/ui/src/button/index.tsx
Normal file
2
packages/ui/src/button/index.tsx
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./button";
|
||||
export * from "./toggle-switch";
|
49
packages/ui/src/button/toggle-switch.tsx
Normal file
49
packages/ui/src/button/toggle-switch.tsx
Normal 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 };
|
@ -1,5 +0,0 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const Button = () => {
|
||||
return <button>button</button>;
|
||||
};
|
@ -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}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
export * from "./buttons";
|
||||
export * from "./button";
|
||||
export * from "./form-fields";
|
||||
export * from "./progress";
|
||||
export * from "./spinners";
|
||||
export * from "./loader";
|
||||
|
30
packages/ui/src/loader.tsx
Normal file
30
packages/ui/src/loader.tsx
Normal 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 };
|
@ -1 +1,2 @@
|
||||
export * from "./radial-progress";
|
||||
export * from "./progress-bar";
|
||||
|
77
packages/ui/src/progress/progress-bar.tsx
Normal file
77
packages/ui/src/progress/progress-bar.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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>
|
||||
</>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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 });
|
||||
}}
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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)}>
|
||||
|
@ -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>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
)}
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
)
|
||||
) : (
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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 && (
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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" && (
|
||||
|
@ -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 ".";
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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";
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user