From ecfec9891d70566f1ec359bd8d80b9d2d77b64e3 Mon Sep 17 00:00:00 2001 From: Amoghavarsha Kudaligi Date: Thu, 16 Oct 2025 12:41:25 +0530 Subject: [PATCH 01/21] Add confirmation dialog. --- package-lock.json | 10 +++ packages/frappe-ui-react/package.json | 3 +- .../components/confirmationDialog/index.tsx | 68 +++++++++++++++++++ .../src/components/dialog/dialog.stories.tsx | 4 +- .../src/utils/mergeClassnames.ts | 6 ++ 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 packages/frappe-ui-react/src/components/confirmationDialog/index.tsx create mode 100644 packages/frappe-ui-react/src/utils/mergeClassnames.ts diff --git a/package-lock.json b/package-lock.json index 5920106d..5b838bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14736,6 +14736,15 @@ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "license": "MIT" }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.14", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz", @@ -16166,6 +16175,7 @@ "react-grid-layout": "^1.5.2", "react-quill-new": "^3.6.0", "styled-components": "^6.1.19", + "tailwind-merge": "^2.4.0", "tailwindcss": "^4.1.11" }, "devDependencies": { diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index c4aac6f5..4d9856dc 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -50,7 +50,8 @@ "react-grid-layout": "^1.5.2", "react-quill-new": "^3.6.0", "styled-components": "^6.1.19", - "tailwindcss": "^4.1.11" + "tailwindcss": "^4.1.11", + "tailwind-merge": "^2.4.0" }, "devDependencies": { "@types/feather-icons": "^4.29.4", diff --git a/packages/frappe-ui-react/src/components/confirmationDialog/index.tsx b/packages/frappe-ui-react/src/components/confirmationDialog/index.tsx new file mode 100644 index 00000000..6a8317d8 --- /dev/null +++ b/packages/frappe-ui-react/src/components/confirmationDialog/index.tsx @@ -0,0 +1,68 @@ +/** + * External dependencies. + */ +import { Description } from "@headlessui/react"; +import { LoaderCircle, Trash2, X } from "lucide-react"; +/** + * External dependencies. + */ +import { Button } from "../button"; +import { Dialog } from "../dialog"; +/** + * The resource delete allocation alert dialog. + * + * Why not use react-alert-dialog? + * The above package was creating issues for form dynamic field selection, also it has bugs in recent versions: https://github.com/shadmergeClassNames-ui/ui/issues/1655 so for now I have used dialog only. + * + * @param props.onDelete The function to be called when delete dialog is clicked. + * @param props.isOpen The state to open the dialog. + * @param props.isLoading The state to show the loader. + * @param props.onOpen The function to open the dialog. + * @param props.onCancel The function to cancel the dialog. + * @returns React.FC + */ +export const DeleteConfirmationDialog = ({ + onDelete, + isOpen, + isLoading, + onCancel, + title, + description, +}: { + onDelete: () => void; + isOpen: boolean; + isLoading: boolean; + onOpen: () => void; + onCancel: () => void; + buttonClassName?: string; + title: string; + description: string; +}) => { + return ( + !open && onCancel()}> +
+
+ +

{title}

+

{description}

+
+
+ +
+ + +
+
+
+ ); +}; \ No newline at end of file diff --git a/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx b/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx index e48ca18c..c62411de 100644 --- a/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx +++ b/packages/frappe-ui-react/src/components/dialog/dialog.stories.tsx @@ -3,8 +3,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import Dialog from "./dialog"; import { Button } from "../button"; import { Dropdown } from "../dropdown"; -import { Autocomplete, AutocompleteOption } from "../autoComplete"; -import { DialogOptions } from "./types"; +import { Autocomplete, type AutocompleteOption } from "../autoComplete"; +import { type DialogOptions } from "./types"; const meta: Meta = { title: "Components/Dialog", diff --git a/packages/frappe-ui-react/src/utils/mergeClassnames.ts b/packages/frappe-ui-react/src/utils/mergeClassnames.ts new file mode 100644 index 00000000..086f326c --- /dev/null +++ b/packages/frappe-ui-react/src/utils/mergeClassnames.ts @@ -0,0 +1,6 @@ +import { twMerge } from "tailwind-merge"; +import { type ClassValue, clsx} from "clsx"; + +export function mergeClassNames(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} \ No newline at end of file From 4afd2521bc46a56dce2f38f17857d8b2900529db Mon Sep 17 00:00:00 2001 From: Amoghavarsha Kudaligi Date: Thu, 16 Oct 2025 12:58:21 +0530 Subject: [PATCH 02/21] Add hover card component --- package-lock.json | 31 +++++++++++++++++++ packages/frappe-ui-react/package.json | 1 + .../src/components/hoverCard/index.tsx | 28 +++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 packages/frappe-ui-react/src/components/hoverCard/index.tsx diff --git a/package-lock.json b/package-lock.json index 5b838bea..9f837c82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3649,6 +3649,36 @@ } } }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", + "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -16156,6 +16186,7 @@ "@popperjs/core": "^2.11.8", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index 4d9856dc..e62e7194 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -31,6 +31,7 @@ "@popperjs/core": "^2.11.8", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-hover-card": "^1.1.1", "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", diff --git a/packages/frappe-ui-react/src/components/hoverCard/index.tsx b/packages/frappe-ui-react/src/components/hoverCard/index.tsx new file mode 100644 index 00000000..ed71766e --- /dev/null +++ b/packages/frappe-ui-react/src/components/hoverCard/index.tsx @@ -0,0 +1,28 @@ +/* + * External dependencies. + */ +import { Root, Trigger, Content, type HoverCardContentProps } from "@radix-ui/react-hover-card"; + +/** + * Internal dependencies. + */ +import { mergeClassNames } from "../../utils/mergeClassnames"; + +const HoverCard = Root; + +const HoverCardTrigger = Trigger; + +const HoverCardContent = ({ className = "", align = "center", sideOffset = 4, ...props }: HoverCardContentProps, ref: React.Ref) => ( + +); + +export { HoverCard, HoverCardTrigger, HoverCardContent }; \ No newline at end of file From c3d6c9afc2b65c21abc9dbc54547951d69213538 Mon Sep 17 00:00:00 2001 From: Amoghavarsha Kudaligi Date: Thu, 16 Oct 2025 13:31:14 +0530 Subject: [PATCH 03/21] Add sheet component. --- package-lock.json | 12 ++ packages/frappe-ui-react/package.json | 1 + .../src/components/sheet/index.tsx | 104 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 packages/frappe-ui-react/src/components/sheet/index.tsx diff --git a/package-lock.json b/package-lock.json index 9f837c82..74534770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7389,6 +7389,17 @@ "dev": true, "license": "MIT" }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -16190,6 +16201,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", + "class-variance-authority": "^0.7.1", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index e62e7194..5b8c5be6 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -35,6 +35,7 @@ "@radix-ui/react-toast": "^1.2.15", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", + "class-variance-authority": "^0.7.1", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/src/components/sheet/index.tsx b/packages/frappe-ui-react/src/components/sheet/index.tsx new file mode 100644 index 00000000..e315fdf0 --- /dev/null +++ b/packages/frappe-ui-react/src/components/sheet/index.tsx @@ -0,0 +1,104 @@ +/** + * External dependencies. + */ +import * as React from "react"; +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { cva } from "class-variance-authority"; +/** + * Internal dependencies. + */ +import { mergeClassNames } from "../../utils/mergeClassnames"; +import { Dialog } from "../dialog"; + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +); + +type SheetProps = { + titleClasses?: string; + descriptionClasses?: string; + headerClasses?: string; + footerClasses?: string; + className?: string; + open: boolean; + onOpenChange: (open: boolean) => void; + showTitle?: boolean; + contentClassName?: string; + contentAlign?: "top" | "bottom" | "left" | "right" | null; + children?: React.ReactNode; +}; +const Sheet = ({ + titleClasses, + descriptionClasses, + headerClasses, + footerClasses, + open, + onOpenChange, + showTitle = false, + contentAlign = "right", + contentClassName, + children, + ...props +}: SheetProps) => { + return ( + + showTitle && ( + + ), + }} + > +
+

