From c3660812697f864d1da9355b2b174c13f286846e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adel=20Rodr=C3=ADguez?= Date: Thu, 6 Aug 2026 12:01:22 -0400 Subject: [PATCH 1/2] feat: replace sonner with base-ui toast component --- .../auth/components/forgot-password-form.tsx | 7 +- .../auth/components/reset-password-form.tsx | 6 +- .../components/sign-in-with-password-form.tsx | 4 +- .../sign-in-with-social-buttons.tsx | 6 +- apps/app/src/shared/components/providers.tsx | 2 +- bun.lock | 17 +- packages/ui/package.json | 7 +- packages/ui/src/components/attachment.tsx | 195 +++++++++++ packages/ui/src/components/bubble.tsx | 124 +++++++ packages/ui/src/components/data-table.tsx | 78 +++++ packages/ui/src/components/direction.tsx | 1 + packages/ui/src/components/icon.tsx | 2 + packages/ui/src/components/marker.tsx | 68 ++++ .../ui/src/components/message-scroller.tsx | 130 ++++++++ packages/ui/src/components/message.tsx | 85 +++++ packages/ui/src/components/questionnaire.tsx | 313 ++++++++++++++++++ packages/ui/src/components/sonner.tsx | 38 --- packages/ui/src/components/toast.tsx | 215 ++++++++++++ 18 files changed, 1240 insertions(+), 58 deletions(-) create mode 100644 packages/ui/src/components/attachment.tsx create mode 100644 packages/ui/src/components/bubble.tsx create mode 100644 packages/ui/src/components/data-table.tsx create mode 100644 packages/ui/src/components/direction.tsx create mode 100644 packages/ui/src/components/marker.tsx create mode 100644 packages/ui/src/components/message-scroller.tsx create mode 100644 packages/ui/src/components/message.tsx create mode 100644 packages/ui/src/components/questionnaire.tsx delete mode 100644 packages/ui/src/components/sonner.tsx create mode 100644 packages/ui/src/components/toast.tsx diff --git a/apps/app/src/features/auth/components/forgot-password-form.tsx b/apps/app/src/features/auth/components/forgot-password-form.tsx index eaaf671e..b4032b41 100644 --- a/apps/app/src/features/auth/components/forgot-password-form.tsx +++ b/apps/app/src/features/auth/components/forgot-password-form.tsx @@ -1,6 +1,6 @@ import { FieldGroup } from "@init/ui/components/field" import { useForm } from "@init/ui/components/form" -import { toast } from "@init/ui/components/sonner" +import { toast } from "@init/ui/components/toast" import { useServerFn } from "@tanstack/react-start" import { forgotPassword } from "#features/auth/server/functions.ts" import { ForgotPasswordFormSchema as schema } from "#features/auth/validation.ts" @@ -14,8 +14,9 @@ export default function ForgotPasswordForm() { data: { email: value.email }, }) - toast.success("Password reset link sent to your email", { - position: "bottom-center", + toast.add({ + title: "Password reset link sent to your email", + type: "success", }) form.reset() diff --git a/apps/app/src/features/auth/components/reset-password-form.tsx b/apps/app/src/features/auth/components/reset-password-form.tsx index 2f2ddc9e..1ff9b4b3 100644 --- a/apps/app/src/features/auth/components/reset-password-form.tsx +++ b/apps/app/src/features/auth/components/reset-password-form.tsx @@ -1,6 +1,6 @@ import { FieldGroup } from "@init/ui/components/field" import { useForm } from "@init/ui/components/form" -import { toast } from "@init/ui/components/sonner" +import { toast } from "@init/ui/components/toast" import { useNavigate } from "@tanstack/react-router" import { ResetPasswordFormSchema as schema } from "#features/auth/validation.ts" import { authClient } from "#shared/auth.ts" @@ -17,10 +17,10 @@ export default function ResetPasswordForm({ token }: { token: string }) { }, { onError: ({ error }) => { - toast.error(error.message, { position: "bottom-center" }) + toast.add({ title: error.message, type: "error" }) }, onSuccess: () => { - toast.success("Your password has been reset", { position: "bottom-center" }) + toast.add({ title: "Your password has been reset", type: "success" }) void navigate({ to: "/sign-in" }) }, } diff --git a/apps/app/src/features/auth/components/sign-in-with-password-form.tsx b/apps/app/src/features/auth/components/sign-in-with-password-form.tsx index 63b8105c..8f3fc36b 100644 --- a/apps/app/src/features/auth/components/sign-in-with-password-form.tsx +++ b/apps/app/src/features/auth/components/sign-in-with-password-form.tsx @@ -1,7 +1,7 @@ import { Button } from "@init/ui/components/button" import { FieldGroup } from "@init/ui/components/field" import { useForm } from "@init/ui/components/form" -import { toast } from "@init/ui/components/sonner" +import { toast } from "@init/ui/components/toast" import { Link, useNavigate } from "@tanstack/react-router" import { AUTHENTICATED_PATHNAME } from "#features/auth/constants.ts" import { SignInWithPasswordFormSchema as schema } from "#features/auth/validation.ts" @@ -16,7 +16,7 @@ export default function SignInWithPasswordForm() { { email: value.email, password: value.password }, { onError: (error) => { - toast.error(error.error.message, { position: "bottom-center" }) + toast.add({ title: error.error.message, type: "error" }) }, onSuccess: () => { void navigate({ to: AUTHENTICATED_PATHNAME }) diff --git a/apps/app/src/features/auth/components/sign-in-with-social-buttons.tsx b/apps/app/src/features/auth/components/sign-in-with-social-buttons.tsx index 0d450e71..abc7ced0 100644 --- a/apps/app/src/features/auth/components/sign-in-with-social-buttons.tsx +++ b/apps/app/src/features/auth/components/sign-in-with-social-buttons.tsx @@ -1,6 +1,6 @@ import { Button } from "@init/ui/components/button" import { Icon } from "@init/ui/components/icon" -import { toast } from "@init/ui/components/sonner" +import { toast } from "@init/ui/components/toast" import { cn } from "@init/utils/ui" import { useState } from "react" import { AUTHENTICATED_PATHNAME } from "#features/auth/constants.ts" @@ -21,7 +21,7 @@ export function SignInWithGoogleButton({ className }: { className?: string }) { fetchOptions: { onError() { setLoading(false) - toast.error("Failed to sign in with Google") + toast.add({ title: "Failed to sign in with Google", type: "error" }) }, }, provider: "google", @@ -59,7 +59,7 @@ export function SignInWithGitHubButton({ className }: { className?: string }) { fetchOptions: { onError() { setLoading(false) - toast.error("Failed to sign in with GitHub") + toast.add({ title: "Failed to sign in with GitHub", type: "error" }) }, }, provider: "github", diff --git a/apps/app/src/shared/components/providers.tsx b/apps/app/src/shared/components/providers.tsx index ab5e2e04..2044a2da 100644 --- a/apps/app/src/shared/components/providers.tsx +++ b/apps/app/src/shared/components/providers.tsx @@ -1,7 +1,7 @@ import type { Theme } from "@init/ui/constants" import type { ReactNode } from "react" -import { Toaster } from "@init/ui/components/sonner" import { ThemeProvider } from "@init/ui/components/theme" +import { Toaster } from "@init/ui/components/toast" import { TooltipProvider } from "@init/ui/components/tooltip" export default function Providers({ diff --git a/bun.lock b/bun.lock index bc21f8b9..5349e64b 100644 --- a/bun.lock +++ b/bun.lock @@ -455,7 +455,9 @@ "dependencies": { "@base-ui/react": "1.6.0", "@init/utils": "workspace:*", + "@shadcn/react": "0.3.0", "@tanstack/react-form": "1.27.7", + "@tanstack/react-table": "8.21.3", "class-variance-authority": "0.7.1", "cmdk": "1.1.1", "date-fns": "4.1.0", @@ -465,13 +467,12 @@ "react-icons": "5.5.0", "react-resizable-panels": "4.12.2", "recharts": "3.10.1", - "sonner": "2.0.7", }, "devDependencies": { "@tooling/tsconfig": "workspace:*", "@types/react": "19.2.4", "@types/react-dom": "19.1.9", - "shadcn": "4.15.0", + "shadcn": "4.16.2", "tailwindcss": "4.3.3", "tw-animate-css": "1.4.0", "typescript": "7.0.2", @@ -1844,6 +1845,8 @@ "@sentry/types": ["@sentry/types@10.37.0", "", { "dependencies": { "@sentry/core": "10.37.0" } }, "sha512-umpnUKRC0AAbJrADg6SlFtqN2yzf7NHciCF9lkHau+ax2PIZ/NDmoG4RQujFVflVaVoD60Ly2t+CcPnYIWMPlw=="], + "@shadcn/react": ["@shadcn/react@0.3.0", "", { "peerDependencies": { "@types/react": ">=19", "react": ">=19" }, "optionalPeers": ["@types/react", "react"] }, "sha512-iKN0NuYe850VDHlxEfvppFrpa7CMV3zArTHusXlaSmeFeQhohGbIqclv6WwPTs/44I8EkXQpcuRt6sXEVKkWqQ=="], + "@shikijs/core": ["@shikijs/core@4.4.1", "", { "dependencies": { "@shikijs/primitive": "4.4.1", "@shikijs/types": "4.4.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" } }, "sha512-VeR2CY6Nn9/WbisoYLOQZ7HZOnwTrpBuOw4wExjqLnBCi62BNWynBUO6K2uPIASPFJwAv7cX1fUu+LrPlSstcw=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.4.1", "", { "dependencies": { "@shikijs/types": "4.4.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-6U4lJBh8LTvIkEVqRHv/rr3ruwtO6IweFQt1ME1ntHJMGHS+6N86vfYGO1o8c/DtOCTia2lfhdQBtBrps1sDfQ=="], @@ -1986,6 +1989,8 @@ "@tanstack/react-store": ["@tanstack/react-store@0.8.1", "", { "dependencies": { "@tanstack/store": "0.8.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-XItJt+rG8c5Wn/2L/bnxys85rBpm0BfMbhb4zmPVLXAKY9POrp1xd6IbU4PKoOI+jSEGc3vntPRfLGSgXfE2Ig=="], + "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], + "@tanstack/router-core": ["@tanstack/router-core@1.171.15", "", { "dependencies": { "@tanstack/history": "1.162.0", "cookie-es": "^3.0.0", "seroval": "^1.5.4", "seroval-plugins": "^1.5.4" } }, "sha512-IILCDcLaItMZQ2jEmCABHY1Nhjjn5XUvwpQp3e4Nmu+vfg0BgYFuu/QASz2SwE2ZNbVMrvt8X/wxa+Gg5aErxA=="], "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.168.0", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16" }, "peerDependencies": { "@tanstack/router-core": "^1.170.0", "csstype": "^3.0.10" }, "optionalPeers": ["csstype"] }, "sha512-wQoQhlBK7nlZgqzaqdYXKWNTpdHdsaREdaPhFZVH0/Ador+F+eM3/NF2i3f2LPeS0GgKraZUQXe1Q/1+KHyEYg=="], @@ -2010,6 +2015,8 @@ "@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.162.0", "", {}, "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA=="], "@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], @@ -4168,7 +4175,7 @@ "sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="], - "shadcn": ["shadcn@4.15.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-fFTpfOuRwqjpXGp/sKpAxJkjdgv1jf8bDrW1xi0cVn2k7WJ5ijV/gjkAlkAidGDjhEkgcUuxVdm4oRB9r8BivA=="], + "shadcn": ["shadcn@4.16.2", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/plugin-transform-typescript": "^7.28.0", "@babel/preset-typescript": "^7.27.1", "@dotenvx/dotenvx": "^1.48.4", "@modelcontextprotocol/sdk": "^1.26.0", "@types/validate-npm-package-name": "^4.0.2", "browserslist": "^4.26.2", "commander": "^14.0.0", "cosmiconfig": "^9.0.0", "dedent": "^1.6.0", "deepmerge": "^4.3.1", "diff": "^8.0.2", "execa": "^9.6.0", "fast-glob": "^3.3.3", "fs-extra": "^11.3.1", "fuzzysort": "^3.1.0", "kleur": "^4.1.5", "open": "^11.0.0", "ora": "^8.2.0", "postcss": "^8.5.6", "postcss-selector-parser": "^7.1.0", "prompts": "^2.4.2", "recast": "^0.23.11", "stringify-object": "^5.0.0", "tailwind-merge": "^3.0.1", "ts-morph": "^26.0.0", "tsconfig-paths": "^4.2.0", "undici": "^7.27.2", "validate-npm-package-name": "^7.0.1", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "shadcn": "dist/index.js" } }, "sha512-M1AvZKFWcCzWRDoyApIqJMSLIpY8Ev4uBGuiPLSFmiTbixXhPmzotSTvLzFmBrfoIxG9aIg2dZOETblEaXGUnQ=="], "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], @@ -4236,8 +4243,6 @@ "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], - "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], - "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -5350,6 +5355,8 @@ "rolldown/@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], + "shadcn/@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + "shadcn/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], diff --git a/packages/ui/package.json b/packages/ui/package.json index df092f67..c286048c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -22,7 +22,9 @@ "dependencies": { "@base-ui/react": "1.6.0", "@init/utils": "workspace:*", + "@shadcn/react": "0.3.0", "@tanstack/react-form": "1.27.7", + "@tanstack/react-table": "8.21.3", "class-variance-authority": "0.7.1", "cmdk": "1.1.1", "date-fns": "4.1.0", @@ -31,14 +33,13 @@ "react-day-picker": "^10.0.1", "react-icons": "5.5.0", "react-resizable-panels": "4.12.2", - "recharts": "3.10.1", - "sonner": "2.0.7" + "recharts": "3.10.1" }, "devDependencies": { "@tooling/tsconfig": "workspace:*", "@types/react": "19.2.4", "@types/react-dom": "19.1.9", - "shadcn": "4.15.0", + "shadcn": "4.16.2", "tailwindcss": "4.3.3", "tw-animate-css": "1.4.0", "typescript": "7.0.2" diff --git a/packages/ui/src/components/attachment.tsx b/packages/ui/src/components/attachment.tsx new file mode 100644 index 00000000..2c8a95ff --- /dev/null +++ b/packages/ui/src/components/attachment.tsx @@ -0,0 +1,195 @@ +import { mergeProps } from "@base-ui/react/merge-props" +import { useRender } from "@base-ui/react/use-render" +import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react" + +import { Button } from "#components/button.tsx" +import { cn } from "#utils" + +const attachmentVariants = cva( + "group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed", + { + variants: { + size: { + default: + "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2", + sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5", + xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1", + }, + orientation: { + horizontal: "min-w-40 items-center", + vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30", + }, + }, + } +) + +function Attachment({ + className, + state = "done", + size = "default", + orientation = "horizontal", + ...props +}: React.ComponentProps<"div"> & + VariantProps & { + state?: "idle" | "uploading" | "processing" | "error" | "done" + }) { + return ( +
+ ) +} + +const attachmentMediaVariants = cva( + "relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5", + { + variants: { + variant: { + icon: "", + image: + "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover", + }, + }, + defaultVariants: { + variant: "icon", + }, + } +) + +function AttachmentMedia({ + className, + variant = "icon", + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AttachmentContent({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AttachmentTitle({ className, ...props }: React.ComponentProps<"span">) { + return ( + + ) +} + +function AttachmentDescription({ className, ...props }: React.ComponentProps<"span">) { + return ( + + ) +} + +function AttachmentActions({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AttachmentAction({ + className, + variant, + size = "icon-xs", + ...props +}: React.ComponentProps) { + return ( +