story: Join the toolbar buttons and add a million-row table example (#2697)
## Description Two related bits of story polish. **Toolbar buttons now read as one control.** `dropdown_child` gave every dropdown its own `ButtonGroup` holding a single button, and `ButtonGroup` skips all border merging at one child — so a typical toolbar (Size + Options) rendered as two fully-rounded buttons touching each other, with a doubled border down the middle. `StoryToolbar` now keeps its items unbuilt until render, where it knows the count and each index, and squares off the inner corners. This also let the `StoryToolbarMenuTrigger` wrapper go (struct plus four trait impls): `Popover::trigger` only requires `Selectable + IntoElement`, which `Button` already satisfies. No component was changed — this is entirely inside `crates/story`. **DataTable story: a million-row example.** Replaces the 1,000-row option; 5,000 stays, since that is what the story opens with. The rarely-used Clear button moves into the Options menu, and the ID column no longer centers its text. ## Performance Two things had to change before a million rows was worth showing. | | before | after | | :--- | ---: | ---: | | `random_stocks(1_000_000)`, debug | 24.0 s | 0.71 s | | `random_stocks(10_000)`, debug | 231 ms | 27.7 ms | Every row randomized all 40-odd fields independently. Rows are now cloned from a 512-row random pool, each still getting its own id and `Counter::random()`, so no two rows read alike on screen. Retained size is unchanged at 408 B/row (389 MB at a million rows). The remaining ~0.7 s still landed on the main thread between two paints, which was visible as a hitch on switching. Generation moved to the background executor behind the story's existing `full_loading` state, so no frame is dropped. Separately, the "Refresh Data" tick walked the entire table every 33 ms and randomized ~1/3 of the rows. At a million rows that is millions of field draws per frame to update the few dozen rows on screen, which would have made the new example unusable with that option on. It is now bounded to the first 2,000 rows. ## How to Test ```sh cargo run ``` Open the DataTable story: - the toolbar buttons form one segmented control, no doubled borders; - Rows offers 100 / 500 / 5,000 / 10,000 / 1,000,000, with 5,000 checked on open; - switching to 1,000,000 shows the loading state and does not hitch; - Options ends with a separator and Clear Selection; - Refresh Data stays smooth at a million rows. `cargo check`, `cargo clippy` and `rustfmt --check` are clean for the changed files. ## AI Assistance AI assisted with the implementation and with measuring the generation timings quoted above. ## Checklist - [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and followed the guidelines. - [x] Reviewed the changes in this PR and confirmed AI generated code (if any) is accurate. - [x] `cargo run` story verification — see How to Test. - [x] Cross-platform performance testing is not applicable; no platform-specific path changed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
J
Jason Lee committed
dc2943fac0ee9b907e21cc415867bd45615c220c
Parent: bbe0675
Committed by GitHub <noreply@github.com>
on 8/13/2026, 3:39:07 PM