feat: add core UI components and baseline tests

This commit is contained in:
2026-03-19 16:56:27 +08:00
parent 12642e0a92
commit 063179933c
73 changed files with 5756 additions and 2 deletions
@@ -0,0 +1,231 @@
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import {
forwardRef,
type ComponentPropsWithoutRef,
type ElementRef,
type HTMLAttributes
} from "react";
import {
dropdownMenuContentVariants,
dropdownMenuItemVariants,
dropdownMenuLabelVariants,
dropdownMenuSeparatorVariants
} from "./dropdown-menu.variants";
import { cn } from "../lib/cn";
import type { VariantProps } from "../lib/cva";
import { createDataAttributes, createSlot } from "../lib/contracts";
export const DropdownMenu = DropdownMenuPrimitive.Root;
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
export const DropdownMenuSub = DropdownMenuPrimitive.Sub;
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
export type DropdownMenuContentProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content> &
VariantProps<typeof dropdownMenuContentVariants>;
export const DropdownMenuContent = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.Content>,
DropdownMenuContentProps
>(function DropdownMenuContent(
{ className, sideOffset = 8, size, ...props },
ref
) {
return (
<DropdownMenuPortal>
<DropdownMenuPrimitive.Content
{...props}
{...createSlot("content")}
{...createDataAttributes({ size })}
className={cn(dropdownMenuContentVariants({ size }), className)}
ref={ref}
sideOffset={sideOffset}
/>
</DropdownMenuPortal>
);
});
export type DropdownMenuSubContentProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent> &
VariantProps<typeof dropdownMenuContentVariants>;
export const DropdownMenuSubContent = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.SubContent>,
DropdownMenuSubContentProps
>(function DropdownMenuSubContent(
{ className, sideOffset = 10, size, ...props },
ref
) {
return (
<DropdownMenuPortal>
<DropdownMenuPrimitive.SubContent
{...props}
{...createSlot("content")}
{...createDataAttributes({ size })}
className={cn(dropdownMenuContentVariants({ size }), className)}
ref={ref}
sideOffset={sideOffset}
/>
</DropdownMenuPortal>
);
});
export type DropdownMenuItemProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> &
VariantProps<typeof dropdownMenuItemVariants>;
export const DropdownMenuItem = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.Item>,
DropdownMenuItemProps
>(function DropdownMenuItem(
{ className, inset, variant, ...props },
ref
) {
return (
<DropdownMenuPrimitive.Item
{...props}
{...createSlot("item")}
{...createDataAttributes({ inset, variant })}
className={cn(dropdownMenuItemVariants({ inset, variant }), className)}
ref={ref}
/>
);
});
export type DropdownMenuCheckboxItemProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem> &
VariantProps<typeof dropdownMenuItemVariants>;
export const DropdownMenuCheckboxItem = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
DropdownMenuCheckboxItemProps
>(function DropdownMenuCheckboxItem(
{ checked, children, className, inset = true, variant, ...props },
ref
) {
return (
<DropdownMenuPrimitive.CheckboxItem
{...props}
checked={checked}
{...createSlot("item")}
{...createDataAttributes({ checked: checked === true, inset, variant })}
className={cn(dropdownMenuItemVariants({ inset, variant }), className)}
ref={ref}
>
<span
{...createSlot("icon")}
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-xs"
>
<DropdownMenuPrimitive.ItemIndicator></DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
});
export type DropdownMenuRadioItemProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem> &
VariantProps<typeof dropdownMenuItemVariants>;
export const DropdownMenuRadioItem = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
DropdownMenuRadioItemProps
>(function DropdownMenuRadioItem(
{ children, className, inset = true, variant, ...props },
ref
) {
return (
<DropdownMenuPrimitive.RadioItem
{...props}
{...createSlot("item")}
{...createDataAttributes({ inset, variant })}
className={cn(dropdownMenuItemVariants({ inset, variant }), className)}
ref={ref}
>
<span
{...createSlot("icon")}
className="absolute left-2.5 inline-flex size-4 items-center justify-center text-xs"
>
<DropdownMenuPrimitive.ItemIndicator></DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
});
export type DropdownMenuLabelProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> &
VariantProps<typeof dropdownMenuLabelVariants>;
export const DropdownMenuLabel = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.Label>,
DropdownMenuLabelProps
>(function DropdownMenuLabel({ className, inset, ...props }, ref) {
return (
<DropdownMenuPrimitive.Label
{...props}
{...createSlot("label")}
{...createDataAttributes({ inset })}
className={cn(dropdownMenuLabelVariants({ inset }), className)}
ref={ref}
/>
);
});
export const DropdownMenuSeparator = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.Separator>,
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(function DropdownMenuSeparator({ className, ...props }, ref) {
return (
<DropdownMenuPrimitive.Separator
{...props}
{...createSlot("separator")}
className={cn(dropdownMenuSeparatorVariants(), className)}
ref={ref}
/>
);
});
export type DropdownMenuSubTriggerProps =
ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> &
VariantProps<typeof dropdownMenuItemVariants>;
export const DropdownMenuSubTrigger = forwardRef<
ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
DropdownMenuSubTriggerProps
>(function DropdownMenuSubTrigger(
{ children, className, inset, variant, ...props },
ref
) {
return (
<DropdownMenuPrimitive.SubTrigger
{...props}
{...createSlot("trigger")}
{...createDataAttributes({ inset, variant })}
className={cn(dropdownMenuItemVariants({ inset, variant }), className)}
ref={ref}
>
{children}
<span className="ml-auto text-xs text-[var(--color-muted-foreground)]"></span>
</DropdownMenuPrimitive.SubTrigger>
);
});
export function DropdownMenuShortcut({
className,
...props
}: HTMLAttributes<HTMLSpanElement>) {
return (
<span
{...props}
{...createSlot("shortcut")}
className={cn(
"ml-auto text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]",
className
)}
/>
);
}