+

+ {children} + +
+
+
+ ); +}; + +export default Sheet; From 3988177f54964d2aebc6d7301f934af40d1a69f8 Mon Sep 17 00:00:00 2001 From: Amoghavarsha Kudaligi Date: Thu, 16 Oct 2025 15:47:33 +0530 Subject: [PATCH 04/21] Add comments and typography component --- package-lock.json | 10 + packages/frappe-ui-react/package.json | 1 + .../src/components/comments/commentForm.tsx | 69 +++++ .../src/components/comments/commentInput.tsx | 115 +++++++++ .../src/components/comments/commentItem.tsx | 241 ++++++++++++++++++ .../src/components/comments/commentList.tsx | 88 +++++++ .../src/components/comments/comments.tsx | 99 +++++++ .../src/components/comments/index.ts | 6 + .../src/components/comments/types.ts | 41 +++ .../components/dropdown/dropdown.stories.tsx | 2 +- .../src/components/hoverCard/index.tsx | 2 +- .../src/components/sheet/index.tsx | 2 +- .../src/components/textarea/textarea.tsx | 5 +- .../src/components/textarea/types.ts | 2 + .../src/components/typography/index.tsx | 55 ++++ packages/frappe-ui-react/src/utils/date.ts | 153 +++++++++++ packages/frappe-ui-react/src/utils/index.ts | 2 + 17 files changed, 889 insertions(+), 4 deletions(-) create mode 100644 packages/frappe-ui-react/src/components/comments/commentForm.tsx create mode 100644 packages/frappe-ui-react/src/components/comments/commentInput.tsx create mode 100644 packages/frappe-ui-react/src/components/comments/commentItem.tsx create mode 100644 packages/frappe-ui-react/src/components/comments/commentList.tsx create mode 100644 packages/frappe-ui-react/src/components/comments/comments.tsx create mode 100644 packages/frappe-ui-react/src/components/comments/index.ts create mode 100644 packages/frappe-ui-react/src/components/comments/types.ts create mode 100644 packages/frappe-ui-react/src/components/typography/index.tsx create mode 100644 packages/frappe-ui-react/src/utils/date.ts diff --git a/package-lock.json b/package-lock.json index 74534770..f578d906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7798,6 +7798,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/dayjs": { "version": "1.11.18", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", @@ -16202,6 +16211,7 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", "class-variance-authority": "^0.7.1", + "date-fns": "^3.6.0", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/package.json b/packages/frappe-ui-react/package.json index 5b8c5be6..1d7589aa 100644 --- a/packages/frappe-ui-react/package.json +++ b/packages/frappe-ui-react/package.json @@ -36,6 +36,7 @@ "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/vite": "^4.1.11", "class-variance-authority": "^0.7.1", + "date-fns": "^3.6.0", "dayjs": "^1.11.13", "dompurify": "^3.2.6", "echarts": "^5.6.0", diff --git a/packages/frappe-ui-react/src/components/comments/commentForm.tsx b/packages/frappe-ui-react/src/components/comments/commentForm.tsx new file mode 100644 index 00000000..1ab723f4 --- /dev/null +++ b/packages/frappe-ui-react/src/components/comments/commentForm.tsx @@ -0,0 +1,69 @@ +/** + * External dependencies. + */ +import * as React from "react"; +import { useState } from "react"; +import { Send } from "lucide-react"; +/** + * Internal dependencies. + */ +import type { CommentFormProps } from "./types"; +import { mergeClassNames } from "../../utils"; +import { Spinner } from "../spinner"; +import { Textarea } from "../textarea"; +import { Button } from "../button"; + +const CommentForm = React.forwardRef( + ({ onSubmit, isSubmitting = false, placeholder = "Write a comment...", className, ...props }, ref) => { + const [content, setContent] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (content.trim() && !isSubmitting) { + onSubmit(content.trim()); + setContent(""); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && (e.ctrlKey || e.metaKey) && !isSubmitting) { + e.preventDefault(); + handleSubmit(e); + } + }; + + return ( +
+