Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions packages/ui/src/components/badge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"

import { badgeVariants } from "@workspace/ui/components/badge"

const COLOR_VARIANTS = [
"default",
"secondary",
"destructive",
"outline",
"ghost",
"link",
"neutral",
"info",
"success",
"warning",
"danger",
"long",
"short",
] as const

const SIZES = ["default", "sm"] as const

describe("badgeVariants", () => {
for (const variant of COLOR_VARIANTS) {
for (const size of SIZES) {
it(`produces a non-empty class string for variant=${variant} size=${size}`, () => {
const result = badgeVariants({ variant, size })
assert.ok(result)
assert.equal(typeof result, "string")
assert.ok(result.length > 0)
})
}
}

it("applies size classes correctly", () => {
const defaultSize = badgeVariants({ variant: "default", size: "default" })
const smallSize = badgeVariants({ variant: "default", size: "sm" })
assert.ok(defaultSize.includes("h-5"))
assert.ok(smallSize.includes("h-4"))
})

it("includes semantic token classes for each state variant", () => {
const asserts: Record<string, string> = {
neutral: "bg-neutral/10",
info: "bg-info/10",
success: "bg-success/10",
warning: "bg-warning/10",
danger: "bg-danger/10",
}
for (const [variant, tokenClass] of Object.entries(asserts)) {
const result = badgeVariants({ variant: variant as typeof COLOR_VARIANTS[number] })
assert.ok(result.includes(tokenClass), `${variant} should include ${tokenClass}`)
}
})

it("long variant includes extended padding", () => {
const result = badgeVariants({ variant: "long" })
assert.ok(result.includes("px-3"))
})

it("short variant uses dot-like dimensions", () => {
const result = badgeVariants({ variant: "short" })
assert.ok(result.includes("size-2.5"))
assert.ok(result.includes("min-w-0"))
})

it("defaults to variant=default size=default", () => {
const result = badgeVariants({})
assert.ok(result.includes("bg-primary"))
assert.ok(result.includes("h-5"))
})
})
19 changes: 17 additions & 2 deletions packages/ui/src/components/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { cn } from "@workspace/ui/lib/utils"
import type { VariantProps } from "class-variance-authority"

const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-10 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-2.5!",
"group/badge inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent 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-2.5!",
{
variants: {
variant: {
Expand All @@ -26,32 +26,47 @@ const badgeVariants = cva(
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
neutral:
"bg-neutral/10 text-neutral focus-visible:ring-neutral/20 dark:bg-neutral/20 dark:focus-visible:ring-neutral/40 [a]:hover:bg-neutral/20",
danger:
"bg-danger/10 text-danger focus-visible:ring-danger/20 dark:bg-danger/20 dark:focus-visible:ring-danger/40 [a]:hover:bg-danger/20",
long:
"bg-neutral/10 text-neutral focus-visible:ring-neutral/20 dark:bg-neutral/20 dark:focus-visible:ring-neutral/40 [a]:hover:bg-neutral/20 px-3 py-1 has-data-[slot=badge-icon]:pl-2",
short:
"bg-neutral/10 text-neutral focus-visible:ring-neutral/20 dark:bg-neutral/20 dark:focus-visible:ring-neutral/40 size-2.5 min-w-0 p-0 justify-center [&>svg]:size-2 [&>svg]:mx-auto",
},
size: {
default: "h-5 text-10 px-2 py-0.5",
sm: "h-4 text-[0.5rem] px-1.5 py-px",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)

function Badge({
className,
variant = "default",
size = "default",
render,
...props
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
return useRender({
defaultTagName: "span",
props: mergeProps<"span">(
{
className: cn(badgeVariants({ variant }), className),
className: cn(badgeVariants({ variant, size }), className),
},
props
),
render,
state: {
slot: "badge",
variant,
size,
},
})
}
Expand Down
124 changes: 124 additions & 0 deletions packages/ui/src/components/dropdown-menu.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import assert from "node:assert/strict"
import { describe, it } from "node:test"

import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuGroupLabel,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"

describe("DropdownMenu component exports", () => {
it("exports all expected parts", () => {
assert.equal(typeof DropdownMenu, "function")
assert.equal(typeof DropdownMenuTrigger, "function")
assert.equal(typeof DropdownMenuContent, "function")
assert.equal(typeof DropdownMenuItem, "function")
assert.equal(typeof DropdownMenuCheckboxItem, "function")
assert.equal(typeof DropdownMenuRadioGroup, "function")
assert.equal(typeof DropdownMenuRadioItem, "function")
assert.equal(typeof DropdownMenuGroup, "function")
assert.equal(typeof DropdownMenuGroupLabel, "function")
assert.equal(typeof DropdownMenuSeparator, "function")
assert.equal(typeof DropdownMenuShortcut, "function")
})
})

describe("DropdownMenuItem", () => {
it("renders without destructive prop", () => {
const item = DropdownMenuItem({ children: "Item" })
assert.ok(item)
})

it("renders with destructive prop", () => {
const item = DropdownMenuItem({ destructive: true, children: "Delete" })
assert.ok(item)
})

it("renders with disabled state", () => {
const item = DropdownMenuItem({ disabled: true, children: "Item" })
assert.ok(item)
})
})

describe("DropdownMenuCheckboxItem", () => {
it("renders in unchecked state", () => {
const item = DropdownMenuCheckboxItem({ children: "Option" })
assert.ok(item)
})

it("renders in checked state", () => {
const item = DropdownMenuCheckboxItem({ checked: true, children: "Option" })
assert.ok(item)
})

it("renders with defaultChecked", () => {
const item = DropdownMenuCheckboxItem({
defaultChecked: true,
children: "Option",
})
assert.ok(item)
})
})

describe("DropdownMenuRadioGroup and RadioItem", () => {
it("renders a radio group with items", () => {
const group = DropdownMenuRadioGroup({
value: "a",
children: [
DropdownMenuRadioItem({ value: "a", key: "a", children: "Option A" }),
DropdownMenuRadioItem({ value: "b", key: "b", children: "Option B" }),
],
})
assert.ok(group)
})

it("renders a radio item with value", () => {
const item = DropdownMenuRadioItem({ value: "a", children: "Option A" })
assert.ok(item)
})
})

describe("DropdownMenuContent", () => {
it("accepts positioning props", () => {
const content = DropdownMenuContent({
side: "bottom",
align: "start",
children: "Content",
})
assert.ok(content)
})
})

describe("DropdownMenuGroup and GroupLabel", () => {
it("renders a group with label", () => {
const label = DropdownMenuGroupLabel({ children: "Group" })
assert.ok(label)
})

it("renders a group wrapper", () => {
const group = DropdownMenuGroup({ children: "Content" })
assert.ok(group)
})
})

describe("DropdownMenuSeparator", () => {
it("renders a separator", () => {
const separator = DropdownMenuSeparator({})
assert.ok(separator)
})
})

describe("DropdownMenuShortcut", () => {
it("renders a shortcut hint", () => {
const shortcut = DropdownMenuShortcut({ children: "⌘K" })
assert.ok(shortcut)
})
})
Loading
Loading