Skip to main content
Glama

Convex MCP server

Official
by get-convex
Button.tsx8.45 kB
import React, { forwardRef, useContext } from "react"; import { tv } from "tailwind-variants"; import { Tooltip, TooltipSide } from "@ui/Tooltip"; import { UrlObject } from "url"; import { UIContext } from "@ui/UIContext"; import { Spinner } from "@ui/Spinner"; import classNames from "classnames"; export type ButtonProps = { id?: string; children?: React.ReactNode; variant?: "primary" | "danger" | "neutral" | "unstyled"; inline?: boolean; size?: "xs" | "sm" | "md" | "lg"; focused?: boolean; icon?: React.ReactNode; className?: string; disabled?: boolean; tip?: React.ReactNode; tipSide?: TooltipSide; tipDisableHoverableContent?: boolean; loading?: boolean; } & Pick< React.HTMLProps<HTMLElement>, | "tabIndex" | "role" | "aria-label" | "style" | "title" | "onMouseOver" | "onKeyDown" > & ( | { href?: never; onClick?: React.ButtonHTMLAttributes<HTMLButtonElement>["onClick"]; onClickOfAnchorLink?: never; type?: React.ButtonHTMLAttributes<HTMLButtonElement>["type"]; target?: never; } | { href: React.AnchorHTMLAttributes<HTMLAnchorElement>["href"] | UrlObject; onClick?: never; // In most cases you shouldn’t use this. This is only useful when you // need the event sent before the native link behavior is handled. onClickOfAnchorLink?: React.AnchorHTMLAttributes<HTMLAnchorElement>["onClick"]; download?: boolean; type?: never; target?: React.AnchorHTMLAttributes<HTMLAnchorElement>["target"]; } ); export const Button = forwardRef<HTMLElement, ButtonProps>(function Button( { id, children, inline = false, variant = "primary", className = "", size = "sm", disabled = false, focused = false, icon, tip, tipSide, tipDisableHoverableContent, loading = false, ...props }, ref, ) { const Link = useContext(UIContext); const { href, onClick, target, type, onClickOfAnchorLink, ...htmlProps } = "href" in props ? { ...props, type: undefined } : { ...props, href: undefined, target: undefined }; const buttonClassName = variant === "unstyled" ? className : buttonClasses({ inline, icon, variant, disabled, focused, className, size, loading, }); if (href !== undefined && !disabled) { return ( <Tooltip tip={tip} side={tipSide} disableHoverableContent={tipDisableHoverableContent} wrapsButton > <Link passHref href={href} // There is something weird here with `forwardRef`, I’d expect this to work without `any` ref={ref as any} role="link" rel="noopener noreferrer" className={buttonClassName} tabIndex={0} target={target} onClick={onClickOfAnchorLink} {...htmlProps} > {icon && <div>{icon}</div>} {children} </Link> </Tooltip> ); } return ( <Tooltip tip={tip} side={tipSide} disableHoverableContent={tipDisableHoverableContent} wrapsButton > {/* we're allowed to use button here. It's the Button component */} {/* eslint-disable-next-line react/forbid-elements */} <button id={id} // eslint-disable-next-line react/button-has-type type={type ?? "button"} tabIndex={0} onClick={onClick} style={{ "--final-opacity": disabled ? 0.5 : 1 } as React.CSSProperties} className={buttonClassName} disabled={disabled || loading} // There is something weird here with `forwardRef`, I’d expect this to work without `any` ref={ref as any} {...htmlProps} > {/* This needs to be wrapped in a dom element to fix an issue with the google translate extension throwing errors when the icon switches between different icons. The negative margin is added when the icon doesn't exist to not render the flex gap. https://github.com/facebook/react/issues/11538#issuecomment-390386520 */} {icon && <div>{icon}</div>} {children} {loading && ( <div className={classNames( "transition-none absolute left-1/2 -translate-x-1/2", )} > <span className="sr-only">(Loading...)</span> <Spinner className={ variant === "primary" ? "text-white" : variant === "danger" ? "text-content-error" : undefined } /> </div> )} </button> </Tooltip> ); }); const button = tv({ base: "animate-fadeInToVar relative inline-flex items-center rounded-md text-sm font-medium whitespace-nowrap transition-colors select-none focus-visible:border focus-visible:border-border-selected focus-visible:outline-hidden", variants: { variant: { primary: "border-white/30 bg-util-accent text-white", neutral: "text-content-primary", danger: "text-content-error", }, icon: { true: "gap-1.5", false: "gap-2.5", }, accent: { none: "border", inline: "", }, disabled: { true: "cursor-not-allowed opacity-50 disabled:cursor-not-allowed disabled:opacity-50", false: "cursor-pointer", }, loading: { true: "cursor-not-allowed text-transparent", false: "", }, focused: { true: "", false: "", }, size: { xs: "p-1", sm: "p-1.5", md: "px-4 py-3", lg: "rounded-lg px-6 py-[1.125rem]", }, }, compoundVariants: [ { variant: "primary", accent: "inline", class: "bg-transparent text-content-accent hover:bg-background-tertiary", loading: false, }, { variant: "primary", accent: "inline", class: "hover:bg-transparent", disabled: true, }, { variant: "primary", focused: true, accent: "none", class: "bg-util-accent/80 text-white", }, { variant: "primary", focused: true, accent: "inline", class: "bg-background-tertiary", }, { variant: "neutral", accent: "none", class: "bg-background-tertiary", }, { variant: "neutral", accent: "none", class: "border bg-background-secondary", }, { variant: "neutral", class: "hover:bg-background-tertiary", disabled: false, loading: false, }, { variant: "neutral", focused: true, accent: ["none", "inline"], class: "bg-background-primary", }, { variant: "neutral", focused: true, accent: "none", class: "border border-border-selected bg-background-secondary", }, { variant: "danger", accent: "none", class: "border-content-error/30 bg-background-error", }, { variant: "danger", focused: true, class: "bg-background-errorSecondary text-content-error", }, { variant: "primary", disabled: false, accent: "none", class: "hover:bg-util-accent/80", loading: false, }, { variant: "danger", disabled: false, class: "hover:bg-background-errorSecondary", loading: false, }, { variant: "neutral", disabled: false, class: "hover:bg-background-primary", loading: false, }, { variant: "danger", disabled: false, class: "hover:bg-background-errorSecondary", loading: false, }, ], defaultVariants: { size: "sm", variant: "primary", focused: false, disabled: false, accent: "none", icon: false, }, }); export function buttonClasses({ variant, disabled, inline, icon, focused, className, size, loading, }: Pick< ButtonProps, | "variant" | "disabled" | "focused" | "className" | "size" | "inline" | "icon" | "loading" >) { return variant === "unstyled" ? className : button({ accent: inline ? "inline" : "none", icon: !!icon, variant, disabled, focused, className, size, loading, }); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/get-convex/convex-backend'

If you have feedback or need assistance with the MCP directory API, please join our Discord server