diff --git a/packages/tailwind-config-custom/tailwind.config.js b/packages/tailwind-config-custom/tailwind.config.js index 061168c4f..4e727ecc1 100644 --- a/packages/tailwind-config-custom/tailwind.config.js +++ b/packages/tailwind-config-custom/tailwind.config.js @@ -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: { diff --git a/packages/ui/dist/index.d.ts b/packages/ui/dist/index.d.ts index 48321708f..e3b790024 100644 --- a/packages/ui/dist/index.d.ts +++ b/packages/ui/dist/index.d.ts @@ -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 { + 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>; + +interface IToggleSwitchProps { + value: boolean; + onChange: (value: boolean) => void; + label?: string; + size?: "sm" | "md" | "lg"; + disabled?: boolean; + className?: string; +} +declare const ToggleSwitch: React.FC; + +interface InputProps extends React.InputHTMLAttributes { + mode?: "primary" | "transparent" | "true-transparent"; + inputSize?: "sm" | "md"; + hasError?: boolean; + className?: string; } declare const Input: React.ForwardRefExoticComponent>; -interface TextAreaProps { - id: string; - name: string; - placeholder?: string; - value?: string; - rows?: number; - cols?: number; - disabled?: boolean; - onChange: () => void; +interface TextAreaProps extends React.TextareaHTMLAttributes { mode?: "primary" | "transparent"; hasError?: boolean; className?: string; @@ -38,6 +45,30 @@ interface IRadialProgressBar { } declare const RadialProgressBar: FC; +declare type Props$1 = { + maxValue?: number; + value?: number; + radius?: number; + strokeWidth?: number; + activeStrokeColor?: string; + inactiveStrokeColor?: string; +}; +declare const ProgressBar: React__default.FC; + 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; + displayName: string; +}; +declare type ItemProps = { + height?: string; + width?: string; +}; + +export { Button, ButtonProps, Input, InputProps, Loader, ProgressBar, RadialProgressBar, Spinner, TextArea, TextAreaProps, ToggleSwitch }; diff --git a/packages/ui/dist/index.js b/packages/ui/dist/index.js index 72851d0e7..7ac83b1ae 100644 --- a/packages/ui/dist/index.js +++ b/packages/ui/dist/index.js @@ -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 }); diff --git a/packages/ui/dist/index.mjs b/packages/ui/dist/index.mjs index 97590dbcf..d9112b832 100644 --- a/packages/ui/dist/index.mjs +++ b/packages/ui/dist/index.mjs @@ -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 }; diff --git a/packages/ui/package.json b/packages/ui/package.json index 1f95e468b..9385a26c7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -29,5 +29,9 @@ }, "publishConfig": { "access": "public" + }, + "dependencies": { + "@headlessui/react": "^1.7.17", + "clsx": "^2.0.0" } } diff --git a/packages/ui/src/button/button.tsx b/packages/ui/src/button/button.tsx new file mode 100644 index 000000000..a11ce9e8b --- /dev/null +++ b/packages/ui/src/button/button.tsx @@ -0,0 +1,61 @@ +import * as React from "react"; + +import { getIconStyling, getButtonStyling, TButtonVariant } from "./helper"; + +export interface ButtonProps + extends React.ButtonHTMLAttributes { + variant?: TButtonVariant; + size?: "sm" | "md" | "lg"; + className?: string; + loading?: boolean; + disabled?: boolean; + appendIcon?: any; + prependIcon?: any; + children: React.ReactNode; +} + +const 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 ( + + ); + } +); + +Button.displayName = "plane-ui-button"; + +export { Button }; diff --git a/packages/ui/src/button/helper.tsx b/packages/ui/src/button/helper.tsx new file mode 100644 index 000000000..1eaee5134 --- /dev/null +++ b/packages/ui/src/button/helper.tsx @@ -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; +}; diff --git a/packages/ui/src/button/index.tsx b/packages/ui/src/button/index.tsx new file mode 100644 index 000000000..f1a2d03d4 --- /dev/null +++ b/packages/ui/src/button/index.tsx @@ -0,0 +1,2 @@ +export * from "./button"; +export * from "./toggle-switch"; diff --git a/packages/ui/src/button/toggle-switch.tsx b/packages/ui/src/button/toggle-switch.tsx new file mode 100644 index 000000000..9888dd205 --- /dev/null +++ b/packages/ui/src/button/toggle-switch.tsx @@ -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 = (props) => { + const { value, onChange, label, size = "sm", disabled, className } = props; + + return ( + + {label} + + ); +}; + +ToggleSwitch.displayName = "plane-ui-toggle-switch"; + +export { ToggleSwitch }; diff --git a/packages/ui/src/buttons/index.tsx b/packages/ui/src/buttons/index.tsx deleted file mode 100644 index de1b0da31..000000000 --- a/packages/ui/src/buttons/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import * as React from "react"; - -export const Button = () => { - return ; -}; diff --git a/packages/ui/src/form-fields/input.tsx b/packages/ui/src/form-fields/input.tsx index 7c9389848..648b78aa7 100644 --- a/packages/ui/src/form-fields/input.tsx +++ b/packages/ui/src/form-fields/input.tsx @@ -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 { mode?: "primary" | "transparent" | "true-transparent"; - size?: "sm" | "md" | "lg"; + inputSize?: "sm" | "md"; hasError?: boolean; - placeholder?: string; - disabled?: boolean; + className?: string; } const Input = React.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 ( @@ -34,11 +25,7 @@ const Input = React.forwardRef((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((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} /> ); }); diff --git a/packages/ui/src/form-fields/textarea.tsx b/packages/ui/src/form-fields/textarea.tsx index e9b748230..93a850059 100644 --- a/packages/ui/src/form-fields/textarea.tsx +++ b/packages/ui/src/form-fields/textarea.tsx @@ -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 { mode?: "primary" | "transparent"; hasError?: boolean; className?: string; @@ -37,15 +30,13 @@ const TextArea = React.forwardRef( const { id, name, - placeholder = "", value = "", rows = 1, cols = 1, - disabled, - onChange, mode = "primary", hasError = false, className = "", + ...rest } = props; const textAreaRef = React.useRef(ref); @@ -57,12 +48,9 @@ const TextArea = React.forwardRef( 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( } ${hasError ? "border-red-500" : ""} ${ hasError && mode === "primary" ? "bg-red-100" : "" } ${className}`} + {...rest} /> ); } diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index 1e80ad867..36eb08ef4 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -1,4 +1,5 @@ -export * from "./buttons"; +export * from "./button"; export * from "./form-fields"; export * from "./progress"; export * from "./spinners"; +export * from "./loader"; diff --git a/packages/ui/src/loader.tsx b/packages/ui/src/loader.tsx new file mode 100644 index 000000000..cd12bc22f --- /dev/null +++ b/packages/ui/src/loader.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +type Props = { + children: React.ReactNode; + className?: string; +}; + +const Loader = ({ children, className = "" }: Props) => ( +
+ {children} +
+); + +type ItemProps = { + height?: string; + width?: string; +}; + +const Item: React.FC = ({ height = "auto", width = "auto" }) => ( +
+); + +Loader.Item = Item; + +Loader.displayName = "plane-ui-loader"; + +export { Loader }; diff --git a/packages/ui/src/progress/index.tsx b/packages/ui/src/progress/index.tsx index caee5259d..288a0c1fc 100644 --- a/packages/ui/src/progress/index.tsx +++ b/packages/ui/src/progress/index.tsx @@ -1 +1,2 @@ export * from "./radial-progress"; +export * from "./progress-bar"; diff --git a/packages/ui/src/progress/progress-bar.tsx b/packages/ui/src/progress/progress-bar.tsx new file mode 100644 index 000000000..d241e3d5e --- /dev/null +++ b/packages/ui/src/progress/progress-bar.tsx @@ -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 = ({ + 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 ( + + ); + }; + + // combining the Pies + const renderOuterCircle = () => + [...Array(maxValue + 1)].map((e, i) => renderPie(i)); + + return ( + + {renderOuterCircle()} + + + ); +}; diff --git a/space/components/accounts/email-reset-password-form.tsx b/space/components/accounts/email-reset-password-form.tsx index c850b305c..ee71890ec 100644 --- a/space/components/accounts/email-reset-password-form.tsx +++ b/space/components/accounts/email-reset-password-form.tsx @@ -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>; @@ -77,12 +78,12 @@ export const EmailResetPasswordForm: React.FC = ({ setIsResettingPassword {errors.email &&
{errors.email.message}
}
- setIsResettingPassword(false)}> + +
); diff --git a/web/components/account/email-code-form.tsx b/web/components/account/email-code-form.tsx index 1e68cbb29..1a4b1208d 100644 --- a/web/components/account/email-code-form.tsx +++ b/web/components/account/email-code-form.tsx @@ -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) => { )}
- /^(([^<>()[\]\\.,;:\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} - placeholder="Enter your email address..." - className="border-custom-border-300 h-[46px]" + render={({ field: { value, onChange, ref } }) => ( + + )} />
{codeSent && ( <> - ( + + )} /> ) : ( - { handleSubmit(onSubmit)().then(() => { @@ -207,7 +222,7 @@ export const EmailCodeForm = ({ handleSignIn }: any) => { loading={isSubmitting} > {isSubmitting ? "Sending code..." : "Send sign in code"} - + )}
diff --git a/web/components/account/email-signup-form.tsx b/web/components/account/email-signup-form.tsx index 0a219741f..76c7e523b 100644 --- a/web/components/account/email-signup-form.tsx +++ b/web/components/account/email-signup-form.tsx @@ -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) => { const { register, handleSubmit, + control, watch, formState: { errors, isSubmitting, isValid, isDirty }, } = useForm({ @@ -36,49 +37,60 @@ export const EmailSignUpForm: React.FC = (props) => { return ( <> -
+
- /^(([^<>()[\]\\.,;:\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} - placeholder="Enter your email address..." - className="border-custom-border-300 h-[46px]" + render={({ field: { value, onChange, ref } }) => ( + + )} />
- ( + + )} />
- { if (watch("password") != val) { @@ -86,27 +98,36 @@ export const EmailSignUpForm: React.FC = (props) => { } }, }} - error={errors.confirm_password} - placeholder="Confirm your password..." - className="border-custom-border-300 h-[46px]" + render={({ field: { value, onChange, ref } }) => ( + + )} />
- {isSubmitting ? "Signing up..." : "Sign up"} - +
diff --git a/web/components/analytics/custom-analytics/create-update-analytics-modal.tsx b/web/components/analytics/custom-analytics/create-update-analytics-modal.tsx index 66917ed7c..508f5722e 100644 --- a/web/components/analytics/custom-analytics/create-update-analytics-modal.tsx +++ b/web/components/analytics/custom-analytics/create-update-analytics-modal.tsx @@ -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 = ({ isOpen, handleClos const { setToastAlert } = useToast(); const { - register, formState: { errors, isSubmitting }, handleSubmit, + control, reset, } = useForm({ defaultValues, @@ -114,41 +114,54 @@ export const CreateUpdateAnalyticsModal: React.FC = ({ isOpen, handleClos
- + Save Analytics
- ( + + )} /> -