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
5 changes: 5 additions & 0 deletions .changeset/gentle-empty-titles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudflare/kumo": patch
---

Refine Empty state typography and balance description text when it wraps.
68 changes: 68 additions & 0 deletions packages/kumo/src/components/empty/empty.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vite-plus/test";
import { Empty } from "./empty";

describe("Empty", () => {
it("renders the title as a large heading when a description is present", () => {
render(
<Empty
title="No results found"
description="Try adjusting your search."
/>,
);

const title = screen.getByText("No results found");
const description = screen.getByText("Try adjusting your search.");

expect(title.tagName).toBe("H2");
expect(title.className).toContain("text-xl");
expect(title.className).toContain("font-semibold");
expect(description.className).toContain("text-base/[inherit]");
expect(description.className).toContain("text-kumo-subtle");
expect(description.className).toContain("text-balance");
expect(description.className).toContain("leading-normal");
expect(title.parentElement).toBe(description.parentElement);
expect(title.parentElement?.className).toContain("gap-2.5");
});

it("renders the title as base secondary text without a description", () => {
render(<Empty title="Nothing here" />);

const title = screen.getByText("Nothing here");

expect(title.tagName).toBe("H2");
expect(title.className).toContain("text-base/[inherit]");
expect(title.className).toContain("text-kumo-subtle");
expect(title.className).not.toContain("font-semibold");
});

it("renders the command line with inset styling and without brand-colored text", () => {
render(<Empty title="Install Kumo" commandLine="npm install kumo" />);

const command = screen.getByText("npm install kumo");
const commandTextGroup = command.parentElement;
const commandContainer = commandTextGroup?.parentElement;

expect(command.className).not.toContain("text-kumo-brand");
expect(commandContainer?.className).toContain("bg-kumo-overlay");
expect(commandContainer?.className.split(" ")).toContain("border");
expect(commandContainer?.className).toContain("border-white");
expect(commandContainer?.className.split(" ")).toContain("ring");
expect(commandContainer?.className).toContain("ring-kumo-line");
expect(commandContainer?.className).toContain("items-center");
expect(commandTextGroup?.className).toContain("items-baseline");
expect(commandContainer?.className).toContain("shadow-xs");
expect(commandContainer?.className).not.toContain("shadow-inner");
expect(commandContainer?.className).not.toContain("hover:");

const prompt = screen.getByText("$");
expect(prompt.className).toContain("text-kumo-subtle");
expect(prompt.className).not.toContain("text-xs");

const copyButton = screen.getByRole("button", { name: "Copy command" });
expect(copyButton.className).toContain("text-kumo-subtle");

const copyIcon = copyButton.querySelector("svg");
expect(copyIcon?.getAttribute("class") ?? "").not.toContain("group-hover:");
});
});
45 changes: 29 additions & 16 deletions packages/kumo/src/components/empty/empty.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { CheckIcon, CopyIcon } from "@phosphor-icons/react";
import { useState } from "react";
import { Button } from "../../components/button";
import { Text } from "../../components/text";
import { cn } from "../../utils/cn";
import { resolveVariant } from "../../utils/resolve-variant";

Expand Down Expand Up @@ -102,27 +103,42 @@ export function Empty({
return (
<div className={cn(emptyVariants({ size }), className)}>
{icon}
<h2 className="text-2xl font-semibold">{title}</h2>
<div className="flex flex-col items-center gap-2.5">
{description ? (
<Text variant="heading" size="lg" as="h2">
{title}
</Text>
) : (
<Text variant="secondary" size="base" as="h2">
{title}
</Text>
)}

{description && (
<p className="max-w-140 text-center text-kumo-subtle">{description}</p>
)}
{description && (
<Text
variant="secondary"
size="base"
DANGEROUS_className="max-w-140 text-center text-balance leading-normal"
>
{description}
</Text>
)}
</div>

{commandLine && (
<div
className={cn(
"group/cmd relative inline-flex h-10 max-w-8/10 transform-gpu items-center gap-2 rounded-lg font-mono shadow-sm",
"bg-kumo-overlay pr-2 pl-3",
"transition-all duration-300 hover:border-kumo-interact/80 hover:shadow-md",
"border border-kumo-fill/60",
"relative inline-flex h-10 max-w-8/10 transform-gpu items-center gap-2 rounded-lg border border-white bg-kumo-overlay pr-2 pl-3 font-mono shadow-xs ring ring-kumo-line",
)}
>
<span className="text-xs text-kumo-inactive select-none">$</span>
<span className="no-scrollbar overflow-scroll text-base whitespace-nowrap text-kumo-brand">
{commandLine}
<span className="inline-flex min-w-0 items-baseline gap-2">
<span className="text-kumo-subtle select-none">$</span>
<span className="no-scrollbar overflow-scroll text-base whitespace-nowrap">
{commandLine}
</span>
</span>
<Button
className="group"
className="text-kumo-subtle"
size="sm"
variant="ghost"
shape="square"
Expand All @@ -141,10 +157,7 @@ export function Empty({
className="animate-bounce-in text-kumo-success"
/>
) : (
<CopyIcon
size={16}
className="text-kumo-inactive group-hover:text-kumo-brand"
/>
<CopyIcon size={16} />
)}
</Button>
</div>
Expand Down
Loading