diff --git a/packages/shadcn/components.json b/packages/shadcn/components.json index eafc961dca..6b23cf1dd6 100644 --- a/packages/shadcn/components.json +++ b/packages/shadcn/components.json @@ -1,8 +1,9 @@ { "$schema": "https://ui.shadcn.com/schema/index.json", - "style": "default", + "style": "base-vega", "rsc": false, "tsx": true, + "iconLibrary": "lucide", "tailwind": { "css": "src/style.css", "baseColor": "slate", diff --git a/packages/shadcn/package.json b/packages/shadcn/package.json index 25e4f70ce1..a1ba5830b7 100644 --- a/packages/shadcn/package.json +++ b/packages/shadcn/package.json @@ -55,25 +55,15 @@ "clean": "rimraf dist && rimraf types" }, "dependencies": { + "@base-ui/react": "^1.6.0", "@blocknote/core": "workspace:^", "@blocknote/react": "workspace:^", - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-toggle": "^1.1.10", - "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.525.0", - "react-hook-form": "^7.65.0", "tailwind-merge": "^2.6.0" }, "devDependencies": { - "@radix-ui/colors": "^3.0.0", "@types/node": "^20.19.22", "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", diff --git a/packages/shadcn/src/ShadCNComponentsContext.tsx b/packages/shadcn/src/ShadCNComponentsContext.tsx index 29a4ea0673..d7a891b12d 100644 --- a/packages/shadcn/src/ShadCNComponentsContext.tsx +++ b/packages/shadcn/src/ShadCNComponentsContext.tsx @@ -13,6 +13,7 @@ import { DropdownMenu as ShadCNDropdownMenu, DropdownMenuCheckboxItem as ShadCNDropdownMenuCheckboxItem, DropdownMenuContent as ShadCNDropdownMenuContent, + DropdownMenuGroup as ShadCNDropdownMenuGroup, DropdownMenuItem as ShadCNDropdownMenuItem, DropdownMenuLabel as ShadCNDropdownMenuLabel, DropdownMenuSeparator as ShadCNDropdownMenuSeparator, @@ -21,7 +22,6 @@ import { DropdownMenuSubTrigger as ShadCNDropdownMenuSubTrigger, DropdownMenuTrigger as ShadCNDropdownMenuTrigger, } from "./components/ui/dropdown-menu.js"; -import { Form as ShadCNForm } from "./components/ui/form.js"; import { Input as ShadCNInput } from "./components/ui/input.js"; import { Label as ShadCNLabel } from "./components/ui/label.js"; import { @@ -73,6 +73,7 @@ export const ShadCNDefaultComponents = { DropdownMenu: ShadCNDropdownMenu, DropdownMenuCheckboxItem: ShadCNDropdownMenuCheckboxItem, DropdownMenuContent: ShadCNDropdownMenuContent, + DropdownMenuGroup: ShadCNDropdownMenuGroup, DropdownMenuItem: ShadCNDropdownMenuItem, DropdownMenuLabel: ShadCNDropdownMenuLabel, DropdownMenuSeparator: ShadCNDropdownMenuSeparator, @@ -81,9 +82,6 @@ export const ShadCNDefaultComponents = { DropdownMenuSubTrigger: ShadCNDropdownMenuSubTrigger, DropdownMenuTrigger: ShadCNDropdownMenuTrigger, }, - Form: { - Form: ShadCNForm, - }, Input: { Input: ShadCNInput, }, diff --git a/packages/shadcn/src/badge/Badge.tsx b/packages/shadcn/src/badge/Badge.tsx index 74c1787953..a6417a8b27 100644 --- a/packages/shadcn/src/badge/Badge.tsx +++ b/packages/shadcn/src/badge/Badge.tsx @@ -1,5 +1,5 @@ import { assertEmpty } from "@blocknote/core"; -import { ComponentProps } from "@blocknote/react"; +import { ComponentProps, useBlockNoteEditor } from "@blocknote/react"; import { forwardRef } from "react"; import { cn } from "../lib/utils.js"; @@ -25,6 +25,10 @@ export const Badge = forwardRef< const ShadCNComponents = useShadCNComponentsContext()!; + // Portal the tooltip into the editor's portal element so it inherits the + // editor's light/dark color scheme instead of the document body's. + const editor = useBlockNoteEditor(); + const badge = ( - - {badge} - + {mainTooltip} @@ -71,7 +74,7 @@ export const BadgeGroup = forwardRef< const ShadCNComponents = useShadCNComponentsContext()!; return ( - +
) { +}: AvatarPrimitive.Root.Props & { + size?: "default" | "sm" | "lg"; +}) { return ( ) { +function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) { return ( ); @@ -37,12 +41,57 @@ function AvatarImage({ function AvatarFallback({ className, ...props -}: React.ComponentProps) { +}: AvatarPrimitive.Fallback.Props) { return ( + ); +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className, + )} + {...props} + /> + ); +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3", className, )} {...props} @@ -50,4 +99,11 @@ function AvatarFallback({ ); } -export { Avatar, AvatarImage, AvatarFallback }; +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +}; diff --git a/packages/shadcn/src/components/ui/badge.tsx b/packages/shadcn/src/components/ui/badge.tsx index b1c2ab1b85..729a19c36b 100644 --- a/packages/shadcn/src/components/ui/badge.tsx +++ b/packages/shadcn/src/components/ui/badge.tsx @@ -1,22 +1,26 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; import { cva, type VariantProps } from "class-variance-authority"; +// Manual change from the shadcn CLI output: relative import instead of the +// CLI's "@/lib/utils" alias (not resolvable when consumed from source). import { cn } from "../../lib/utils"; const badgeVariants = cva( - "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + "group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!", { variants: { variant: { - default: - "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", secondary: - "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: - "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20", outline: - "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + ghost: + "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", + link: "text-primary underline-offset-4 hover:underline", }, }, defaultVariants: { @@ -27,20 +31,24 @@ const badgeVariants = cva( function Badge({ className, - variant, - asChild = false, + variant = "default", + render, ...props -}: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span"; - - return ( - - ); +}: useRender.ComponentProps<"span"> & VariantProps) { + return useRender({ + defaultTagName: "span", + props: mergeProps<"span">( + { + className: cn(badgeVariants({ variant }), className), + }, + props, + ), + render, + state: { + slot: "badge", + variant, + }, + }); } export { Badge, badgeVariants }; diff --git a/packages/shadcn/src/components/ui/button.tsx b/packages/shadcn/src/components/ui/button.tsx index 43ab1151fc..52026264b9 100644 --- a/packages/shadcn/src/components/ui/button.tsx +++ b/packages/shadcn/src/components/ui/button.tsx @@ -1,31 +1,38 @@ -import * as React from "react"; -import { Slot } from "@radix-ui/react-slot"; +import { Button as ButtonPrimitive } from "@base-ui/react/button"; import { cva, type VariantProps } from "class-variance-authority"; +// Manual change from the shadcn CLI output: relative import instead of the +// CLI's "@/lib/utils" alias (not resolvable when consumed from source). import { cn } from "../../lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*=size-])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", { variants: { variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", - destructive: - "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + default: "bg-primary text-primary-foreground hover:bg-primary/80", outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", + "border-border bg-background shadow-xs hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", secondary: - "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", + "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground", ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50", + destructive: + "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40", link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + default: + "h-9 gap-1.5 px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", + xs: "h-6 gap-1 rounded-[min(var(--radius-md),8px)] px-2 text-xs in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1 rounded-[min(var(--radius-md),10px)] px-2.5 in-data-[slot=button-group]:rounded-md has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5", + lg: "h-10 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2", icon: "size-9", + "icon-xs": + "size-6 rounded-[min(var(--radius-md),8px)] in-data-[slot=button-group]:rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": + "size-8 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-md", + "icon-lg": "size-10", }, }, defaultVariants: { @@ -37,18 +44,12 @@ const buttonVariants = cva( function Button({ className, - variant, - size, - asChild = false, + variant = "default", + size = "default", ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean; - }) { - const Comp = asChild ? Slot : "button"; - +}: ButtonPrimitive.Props & VariantProps) { return ( - ) { +function Card({ + className, + size = "default", + ...props +}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { return (
img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className, )} {...props} @@ -20,7 +27,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
) { return (
); @@ -42,7 +52,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) { return (
); @@ -65,7 +75,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) { return (
); @@ -75,7 +85,10 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) { return (
); diff --git a/packages/shadcn/src/components/ui/dropdown-menu.tsx b/packages/shadcn/src/components/ui/dropdown-menu.tsx index 568aad2a78..a767aceca8 100644 --- a/packages/shadcn/src/components/ui/dropdown-menu.tsx +++ b/packages/shadcn/src/components/ui/dropdown-menu.tsx @@ -1,47 +1,85 @@ "use client"; import * as React from "react"; -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; -import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"; +import { Menu as MenuPrimitive } from "@base-ui/react/menu"; +// Manual change from the shadcn CLI output: relative import instead of the +// CLI's "@/lib/utils" alias (not resolvable when consumed from source). import { cn } from "../../lib/utils"; +import { ChevronRightIcon, CheckIcon } from "lucide-react"; -function DropdownMenu({ - ...props -}: React.ComponentProps) { - return ; +function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { + return ; } -function DropdownMenuPortal({ - ...props -}: React.ComponentProps) { - return ( - - ); +function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) { + return ; } -function DropdownMenuTrigger({ +function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) { + return ; +} + +// Manual changes from the shadcn CLI output: +// - Added a `container` prop (forwarded to the Base UI Portal) so BlockNote can +// portal the menu into the editor's portal element, which carries the +// light/dark color-scheme class. +// - Removed the CLI's `w-(--anchor-width)` from the popup className so menus +// size to their content rather than the (icon-button) trigger width, which +// otherwise clamped them to `min-w-32` and wrapped long items. +function DropdownMenuContent({ + align = "start", + alignOffset = 0, + side = "bottom", + sideOffset = 4, + className, + container, ...props -}: React.ComponentProps) { +}: MenuPrimitive.Popup.Props & + Pick< + MenuPrimitive.Positioner.Props, + "align" | "alignOffset" | "side" | "sideOffset" + > & + Pick) { return ( - + + + + + ); } -function DropdownMenuContent({ +function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) { + return ; +} + +function DropdownMenuLabel({ className, - sideOffset = 4, + inset, ...props -}: React.ComponentProps) { +}: MenuPrimitive.GroupLabel.Props & { + inset?: boolean; +}) { return ( - ) { - return ( - - ); -} - function DropdownMenuItem({ className, inset, variant = "default", ...props -}: React.ComponentProps & { +}: MenuPrimitive.Item.Props & { inset?: boolean; variant?: "default" | "destructive"; }) { return ( - ; +} + +function DropdownMenuSubTrigger({ className, + inset, children, - checked, ...props -}: React.ComponentProps) { +}: MenuPrimitive.SubmenuTrigger.Props & { + inset?: boolean; +}) { return ( - - - - - - {children} - + + ); } -function DropdownMenuRadioGroup({ +function DropdownMenuSubContent({ + align = "start", + alignOffset = -3, + side = "right", + sideOffset = 0, + className, ...props -}: React.ComponentProps) { +}: React.ComponentProps) { return ( - ); } -function DropdownMenuRadioItem({ +function DropdownMenuCheckboxItem({ className, children, + checked, + inset, ...props -}: React.ComponentProps) { +}: MenuPrimitive.CheckboxItem.Props & { + inset?: boolean; +}) { return ( - - - - - + + + + {children} - + ); } -function DropdownMenuLabel({ +function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) { + return ( + + ); +} + +function DropdownMenuRadioItem({ className, + children, inset, ...props -}: React.ComponentProps & { +}: MenuPrimitive.RadioItem.Props & { inset?: boolean; }) { return ( - + > + + + + + + {children} + ); } function DropdownMenuSeparator({ className, ...props -}: React.ComponentProps) { +}: MenuPrimitive.Separator.Props) { return ( - ); @@ -182,53 +256,7 @@ function DropdownMenuShortcut({ - ); -} - -function DropdownMenuSub({ - ...props -}: React.ComponentProps) { - return ; -} - -function DropdownMenuSubTrigger({ - className, - inset, - children, - ...props -}: React.ComponentProps & { - inset?: boolean; -}) { - return ( - - {children} - - - ); -} - -function DropdownMenuSubContent({ - className, - ...props -}: React.ComponentProps) { - return ( - = FieldPath, -> = { - name: TName; -}; - -const FormFieldContext = React.createContext( - {} as FormFieldContextValue, -); - -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { - return ( - - - - ); -}; - -const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext); - const itemContext = React.useContext(FormItemContext); - const { getFieldState } = useFormContext(); - const formState = useFormState({ name: fieldContext.name }); - const fieldState = getFieldState(fieldContext.name, formState); - - if (!fieldContext) { - throw new Error("useFormField should be used within "); - } - - const { id } = itemContext; - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, - }; -}; - -type FormItemContextValue = { - id: string; -}; - -const FormItemContext = React.createContext( - {} as FormItemContextValue, -); - -function FormItem({ className, ...props }: React.ComponentProps<"div">) { - const id = React.useId(); - - return ( - -
- - ); -} - -function FormLabel({ - className, - ...props -}: React.ComponentProps) { - const { error, formItemId } = useFormField(); - - return ( -