Skip to content

Commit 42721ea

Browse files
suxiaoshaohuacnleeclaude
authored
empty: Add composable empty states (longbridge#3030)
Closes longbridge#2862 ## Description - Add six stateless, composable parts: `Empty`, `EmptyHeader`, `EmptyMedia`, `EmptyTitle`, `EmptyDescription`, and `EmptyContent`. - Provide optional typed header/content and media/title/description slots with replacement semantics and stable rendering order. Extra root children follow the named slots. - Keep every part independently styleable, with theme-derived colors/radii and rem-based spacing. Media accepts icons, avatars, avatar groups, and arbitrary elements; actions and retained input state stay in the application. No Base-layer changes, dependencies, or component-specific global theme fields are added. - Add Story examples for default actions, outline/background styling, avatars, avatar groups, search results, embedded inputs, constrained layouts, and minimal content, plus matching English and Chinese documentation. - Cover builders and real component composition. The layout regression catches zero-width avatar groups and verifies centered/leading alignment, two rem sizes, and named-slot ordering. - Register all six Empty parts in Component Shell, preserve typed named slots and independently styled parts, and add the inventory entries and a JavaScript Story with application-owned actions and retained input state. - Keep element arguments intact across the shared named-slot bridge so registered `header`/`content` methods receive the same Element values as other registered methods. Existing built-in slots retain their ownership checks. - Remove the optional external reference links from both Empty documentation pages so the generated documentation bundle passes its canonical-route check without changing that check. Composition and spacing reference the shadcn/ui Empty Nova examples while retaining GPUI Component's theme and native controls. ## Screenshot Screenshots are not attached. Visual verification was completed in the signed macOS Story app: individual avatars and avatar groups are centered, constrained Chinese text wraps without clipping, and Empty follows the theme's font-size and corner-radius controls. ## Breaking Changes None. The API is additive; existing imports and consumers remain unchanged. Applications can optionally adopt named parts in place of their own placeholder markup (this is not a required migration): ```diff - div().child("No projects yet") + Empty::new().header( + EmptyHeader::new().title(EmptyTitle::new().child("No projects yet")), + ) ``` The new types are imported from `gpui_kit::component::empty`; GPUI's existing `gpui_kit::Empty` remains unchanged. ## How to Test Passed: ```sh cargo build --locked -p gpui-component -p gpui-component-story cargo test --locked -p gpui-component --lib empty::tests cargo clippy --locked -p gpui-component -p gpui-component-story --all-targets -- --deny warnings cargo test --locked -p gpui-component-shell --all-targets cargo test --locked -p gpui-shell --lib tests::render cargo clippy --locked -p gpui-component-shell --all-targets -- --deny warnings GPUI_COMPONENT_SHELL_BIN=target/debug/gpui-component-shell node crates/component-shell/tests/types/run.mjs node examples/js_story/fixtures/verify-coverage.mjs rustfmt --edition 2024 --check crates/component/src/empty.rs crates/story/src/stories/empty_story.rs typos crates/component/src/empty.rs crates/story/src/stories/empty_story.rs website/component/empty.md website/zh-CN/component/empty.md git diff --check ``` Native verification used `./script/run-story-macos Empty`: - Create project shows `Untitled project`; Reset example restores the empty state. - Clear search removes `Archive` and restores all results; typing `Website` immediately afterward filters the list without refocusing the field. - The input embedded in EmptyContent edits independently of the project-search input. - Avatar and AvatarGroup centering, leading-aligned constrained content, long Chinese text, larger font size, and zero/default theme radii were checked visually. Both Markdown files parse, and their eight Rust code blocks match. The production `buildLlmsContent` output passes the canonical-route assertion and includes both Empty pages. The new host tests cover wrong slot types, replacement semantics, standalone parts, styles, and a working child-button callback; all registered Story examples materialize. Shell CLI tests were run with macOS desktop-service access. The full documentation-site build and Windows/Linux native runs were not performed. ## Checklist - [x] Read CONTRIBUTING and followed the component/design guides. - [x] Reviewed the scoped API and implementation; this component, Story, documentation, and regression coverage were AI-assisted. - [x] Ran the affected Story using the documented signed macOS launch script. - [x] No platform-specific implementation changes. --------- Co-authored-by: Jason Lee <huacnlee@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 6fbc32d commit 42721ea

19 files changed

Lines changed: 2176 additions & 4 deletions

File tree

‎crates/component-shell/component-inventory.json‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,55 @@
346346
"classification": "infrastructure",
347347
"explanation": "Dock is an application layout subsystem already exposed directly by gpui-shell; it is not a singular gpui-component constructor."
348348
},
349+
{
350+
"source": "ui",
351+
"name": "empty",
352+
"classification": "component",
353+
"registration": {
354+
"status": "registered",
355+
"descriptor": "Empty",
356+
"exports": [
357+
"Empty"
358+
],
359+
"related": [
360+
{
361+
"descriptor": "EmptyHeader",
362+
"exports": [
363+
"EmptyHeader"
364+
],
365+
"role": "named-slot-part"
366+
},
367+
{
368+
"descriptor": "EmptyMedia",
369+
"exports": [
370+
"EmptyMedia"
371+
],
372+
"role": "named-slot-part"
373+
},
374+
{
375+
"descriptor": "EmptyTitle",
376+
"exports": [
377+
"EmptyTitle"
378+
],
379+
"role": "named-slot-part"
380+
},
381+
{
382+
"descriptor": "EmptyDescription",
383+
"exports": [
384+
"EmptyDescription"
385+
],
386+
"role": "named-slot-part"
387+
},
388+
{
389+
"descriptor": "EmptyContent",
390+
"exports": [
391+
"EmptyContent"
392+
],
393+
"role": "named-slot-part"
394+
}
395+
]
396+
}
397+
},
349398
{
350399
"source": "ui",
351400
"name": "form",
@@ -1477,6 +1526,55 @@
14771526
]
14781527
}
14791528
},
1529+
{
1530+
"source": "story",
1531+
"name": "empty",
1532+
"classification": "component",
1533+
"registration": {
1534+
"status": "registered",
1535+
"descriptor": "Empty",
1536+
"exports": [
1537+
"Empty"
1538+
],
1539+
"related": [
1540+
{
1541+
"descriptor": "EmptyHeader",
1542+
"exports": [
1543+
"EmptyHeader"
1544+
],
1545+
"role": "named-slot-part"
1546+
},
1547+
{
1548+
"descriptor": "EmptyMedia",
1549+
"exports": [
1550+
"EmptyMedia"
1551+
],
1552+
"role": "named-slot-part"
1553+
},
1554+
{
1555+
"descriptor": "EmptyTitle",
1556+
"exports": [
1557+
"EmptyTitle"
1558+
],
1559+
"role": "named-slot-part"
1560+
},
1561+
{
1562+
"descriptor": "EmptyDescription",
1563+
"exports": [
1564+
"EmptyDescription"
1565+
],
1566+
"role": "named-slot-part"
1567+
},
1568+
{
1569+
"descriptor": "EmptyContent",
1570+
"exports": [
1571+
"EmptyContent"
1572+
],
1573+
"role": "named-slot-part"
1574+
}
1575+
]
1576+
}
1577+
},
14801578
{
14811579
"source": "story",
14821580
"name": "form",

0 commit comments

Comments
 (0)