From 95f77173299753192ae0928d7996be2e961c09fc Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:01:56 +0200 Subject: [PATCH] feat: add Alert component and migrate alert banners to it Add a reusable Alert component (@librechat/client) with error/success/warning/ info/neutral variants backed by the status-color tokens, default per-variant icons, and role=alert. Migrate the duplicated colored-div banners to it: Auth ErrorMessage, RequestPasswordReset success, and the identical error boxes in ToolSelectDialog, AssistantToolsDialog, and MCPToolSelectDialog. --- client/src/components/Auth/ErrorMessage.tsx | 11 ++-- .../components/Auth/RequestPasswordReset.tsx | 9 +-- .../components/Tools/AssistantToolsDialog.tsx | 8 +-- packages/client/src/components/Alert.tsx | 56 +++++++++++++++++++ packages/client/src/components/index.ts | 1 + 5 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 packages/client/src/components/Alert.tsx diff --git a/client/src/components/Auth/ErrorMessage.tsx b/client/src/components/Auth/ErrorMessage.tsx index 383ff9a930..562212bda9 100644 --- a/client/src/components/Auth/ErrorMessage.tsx +++ b/client/src/components/Auth/ErrorMessage.tsx @@ -1,9 +1,12 @@ +import { Alert } from '@librechat/client'; + export const ErrorMessage = ({ children }: { children: React.ReactNode }) => ( -
{children} -
+ ); diff --git a/client/src/components/Auth/RequestPasswordReset.tsx b/client/src/components/Auth/RequestPasswordReset.tsx index 10996847fe..d95954cf00 100644 --- a/client/src/components/Auth/RequestPasswordReset.tsx +++ b/client/src/components/Auth/RequestPasswordReset.tsx @@ -1,6 +1,6 @@ import { useForm } from 'react-hook-form'; import { useState, ReactNode } from 'react'; -import { Spinner, Button } from '@librechat/client'; +import { Spinner, Button, Alert } from '@librechat/client'; import { useOutletContext } from 'react-router-dom'; import { useRequestPasswordResetMutation } from 'librechat-data-provider/react-query'; import { loginPage } from 'librechat-data-provider'; @@ -11,12 +11,9 @@ import { useLocalize } from '~/hooks'; const BodyTextWrapper: FC<{ children: ReactNode }> = ({ children }) => { return ( -
+ {children} -
+ ); }; diff --git a/client/src/components/Tools/AssistantToolsDialog.tsx b/client/src/components/Tools/AssistantToolsDialog.tsx index 98e8043ebe..7124afe5af 100644 --- a/client/src/components/Tools/AssistantToolsDialog.tsx +++ b/client/src/components/Tools/AssistantToolsDialog.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; import { Search, X } from 'lucide-react'; +import { Alert } from '@librechat/client'; import { Dialog, DialogPanel, DialogTitle, Description } from '@headlessui/react'; import { useFormContext } from 'react-hook-form'; import { isAgentsEndpoint } from 'librechat-data-provider'; @@ -186,12 +187,9 @@ function AssistantToolsDialog({ {error && ( -
+ {localize('com_nav_plugin_auth_error')} {errorMessage} -
+ )} {showPluginAuthForm && (
diff --git a/packages/client/src/components/Alert.tsx b/packages/client/src/components/Alert.tsx new file mode 100644 index 0000000000..49155d1c23 --- /dev/null +++ b/packages/client/src/components/Alert.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { Info, AlertCircle, AlertTriangle, CheckCircle2 } from 'lucide-react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { cn } from '~/utils'; + +const alertVariants = cva('relative flex gap-3 rounded-xl border px-4 py-3 text-sm', { + variants: { + variant: { + info: 'border-status-info bg-status-info-subtle text-status-info', + success: 'border-status-success bg-status-success-subtle text-status-success', + warning: 'border-status-warning bg-status-warning-subtle text-status-warning', + error: 'border-status-error bg-status-error-subtle text-status-error', + neutral: 'border-status-neutral bg-status-neutral-subtle text-status-neutral', + }, + }, + defaultVariants: { + variant: 'info', + }, +}); + +type AlertVariant = NonNullable['variant']>; + +const defaultIcons: Record> = { + info: Info, + success: CheckCircle2, + warning: AlertTriangle, + error: AlertCircle, + neutral: Info, +}; + +export interface AlertProps + extends React.HTMLAttributes, + VariantProps { + /** Override the default per-variant icon, or pass `false` to hide it. */ + icon?: React.ReactNode | false; +} + +const Alert = React.forwardRef( + ({ className, variant = 'info', icon, role = 'alert', children, ...props }, ref) => { + const resolvedVariant = (variant ?? 'info') as AlertVariant; + const DefaultIcon = defaultIcons[resolvedVariant]; + return ( +
+ {icon !== false && ( + + )} +
{children}
+
+ ); + }, +); +Alert.displayName = 'Alert'; + +export { Alert, alertVariants }; diff --git a/packages/client/src/components/index.ts b/packages/client/src/components/index.ts index e3e66b7559..0080307858 100644 --- a/packages/client/src/components/index.ts +++ b/packages/client/src/components/index.ts @@ -1,5 +1,6 @@ export * from './Accordion'; export * from './AnimatedTabs'; +export * from './Alert'; export * from './AlertDialog'; export * from './Breadcrumb'; export * from './Button';