T44d Web · Stories stand alone: nested ones surface, hidden ones drop their demos, early themes land, pastes arrive

This commit is contained in:
ZacharyZhang-NY committed 2026-09-29 19:16:41 -04:00
1 parent 2a132d7d41
commit d2018f8510
22 files changed
+352 -172

No files matched your search

+6 -5
View File
@@ -25,9 +25,9 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- `cargo run --example gallery -- --capture <dir>` writes PNGs of every page, light and dark, top to bottom, then each page's scripted states, including windows the demos open. macOS only.
- `cargo run --example gallery -- --narrow 280 --capture <dir>` lays each page, header and body, at that width in a padded card and shoots it without its script, whose aims are measured at full width. Demos that set their own width run past the card.
- `RUSTC_BOOTSTRAP=1 cargo check --lib --target wasm32-unknown-unknown` checks the web build; gpui_web's `wasm_thread` needs the bootstrap, as Zed's web examples do.
- `ELY_GALLERY_ASSETS=<address of out/assets/> scripts/web.sh <out>` builds the gallery for the browser, profile `web`, and fails past Cloudflare's 25 MiB. It opens at `?page=<slug>&story=<title or slug>&theme=light|dark`; the host page calls `gallery.setTheme(mode)` or posts `{ ely: "theme", theme }` later.
- `python3 scripts/stories.py` writes `examples/gallery/stories.json`, every page's sections in drawing order with their components, for the site. `scripts/check.sh` refuses a stale one.
- `cargo test --lib --features test-support`, `cargo clippy --all-targets --features test-support -- -D warnings`, `cargo fmt --check`. `scripts/check.sh` runs them with the house rules.
- `ELY_GALLERY_ASSETS=<address of out/assets/> scripts/web.sh <out>` builds the gallery for the browser, profile `web`, and fails past Cloudflare's 25 MiB. It opens at `?page=<slug>&story=<title or slug>&theme=light|dark`; the host page calls `gallery.setTheme(mode)` or posts `{ ely: "theme", theme }`, before the wasm runs or after, and the page posts `{ ely: "ready" }` to its parent once it runs.
- `python3 scripts/stories.py` writes `examples/gallery/stories.json`, every page's sections in drawing order with their components, for the site: the names in a title, and in its `SHOWN` table what a story draws beyond them. It fails on a section opened inside another and on a ticked entry with a home of its own that no story names. `scripts/check.sh` refuses a stale one.
- `cargo test --lib --features test-support`, `cargo clippy --all-targets --features test-support -- -D warnings`, `cargo fmt --check`. `scripts/check.sh` runs them with the house rules and lints the gallery for wasm32.
- `rm -rf target/debug/incremental` and `cargo clean -p ely-gpui-component` after each task item keep the disk lean. Cargo keeps every old build of the crate and its tests under a new hash; by T25 they had filled the disk.
- `scripts/icons.sh <lucide-name>...` adds icons. Then add a line to `IconName` in `src/primitives/icon.rs`.
- `python3 scripts/countries.py <ne_110m_admin_0_countries.geojson>` writes the bundled world; `python3 scripts/tiles.py <same file>` writes the gallery's map tiles.
@@ -180,9 +180,10 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- Time reads `web_time::Instant`: std's compiles for wasm32 and panics in the browser. `scripts/check.sh` refuses std's in `src` and the gallery.
- What cannot build for wasm32 is absent there through `cfg(not(target_family = "wasm"))`, never faked: the live terminal, `Terminal` and `Launch` with their grid, keys and search, and the gallery's capture harness.
- A gallery demo a browser cannot run says why on the web through `ui::web_note`, in the blocked style: a second window (`own_window`), files from the dialog or a drop (`NO_FILES`), the menu bar, window moves, a clipboard read.
- A story is a gallery section: its title is a literal, one per page. Under `ui::Story` the one asked for draws without its title and the rest hide, which skips their prepaint and paint; a story the page lacks fails in the page.
- A story is a gallery section: its title is a literal, one per page. Under `ui::Story` the one asked for draws without its title; the rest drop their demos (`ui::Section`), so nothing of theirs lays out, loads or moves. A story the page lacks fails in the page.
- The gallery's pictures go through `asset!` and `ui::picture` or `ui::resource`: a file natively, on the web an address under `ELY_GALLERY_ASSETS`, since gpui reads no files there.
- The first frame wears the host's mode through `Theme::set_mode_now`; `set_mode` fades from the colors on screen.
- On the web a paste reaches the focused field as the browser's paste event, through `EntityInputHandler::paste`: gpui_web reads no clipboard on demand, so Paste stays unbound there (`TextInput`, `CodeEditor`).
- The first frame wears the host's mode through `Theme::set_mode_now`, a mode sent before the window opens included; `set_mode` fades from the colors on screen.
- The system time zone comes from `typography::format::system_zone`, which fails loud; jiff's `TimeZone::system()` falls back to UTC in silence.
- A Select with nothing to pick stays shut: disabled, it shows its placeholder; open, it fails (`media::DeviceSelector` says no device was found).
- A list opens with its cursor on the current choice: Select on the chosen row, Combobox on its value, MultiSelect on the first one ticked, SelectableList on its first selected row. A selection the owner makes, not the echo of the list's own pick, moves SelectableList's cursor there.
+9 -3
View File
@@ -16,16 +16,22 @@
</script>
<script type="module">
import init, { set_theme } from "./gallery.js";
await init();
// The host switches the mode later: gallery.setTheme("dark"), or a message { ely: "theme", theme }.
// The host sets the mode with gallery.setTheme(mode) or a message { ely: "theme", theme }, early or late.
let early = null;
let apply = (theme) => { early = theme; };
const setTheme = (theme) => {
set_theme(theme);
apply(theme);
document.documentElement.classList.toggle("dark", theme === "dark");
};
window.gallery = { setTheme };
addEventListener("message", ({ data }) => {
if (data && data.ely === "theme") setTheme(data.theme);
});
await init();
apply = set_theme;
if (early) set_theme(early);
// A host that waits for this knows the mode it sends lands.
window.parent.postMessage({ ely: "ready" }, "*");
</script>
</head>
<body></body>
+2 -2
View File
@@ -10,7 +10,7 @@ use gpui::{
};
use crate::probe::probe;
use crate::ui::{blocked, section, specimen, specimens};
use crate::ui::{UNREAD, blocked, section, specimen, specimens};
const NAV: [&str; 5] = ["Home", "Docs", "Pricing", "Blog", "About"];
@@ -65,7 +65,7 @@ pub fn skip_link(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
),
)
.child(blocked(
"LiveRegion / Announcer, AccessibleIcon, AriaLabel, ScreenReaderOnly: not yet shown: gpui builds an AccessKit tree from elements with an id and a role, and no screen reader here reads it.",
format!("LiveRegion / Announcer, AccessibleIcon, AriaLabel, ScreenReaderOnly: not yet shown: {UNREAD}."),
cx,
))
}
+59 -60
View File
@@ -193,7 +193,7 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
.child(*name),
)
});
section(
let moves = section(
"Resizable · Rotatable · Selectable / SelectionArea · RubberBandSelection · Sortable",
"A box resized from its edges or its grip; a picture turned by its knob, as gpui turns svgs alone; tiles taken by a band drawn on empty space; rows sorted by drag through motion::Reorder.",
cx,
@@ -254,73 +254,72 @@ pub fn shapes(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
})),
cx,
)),
);
let presses = section(
"LongPress · DoubleClick · Gesture · PanZoom",
"A long press is primitives::Pressable; a double press is gpui's click_count. The wheel pans and, with Command, zooms layout::Viewport; two fingers swipe lists::SwipeableListItem.",
cx,
)
.child(
section(
"LongPress · DoubleClick · Gesture · PanZoom",
"A long press is primitives::Pressable; a double press is gpui's click_count. The wheel pans and, with Command, zooms layout::Viewport; two fingers swipe lists::SwipeableListItem.",
cx,
)
.child(
specimens()
.child(specimen(
"LongPress → primitives::Pressable",
Pressable::new("interaction-press", move |down, _, cx| {
let theme = cx.theme();
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme.colors.border)
.bg(if down { theme.colors.active } else { theme.colors.surface })
.text_size(theme.text_size(TextSize::Sm))
.child(format!("Pressed {presses} · held {held}"))
.into_any_element()
})
.on_press(move |_, cx| change(&press, cx, |shapes| shapes.presses += 1))
.on_long_press(move |_, cx| change(&long, cx, |shapes| shapes.held += 1)),
cx,
))
.child(specimen(
"DoubleClick",
specimens()
.child(specimen(
"LongPress → primitives::Pressable",
Pressable::new("interaction-press", move |down, _, cx| {
let theme = cx.theme();
div()
.id("interaction-double")
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme.colors.border)
.tab_index(0)
.focus_ring(cx)
.bg(if down { theme.colors.active } else { theme.colors.surface })
.text_size(theme.text_size(TextSize::Sm))
.on_click(move |event, _, cx| {
if event.click_count() == 2 {
change(&double, cx, |shapes| shapes.doubles += 1)
}
})
.child(format!("Opened {}", plural(doubles as u64, "time", "times"))),
cx,
))
.child(specimen(
"PanZoom → layout::Viewport",
div().w(px(240.)).h(px(140.)).rounded_lg().border_1().border_color(theme.colors.border).overflow_hidden().child(
Viewport::new("interaction-viewport", |view: Transform, _, cx| {
let theme = cx.theme();
let corner = view.apply(point(px(80.), px(30.)));
div()
.absolute()
.left(corner.x)
.top(corner.y)
.size(px(80.) * view.scale)
.rounded_lg()
.bg(theme.colors.sunken)
.into_any_element()
}),
),
cx,
)),
)
.child(blocked("Pinch: gpui hands a trackpad pinch to on_pinch, and the viewport does not zoom on it yet.", cx)),
.child(format!("Pressed {presses} · held {held}"))
.into_any_element()
})
.on_press(move |_, cx| change(&press, cx, |shapes| shapes.presses += 1))
.on_long_press(move |_, cx| change(&long, cx, |shapes| shapes.held += 1)),
cx,
))
.child(specimen(
"DoubleClick",
div()
.id("interaction-double")
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme.colors.border)
.tab_index(0)
.focus_ring(cx)
.text_size(theme.text_size(TextSize::Sm))
.on_click(move |event, _, cx| {
if event.click_count() == 2 {
change(&double, cx, |shapes| shapes.doubles += 1)
}
})
.child(format!("Opened {}", plural(doubles as u64, "time", "times"))),
cx,
))
.child(specimen(
"PanZoom → layout::Viewport",
div().w(px(240.)).h(px(140.)).rounded_lg().border_1().border_color(theme.colors.border).overflow_hidden().child(
Viewport::new("interaction-viewport", |view: Transform, _, cx| {
let theme = cx.theme();
let corner = view.apply(point(px(80.), px(30.)));
div()
.absolute()
.left(corner.x)
.top(corner.y)
.size(px(80.) * view.scale)
.rounded_lg()
.bg(theme.colors.sunken)
.into_any_element()
}),
),
cx,
)),
)
.child(blocked("Pinch: gpui hands a trackpad pinch to on_pinch, and the viewport does not zoom on it yet.", cx));
div().child(moves).child(presses)
}
+1 -1
View File
@@ -27,7 +27,7 @@ pub fn menu_bar(cx: &mut App) -> impl IntoElement + use<> {
cx,
)
.children(web_note(
"A browser page has no menu bar, and gpui on the web keeps no cx.set_menus: this bar draws the gallery's own list of the same menus.",
"A browser page has no menu bar, and gpui on the web keeps no cx.set_menus: this bar draws the gallery's own list of the same menus. About Ely would open a second window and Quit would end the app, neither of which a page can, so both only log.",
cx,
))
.child(specimen(
+3 -5
View File
@@ -15,7 +15,7 @@ use gpui::{
use crate::{
probe::probe,
ui::{blocked, code, picture, row, section, specimen, specimens},
ui::{UNREAD, blocked, code, picture, row, section, specimen, specimens},
};
const ATRIUM: &str = asset!("atrium.jpg");
@@ -193,10 +193,8 @@ pub fn backdrop(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
}
pub fn visually_hidden(cx: &App) -> impl IntoElement + use<> {
section("VisuallyHidden", "Text for screen readers only.", cx).child(blocked(
"Not yet shown: gpui builds an AccessKit tree from elements with an id and a role, and no screen reader here reads it.",
cx,
))
section("VisuallyHidden", "Text for screen readers only.", cx)
.child(blocked(format!("Not yet shown: {UNREAD}."), cx))
}
fn chip(id: &'static str, label: &'static str, cx: &App) -> impl IntoElement + use<> {
+5 -1
View File
@@ -8,7 +8,7 @@ use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, p
use crate::{
probe::probe,
ui::{keep, section, set},
ui::{keep, section, set, web_note},
};
type Cells = HashMap<(usize, usize), SharedString>;
@@ -35,6 +35,10 @@ pub fn data_grid(window: &mut Window, cx: &mut App) -> impl IntoElement + use<>
"Cells to edit like a spreadsheet's. Arrows walk, Shift stretches a range, typing or a double press edits, Delete clears, and Cmd-C and Cmd-V copy and paste.",
cx,
)
.children(web_note(
"A browser reads its clipboard only as it pastes into a text field, so Cmd-V pastes no cells here.",
cx,
))
.child(probe(
"grid",
div().child(
+1 -1
View File
@@ -50,7 +50,7 @@ fn live(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
{
let _ = window;
section.child(crate::ui::blocked(
"A browser starts no processes and has no pseudo-terminal, so no shell runs here, and alacritty's grid, which draws the build's output, does not build for the web. The blocks, colored output, logs and processes below draw without one.",
"A browser starts no processes and has no pseudo-terminal, so no shell runs here, and alacritty's grid, which draws the build's output, does not build for the web. The page's other stories, command blocks, colored output, logs and processes, draw without one.",
cx,
))
}
+3 -2
View File
@@ -288,9 +288,10 @@ impl Render for Gallery {
return self.root(cx).child(
on_axis(div().id(("story", self.pass)))
.size_full()
.overflow_y_scroll()
.overflow_scroll()
.track_scroll(&self.scroll)
.child(div().p_6().child(body)),
// The page column's 848 plus padding; narrower, the story scrolls sideways.
.child(div().min_w(px(896.0)).p_6().child(body)),
);
}
let sidebar = self.sidebar(cx);
+6 -6
View File
@@ -2,9 +2,9 @@
{"number": 1, "slug": "primitives", "title": "Primitives", "summary": "The smallest parts. Most are gpui itself; Ely adds the rest.", "stories": [{"title": "Box / View", "slug": "box-view", "components": ["Box", "View"]}, {"title": "Text", "slug": "text", "components": ["Text"]}, {"title": "Icon", "slug": "icon", "components": ["Icon"]}, {"title": "IconSet / IconRegistry", "slug": "iconset-iconregistry", "components": ["IconSet", "IconRegistry"]}, {"title": "Svg", "slug": "svg", "components": ["Svg"]}, {"title": "Image", "slug": "image", "components": ["Image"]}, {"title": "Canvas", "slug": "canvas", "components": ["Canvas"]}, {"title": "Divider / Separator", "slug": "divider-separator", "components": ["Divider", "Separator"]}, {"title": "Spacer", "slug": "spacer", "components": ["Spacer"]}, {"title": "Portal", "slug": "portal", "components": ["Portal"]}, {"title": "Overlay / Backdrop", "slug": "overlay-backdrop", "components": ["Overlay", "Backdrop"]}, {"title": "Slot", "slug": "slot", "components": ["Slot"]}, {"title": "Show / When", "slug": "show-when", "components": ["Show", "When"]}, {"title": "For / Each", "slug": "for-each", "components": ["For", "Each"]}, {"title": "Fragment", "slug": "fragment", "components": ["Fragment"]}, {"title": "VisuallyHidden", "slug": "visuallyhidden", "components": ["VisuallyHidden"]}, {"title": "FocusRing", "slug": "focusring", "components": ["FocusRing"]}, {"title": "FocusScope / FocusTrap", "slug": "focusscope-focustrap", "components": ["FocusScope", "FocusTrap"]}, {"title": "ClickOutside", "slug": "clickoutside", "components": ["ClickOutside"]}, {"title": "HoverArea", "slug": "hoverarea", "components": ["HoverArea"]}, {"title": "Pressable", "slug": "pressable", "components": ["Pressable"]}, {"title": "KeyboardHandler", "slug": "keyboardhandler", "components": ["KeyboardHandler"]}, {"title": "Measure", "slug": "measure", "components": ["Measure"]}, {"title": "IntersectionObserver", "slug": "intersectionobserver", "components": ["IntersectionObserver"]}, {"title": "Clipboard", "slug": "clipboard", "components": ["Clipboard"]}, {"title": "Tooltip Trigger", "slug": "tooltip-trigger", "components": ["Tooltip Trigger"]}]},
{"number": 2, "slug": "typography", "title": "Typography", "summary": "Inter for words, tabular figures for numbers, one ink for everything.", "stories": [{"title": "Heading (H1–H6)", "slug": "heading-h1-h6", "components": ["Heading"]}, {"title": "Title / Subtitle", "slug": "title-subtitle", "components": ["Title", "Subtitle"]}, {"title": "Paragraph", "slug": "paragraph", "components": ["Paragraph"]}, {"title": "Label", "slug": "label", "components": ["Label"]}, {"title": "Caption", "slug": "caption", "components": ["Caption"]}, {"title": "Overline", "slug": "overline", "components": ["Overline"]}, {"title": "Code", "slug": "code", "components": ["Code"]}, {"title": "Kbd", "slug": "kbd", "components": ["Kbd"]}, {"title": "KbdCombo", "slug": "kbdcombo", "components": ["KbdCombo"]}, {"title": "Blockquote", "slug": "blockquote", "components": ["Blockquote"]}, {"title": "Highlight / Mark", "slug": "highlight-mark", "components": ["Highlight", "Mark"]}, {"title": "Truncate / Ellipsis", "slug": "truncate-ellipsis", "components": ["Truncate"]}, {"title": "LineClamp", "slug": "lineclamp", "components": ["LineClamp"]}, {"title": "MiddleEllipsis", "slug": "middleellipsis", "components": ["MiddleEllipsis"]}, {"title": "EllipsisTooltip", "slug": "ellipsistooltip", "components": ["EllipsisTooltip"]}, {"title": "Link", "slug": "link", "components": ["Link"]}, {"title": "ExternalLink", "slug": "externallink", "components": ["ExternalLink"]}, {"title": "RelativeTime", "slug": "relativetime", "components": ["RelativeTime"]}, {"title": "DateTimeText", "slug": "datetimetext", "components": ["DateTimeText"]}, {"title": "NumberText", "slug": "numbertext", "components": ["NumberText"]}, {"title": "CurrencyText", "slug": "currencytext", "components": ["CurrencyText"]}, {"title": "PercentText", "slug": "percenttext", "components": ["PercentText"]}, {"title": "FileSizeText", "slug": "filesizetext", "components": ["FileSizeText"]}, {"title": "DurationText", "slug": "durationtext", "components": ["DurationText"]}, {"title": "PluralText", "slug": "pluraltext", "components": ["PluralText"]}, {"title": "CopyableText", "slug": "copyabletext", "components": ["CopyableText"]}, {"title": "SelectableText", "slug": "selectabletext", "components": ["SelectableText"]}, {"title": "Emoji", "slug": "emoji", "components": ["Emoji"]}, {"title": "Latex / MathInline", "slug": "latex-mathinline", "components": ["Latex", "MathInline"]}, {"title": "AnimatedNumber", "slug": "animatednumber", "components": ["AnimatedNumber"]}, {"title": "Typewriter", "slug": "typewriter", "components": ["Typewriter"]}, {"title": "GradientText", "slug": "gradienttext", "components": ["GradientText"]}, {"title": "ShimmerText", "slug": "shimmertext", "components": ["ShimmerText"]}]},
{"number": 3, "slug": "layout", "title": "Layout", "summary": "Stacks, grids, splits, docks. Structure that stays out of the way.", "stories": [{"title": "Stack / HStack / VStack", "slug": "stack-hstack-vstack", "components": ["Stack", "HStack", "VStack"]}, {"title": "Flex", "slug": "flex", "components": ["Flex"]}, {"title": "Grid / GridItem", "slug": "grid-griditem", "components": ["Grid", "GridItem"]}, {"title": "SimpleGrid", "slug": "simplegrid", "components": ["SimpleGrid"]}, {"title": "Masonry", "slug": "masonry", "components": ["Masonry"]}, {"title": "Center", "slug": "center", "components": ["Center"]}, {"title": "Container", "slug": "container", "components": ["Container"]}, {"title": "AspectRatio", "slug": "aspectratio", "components": ["AspectRatio"]}, {"title": "Inset / Padding", "slug": "inset-padding", "components": ["Inset", "Padding"]}, {"title": "Wrap", "slug": "wrap", "components": ["Wrap"]}, {"title": "Absolute / Positioned", "slug": "absolute-positioned", "components": ["Absolute", "Positioned"]}, {"title": "Layer / ZStack", "slug": "layer-zstack", "components": ["Layer", "ZStack"]}, {"title": "ScrollArea", "slug": "scrollarea", "components": ["ScrollArea"]}, {"title": "Scrollbar", "slug": "scrollbar", "components": ["Scrollbar"]}, {"title": "ScrollShadow", "slug": "scrollshadow", "components": ["ScrollShadow"]}, {"title": "ScrollToTop", "slug": "scrolltotop", "components": ["ScrollToTop"]}, {"title": "StickyHeader", "slug": "stickyheader", "components": ["StickyHeader"]}, {"title": "SplitPane", "slug": "splitpane", "components": ["SplitPane"]}, {"title": "ResizeHandle", "slug": "resizehandle", "components": ["ResizeHandle"]}, {"title": "Collapsible", "slug": "collapsible", "components": ["Collapsible"]}, {"title": "Accordion / AccordionItem", "slug": "accordion-accordionitem", "components": ["Accordion", "AccordionItem"]}, {"title": "Card / CardHeader / CardBody / CardFooter", "slug": "card-cardheader-cardbody-cardfooter", "components": ["Card", "CardHeader", "CardBody", "CardFooter"]}, {"title": "Panel / PanelHeader", "slug": "panel-panelheader", "components": ["Panel", "PanelHeader"]}, {"title": "Section / SectionHeader", "slug": "section-sectionheader", "components": ["Section", "SectionHeader"]}, {"title": "Group / Fieldset", "slug": "group-fieldset", "components": ["Group", "Fieldset"]}, {"title": "Frame", "slug": "frame", "components": ["Frame"]}, {"title": "Well", "slug": "well", "components": ["Well"]}, {"title": "Sidebar", "slug": "sidebar", "components": ["Sidebar"]}, {"title": "Drawer", "slug": "drawer", "components": ["Drawer"]}, {"title": "Sheet", "slug": "sheet", "components": ["Sheet"]}, {"title": "Page / PageHeader / PageContent", "slug": "page-pageheader-pagecontent", "components": ["Page", "PageHeader", "PageContent"]}, {"title": "AppShell", "slug": "appshell", "components": ["AppShell"]}, {"title": "MasterDetail", "slug": "masterdetail", "components": ["MasterDetail"]}, {"title": "Dock / DockPanel", "slug": "dock-dockpanel", "components": ["Dock", "DockPanel"]}, {"title": "DockZone / DropIndicator", "slug": "dockzone-dropindicator", "components": ["DockZone", "DropIndicator"]}, {"title": "FloatingPanel", "slug": "floatingpanel", "components": ["FloatingPanel"]}, {"title": "Pane / PaneGroup", "slug": "pane-panegroup", "components": ["Pane", "PaneGroup"]}, {"title": "Workspace", "slug": "workspace", "components": ["Workspace"]}, {"title": "Viewport", "slug": "viewport", "components": ["Viewport"]}]},
{"number": 4, "slug": "shell", "title": "Window & Shell", "summary": "Title bars, rails, tabs, and the windows around an app.", "stories": [{"title": "TitleBar", "slug": "titlebar", "components": ["TitleBar"]}, {"title": "WindowControls / TrafficLights", "slug": "windowcontrols-trafficlights", "components": ["WindowControls", "TrafficLights"]}, {"title": "WindowDragRegion", "slug": "windowdragregion", "components": ["WindowDragRegion"]}, {"title": "WindowResizeBorder", "slug": "windowresizeborder", "components": ["WindowResizeBorder"]}, {"title": "NativeMenu / MenuBar / JumpList", "slug": "nativemenu-menubar-jumplist", "components": ["NativeMenu", "MenuBar", "JumpList"]}, {"title": "Toolbar / ToolbarGroup / ToolbarSeparator", "slug": "toolbar-toolbargroup-toolbarseparator", "components": ["Toolbar", "ToolbarGroup", "ToolbarSeparator"]}, {"title": "StatusBar / StatusBarItem", "slug": "statusbar-statusbaritem", "components": ["StatusBar", "StatusBarItem"]}, {"title": "ActivityBar / NavigationRail", "slug": "activitybar-navigationrail", "components": ["ActivityBar", "NavigationRail"]}, {"title": "TabBar", "slug": "tabbar", "components": ["TabBar"]}, {"title": "SplashScreen", "slug": "splashscreen", "components": ["SplashScreen"]}, {"title": "AboutDialog", "slug": "aboutdialog", "components": ["AboutDialog"]}, {"title": "TrayIcon / TrayMenu", "slug": "trayicon-traymenu", "components": ["TrayIcon", "TrayMenu"]}, {"title": "DockBadge", "slug": "dockbadge", "components": ["DockBadge"]}, {"title": "SystemNotification", "slug": "systemnotification", "components": ["SystemNotification"]}, {"title": "MultiWindow Manager", "slug": "multiwindow-manager", "components": ["MultiWindow Manager"]}, {"title": "WindowSwitcher", "slug": "windowswitcher", "components": ["WindowSwitcher"]}, {"title": "QuickLauncher", "slug": "quicklauncher", "components": ["QuickLauncher"]}, {"title": "MiniWindow / CompactMode / PictureInPicture", "slug": "miniwindow-compactmode-pictureinpicture", "components": ["MiniWindow", "CompactMode", "PictureInPicture"]}, {"title": "UpdateBanner", "slug": "updatebanner", "components": ["UpdateBanner"]}, {"title": "UpdateDialog", "slug": "updatedialog", "components": ["UpdateDialog"]}, {"title": "CrashReporter", "slug": "crashreporter", "components": ["CrashReporter"]}, {"title": "OfflineIndicator", "slug": "offlineindicator", "components": ["OfflineIndicator"]}, {"title": "ZoomControl", "slug": "zoomcontrol", "components": ["ZoomControl"]}]},
{"number": 4, "slug": "shell", "title": "Window & Shell", "summary": "Title bars, rails, tabs, and the windows around an app.", "stories": [{"title": "TitleBar", "slug": "titlebar", "components": ["TitleBar"]}, {"title": "WindowControls / TrafficLights", "slug": "windowcontrols-trafficlights", "components": ["WindowControls", "TrafficLights"]}, {"title": "WindowDragRegion", "slug": "windowdragregion", "components": ["WindowDragRegion"]}, {"title": "WindowResizeBorder", "slug": "windowresizeborder", "components": ["WindowResizeBorder"]}, {"title": "NativeMenu / MenuBar / JumpList", "slug": "nativemenu-menubar-jumplist", "components": ["NativeMenu", "MenuBar", "JumpList"]}, {"title": "Toolbar / ToolbarGroup / ToolbarSeparator", "slug": "toolbar-toolbargroup-toolbarseparator", "components": ["Toolbar", "ToolbarGroup", "ToolbarSeparator"]}, {"title": "StatusBar / StatusBarItem", "slug": "statusbar-statusbaritem", "components": ["StatusBar", "StatusBarItem"]}, {"title": "ActivityBar / NavigationRail", "slug": "activitybar-navigationrail", "components": ["ActivityBar", "NavigationRail"]}, {"title": "TabBar", "slug": "tabbar", "components": ["TabBar"]}, {"title": "SplashScreen", "slug": "splashscreen", "components": ["SplashScreen"]}, {"title": "AboutDialog", "slug": "aboutdialog", "components": ["AboutDialog"]}, {"title": "TrayIcon / TrayMenu", "slug": "trayicon-traymenu", "components": ["TrayIcon", "TrayMenu"]}, {"title": "DockBadge", "slug": "dockbadge", "components": ["DockBadge"]}, {"title": "SystemNotification", "slug": "systemnotification", "components": ["SystemNotification"]}, {"title": "MultiWindow Manager", "slug": "multiwindow-manager", "components": ["MultiWindow Manager"]}, {"title": "WindowSwitcher", "slug": "windowswitcher", "components": ["WindowSwitcher"]}, {"title": "QuickLauncher", "slug": "quicklauncher", "components": ["QuickLauncher"]}, {"title": "MiniWindow / CompactMode / PictureInPicture", "slug": "miniwindow-compactmode-pictureinpicture", "components": ["MiniWindow", "CompactMode", "PictureInPicture", "AlwaysOnTop"]}, {"title": "UpdateBanner", "slug": "updatebanner", "components": ["UpdateBanner"]}, {"title": "UpdateDialog", "slug": "updatedialog", "components": ["UpdateDialog"]}, {"title": "CrashReporter", "slug": "crashreporter", "components": ["CrashReporter"]}, {"title": "OfflineIndicator", "slug": "offlineindicator", "components": ["OfflineIndicator"]}, {"title": "ZoomControl", "slug": "zoomcontrol", "components": ["ZoomControl"]}]},
{"number": 5, "slug": "buttons", "title": "Buttons & Actions", "summary": "Seven variants, three sizes, one focus ring, and the verbs around them.", "stories": [{"title": "Button", "slug": "button", "components": ["Button"]}, {"title": "Sizes", "slug": "sizes", "components": []}, {"title": "With icons", "slug": "with-icons", "components": []}, {"title": "Icon button", "slug": "icon-button", "components": ["IconButton"]}, {"title": "ShortcutHint", "slug": "shortcuthint", "components": ["ShortcutHint"]}, {"title": "ButtonGroup", "slug": "buttongroup", "components": ["ButtonGroup"]}, {"title": "SplitButton / DropdownButton / MenuButton / LoadingButton", "slug": "splitbutton-dropdownbutton-menubutton-loadingbutton", "components": ["SplitButton", "DropdownButton", "MenuButton", "LoadingButton"]}, {"title": "ToggleButton", "slug": "togglebutton", "components": ["ToggleButton"]}, {"title": "ToggleGroup", "slug": "togglegroup", "components": ["ToggleGroup"]}, {"title": "SegmentedControl", "slug": "segmentedcontrol", "components": ["SegmentedControl"]}, {"title": "FloatingActionButton", "slug": "floatingactionbutton", "components": ["FloatingActionButton"]}, {"title": "ConfirmButton / HoldToConfirm", "slug": "confirmbutton-holdtoconfirm", "components": ["ConfirmButton", "HoldToConfirm"]}, {"title": "CopyButton / ShareButton / CloseButton / BackButton / MoreButton", "slug": "copybutton-sharebutton-closebutton-backbutton-morebutton", "components": ["CopyButton", "ShareButton", "CloseButton", "BackButton", "MoreButton"]}, {"title": "ActionBar", "slug": "actionbar", "components": ["ActionBar"]}, {"title": "BulkActionBar", "slug": "bulkactionbar", "components": ["BulkActionBar"]}, {"title": "ActionSheet", "slug": "actionsheet", "components": ["ActionSheet"]}, {"title": "QuickActions", "slug": "quickactions", "components": ["QuickActions"]}]},
{"number": 6, "slug": "forms", "title": "Forms", "summary": "Text, choices, dates, colors, files: every way to say what you mean.", "stories": [{"title": "Input / TextField / ClearableInput", "slug": "input-textfield-clearableinput", "components": ["Input", "TextField", "ClearableInput"]}, {"title": "TextArea / AutoResizeInput", "slug": "textarea-autoresizeinput", "components": ["TextArea", "AutoResizeInput"]}, {"title": "PasswordInput", "slug": "passwordinput", "components": ["PasswordInput"]}, {"title": "SearchInput", "slug": "searchinput", "components": ["SearchInput"]}, {"title": "NumberInput", "slug": "numberinput", "components": ["NumberInput"]}, {"title": "ScrubInput", "slug": "scrubinput", "components": ["ScrubInput"]}, {"title": "CurrencyInput / PercentInput / UnitInput", "slug": "currencyinput-percentinput-unitinput", "components": ["CurrencyInput", "PercentInput", "UnitInput"]}, {"title": "EmailInput / UrlInput", "slug": "emailinput-urlinput", "components": ["EmailInput", "UrlInput"]}, {"title": "PhoneInput / MaskedInput", "slug": "phoneinput-maskedinput", "components": ["PhoneInput", "MaskedInput"]}, {"title": "PinInput / OTPInput", "slug": "pininput-otpinput", "components": ["PinInput", "OTPInput"]}, {"title": "InlineEdit / EditableText", "slug": "inlineedit-editabletext", "components": ["InlineEdit", "EditableText"]}, {"title": "InputGroup / InputAddon", "slug": "inputgroup-inputaddon", "components": ["InputGroup", "InputAddon"]}, {"title": "MentionInput / HashtagInput", "slug": "mentioninput-hashtaginput", "components": ["MentionInput", "HashtagInput"]}, {"title": "TagInput / TokenInput", "slug": "taginput-tokeninput", "components": ["TagInput", "TokenInput"]}, {"title": "KeyValueInput / ListInput", "slug": "keyvalueinput-listinput", "components": ["KeyValueInput", "ListInput"]}, {"title": "PathInput", "slug": "pathinput", "components": ["PathInput"]}, {"title": "RegexInput", "slug": "regexinput", "components": ["RegexInput"]}, {"title": "ExpressionInput", "slug": "expressioninput", "components": ["ExpressionInput"]}, {"title": "HotkeyInput / ShortcutRecorder", "slug": "hotkeyinput-shortcutrecorder", "components": ["HotkeyInput", "ShortcutRecorder"]}, {"title": "Checkbox / CheckboxGroup", "slug": "checkbox-checkboxgroup", "components": ["Checkbox", "CheckboxGroup"]}, {"title": "Radio / RadioGroup", "slug": "radio-radiogroup", "components": ["Radio", "RadioGroup"]}, {"title": "CheckboxCard / RadioCard", "slug": "checkboxcard-radiocard", "components": ["CheckboxCard", "RadioCard"]}, {"title": "Switch / Toggle", "slug": "switch-toggle", "components": ["Switch", "Toggle"]}, {"title": "Select / NativeSelect", "slug": "select-nativeselect", "components": ["Select", "NativeSelect"]}, {"title": "ListBox", "slug": "listbox", "components": ["ListBox"]}, {"title": "Combobox / Autocomplete", "slug": "combobox-autocomplete", "components": ["Combobox", "Autocomplete"]}, {"title": "MultiSelect", "slug": "multiselect", "components": ["MultiSelect"]}, {"title": "CascadeSelect / Cascader", "slug": "cascadeselect-cascader", "components": ["CascadeSelect", "Cascader"]}, {"title": "TransferList", "slug": "transferlist", "components": ["Transfer List"]}, {"title": "ChoiceChips / FilterChips", "slug": "choicechips-filterchips", "components": ["ChoiceChips", "FilterChips"]}, {"title": "Rating", "slug": "rating", "components": ["Rating"]}, {"title": "Slider / RangeSlider / VerticalSlider", "slug": "slider-rangeslider-verticalslider", "components": ["Slider", "RangeSlider", "VerticalSlider"]}, {"title": "Knob / Dial / Stepper", "slug": "knob-dial-stepper", "components": ["Knob", "Dial", "Stepper"]}, {"title": "Calendar", "slug": "calendar", "components": ["Calendar"]}, {"title": "DatePicker / DateRangePicker / DateTimePicker / TimePicker", "slug": "datepicker-daterangepicker-datetimepicker-timepicker", "components": ["DatePicker", "DateRangePicker", "DateTimePicker", "TimePicker"]}, {"title": "MonthPicker / YearPicker / QuarterPicker / WeekPicker", "slug": "monthpicker-yearpicker-quarterpicker-weekpicker", "components": ["MonthPicker", "YearPicker", "QuarterPicker", "WeekPicker"]}, {"title": "RelativeDatePicker / TimezoneSelect / DurationPicker", "slug": "relativedatepicker-timezoneselect-durationpicker", "components": ["RelativeDatePicker", "TimezoneSelect", "DurationPicker"]}, {"title": "CronEditor", "slug": "croneditor", "components": ["CronEditor"]}, {"title": "ColorPicker", "slug": "colorpicker", "components": ["ColorPicker"]}, {"title": "ColorSwatch / ColorPalette / EyeDropper", "slug": "colorswatch-colorpalette-eyedropper", "components": ["ColorSwatch", "ColorPalette", "EyeDropper"]}, {"title": "GradientEditor", "slug": "gradienteditor", "components": ["GradientEditor"]}, {"title": "FileInput / DropZone", "slug": "fileinput-dropzone", "components": ["FileInput", "DropZone"]}, {"title": "SignaturePad", "slug": "signaturepad", "components": ["SignaturePad"]}, {"title": "CodeInput / JsonInput", "slug": "codeinputLine truncated
{"number": 6, "slug": "forms", "title": "Forms", "summary": "Text, choices, dates, colors, files: every way to say what you mean.", "stories": [{"title": "Input / TextField / ClearableInput", "slug": "input-textfield-clearableinput", "components": ["Input", "TextField", "ClearableInput"]}, {"title": "TextArea / AutoResizeInput", "slug": "textarea-autoresizeinput", "components": ["TextArea", "AutoResizeInput"]}, {"title": "PasswordInput", "slug": "passwordinput", "components": ["PasswordInput"]}, {"title": "SearchInput", "slug": "searchinput", "components": ["SearchInput"]}, {"title": "NumberInput", "slug": "numberinput", "components": ["NumberInput"]}, {"title": "ScrubInput", "slug": "scrubinput", "components": ["ScrubInput"]}, {"title": "CurrencyInput / PercentInput / UnitInput", "slug": "currencyinput-percentinput-unitinput", "components": ["CurrencyInput", "PercentInput", "UnitInput"]}, {"title": "EmailInput / UrlInput", "slug": "emailinput-urlinput", "components": ["EmailInput", "UrlInput"]}, {"title": "PhoneInput / MaskedInput", "slug": "phoneinput-maskedinput", "components": ["PhoneInput", "MaskedInput"]}, {"title": "PinInput / OTPInput", "slug": "pininput-otpinput", "components": ["PinInput", "OTPInput"]}, {"title": "InlineEdit / EditableText", "slug": "inlineedit-editabletext", "components": ["InlineEdit", "EditableText"]}, {"title": "InputGroup / InputAddon", "slug": "inputgroup-inputaddon", "components": ["InputGroup", "InputAddon"]}, {"title": "MentionInput / HashtagInput", "slug": "mentioninput-hashtaginput", "components": ["MentionInput", "HashtagInput"]}, {"title": "TagInput / TokenInput", "slug": "taginput-tokeninput", "components": ["TagInput", "TokenInput"]}, {"title": "KeyValueInput / ListInput", "slug": "keyvalueinput-listinput", "components": ["KeyValueInput", "ListInput"]}, {"title": "PathInput", "slug": "pathinput", "components": ["PathInput"]}, {"title": "RegexInput", "slug": "regexinput", "components": ["RegexInput"]}, {"title": "ExpressionInput", "slug": "expressioninput", "components": ["ExpressionInput"]}, {"title": "HotkeyInput / ShortcutRecorder", "slug": "hotkeyinput-shortcutrecorder", "components": ["HotkeyInput", "ShortcutRecorder"]}, {"title": "Checkbox / CheckboxGroup", "slug": "checkbox-checkboxgroup", "components": ["Checkbox", "CheckboxGroup"]}, {"title": "Radio / RadioGroup", "slug": "radio-radiogroup", "components": ["Radio", "RadioGroup"]}, {"title": "CheckboxCard / RadioCard", "slug": "checkboxcard-radiocard", "components": ["CheckboxCard", "RadioCard"]}, {"title": "Switch / Toggle", "slug": "switch-toggle", "components": ["Switch", "Toggle"]}, {"title": "Select / NativeSelect", "slug": "select-nativeselect", "components": ["Select", "NativeSelect"]}, {"title": "ListBox", "slug": "listbox", "components": ["ListBox"]}, {"title": "Combobox / Autocomplete", "slug": "combobox-autocomplete", "components": ["Combobox", "Autocomplete"]}, {"title": "MultiSelect", "slug": "multiselect", "components": ["MultiSelect"]}, {"title": "CascadeSelect / Cascader", "slug": "cascadeselect-cascader", "components": ["CascadeSelect", "Cascader"]}, {"title": "TransferList", "slug": "transferlist", "components": ["Transfer List"]}, {"title": "ChoiceChips / FilterChips", "slug": "choicechips-filterchips", "components": ["ChoiceChips", "FilterChips"]}, {"title": "Rating", "slug": "rating", "components": ["Rating"]}, {"title": "Slider / RangeSlider / VerticalSlider", "slug": "slider-rangeslider-verticalslider", "components": ["Slider", "RangeSlider", "VerticalSlider"]}, {"title": "Knob / Dial / Stepper", "slug": "knob-dial-stepper", "components": ["Knob", "Dial", "Stepper"]}, {"title": "Calendar", "slug": "calendar", "components": ["Calendar"]}, {"title": "DatePicker / DateRangePicker / DateTimePicker / TimePicker", "slug": "datepicker-daterangepicker-datetimepicker-timepicker", "components": ["DatePicker", "DateRangePicker", "DateTimePicker", "TimePicker"]}, {"title": "MonthPicker / YearPicker / QuarterPicker / WeekPicker", "slug": "monthpicker-yearpicker-quarterpicker-weekpicker", "components": ["MonthPicker", "YearPicker", "QuarterPicker", "WeekPicker"]}, {"title": "RelativeDatePicker / TimezoneSelect / DurationPicker", "slug": "relativedatepicker-timezoneselect-durationpicker", "components": ["RelativeDatePicker", "TimezoneSelect", "DurationPicker"]}, {"title": "CronEditor", "slug": "croneditor", "components": ["CronEditor"]}, {"title": "ColorPicker", "slug": "colorpicker", "components": ["ColorPicker"]}, {"title": "ColorSwatch / ColorPalette / EyeDropper", "slug": "colorswatch-colorpalette-eyedropper", "components": ["ColorSwatch", "ColorPalette", "EyeDropper"]}, {"title": "GradientEditor", "slug": "gradienteditor", "components": ["GradientEditor"]}, {"title": "FileInput / DropZone", "slug": "fileinput-dropzone", "components": ["FileInput", "DropZone"]}, {"title": "SignaturePad", "slug": "signaturepad", "components": ["SignaturePad"]}, {"title": "CodeInput / JsonInput", "slug": "codeinputLine truncated
{"number": 7, "slug": "navigation", "title": "Navigation", "summary": "Tabs, paths, places and palettes: where you are, and the ways out.", "stories": [{"title": "Tabs", "slug": "tabs", "components": ["Tabs"]}, {"title": "EditorTabs / TabOverflowMenu", "slug": "editortabs-taboverflowmenu", "components": ["EditorTabs", "TabOverflowMenu"]}, {"title": "Breadcrumb", "slug": "breadcrumb", "components": ["Breadcrumb"]}, {"title": "Pagination", "slug": "pagination", "components": ["Pagination"]}, {"title": "Steps / Wizard", "slug": "steps-wizard", "components": ["Steps", "Wizard"]}, {"title": "Sidebar Navigation / NavItem / NavGroup", "slug": "sidebar-navigation-navitem-navgroup", "components": ["Sidebar Navigation", "NavItem", "NavGroup"]}, {"title": "NavigationMenu", "slug": "navigationmenu", "components": ["NavigationMenu"]}, {"title": "BackForwardNavigation / History", "slug": "backforwardnavigation-history", "components": ["BackForwardNavigation"]}, {"title": "Anchor / TableOfContents", "slug": "anchor-tableofcontents", "components": ["Anchor", "TableOfContents"]}, {"title": "JumpTo / GoToLine", "slug": "jumpto-gotoline", "components": ["JumpTo", "GoToLine"]}, {"title": "CommandPalette", "slug": "commandpalette", "components": ["CommandPalette"]}, {"title": "QuickOpen", "slug": "quickopen", "components": ["QuickOpen"]}, {"title": "QuickSwitcher", "slug": "quickswitcher", "components": ["QuickSwitcher"]}, {"title": "SearchPalette / SpotlightSearch / QuickLauncher", "slug": "searchpalette-spotlightsearch-quicklauncher", "components": ["SearchPalette", "SpotlightSearch", "QuickLauncher"]}]},
{"number": 8, "slug": "menus", "title": "Menus", "summary": "Rows of actions that open from a button, a right click or an arrow, and nest.", "stories": [{"title": "Menu / MenuItem / MenuGroup / MenuSeparator / SubMenu / MenuItemShortcut / MenuItemIcon", "slug": "menu-menuitem-menugroup-menuseparator-submenu-menuitemshortcut-menuitemicon", "components": ["Menu", "MenuItem", "MenuGroup", "MenuSeparator", "SubMenu", "MenuItemShortcut", "MenuItemIcon"]}, {"title": "CheckboxMenuItem / RadioMenuItem", "slug": "checkboxmenuitem-radiomenuitem", "components": ["CheckboxMenuItem", "RadioMenuItem"]}, {"title": "DropdownMenu / SplitButton / OverflowMenu", "slug": "dropdownmenu-splitbutton-overflowmenu", "components": ["DropdownMenu", "SplitButton", "OverflowMenu"]}, {"title": "ContextMenu", "slug": "contextmenu", "components": ["ContextMenu"]}, {"title": "MenuBar Menu", "slug": "menubar-menu", "components": ["MenuBar Menu"]}, {"title": "MegaMenu", "slug": "megamenu", "components": ["MegaMenu"]}, {"title": "SearchableMenu", "slug": "searchablemenu", "components": ["SearchableMenu"]}, {"title": "PieMenu / RadialMenu", "slug": "piemenu-radialmenu", "components": ["PieMenu", "RadialMenu"]}]},
{"number": 9, "slug": "overlays", "title": "Overlays", "summary": "Panels over the page: anchored to a control, over a scrim, or over everything.", "stories": [{"title": "Popover / DropdownPanel / InlinePopup", "slug": "popover-dropdownpanel-inlinepopup", "components": ["Popover", "DropdownPanel", "InlinePopup"]}, {"title": "HoverCard", "slug": "hovercard", "components": ["HoverCard"]}, {"title": "Dialog / Modal / AlertDialog / ConfirmDialog / PromptDialog / FullscreenDialog", "slug": "dialog-modal-alertdialog-confirmdialog-promptdialog-fullscreendialog", "components": ["Dialog", "Modal", "AlertDialog", "ConfirmDialog", "PromptDialog", "FullscreenDialog"]}, {"title": "Lightbox", "slug": "lightbox", "components": ["Lightbox"]}, {"title": "Spotlight / Coachmark / Tour", "slug": "spotlight-coachmark-tour", "components": ["Spotlight", "Coachmark", "Tour"]}, {"title": "FloatingToolbar", "slug": "floatingtoolbar", "components": ["FloatingToolbar"]}, {"title": "Callout / Peek", "slug": "callout-peek", "components": ["Callout", "Peek"]}, {"title": "Tooltip / Sheet / Drawer / DropdownPanel / InlinePopup / ToastViewport", "slug": "tooltip-sheet-drawer-dropdownpanel-inlinepopup-toastviewport", "components": ["Tooltip", "Sheet", "Drawer", "DropdownPanel", "InlinePopup", "ToastViewport"]}]},
@@ -12,7 +12,7 @@
{"number": 11, "slug": "motion", "title": "Loading & Motion", "summary": "Waiting, shown honestly, and the motion everything else moves with.", "stories": [{"title": "Spinner / DotsLoader / BarsLoader / PulseLoader / RingLoader / OrbitLoader / WaveLoader / InlineLoader", "slug": "spinner-dotsloader-barsloader-pulseloader-ringloader-orbitloader-waveloader-inlineloader", "components": ["Spinner", "DotsLoader", "BarsLoader", "PulseLoader", "RingLoader", "OrbitLoader", "WaveLoader", "InlineLoader"]}, {"title": "Skeleton / SkeletonText / SkeletonAvatar / SkeletonCard / SkeletonTable", "slug": "skeleton-skeletontext-skeletonavatar-skeletoncard-skeletontable", "components": ["Skeleton", "SkeletonText", "SkeletonAvatar", "SkeletonCard", "SkeletonTable"]}, {"title": "Shimmer", "slug": "shimmer", "components": ["Shimmer"]}, {"title": "LoadingButton", "slug": "loadingbutton", "components": ["LoadingButton"]}, {"title": "LoadMore", "slug": "loadmore", "components": ["LoadMore"]}, {"title": "ProgressBar / ProgressCircle / ProgressRing", "slug": "progressbar-progresscircle-progressring", "components": ["ProgressBar", "ProgressCircle", "ProgressRing"]}, {"title": "LoadingOverlay", "slug": "loadingoverlay", "components": ["LoadingOverlay"]}, {"title": "LazyLoad", "slug": "lazyload", "components": ["LazyLoad"]}, {"title": "Suspense / AsyncView", "slug": "suspense-asyncview", "components": ["Suspense", "AsyncView"]}, {"title": "RefreshIndicator / TypingIndicator", "slug": "refreshindicator-typingindicator", "components": ["RefreshIndicator", "TypingIndicator"]}, {"title": "UploadList", "slug": "uploadlist", "components": ["UploadList"]}, {"title": "Transition", "slug": "transition", "components": ["Transition"]}, {"title": "Stagger", "slug": "stagger", "components": ["Stagger"]}, {"title": "AnimatePresence", "slug": "animatepresence", "components": ["AnimatePresence"]}, {"title": "Flip", "slug": "flip", "components": ["Flip"]}, {"title": "Reorder", "slug": "reorder", "components": ["Reorder"]}, {"title": "Marquee", "slug": "marquee", "components": ["Marquee"]}, {"title": "Ripple / Confetti", "slug": "ripple-confetti", "components": ["Ripple", "Confetti"]}, {"title": "Glow / Pulse", "slug": "glow-pulse", "components": ["Glow", "Pulse"]}, {"title": "Blink / Flash / Shake", "slug": "blink-flash-shake", "components": ["Blink", "Flash", "Shake"]}, {"title": "ParticleBackground / AnimatedGradient / Lottie Player", "slug": "particlebackground-animatedgradient-lottie-player", "components": ["ParticleBackground", "AnimatedGradient", "Lottie Player"]}]},
{"number": 12, "slug": "data", "title": "Data Display", "summary": "People, numbers and records, shown plainly.", "stories": [{"title": "Badge / CountBadge / DotBadge", "slug": "badge-countbadge-dotbadge", "components": ["Badge", "CountBadge", "DotBadge"]}, {"title": "Tag / Chip", "slug": "tag-chip", "components": ["Tag", "Chip"]}, {"title": "Avatar / AvatarUpload", "slug": "avatar-avatarupload", "components": ["Avatar", "AvatarUpload"]}, {"title": "AvatarGroup / UserChip", "slug": "avatargroup-userchip", "components": ["AvatarGroup", "UserChip"]}, {"title": "Statistic / KPI Card / Metric / TrendIndicator", "slug": "statistic-kpi-card-metric-trendindicator", "components": ["Statistic", "KPI Card", "Metric", "TrendIndicator"]}, {"title": "DescriptionList / KeyValueList / InfoRow", "slug": "descriptionlist-keyvaluelist-inforow", "components": ["DescriptionList", "KeyValueList", "InfoRow"]}, {"title": "PropertyGrid", "slug": "propertygrid", "components": ["PropertyGrid"]}, {"title": "Timeline / TimelineItem", "slug": "timeline-timelineitem", "components": ["Timeline", "TimelineItem"]}, {"title": "ActivityFeed", "slug": "activityfeed", "components": ["ActivityFeed"]}, {"title": "Changelog", "slug": "changelog", "components": ["Changelog"]}, {"title": "Carousel", "slug": "carousel", "components": ["Carousel"]}, {"title": "Gallery", "slug": "gallery", "components": ["Gallery"]}, {"title": "BeforeAfter", "slug": "beforeafter", "components": ["BeforeAfter"]}, {"title": "Watermark", "slug": "watermark", "components": ["Watermark"]}, {"title": "QRCode / Barcode", "slug": "qrcode-barcode", "components": ["QRCode", "Barcode"]}, {"title": "Meter / Gauge / UsageBar", "slug": "meter-gauge-usagebar", "components": ["Meter", "Gauge", "UsageBar"]}, {"title": "Stars / Rating display", "slug": "stars-rating-display", "components": ["Rating display"]}, {"title": "Comparison", "slug": "comparison", "components": ["Comparison"]}]},
{"number": 13, "slug": "lists", "title": "Lists & Trees", "summary": "Rows to read, pick, reorder and swipe; lists long enough to need care.", "stories": [{"title": "List / ListItem", "slug": "list-listitem", "components": ["List", "ListItem"]}, {"title": "SelectableList", "slug": "selectablelist", "components": ["SelectableList"]}, {"title": "SortableList / ReorderableList", "slug": "sortablelist-reorderablelist", "components": ["SortableList", "ReorderableList"]}, {"title": "SwipeableListItem", "slug": "swipeablelistitem", "components": ["SwipeableListItem"]}, {"title": "VirtualList", "slug": "virtuallist", "components": ["VirtualList"]}, {"title": "InfiniteList", "slug": "infinitelist", "components": ["InfiniteList"]}, {"title": "GroupedList / SectionList", "slug": "groupedlist-sectionlist", "components": ["GroupedList", "SectionList"]}, {"title": "Tree / TreeView / VirtualTree", "slug": "tree-treeview-virtualtree", "components": ["Tree", "TreeView", "VirtualTree"]}, {"title": "CheckboxTree", "slug": "checkboxtree", "components": ["CheckboxTree"]}, {"title": "TreeSelect", "slug": "treeselect", "components": ["TreeSelect"]}, {"title": "Outline / NestedList", "slug": "outline-nestedlist", "components": ["Outline", "NestedList"]}, {"title": "FileTree", "slug": "filetree", "components": ["FileTree"]}, {"title": "DirectoryListing", "slug": "directorylisting", "components": ["DirectoryListing"]}]},
{"number": 14, "slug": "tables", "title": "Tables", "summary": "Rows and columns that sort, filter, page and select, and stay quick at a hundred thousand rows.", "stories": [{"title": "Table", "slug": "table", "components": ["Table"]}, {"title": "DataTable / TableToolbar / FilterBuilder / SortBuilder / ColumnVisibility / CellRenderer / RowSelection / TableDensity / AggregationFooter / ConditionalFormatting", "slug": "datatable-tabletoolbar-filterbuilder-sortbuilder-columnvisibility-cellrenderer-rowselection-tabledensity-aggregationfooter-conditionalformatting", "components": ["DataTable", "TableToolbar", "FilterBuilder", "SortBuilder", "ColumnVisibility", "CellRenderer", "RowSelection", "TableDensity", "AggregationFooter", "ConditionalFormatting"]}, {"title": "ColumnPinning / ColumnResizer / ColumnReorder", "slug": "columnpinning-columnresizer-columnreorder", "components": ["ColumnPinning", "ColumnResizer", "ColumnReorder"]}, {"title": "RowExpansion / InlineRowEdit", "slug": "rowexpansion-inlinerowedit", "components": ["RowExpansion", "InlineRowEdit"]}, {"title": "RowGrouping", "slug": "rowgrouping", "components": ["RowGrouping"]}, {"title": "DataGrid", "slug": "datagrid", "components": ["DataGrid"]}, {"title": "Spreadsheet", "slug": "spreadsheet", "components": ["Spreadsheet"]}, {"title": "TreeTable", "slug": "treetable", "components": ["TreeTable"]}, {"title": "PivotTable", "slug": "pivottable", "components": ["PivotTable"]}, {"title": "VirtualTable", "slug": "virtualtable", "components": ["VirtualTable"]}, {"title": "HeatmapTable", "slug": "heatmaptable", "components": ["HeatmapTable"]}]},
{"number": 14, "slug": "tables", "title": "Tables", "summary": "Rows and columns that sort, filter, page and select, and stay quick at a hundred thousand rows.", "stories": [{"title": "Table", "slug": "table", "components": ["Table"]}, {"title": "DataTable / TableToolbar / FilterBuilder / SortBuilder / ColumnVisibility / CellRenderer / RowSelection / TableDensity / AggregationFooter / ConditionalFormatting", "slug": "datatable-tabletoolbar-filterbuilder-sortbuilder-columnvisibility-cellrenderer-rowselection-tabledensity-aggregationfooter-conditionalformatting", "components": ["DataTable", "TableToolbar", "FilterBuilder", "SortBuilder", "ColumnVisibility", "CellRenderer", "RowSelection", "TableDensity", "AggregationFooter", "ConditionalFormatting", "Sparkline"]}, {"title": "ColumnPinning / ColumnResizer / ColumnReorder", "slug": "columnpinning-columnresizer-columnreorder", "components": ["ColumnPinning", "ColumnResizer", "ColumnReorder"]}, {"title": "RowExpansion / InlineRowEdit", "slug": "rowexpansion-inlinerowedit", "components": ["RowExpansion", "InlineRowEdit"]}, {"title": "RowGrouping", "slug": "rowgrouping", "components": ["RowGrouping"]}, {"title": "DataGrid", "slug": "datagrid", "components": ["DataGrid"]}, {"title": "Spreadsheet", "slug": "spreadsheet", "components": ["Spreadsheet"]}, {"title": "TreeTable", "slug": "treetable", "components": ["TreeTable"]}, {"title": "PivotTable", "slug": "pivottable", "components": ["PivotTable"]}, {"title": "VirtualTable", "slug": "virtualtable", "components": ["VirtualTable"]}, {"title": "HeatmapTable", "slug": "heatmaptable", "components": ["HeatmapTable"]}]},
{"number": 15, "slug": "charts", "title": "Charts", "summary": "Lines, bars, parts of a whole, spreads, flows and schedules on quiet axes, with legends, tooltips, zoom and export.", "stories": [{"title": "LineChart / ChartAxis / ChartGrid / ChartLegend / ChartTooltip / ChartCrosshair / ChartAnnotation / ChartZoom / ChartExport", "slug": "linechart-chartaxis-chartgrid-chartlegend-charttooltip-chartcrosshair-chartannotation-chartzoom-chartexport", "components": ["LineChart", "ChartAxis", "ChartGrid", "ChartLegend", "ChartTooltip", "ChartCrosshair", "ChartAnnotation", "ChartZoom", "ChartExport"]}, {"title": "AreaChart / StackedArea / BarChart", "slug": "areachart-stackedarea-barchart", "components": ["AreaChart", "StackedArea", "BarChart"]}, {"title": "PieChart / DonutChart", "slug": "piechart-donutchart", "components": ["PieChart", "DonutChart"]}, {"title": "ScatterChart / BubbleChart", "slug": "scatterchart-bubblechart", "components": ["ScatterChart", "BubbleChart"]}, {"title": "RadarChart", "slug": "radarchart", "components": ["RadarChart"]}, {"title": "HeatmapChart / CalendarHeatmap", "slug": "heatmapchart-calendarheatmap", "components": ["HeatmapChart", "CalendarHeatmap"]}, {"title": "Treemap / Sunburst", "slug": "treemap-sunburst", "components": ["Treemap", "Sunburst"]}, {"title": "SankeyChart / FunnelChart", "slug": "sankeychart-funnelchart", "components": ["SankeyChart", "FunnelChart"]}, {"title": "WaterfallChart", "slug": "waterfallchart", "components": ["WaterfallChart"]}, {"title": "Histogram / BoxPlot / ViolinPlot", "slug": "histogram-boxplot-violinplot", "components": ["Histogram", "BoxPlot", "ViolinPlot"]}, {"title": "GanttChart", "slug": "ganttchart", "components": ["GanttChart"]}, {"title": "NetworkGraph / ForceGraph / ChordDiagram / ParallelCoordinates", "slug": "networkgraph-forcegraph-chorddiagram-parallelcoordinates", "components": ["NetworkGraph", "ForceGraph", "ChordDiagram", "ParallelCoordinates"]}, {"title": "Bullet Chart / ProgressChart", "slug": "bullet-chart-progresschart", "components": ["Bullet Chart", "ProgressChart"]}, {"title": "RealtimeChart", "slug": "realtimechart", "components": ["RealtimeChart"]}]},
{"number": 16, "slug": "finance", "title": "Finance", "summary": "Market charts with the studies traders read, books, quotes, order entry and portfolios.", "stories": [{"title": "CandlestickChart / OHLCChart / HeikinAshiChart / LineQuoteChart / VolumeChart / ChartTypeSwitcher / TimeRangeSelector / IntervalSelector", "slug": "candlestickchart-ohlcchart-heikinashichart-linequotechart-volumechart-charttypeswitcher-timerangeselector-intervalselector", "components": ["CandlestickChart", "OHLCChart", "HeikinAshiChart", "LineQuoteChart", "VolumeChart", "ChartTypeSwitcher", "TimeRangeSelector", "IntervalSelector"]}, {"title": "IndicatorOverlay / IndicatorPane / IndicatorSelector / CompareSymbol / PriceAlertLine", "slug": "indicatoroverlay-indicatorpane-indicatorselector-comparesymbol-pricealertline", "components": ["IndicatorOverlay", "IndicatorPane", "IndicatorSelector", "CompareSymbol", "PriceAlertLine"]}, {"title": "DrawingTools / DrawingToolbar", "slug": "drawingtools-drawingtoolbar", "components": ["DrawingTools", "DrawingToolbar"]}, {"title": "DepthChart", "slug": "depthchart", "components": ["DepthChart"]}, {"title": "MarketProfile / VolumeProfile", "slug": "marketprofile-volumeprofile", "components": ["MarketProfile", "VolumeProfile"]}, {"title": "RenkoChart / PointAndFigure", "slug": "renkochart-pointandfigure", "components": ["RenkoChart", "PointAndFigure"]}, {"title": "MultiChartLayout / ChartSync", "slug": "multichartlayout-chartsync", "components": ["MultiChartLayout", "ChartSync"]}, {"title": "Ticker / TickerTape / QuoteCard / PriceText / PriceChangeBadge", "slug": "ticker-tickertape-quotecard-pricetext-pricechangebadge", "components": ["Ticker", "TickerTape", "QuoteCard", "PriceText", "PriceChangeBadge"]}, {"title": "OrderBook / SpreadIndicator / BidAskBar / Level2 Quotes", "slug": "orderbook-spreadindicator-bidaskbar-level2-quotes", "components": ["OrderBook", "SpreadIndicator", "BidAskBar", "Level2 Quotes"]}, {"title": "TimeAndSales / TradeTape / MarketDepthLadder / DOM", "slug": "timeandsales-tradetape-marketdepthladder-dom", "components": ["TimeAndSales", "TradeTape", "MarketDepthLadder", "DOM"]}, {"title": "OrderEntry / TradePanel / QuickTradeButtons / OrderConfirmDialog", "slug": "orderentry-tradepanel-quicktradebuttons-orderconfirmdialog", "components": ["OrderEntry", "TradePanel", "QuickTradeButtons", "OrderConfirmDialog"]}, {"title": "PositionTable / OrderTable / TradeHistoryTable / PnLDisplay", "slug": "positiontable-ordertable-tradehistorytable-pnldisplay", "components": ["PositionTable", "OrderTable", "TradeHistoryTable", "PnLDisplay"]}, {"title": "LeverageSlider / MarginIndicator / RiskMeter", "slug": "leverageslider-marginindicator-riskmeter", "components": ["LeverageSlider", "MarginIndicator", "RiskMeter"]}, {"title": "OptionChain / GreeksTable / PayoffDiagram", "slug": "optionchain-greekstable-payoffdiagram", "components": ["OptionChain", "GreeksTable", "PayoffDiagram"]}, {"title": "Watchlist / SymbolBadge / SymbolSearch", "slug": "watchlist-symbolbadge-symbolsearch", "components": ["Watchlist", "SymbolBadge", "SymbolSearch"]}, {"title": "MarketHeatmap / SectorTreemap", "slug": "marketheatmap-sectortreemap", "components": ["MarketHeatmap", "SectorTreemap"]}, {"title": "Screener / FilterPanel", "slug": "screener-filterpanel", "components": ["Screener", "FilterPanel"]}, {"title": "MarketOverview / IndexCard / MarketStatus / TradingSessionClock", "slug": "marketoverview-indexcard-marketstatus-tradingsessionclock", "components": ["MarketOverview", "IndexCard", "MarketStatus", "TradingSessionClock"]}, {"title": "EconomicCalendar / EarningsCalendar / NewsFeed", "slug": "economiccalendar-earningscalendar-newsfeed", "components": ["EconomicCalendar", "EarningsCalendar", "NewsFeed"]}, {"title": "PortfolioSummary / AssetAllocationChart / PerformanceChart / SentimentGauge", "slug": "portfoliosummary-assetallocationchart-performancechart-sentimentgauge", "components": ["PortfolioSummary", "AssetAllocationChart", "PerformanceChart", "SentimentGauge"]}, {"title": "DividendTable / FinancialStatementTable", "slug": "dividendtable-financialstatementtable", "components": ["DividendTable", "FinancialStatementTable"]}, {"title": "CurrencyConverter / CryptoWalletCard / TransactionList / Candle Countdown", "slug": "currencyconverter-cryptowalletcard-transactionlist-candle-countdown", "components": ["CurrencyConverter", "CryptoWalletCard", "TransactionList", "Candle Countdown"]}]},
{"number": 17, "slug": "editor", "title": "Editor", "summary": "A code editor with the gutter, folds and cursors it needs, the help a language server gives, search, the panels around it and its status row.", "stories": [{"title": "CodeEditor / EditorGutter / LineNumbers / FoldingControl / Minimap / Ruler / IndentGuide / BracketMatcher / RainbowBrackets / MultiCursor / SelectionHighlight / StickyScroll / Breadcrumb / InlayHints / CodeLens / InlineDecoration", "slug": "codeeditor-editorgutter-linenumbers-foldingcontrol-minimap-ruler-indentguide-bracketmatcher-rainbowbrackets-multicursor-selectionhighlight-stickyscroll-breadcrumb-inlayhints-codelens-inlinedecoration", "components": ["CodeEditor", "EditorGutter", "LineNumbers", "FoldingControl", "MiniMap", "Ruler", "IndentGuide", "BracketMatcher", "RainbowBrackets", "MultiCursor", "SelectionHighlight", "StickyScroll", "Breadcrumb", "InlayHints", "CodeLens", "InlineDecoration"]}, {"title": "WhitespaceRenderer / CursorRenderer", "slug": "whitespacerenderer-cursorrenderer", "components": ["WhitespaceRenderer", "CursorRenderer"]}, {"title": "InlineDiff / GhostText / InlineChat / ReadOnlyBanner / VimModeIndicator", "slug": "inlinediff-ghosttext-inlinechat-readonlybanner-vimmodeindicator", "components": ["InlineDiff", "GhostText", "InlineChat", "ReadOnlyBanner", "VimModeIndicator"]}, {"title": "CompletionMenu / SignatureHelp / HoverInfo", "slug": "completionmenu-signaturehelp-hoverinfo", "components": ["CompletionMenu", "SignatureHelp", "HoverInfo"]}, {"title": "CodeActionMenu / RenameInput / PeekView / ReferencesPanel / SymbolOutline / GoToSymbol / CallHierarchy / TypeHierarchy", "slug": "codeactionmenu-renameinput-peekview-referencespanel-symboloutline-gotosymbol-callhierarchy-typehierarchy", "components": ["CodeActionMenu", "RenameInput", "PeekView", "ReferencesPanel", "SymbolOutline", "GoToSymbol", "CallHierarchy", "TypeHierarchy"]}, {"title": "FindWidget / ReplaceWidget", "slug": "findwidget-replacewidget", "components": ["FindWidget", "ReplaceWidget"]}, {"title": "SearchPanel / SearchResultItem / SearchFilters", "slug": "searchpanel-searchresultitem-searchfilters", "components": ["SearchPanel", "SearchResultItem", "SearchFilters"]}, {"title": "ProblemsPanel / DiagnosticsList / OutputPanel", "slug": "problemspanel-diagnosticslist-outputpanel", "components": ["ProblemsPanel", "DiagnosticsList", "OutputPanel"]}, {"title": "DebugConsole / ExtensionsPanel / PluginList", "slug": "debugconsole-extensionspanel-pluginlist", "components": ["DebugConsole", "ExtensionsPanel", "PluginList"]}, {"title": "SettingsEditor / KeybindingsEditor", "slug": "settingseditor-keybindingseditor", "components": ["SettingsEditor", "KeybindingsEditor"]}, {"title": "WelcomePage / RecentProjects / ProjectSwitcher / ThemePreview / TaskRunner / RunConfiguration", "slug": "welcomepage-recentprojects-projectswitcher-themepreview-taskrunner-runconfiguration", "components": ["WelcomePage", "RecentProjects", "ProjectSwitcher", "ThemePreview", "TaskRunner", "RunConfiguration"]}, {"title": "CursorPosition / LanguageMode / Encoding / LineEnding / IndentSettings / BranchIndicator / LspStatus / NotificationBell", "slug": "cursorposition-languagemode-encoding-lineending-indentsettings-branchindicator-lspstatus-notificationbell", "components": ["CursorPosition", "LanguageMode", "Encoding", "LineEnding", "IndentSettings", "BranchIndicator", "LspStatus", "NotificationBell"]}]},
@@ -21,7 +21,7 @@
{"number": 20, "slug": "debug", "title": "Debug", "summary": "A paused program: its breakpoints, stack, threads and values, memory and machine code; profiles and network.", "stories": [{"title": "DebugToolbar", "slug": "debugtoolbar", "components": ["DebugToolbar"]}, {"title": "BreakpointList / BreakpointGutter / CallStack / ThreadList", "slug": "breakpointlist-breakpointgutter-callstack-threadlist", "components": ["BreakpointList", "BreakpointGutter", "CallStack", "ThreadList"]}, {"title": "VariablesPanel / WatchPanel / ConsoleREPL", "slug": "variablespanel-watchpanel-consolerepl", "components": ["VariablesPanel", "WatchPanel", "ConsoleREPL"]}, {"title": "MemoryViewer / HexViewer / Disassembly", "slug": "memoryviewer-hexviewer-disassembly", "components": ["MemoryViewer", "HexViewer", "Disassembly"]}, {"title": "PerformanceProfiler / Flamegraph / TimelineProfiler", "slug": "performanceprofiler-flamegraph-timelineprofiler", "components": ["PerformanceProfiler", "Flamegraph", "TimelineProfiler"]}, {"title": "NetworkInspector", "slug": "networkinspector", "components": ["NetworkInspector"]}]},
{"number": 21, "slug": "documents", "title": "Documents", "summary": "Markdown written and read, blocks like a notebook's, and a knowledge base around them.", "stories": [{"title": "RichTextEditor / FixedFormatToolbar / FloatingFormatToolbar / LinkEditor / MentionMenu / EmojiAutocomplete / WordCount / ReadingTime", "slug": "richtexteditor-fixedformattoolbar-floatingformattoolbar-linkeditor-mentionmenu-emojiautocomplete-wordcount-readingtime", "components": ["RichTextEditor", "FixedFormatToolbar", "FloatingFormatToolbar", "LinkEditor", "MentionMenu", "EmojiAutocomplete", "WordCount", "ReadingTime"]}, {"title": "MarkdownEditor", "slug": "markdowneditor", "components": ["MarkdownEditor"]}, {"title": "BlockEditor / BlockHandle / SlashMenu / PlaceholderText", "slug": "blockeditor-blockhandle-slashmenu-placeholdertext", "components": ["BlockEditor", "BlockHandle", "SlashMenu", "PlaceholderText"]}, {"title": "Block types / TableEditor / ImageEditorBlock / MathEditor / DiagramBlock / CodeBlockEditor", "slug": "block-types-tableeditor-imageeditorblock-matheditor-diagramblock-codeblockeditor", "components": ["Block types", "TableEditor", "ImageEditorBlock", "MathEditor", "DiagramBlock", "CodeBlockEditor"]}, {"title": "TodoItem / Checklist", "slug": "todoitem-checklist", "components": ["TodoItem", "Checklist"]}, {"title": "MarkdownRenderer / TableOfContents / DocumentOutline / ReadingProgress", "slug": "markdownrenderer-tableofcontents-documentoutline-readingprogress", "components": ["MarkdownRenderer", "TableOfContents", "DocumentOutline", "ReadingProgress"]}, {"title": "DocumentViewer / PDFViewer / PageThumbnailList", "slug": "documentviewer-pdfviewer-pagethumbnaillist", "components": ["DocumentViewer", "PDFViewer", "PageThumbnailList"]}, {"title": "OfficePreview", "slug": "officepreview", "components": ["OfficePreview"]}, {"title": "EpubReader", "slug": "epubreader", "components": ["EpubReader"]}, {"title": "Footnote / Glossary / TooltipTerm", "slug": "footnote-glossary-tooltipterm", "components": ["Footnote", "Glossary", "TooltipTerm"]}, {"title": "FocusMode / ZenMode", "slug": "focusmode-zenmode", "components": ["FocusMode", "ZenMode"]}, {"title": "PrintPreview", "slug": "printpreview", "components": ["PrintPreview"]}, {"title": "PageTree / Favorites / Pinned", "slug": "pagetree-favorites-pinned", "components": ["PageTree", "Favorites", "Pinned"]}, {"title": "PageCover / PageIcon / PageProperties", "slug": "pagecover-pageicon-pageproperties", "components": ["PageCover", "PageIcon", "PageProperties"]}, {"title": "Backlinks / GraphView", "slug": "backlinks-graphview", "components": ["Backlinks", "GraphView"]}, {"title": "TemplatePicker", "slug": "templatepicker", "components": ["TemplatePicker"]}, {"title": "TrashBin", "slug": "trashbin", "components": ["TrashBin"]}, {"title": "VersionHistory / PageHistoryDiff", "slug": "versionhistory-pagehistorydiff", "components": ["VersionHistory", "PageHistoryDiff"]}]},
{"number": 22, "slug": "collab", "title": "Collaboration", "summary": "People in the same document: who is here and where, comments and reactions, suggested changes, and sharing.", "stories": [{"title": "PresenceAvatars / LiveIndicator / FollowMode / RemoteCursor / RemoteSelection", "slug": "presenceavatars-liveindicator-followmode-remotecursor-remoteselection", "components": ["PresenceAvatars", "LiveIndicator", "FollowMode", "RemoteCursor", "RemoteSelection"]}, {"title": "CommentThread / Reply / Reactions / ReactionPicker", "slug": "commentthread-reply-reactions-reactionpicker", "components": ["CommentThread", "Reply", "Reactions", "ReactionPicker"]}, {"title": "CommentBubble / CommentMarker", "slug": "commentbubble-commentmarker", "components": ["CommentBubble", "CommentMarker"]}, {"title": "CommentSidebar", "slug": "commentsidebar", "components": ["CommentSidebar"]}, {"title": "Annotation / Highlight", "slug": "annotation-highlight", "components": ["Annotation", "Highlight"]}, {"title": "SuggestionMode / TrackChanges", "slug": "suggestionmode-trackchanges", "components": ["SuggestionMode", "TrackChanges"]}, {"title": "ShareDialog / InviteInput / PermissionSelect / AccessList", "slug": "sharedialog-inviteinput-permissionselect-accesslist", "components": ["ShareDialog", "InviteInput", "PermissionSelect", "AccessList"]}, {"title": "ActivityLog → data_display::ActivityFeed", "slug": "activitylog-data-display-activityfeed", "components": ["ActivityLog"]}]},
{"number": 23, "slug": "chat", "title": "AI Chat", "summary": "A conversation with an assistant: messages that stream in, code and media inside them, and what can be done with each.", "stories": [{"title": "ChatContainer / MessageList / MessageBubble / MessageAvatar / MessageHeader / MessageFooter / DateSeparator / ScrollToBottomButton", "slug": "chatcontainer-messagelist-messagebubble-messageavatar-messageheader-messagefooter-dateseparator-scrolltobottombutton", "components": ["ChatContainer", "MessageList", "MessageBubble", "MessageAvatar", "MessageHeader", "MessageFooter", "DateSeparator", "ScrollToBottomButton"]}, {"title": "StreamingText / StreamingMarkdown / StreamingCursor", "slug": "streamingtext-streamingmarkdown-streamingcursor", "components": ["StreamingText", "StreamingMarkdown", "StreamingCursor"]}, {"title": "CodeBlock", "slug": "codeblock", "components": ["CodeBlock"]}, {"title": "MathBlock / TableBlock / MermaidRenderer", "slug": "mathblock-tableblock-mermaidrenderer", "components": ["MathBlock", "TableBlock", "MermaidRenderer"]}, {"title": "ImageMessage / ImageGrid / FileMessage / AudioMessage / VideoMessage / LinkPreviewCard", "slug": "imagemessage-imagegrid-filemessage-audiomessage-videomessage-linkpreviewcard", "components": ["ImageMessage", "ImageGrid", "FileMessage", "AudioMessage", "VideoMessage", "LinkPreviewCard"]}, {"title": "QuoteReply / MessageActions / MessageEditor / BranchNavigator / RegenerateButton / StopGeneratingButton / ContinueButton", "slug": "quotereply-messageactions-messageeditor-branchnavigator-regeneratebutton-stopgeneratingbutton-continuebutton", "components": ["QuoteReply", "MessageActions", "MessageEditor", "BranchNavigator", "RegenerateButton", "StopGeneratingButton", "ContinueButton"]}, {"title": "ErrorMessage / RetryMessage / RateLimitNotice / FeedbackForm", "slug": "errormessage-retrymessage-ratelimitnotice-feedbackform", "components": ["ErrorMessage", "RetryMessage", "RateLimitNotice", "FeedbackForm"]}, {"title": "ThinkingBlock / ReasoningPanel / ThinkingIndicator / ThinkingDuration", "slug": "thinkingblock-reasoningpanel-thinkingindicator-thinkingduration", "components": ["ThinkingBlock", "ReasoningPanel", "ThinkingIndicator", "ThinkingDuration"]}, {"title": "CitationBadge / InlineCitation / SourceCard / SourceList / SourcesPanel", "slug": "citationbadge-inlinecitation-sourcecard-sourcelist-sourcespanel", "components": ["CitationBadge", "InlineCitation", "SourceCard", "SourceList", "SourcesPanel"]}, {"title": "SearchProgress / WebResultCard / DocumentChunkPreview", "slug": "searchprogress-webresultcard-documentchunkpreview", "components": ["SearchProgress", "WebResultCard", "DocumentChunkPreview"]}, {"title": "PromptInput / Composer / AttachmentButton / AttachmentChip / AttachmentPreview / PasteImagePreview / DragDropOverlay / ContextChips / SlashCommandMenu / ContextMentionMenu / SendButton / InputHint", "slug": "promptinput-composer-attachmentbutton-attachmentchip-attachmentpreview-pasteimagepreview-dragdropoverlay-contextchips-slashcommandmenu-contextmentionmenu-sendbutton-inputhint", "components": ["PromptInput", "Composer", "AttachmentButton", "AttachmentChip", "AttachmentPreview", "PasteImagePreview", "DragDropOverlay", "ContextChips", "SlashCommandMenu", "ContextMentionMenu", "SendButton", "InputHint"]}, {"title": "ModelSelector / ModeSelector / ToolToggleMenu / PromptTemplateMenu", "slug": "modelselector-modeselector-tooltogglemenu-prompttemplatemenu", "components": ["ModelSelector", "ModeSelector", "ToolToggleMenu", "PromptTemplateMenu"]}, {"title": "TemperatureSlider / ParameterPanel / SystemPromptEditor / TokenCounter / ContextWindowMeter / CostEstimator", "slug": "temperatureslider-parameterpanel-systemprompteditor-tokencounter-contextwindowmeter-costestimator", "components": ["TemperatureSlider", "ParameterPanel", "SystemPromptEditor", "TokenCounter", "ContextWindowMeter", "CostEstimator"]}, {"title": "ConversationList / ConversationItem / ConversationSearch / NewChatButton", "slug": "conversationlist-conversationitem-conversationsearch-newchatbutton", "components": ["ConversationList", "ConversationItem", "ConversationSearch", "NewChatButton"]}, {"title": "FolderList / ProjectList / ProjectKnowledgePanel", "slug": "folderlist-projectlist-projectknowledgepanel", "components": ["FolderList", "ProjectList", "ProjectKnowledgePanel"]}, {"title": "SharedConversationView / ConversationExport", "slug": "sharedconversationview-conversationexport", "components": ["SharedConversationView", "ConversationExport"]}, {"title": "WelcomeScreen / Greeting / SuggestionChips / StarterPrompts / CapabilityCards", "slug": "welcomescreen-greeting-suggestionchips-starterprompts-capabilitycards", "components": ["WelcomeScreen", "Greeting", "SuggestionChips", "StarterPrompts", "CapabilityCards"]}, {"title": "FollowUpSuggestions", "slug": "followupsuggestions", "components": ["FollowUpSuggestions"]}, {"title": "PromptLibrary", "slug": "promptlibrary", "components": ["PLine truncated
{"number": 23, "slug": "chat", "title": "AI Chat", "summary": "A conversation with an assistant: messages that stream in, code and media inside them, and what can be done with each.", "stories": [{"title": "ChatContainer / MessageList / MessageBubble / MessageAvatar / MessageHeader / MessageFooter / DateSeparator / ScrollToBottomButton", "slug": "chatcontainer-messagelist-messagebubble-messageavatar-messageheader-messagefooter-dateseparator-scrolltobottombutton", "components": ["ChatContainer", "MessageList", "MessageBubble", "MessageAvatar", "MessageHeader", "MessageFooter", "DateSeparator", "ScrollToBottomButton"]}, {"title": "StreamingText / StreamingMarkdown / StreamingCursor", "slug": "streamingtext-streamingmarkdown-streamingcursor", "components": ["StreamingText", "StreamingMarkdown", "StreamingCursor"]}, {"title": "CodeBlock", "slug": "codeblock", "components": ["CodeBlock"]}, {"title": "MathBlock / TableBlock / MermaidRenderer", "slug": "mathblock-tableblock-mermaidrenderer", "components": ["MathBlock", "TableBlock", "MermaidRenderer"]}, {"title": "ImageMessage / ImageGrid / FileMessage / AudioMessage / VideoMessage / LinkPreviewCard", "slug": "imagemessage-imagegrid-filemessage-audiomessage-videomessage-linkpreviewcard", "components": ["ImageMessage", "ImageGrid", "FileMessage", "AudioMessage", "VideoMessage", "LinkPreviewCard"]}, {"title": "QuoteReply / MessageActions / MessageEditor / BranchNavigator / RegenerateButton / StopGeneratingButton / ContinueButton", "slug": "quotereply-messageactions-messageeditor-branchnavigator-regeneratebutton-stopgeneratingbutton-continuebutton", "components": ["QuoteReply", "MessageActions", "MessageEditor", "BranchNavigator", "RegenerateButton", "StopGeneratingButton", "ContinueButton"]}, {"title": "ErrorMessage / RetryMessage / RateLimitNotice / FeedbackForm", "slug": "errormessage-retrymessage-ratelimitnotice-feedbackform", "components": ["ErrorMessage", "RetryMessage", "RateLimitNotice", "FeedbackForm"]}, {"title": "ThinkingBlock / ReasoningPanel / ThinkingIndicator / ThinkingDuration", "slug": "thinkingblock-reasoningpanel-thinkingindicator-thinkingduration", "components": ["ThinkingBlock", "ReasoningPanel", "ThinkingIndicator", "ThinkingDuration"]}, {"title": "CitationBadge / InlineCitation / SourceCard / SourceList / SourcesPanel", "slug": "citationbadge-inlinecitation-sourcecard-sourcelist-sourcespanel", "components": ["CitationBadge", "InlineCitation", "SourceCard", "SourceList", "SourcesPanel"]}, {"title": "SearchProgress / WebResultCard / DocumentChunkPreview", "slug": "searchprogress-webresultcard-documentchunkpreview", "components": ["SearchProgress", "WebResultCard", "DocumentChunkPreview"]}, {"title": "PromptInput / Composer / AttachmentButton / AttachmentChip / AttachmentPreview / PasteImagePreview / DragDropOverlay / ContextChips / SlashCommandMenu / ContextMentionMenu / SendButton / InputHint", "slug": "promptinput-composer-attachmentbutton-attachmentchip-attachmentpreview-pasteimagepreview-dragdropoverlay-contextchips-slashcommandmenu-contextmentionmenu-sendbutton-inputhint", "components": ["PromptInput", "Composer", "AttachmentButton", "AttachmentChip", "AttachmentPreview", "PasteImagePreview", "DragDropOverlay", "ContextChips", "SlashCommandMenu", "ContextMentionMenu", "SendButton", "InputHint", "VoiceInputButton", "VoiceWaveform"]}, {"title": "ModelSelector / ModeSelector / ToolToggleMenu / PromptTemplateMenu", "slug": "modelselector-modeselector-tooltogglemenu-prompttemplatemenu", "components": ["ModelSelector", "ModeSelector", "ToolToggleMenu", "PromptTemplateMenu"]}, {"title": "TemperatureSlider / ParameterPanel / SystemPromptEditor / TokenCounter / ContextWindowMeter / CostEstimator", "slug": "temperatureslider-parameterpanel-systemprompteditor-tokencounter-contextwindowmeter-costestimator", "components": ["TemperatureSlider", "ParameterPanel", "SystemPromptEditor", "TokenCounter", "ContextWindowMeter", "CostEstimator"]}, {"title": "ConversationList / ConversationItem / ConversationSearch / NewChatButton", "slug": "conversationlist-conversationitem-conversationsearch-newchatbutton", "components": ["ConversationList", "ConversationItem", "ConversationSearch", "NewChatButton"]}, {"title": "FolderList / ProjectList / ProjectKnowledgePanel", "slug": "folderlist-projectlist-projectknowledgepanel", "components": ["FolderList", "ProjectList", "ProjectKnowledgePanel"]}, {"title": "SharedConversationView / ConversationExport", "slug": "sharedconversationview-conversationexport", "components": ["SharedConversationView", "ConversationExport"]}, {"title": "WelcomeScreen / Greeting / SuggestionChips / StarterPrompts / CapabilityCards", "slug": "welcomescreen-greeting-suggestionchips-starterprompts-capabilitycards", "components": ["WelcomeScreen", "Greeting", "SuggestionChips", "StarterPrompts", "CapabilityCards"]}, {"title": "FollowUpSuggestions", "slug": "followupsuggestions", "components": ["FollowUpSuggestions"]}, {"title": "PromptLibrary", "slugLine truncated
{"number": 24, "slug": "agent", "title": "Agent", "summary": "An assistant at work: the tools it calls, what it asks before it acts, how far it has come, what it would change, what it sees and what it makes.", "stories": [{"title": "ToolCallCard / ToolCallGroup", "slug": "toolcallcard-toolcallgroup", "components": ["ToolCallCard", "ToolCallGroup"]}, {"title": "ToolApprovalDialog / PermissionPrompt", "slug": "toolapprovaldialog-permissionprompt", "components": ["ToolApprovalDialog", "PermissionPrompt"]}, {"title": "AgentStepList / TaskTimeline / AgentPlan / TodoList / AgentStatus / AgentProgress", "slug": "agentsteplist-tasktimeline-agentplan-todolist-agentstatus-agentprogress", "components": ["AgentStepList", "TaskTimeline", "AgentPlan", "TodoList", "AgentStatus", "AgentProgress"]}, {"title": "SubAgentTree", "slug": "subagenttree", "components": ["SubAgentTree"]}, {"title": "FileChangeCard / MultiFileDiff Review / CommandExecutionCard", "slug": "filechangecard-multifilediff-review-commandexecutioncard", "components": ["FileChangeCard", "MultiFileDiff Review", "CommandExecutionCard"]}, {"title": "BrowserPreview / ScreenshotStream / ComputerUseViewer", "slug": "browserpreview-screenshotstream-computeruseviewer", "components": ["BrowserPreview", "ScreenshotStream", "ComputerUseViewer"]}, {"title": "ArtifactPanel / ArtifactVersionSwitcher / LivePreview", "slug": "artifactpanel-artifactversionswitcher-livepreview", "components": ["ArtifactPanel", "ArtifactVersionSwitcher", "LivePreview"]}, {"title": "SandboxStatus", "slug": "sandboxstatus", "components": ["SandboxStatus"]}, {"title": "MCPServerList / ConnectorList / ToolRegistry Panel", "slug": "mcpserverlist-connectorlist-toolregistry-panel", "components": ["MCPServerList", "ConnectorList", "ToolRegistry Panel"]}, {"title": "MemoryPanel / CheckpointList / Rewind", "slug": "memorypanel-checkpointlist-rewind", "components": ["MemoryPanel", "CheckpointList", "Rewind"]}, {"title": "InterruptButton / HumanInputRequest", "slug": "interruptbutton-humaninputrequest", "components": ["InterruptButton", "HumanInputRequest"]}, {"title": "TokenUsageChart / CostBreakdown", "slug": "tokenusagechart-costbreakdown", "components": ["TokenUsageChart", "CostBreakdown"]}, {"title": "TraceViewer / EvalResultTable", "slug": "traceviewer-evalresulttable", "components": ["TraceViewer", "EvalResultTable"]}]},
{"number": 25, "slug": "generative", "title": "Generative", "summary": "Making pictures, sound and video with a model: the prompt and its settings, the queue and what comes back, the canvas, the models behind them, and the data and prompts they work from.", "stories": [{"title": "ImageGenerationPanel / PromptEnhancer / NegativePromptInput / StylePresetPicker / AspectRatioPicker / SeedInput", "slug": "imagegenerationpanel-promptenhancer-negativepromptinput-stylepresetpicker-aspectratiopicker-seedinput", "components": ["ImageGenerationPanel", "PromptEnhancer", "NegativePromptInput", "StylePresetPicker", "AspectRatioPicker", "SeedInput"]}, {"title": "GenerationQueue", "slug": "generationqueue", "components": ["GenerationQueue"]}, {"title": "GenerationGrid / ImageCompare", "slug": "generationgrid-imagecompare", "components": ["GenerationGrid", "ImageCompare"]}, {"title": "VariationPicker / ABCompareView", "slug": "variationpicker-abcompareview", "components": ["VariationPicker", "ABCompareView"]}, {"title": "InpaintCanvas / MaskBrush", "slug": "inpaintcanvas-maskbrush", "components": ["InpaintCanvas", "MaskBrush"]}, {"title": "AudioGenerationPlayer", "slug": "audiogenerationplayer", "components": ["AudioGenerationPlayer"]}, {"title": "TTSVoicePicker", "slug": "ttsvoicepicker", "components": ["TTSVoicePicker"]}, {"title": "VideoGenerationTimeline", "slug": "videogenerationtimeline", "components": ["VideoGenerationTimeline"]}, {"title": "ModelCard", "slug": "modelcard", "components": ["ModelCard"]}, {"title": "ModelDownloadManager", "slug": "modeldownloadmanager", "components": ["ModelDownloadManager"]}, {"title": "ModelStatus / HardwareMonitor", "slug": "modelstatus-hardwaremonitor", "components": ["ModelStatus", "HardwareMonitor"]}, {"title": "FineTuneJobCard", "slug": "finetunejobcard", "components": ["FineTuneJobCard"]}, {"title": "DatasetViewer → tables::DataTable", "slug": "datasetviewer-tables-datatable", "components": ["DatasetViewer"]}, {"title": "EmbeddingVisualizer", "slug": "embeddingvisualizer", "components": ["EmbeddingVisualizer"]}, {"title": "PromptPlayground", "slug": "promptplayground", "components": ["PromptPlayground"]}, {"title": "PromptDiff / PromptVersionHistory", "slug": "promptdiff-promptversionhistory", "components": ["PromptDiff", "PromptVersionHistory"]}]},
{"number": 26, "slug": "media", "title": "Media", "summary": "Pictures, video and sound: looking closely, cropping and marking up, playing, and the controls and devices around them.", "stories": [{"title": "ImageViewer", "slug": "imageviewer", "components": ["ImageViewer"]}, {"title": "ImageThumbnail / Lightbox → overlays::Lightbox", "slug": "imagethumbnail-lightbox-overlays-lightbox", "components": ["ImageThumbnail", "Lightbox"]}, {"title": "ImageCropper", "slug": "imagecropper", "components": ["ImageCropper"]}, {"title": "ImageUpload", "slug": "imageupload", "components": ["ImageUpload"]}, {"title": "ImageAnnotator", "slug": "imageannotator", "components": ["ImageAnnotator"]}, {"title": "VideoPlayer", "slug": "videoplayer", "components": ["VideoPlayer"]}, {"title": "VideoTimeline / Scrubber", "slug": "videotimeline-scrubber", "components": ["VideoTimeline", "Scrubber"]}, {"title": "VideoThumbnailStrip", "slug": "videothumbnailstrip", "components": ["VideoThumbnailStrip"]}, {"title": "SubtitleEditor", "slug": "subtitleeditor", "components": ["SubtitleEditor"]}, {"title": "AudioPlayer", "slug": "audioplayer", "components": ["AudioPlayer"]}, {"title": "AudioWaveform", "slug": "audiowaveform", "components": ["AudioWaveform"]}, {"title": "AudioSpectrum / Visualizer", "slug": "audiospectrum-visualizer", "components": ["AudioSpectrum", "Visualizer"]}, {"title": "VolumeControl", "slug": "volumecontrol", "components": ["VolumeControl"]}, {"title": "PlaybackSpeedControl", "slug": "playbackspeedcontrol", "components": ["PlaybackSpeedControl"]}, {"title": "MediaControls", "slug": "mediacontrols", "components": ["MediaControls"]}, {"title": "Playlist", "slug": "playlist", "components": ["Playlist"]}, {"title": "ScreenRecorderControls", "slug": "screenrecordercontrols", "components": ["ScreenRecorderControls"]}, {"title": "CameraPreview", "slug": "camerapreview", "components": ["CameraPreview"]}, {"title": "MicLevelMeter", "slug": "miclevelmeter", "components": ["MicLevelMeter"]}, {"title": "DeviceSelector", "slug": "deviceselector", "components": ["DeviceSelector"]}, {"title": "3DModelViewer", "slug": "3dmodelviewer", "components": ["3DModelViewer"]}]},
@@ -33,11 +33,11 @@
{"number": 32, "slug": "canvas", "title": "Canvas & Design", "summary": "An endless plane to lay work on, the tools and panels around it, and graphs of nodes.", "stories": [{"title": "InfiniteCanvas · CanvasGrid / Guides · Ruler · ArtboardFrame · ZoomControls / ZoomIndicator · MiniMap", "slug": "infinitecanvas-canvasgrid-guides-ruler-artboardframe-zoomcontrols-zoomindicator-minimap", "components": ["InfiniteCanvas", "CanvasGrid", "Guides", "Ruler", "ArtboardFrame", "ZoomControls", "ZoomIndicator", "MiniMap"]}, {"title": "ToolPalette · SelectionBox · TransformHandles · SnapIndicator · ShapeTools · PenTool / PathEditor · TextTool · BrushTool / BrushSettings · HistoryPanel", "slug": "toolpalette-selectionbox-transformhandles-snapindicator-shapetools-pentool-patheditor-texttool-brushtool-brushsettings-historypanel", "components": ["ToolPalette", "SelectionBox", "TransformHandles", "SnapIndicator", "ShapeTools", "PenTool", "PathEditor", "TextTool", "BrushTool", "BrushSettings", "HistoryPanel"]}, {"title": "LayerPanel · InspectorPanel · AlignmentToolbar · AssetPanel", "slug": "layerpanel-inspectorpanel-alignmenttoolbar-assetpanel", "components": ["LayerPanel", "InspectorPanel", "AlignmentToolbar", "AssetPanel"]}, {"title": "ColorPanel · GradientPanel · ShadowEditor · BorderEditor · TypographyPanel", "slug": "colorpanel-gradientpanel-shadoweditor-bordereditor-typographypanel", "components": ["ColorPanel", "GradientPanel", "ShadowEditor", "BorderEditor", "TypographyPanel"]}, {"title": "ConstraintsEditor · AutoLayoutControls · ExportPanel", "slug": "constraintseditor-autolayoutcontrols-exportpanel", "components": ["ConstraintsEditor", "AutoLayoutControls", "ExportPanel"]}, {"title": "NodeEditor / NodeGraph · Node / Port / Edge / Connection", "slug": "nodeeditor-nodegraph-node-port-edge-connection", "components": ["NodeEditor", "NodeGraph", "Node", "Port", "Edge", "Connection"]}, {"title": "FlowChart / DiagramEditor · Connector Line", "slug": "flowchart-diagrameditor-connector-line", "components": ["FlowChart", "DiagramEditor", "Connector Line"]}, {"title": "MindMap", "slug": "mindmap", "components": ["MindMap"]}, {"title": "Whiteboard · StickyNote", "slug": "whiteboard-stickynote", "components": ["Whiteboard", "StickyNote"]}]},
{"number": 33, "slug": "devtools", "title": "DB & Dev Tools", "summary": "Connections, schemas and queries, data to read, requests to send, and the machines that run them.", "stories": [{"title": "ConnectionManager · ConnectionForm", "slug": "connectionmanager-connectionform", "components": ["ConnectionManager", "ConnectionForm"]}, {"title": "SchemaTree · ERDiagram", "slug": "schematree-erdiagram", "components": ["SchemaTree", "ERDiagram"]}, {"title": "QueryEditor → editor::CodeEditor · QueryResultGrid → tables::DataGrid · QueryHistory · QueryPlanViewer", "slug": "queryeditor-editor-codeeditor-queryresultgrid-tables-datagrid-queryhistory-queryplanviewer", "components": ["QueryEditor", "QueryResultGrid", "QueryHistory", "QueryPlanViewer"]}, {"title": "TableStructureEditor · IndexManager", "slug": "tablestructureeditor-indexmanager", "components": ["TableStructureEditor", "IndexManager"]}, {"title": "RedisKeyBrowser", "slug": "rediskeybrowser", "components": ["RedisKeyBrowser"]}, {"title": "DocumentViewer (MongoDB / JSON) → devtools::JsonViewer · JsonViewer / JsonTree · JsonEditor · YamlViewer · XmlViewer", "slug": "documentviewer-mongodb-json-devtools-jsonviewer-jsonviewer-jsontree-jsoneditor-yamlviewer-xmlviewer", "components": ["DocumentViewer", "JsonViewer", "JsonTree", "JsonEditor", "YamlViewer", "XmlViewer"]}, {"title": "ApiRequestBuilder · ResponseViewer · HeadersTable · QueryParamsEditor → forms::KeyValueInput · EnvironmentSelector · CollectionTree", "slug": "apirequestbuilder-responseviewer-headerstable-queryparamseditor-forms-keyvalueinput-environmentselector-collectiontree", "components": ["ApiRequestBuilder", "ResponseViewer", "HeadersTable", "QueryParamsEditor", "EnvironmentSelector", "CollectionTree"]}, {"title": "WebSocketConsole", "slug": "websocketconsole", "components": ["WebSocketConsole"]}, {"title": "GraphQLExplorer", "slug": "graphqlexplorer", "components": ["GraphQLExplorer"]}, {"title": "RegexTester · Base64 / Encoder · ColorContrastChecker", "slug": "regextester-base64-encoder-colorcontrastchecker", "components": ["RegexTester", "Base64", "Encoder", "ColorContrastChecker"]}, {"title": "ContainerList · PodList · ResourceMonitor", "slug": "containerlist-podlist-resourcemonitor", "components": ["ContainerList", "PodList", "ResourceMonitor"]}]},
{"number": 34, "slug": "dashboard", "title": "Dashboard & Monitoring", "summary": "Widgets on a grid you arrange, what is firing and what is up, and the stream of what happens.", "stories": [{"title": "Dashboard Grid · Widget / DashboardCard · RefreshIntervalSelector · DashboardFilterBar · StatCard → data_display::Statistic · MetricsChart → charts::LineChart · ResourceGauge → data_display::Gauge", "slug": "dashboard-grid-widget-dashboardcard-refreshintervalselector-dashboardfilterbar-statcard-data-display-statistic-metricschart-charts-linechart-resourcegauge-data-display-gauge", "components": ["Dashboard Grid", "Widget", "DashboardCard", "RefreshIntervalSelector", "DashboardFilterBar", "StatCard", "MetricsChart", "ResourceGauge"]}, {"title": "UptimeBar · ServiceStatus · HealthCheck Indicator", "slug": "uptimebar-servicestatus-healthcheck-indicator", "components": ["UptimeBar", "ServiceStatus", "HealthCheck Indicator"]}, {"title": "AlertList · IncidentCard", "slug": "alertlist-incidentcard", "components": ["AlertList", "IncidentCard"]}, {"title": "EventStream · LogStream → terminal::LogViewer · TraceWaterfall → debug::TimelineProfiler · SystemMonitor → devtools::ResourceMonitor · ProcessTable → terminal::ProcessList · NetworkGraph (throughput) → charts::RealtimeChart · MapView / GeoHeatmap → maps::MapView, maps::GeoHeatmap · WorldMap → maps::WorldMap", "slug": "eventstream-logstream-terminal-logviewer-tracewaterfall-debug-timelineprofiler-systemmonitor-devtools-resourcemonitor-processtable-terminal-processlist-networkgraph-throughput-charts-realtimechart-mapview-geoheatmap-maps-mapview-maps-geoheatmap-worldmap-maps-worldmap", "components": ["EventStream", "LogStream", "TraceWaterfall", "SystemMonitor", "ProcessTable", "NetworkGraph", "MapView", "GeoHeatmap", "WorldMap"]}]},
{"number": 35, "slug": "settings", "title": "Settings", "summary": "A settings window: its sections, a search over them, and the controls each one holds.", "stories": [{"title": "SettingsLayout · SettingsSection · SettingsRow · SettingsSearch", "slug": "settingslayout-settingssection-settingsrow-settingssearch", "components": ["SettingsLayout", "SettingsSection", "SettingsRow", "SettingsSearch"]}]},
{"number": 35, "slug": "settings", "title": "Settings", "summary": "A settings window: its sections, a search over them, and the controls each one holds.", "stories": [{"title": "SettingsLayout · SettingsSection · SettingsRow · SettingsSearch", "slug": "settingslayout-settingssection-settingsrow-settingssearch", "components": ["SettingsLayout", "ThemeSelector", "AccentColorPicker", "FontSizeControl", "DensitySelector", "KeyboardShortcutsList", "ProxySettings", "PrivacySettings", "NotificationSettings", "StartupSettings", "StorageSettings", "ResetToDefault", "ImportExportSettings", "AdvancedSettings", "DeveloperMode Toggle", "SettingsSection", "SettingsRow", "SettingsSearch"]}]},
{"number": 36, "slug": "account", "title": "Account", "summary": "Signing in and up, a link by email, a second factor, and the account once inside.", "stories": [{"title": "LoginForm · OAuthButtons", "slug": "loginform-oauthbuttons", "components": ["LoginForm", "OAuthButtons"]}, {"title": "SignupForm", "slug": "signupform", "components": ["SignupForm"]}, {"title": "MagicLinkForm · ForgotPassword", "slug": "magiclinkform-forgotpassword", "components": ["MagicLinkForm", "ForgotPassword"]}, {"title": "TwoFactorInput", "slug": "twofactorinput", "components": ["TwoFactorInput"]}, {"title": "AccountSwitcher · WorkspaceSwitcher / OrgSwitcher · UserMenu", "slug": "accountswitcher-workspaceswitcher-orgswitcher-usermenu", "components": ["AccountSwitcher", "WorkspaceSwitcher", "OrgSwitcher", "UserMenu"]}, {"title": "ProfileCard · ProfileEditor", "slug": "profilecard-profileeditor", "components": ["ProfileCard", "ProfileEditor"]}, {"title": "SessionList · ApiKeyManager", "slug": "sessionlist-apikeymanager", "components": ["SessionList", "ApiKeyManager"]}, {"title": "SubscriptionCard / PlanBadge · UsageQuota · PaymentMethodForm", "slug": "subscriptioncard-planbadge-usagequota-paymentmethodform", "components": ["SubscriptionCard", "PlanBadge", "UsageQuota", "PaymentMethodForm"]}, {"title": "BillingHistory · UpgradePrompt / Paywall", "slug": "billinghistory-upgradeprompt-paywall", "components": ["BillingHistory", "UpgradePrompt", "Paywall"]}, {"title": "TeamMemberTable · RoleSelector · InvitationList", "slug": "teammembertable-roleselector-invitationlist", "components": ["TeamMemberTable", "RoleSelector", "InvitationList"]}]},
{"number": 37, "slug": "onboarding", "title": "Onboarding & Help", "summary": "A first run in steps, what is new called out, first things to do, and help where it is needed.", "stories": [{"title": "OnboardingWizard · WelcomeTour", "slug": "onboardingwizard-welcometour", "components": ["OnboardingWizard", "WelcomeTour"]}, {"title": "Coachmark / Hotspot · FeatureHighlight / NewBadge", "slug": "coachmark-hotspot-featurehighlight-newbadge", "components": ["Coachmark", "Hotspot", "FeatureHighlight", "NewBadge"]}, {"title": "Checklist (first-run tasks)", "slug": "checklist-first-run-tasks", "components": ["Checklist"]}, {"title": "HelpTooltip · InlineHelp · EmptyStateWithAction", "slug": "helptooltip-inlinehelp-emptystatewithaction", "components": ["HelpTooltip", "InlineHelp", "EmptyStateWithAction"]}, {"title": "HelpPanel / DocsSidebar", "slug": "helppanel-docssidebar", "components": ["HelpPanel", "DocsSidebar"]}, {"title": "KeyboardShortcutCheatsheet · WhatsNewDialog", "slug": "keyboardshortcutcheatsheet-whatsnewdialog", "components": ["KeyboardShortcutCheatsheet", "WhatsNewDialog"]}, {"title": "FeedbackWidget · ContactSupport", "slug": "feedbackwidget-contactsupport", "components": ["FeedbackWidget", "ContactSupport"]}]},
{"number": 38, "slug": "interaction", "title": "Interaction", "summary": "Drag and drop, resizing and turning, band selection, presses and gestures, keys, scrolling and focus.", "stories": [{"title": "Draggable · Droppable / DropTarget · DragOverlay / DragPreview · DragGhost", "slug": "draggable-droppable-droptarget-dragoverlay-dragpreview-dragghost", "components": ["Draggable", "Droppable", "DropTarget", "DragOverlay", "DragPreview", "DragGhost"]}, {"title": "Resizable · Rotatable · Selectable / SelectionArea · RubberBandSelection · Sortable", "slug": "resizable-rotatable-selectable-selectionarea-rubberbandselection-sortable", "components": ["Resizable", "Rotatable", "Selectable", "SelectionArea", "RubberBandSelection", "Sortable"]}, {"title": "LongPress · DoubleClick · Gesture · PanZoom", "slug": "longpress-doubleclick-gesture-panzoom", "components": ["LongPress", "DoubleClick", "Gesture", "PanZoom"]}, {"title": "Hotkeys / KeyBinding · KeyChord", "slug": "hotkeys-keybinding-keychord", "components": ["Hotkeys", "KeyBinding", "KeyChord"]}, {"title": "Undo / Redo Manager", "slug": "undo-redo-manager", "components": ["Undo", "Redo Manager"]}, {"title": "VirtualScroller · ScrollSync · InfiniteScroll Trigger", "slug": "virtualscroller-scrollsync-infinitescroll-trigger", "components": ["VirtualScroller", "ScrollSync", "InfiniteScroll Trigger"]}, {"title": "Autofocus · RovingTabIndex · TypeAhead · FocusVisible", "slug": "autofocus-rovingtabindex-typeahead-focusvisible", "components": ["Autofocus", "RovingTabIndex", "TypeAhead", "FocusVisible"]}]},
{"number": 39, "slug": "theme", "title": "Theme", "summary": "Tokens every component reads. Light and dark share one grammar.", "stories": [{"title": "Surfaces", "slug": "surfaces", "components": []}, {"title": "Ink", "slug": "ink", "components": []}, {"title": "Signals", "slug": "signals", "components": []}, {"title": "Series", "slug": "series", "components": []}, {"title": "Syntax", "slug": "syntax", "components": []}, {"title": "Type", "slug": "type", "components": []}, {"title": "Shape", "slug": "shape", "components": []}, {"title": "Motion", "slug": "motion", "components": ["Motion"]}, {"title": "ThemeEditor · DensityProvider · RadiusProvider · FontProvider · HighContrastMode · ReducedMotionProvider", "slug": "themeeditor-densityprovider-radiusprovider-fontprovider-highcontrastmode-reducedmotionprovider", "components": ["ThemeEditor", "DensityProvider", "RadiusProvider", "FontProvider", "HighContrastMode", "ReducedMotionProvider"]}, {"title": "ThemeImporter · SyntaxThemes", "slug": "themeimporter-syntaxthemes", "components": ["ThemeImporter", "SyntaxThemes"]}, {"title": "Platform Style · IconThemeProvider · VibrancyBackground", "slug": "platform-style-iconthemeprovider-vibrancybackground", "components": ["Platform Style", "IconThemeProvider", "VibrancyBackground"]}]},
{"number": 39, "slug": "theme", "title": "Theme", "summary": "Tokens every component reads. Light and dark share one grammar.", "stories": [{"title": "Surfaces", "slug": "surfaces", "components": ["ColorTokens"]}, {"title": "Ink", "slug": "ink", "components": ["ColorTokens"]}, {"title": "Signals", "slug": "signals", "components": ["ColorTokens"]}, {"title": "Series", "slug": "series", "components": ["ColorTokens"]}, {"title": "Syntax", "slug": "syntax", "components": ["ColorTokens"]}, {"title": "Type", "slug": "type", "components": []}, {"title": "Shape", "slug": "shape", "components": ["Elevation"]}, {"title": "Motion", "slug": "motion", "components": ["Motion", "Spring"]}, {"title": "ThemeEditor · DensityProvider · RadiusProvider · FontProvider · HighContrastMode · ReducedMotionProvider", "slug": "themeeditor-densityprovider-radiusprovider-fontprovider-highcontrastmode-reducedmotionprovider", "components": ["ThemeEditor", "ThemeProvider", "DensityProvider", "RadiusProvider", "FontProvider", "HighContrastMode", "ReducedMotionProvider"]}, {"title": "ThemeImporter · SyntaxThemes", "slug": "themeimporter-syntaxthemes", "components": ["ThemeImporter", "SyntaxThemes"]}, {"title": "Platform Style · IconThemeProvider · VibrancyBackground", "slug": "platform-style-iconthemeprovider-vibrancybackground", "components": ["Platform Style", "IconThemeProvider", "VibrancyBackground"]}]},
{"number": 40, "slug": "i18n", "title": "i18n & a11y", "summary": "Messages, numbers and dates in the reader's locale. A right-to-left locale mirrors its stacks.", "stories": [{"title": "I18nProvider · Trans / LocalizedText", "slug": "i18nprovider-trans-localizedtext", "components": ["I18nProvider", "Trans", "LocalizedText"]}, {"title": "LocaleNumber / LocaleDate", "slug": "localenumber-localedate", "components": ["LocaleNumber", "LocaleDate"]}, {"title": "RTL layout", "slug": "rtl-layout", "components": ["RTL layout"]}, {"title": "SkipLink · KeyboardNavigationHelper", "slug": "skiplink-keyboardnavigationhelper", "components": ["SkipLink", "KeyboardNavigationHelper"]}, {"title": "ColorBlindSafe Palette", "slug": "colorblindsafe-palette", "components": ["ColorBlindSafe Palette"]}]},
{"number": 41, "slug": "maps", "title": "Maps", "summary": "Tiles the host supplies, pins with a popup, a picker's crosshair, routes, GeoJSON, heat, clusters and choropleths. The land is Natural Earth's.", "stories": [{"title": "MapView · MapMarker · MapPopup", "slug": "mapview-mapmarker-mappopup", "components": ["MapView", "MapMarker", "MapPopup"]}, {"title": "CoordinateDisplay", "slug": "coordinatedisplay", "components": ["CoordinateDisplay"]}, {"title": "LocationPicker", "slug": "locationpicker", "components": ["LocationPicker"]}, {"title": "RouteLine · GeoJSON Layer", "slug": "routeline-geojson-layer", "components": ["RouteLine", "GeoJSON Layer"]}, {"title": "GeoHeatmap", "slug": "geoheatmap", "components": ["GeoHeatmap"]}, {"title": "MapCluster", "slug": "mapcluster", "components": ["MapCluster"]}, {"title": "ChoroplethMap · WorldMap", "slug": "choroplethmap-worldmap", "components": ["ChoroplethMap", "WorldMap"]}]},
{"number": 42, "slug": "misc", "title": "Misc", "summary": "Small tools that stand alone: clocks, a stopwatch, a calculator, a unit converter, a QR code scanner, a captcha, a poll, a survey, a quiz, flashcards, licenses, terms, a cookie banner, data out and in, and web views.", "stories": [{"title": "Clock / WorldClock", "slug": "clock-worldclock", "components": ["Clock", "WorldClock"]}, {"title": "Stopwatch", "slug": "stopwatch", "components": ["Stopwatch"]}, {"title": "Calculator", "slug": "calculator", "components": ["Calculator"]}, {"title": "UnitConverter", "slug": "unitconverter", "components": ["UnitConverter"]}, {"title": "QRCodeScanner", "slug": "qrcodescanner", "components": ["QRCodeScanner"]}, {"title": "Captcha", "slug": "captcha", "components": ["Captcha"]}, {"title": "BarcodeDisplay · Signature · Rating Widget", "slug": "barcodedisplay-signature-rating-widget", "components": ["BarcodeDisplay", "Signature", "Rating Widget"]}, {"title": "Poll / Survey", "slug": "poll-survey", "components": ["Poll", "Survey"]}, {"title": "Quiz / Flashcard", "slug": "quiz-flashcard", "components": ["Quiz", "Flashcard"]}, {"title": "ChangeLog Viewer", "slug": "changelog-viewer", "components": ["ChangeLog Viewer"]}, {"title": "LicenseViewer", "slug": "licenseviewer", "components": ["LicenseViewer"]}, {"title": "Terms / Consent Dialog / CookieBanner", "slug": "terms-consent-dialog-cookiebanner", "components": ["Terms", "Consent Dialog", "CookieBanner"]}, {"title": "ExportDialog / ImportDialog / CSVImporter", "slug": "exportdialog-importdialog-csvimporter", "components": ["ExportDialog", "ImportDialog", "CSVImporter"]}, {"title": "WebView / HTMLPreview", "slug": "webview-htmlpreview", "components": ["WebView", "HTMLPreview"]}, {"title": "IframeEmbed / EmbedCard", "slug": "iframeembed-embedcard", "components": ["IframeEmbed", "EmbedCard"]}, {"title": "PrintButton · ShareSheet", "slug": "printbutton-sharesheet", "components": ["PrintButton", "ShareSheet"]}]},
+59 -27
View File
@@ -55,7 +55,7 @@ pub fn slug(title: &str) -> String {
}
/// Titled block of demos. Under a story, only the one asked for draws, without its title.
pub fn section(title: impl Into<SharedString>, note: impl Into<SharedString>, cx: &App) -> Div {
pub fn section(title: impl Into<SharedString>, note: impl Into<SharedString>, cx: &App) -> Section {
let title = title.into();
let theme = cx.theme();
let story = cx.try_global::<Story>();
@@ -63,33 +63,58 @@ pub fn section(title: impl Into<SharedString>, note: impl Into<SharedString>, cx
story.met.borrow_mut().push(title.clone());
story.answers(&title)
});
div()
.flex()
.flex_col()
.gap_4()
.when(story.is_none(), |section| section.pt_10())
.when(!shown, |section| section.hidden())
.child(
div()
.flex()
.flex_col()
.gap_1()
.when(story.is_none(), |header| {
header.child(
div()
.text_size(theme.text_size(TextSize::Md))
.font_weight(FontWeight::SEMIBOLD)
.text_color(theme.colors.fg)
.child(title),
)
})
.child(
Section {
div: div()
.flex()
.flex_col()
.gap_4()
.when(story.is_none(), |section| section.pt_10())
.when(!shown, |section| section.hidden()),
shown,
}
.child(
div()
.flex()
.flex_col()
.gap_1()
.when(story.is_none(), |header| {
header.child(
div()
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg_muted)
.child(note.into()),
),
)
.text_size(theme.text_size(TextSize::Md))
.font_weight(FontWeight::SEMIBOLD)
.text_color(theme.colors.fg)
.child(title),
)
})
.child(
div()
.text_size(theme.text_size(TextSize::Sm))
.text_color(theme.colors.fg_muted)
.child(note.into()),
),
)
}
/// A section's box. One a story passes over drops its demos, so none lays out, loads or moves.
pub struct Section {
div: Div,
shown: bool,
}
impl ParentElement for Section {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
if self.shown {
self.div.extend(elements);
}
}
}
impl IntoElement for Section {
type Element = Div;
fn into_element(self) -> Div {
self.div
}
}
/// A picture under the gallery's assets: a file natively, an address on the web.
@@ -163,6 +188,13 @@ pub fn blocked(text: impl Into<SharedString>, cx: &App) -> impl IntoElement {
.child(div().flex_1().min_w_0().child(text.into()))
}
/// Why no screen reader reads a demo here.
pub const UNREAD: &str = if cfg!(target_family = "wasm") {
"gpui builds no AccessKit tree on the web, so no screen reader reads it here"
} else {
"gpui builds an AccessKit tree from elements with an id and a role, and no screen reader here reads it"
};
/// Why a demo that takes files takes none on the web.
pub const NO_FILES: &str = "A browser hands gpui no files: on the web the file dialog fails and dropped files never arrive, so nothing is taken here.";
+21 -11
View File
@@ -1,4 +1,9 @@
use std::{borrow::Cow, cell::RefCell, rc::Rc, sync::Arc};
use std::{
borrow::Cow,
cell::{Cell, RefCell},
rc::Rc,
sync::Arc,
};
use anyhow::{Context as _, Result, anyhow, bail};
use ely_gpui_component::Assets;
@@ -18,6 +23,8 @@ const HEBREW: &[u8] = include_bytes!("fonts/NotoSansHebrew-Regular.ttf");
thread_local! {
/// The running app and its window, for the host page's calls.
static HOST: RefCell<Option<(AsyncApp, WindowHandle<Gallery>)>> = const { RefCell::new(None) };
/// A mode the host page chose before the window opened, which it opens in.
static EARLY: Cell<Option<Choice>> = const { Cell::new(None) };
}
fn js(error: JsValue) -> anyhow::Error {
@@ -74,7 +81,7 @@ pub fn start() {
.location()
.search()
.expect("the page's address");
let start = read(&search).unwrap_or_else(|error| {
let mut start = read(&search).unwrap_or_else(|error| {
log::error!("gallery: {search} -> {error:#}");
Start {
failure: Some(SharedString::from(format!("{search}: {error:#}"))),
@@ -95,21 +102,24 @@ pub fn start() {
cx.text_system()
.add_fonts(vec![Cow::Borrowed(HEBREW)])
.expect("the gallery's Hebrew face registers");
if let Some(choice) = EARLY.take() {
start.choice = choice;
}
let window = launch(start, cx);
HOST.with(|host| *host.borrow_mut() = Some((cx.to_async(), window)));
});
}
/// Switches Ely's mode from the host page: `light` or `dark`.
/// Switches Ely's mode from the host page: `light` or `dark`, kept until the window opens.
#[wasm_bindgen]
pub fn set_theme(theme: &str) -> Result<(), JsError> {
let choice = choice(theme).map_err(|error| JsError::new(&format!("{error:#}")))?;
HOST.with(|host| {
let host = host.borrow();
let (app, window) = host
.as_ref()
.ok_or_else(|| JsError::new("the gallery's window is not open yet"))?;
app.update(|cx| choose(*window, choice, cx));
Ok(())
})
HOST.with(|host| match host.borrow().as_ref() {
Some((app, window)) => app.update(|cx| choose(*window, choice, cx)),
None => {
log::info!("gallery: theme {theme} waits for the window");
EARLY.set(Some(choice));
}
});
Ok(())
}
+7
View File
@@ -4,6 +4,13 @@ set -eu
cargo fmt --check
cargo clippy --all-targets --features test-support -- -D warnings
cargo test --lib --features test-support --quiet
rustup target list --installed | grep -qx wasm32-unknown-unknown || {
echo "the web lint needs wasm32: rustup target add wasm32-unknown-unknown" >&2
exit 1
}
# gpui_web's wasm_thread asks for an unstable feature, as in scripts/web.sh.
RUSTC_BOOTSTRAP=1 ELY_GALLERY_ASSETS=https://example.invalid/ \
cargo clippy --example gallery --target wasm32-unknown-unknown -- -D warnings
python3 scripts/stories.py --check
fail=0
files=$(git ls-files -co --exclude-standard '*.rs')
+102 -34
View File
@@ -1,9 +1,4 @@
"""Writes examples/gallery/stories.json: every gallery page and its sections, in the order they draw.
A section is a story the web gallery shows alone at ?page=<page slug>&story=<story slug>. Its
components are the names in its title that are entries in tasks/*.md, so "Sizes" names none.
`--check` fails if the committed file differs.
"""
"""Writes examples/gallery/stories.json: each page's stories and the components they show."""
import json
import re
@@ -13,10 +8,34 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
PAGES = ROOT / "examples/gallery/pages"
OUT = ROOT / "examples/gallery/stories.json"
CALL = re.compile(r"(?<![\w.])(?:(\w+)::)?(\w+)\s*\(")
CHAIN = re.compile(r"\s*\.\s*\w+\s*(?:::<[^>]*>\s*)?\(")
# Components a story draws beyond its title's, by page and the title's first name.
SHOWN = {
("forms", "Form"): ["FormLabel", "FormError"],
("tables", "DataTable"): ["Sparkline"],
("chat", "PromptInput"): ["VoiceInputButton", "VoiceWaveform"],
("shell", "MiniWindow"): ["AlwaysOnTop"],
("settings", "SettingsLayout"): [
"ThemeSelector", "AccentColorPicker", "FontSizeControl", "DensitySelector",
"KeyboardShortcutsList", "ProxySettings", "PrivacySettings", "NotificationSettings",
"StartupSettings", "StorageSettings", "ResetToDefault", "ImportExportSettings",
"AdvancedSettings", "DeveloperMode Toggle",
],
("theme", "Surfaces"): ["ColorTokens"],
("theme", "Ink"): ["ColorTokens"],
("theme", "Signals"): ["ColorTokens"],
("theme", "Series"): ["ColorTokens"],
("theme", "Syntax"): ["ColorTokens"],
("theme", "Shape"): ["Elevation"],
("theme", "Motion"): ["Spring"],
("theme", "ThemeEditor"): ["ThemeProvider"],
}
def strip(text):
"""Blanks comments and literals' insides, keeping offsets, so braces and calls parse."""
"""Blanks comments and literals' insides, keeping every offset."""
out, i, n = list(text), 0, len(text)
def blank(a, b):
@@ -60,7 +79,7 @@ def literal(text, at):
def functions(folder):
"""Every `fn` in a page's folder: (module, name) -> (source, stripped, body start, body end)."""
"""Every `fn` in a page's folder, by module and name, with its body's span."""
found = {}
for file in sorted(folder.rglob("*.rs")):
module = file.stem
@@ -80,26 +99,52 @@ def functions(folder):
return found
def close(bare, at):
"""The offset past the bracket that closes the one at `at`."""
depth = 0
for k in range(at, len(bare)):
depth += {"(": 1, "[": 1, "{": 1, ")": -1, "]": -1, "}": -1}.get(bare[k], 0)
if depth == 0:
return k + 1
def sections(folder):
"""Section titles in the order the page's render reaches them; fails on any it never reaches."""
"""Section titles in drawing order; fails on one never drawn or drawn inside another."""
fns = functions(folder)
order, seen = [], set()
order, seen, opens = [], set(), {}
def calls(module, name):
text, bare, start, end = fns[(module, name)]
for m in CALL.finditer(bare, start, end):
owner = m.group(1) or module
yield m, (owner, m.group(2)), m.group(2) == "section" and m.group(1) is None
def draws(fn):
"""Whether the fn opens a section, itself or through what it calls."""
if fn not in opens:
opens[fn] = False
opens[fn] = any(own or (callee in fns and draws(callee)) for _, callee, own in calls(*fn))
return opens[fn]
def walk(module, name):
if (module, name) in seen:
return
seen.add((module, name))
text, bare, start, end = fns[(module, name)]
call = re.compile(r"(?<![\w.])(?:(\w+)::)?(\w+)\s*\(")
for m in call.finditer(bare, start, end):
owner, callee = m.group(1) or module, m.group(2)
if callee == "section" and m.group(1) is None:
text, bare, _, _ = fns[(module, name)]
inside = 0
for m, callee, own in calls(module, name):
if m.start() < inside and (own or (callee in fns and draws(callee))):
sys.exit(f"{folder.name}/{module}.rs: a section opens inside {order[-1][0]!r}")
if own:
at = m.end() + len(bare[m.end():]) - len(bare[m.end():].lstrip())
if text[at] != '"':
sys.exit(f"{folder.name}/{module}.rs: a section whose title is not a literal")
order.append((literal(text, at), module))
elif (owner, callee) in fns:
walk(owner, callee)
inside = close(bare, m.end() - 1)
while chained := CHAIN.match(bare, inside):
inside = close(bare, chained.end() - 1)
elif callee in fns:
walk(*callee)
walk("mod", "render")
reached = {title for title, _ in order}
@@ -112,31 +157,42 @@ def sections(folder):
def slug(title):
"""As the gallery's ui::slug: ASCII letters and digits, lowercased, dashes between."""
"""As the gallery's ui::slug: lowercase letters and digits, dashes between."""
out = re.sub(r"[^a-z0-9]+", "-", "".join(c.lower() if c.isascii() else " " for c in title))
return out.strip("-")
def entries():
"""Names the task lists give components, keyed by letters alone."""
names = {}
"""The task lists' component names by key, and the ticked entries with a home."""
names, homed = {}, []
for file in sorted((ROOT / "tasks").glob("*.md")):
for line in file.read_text().splitlines():
if m := re.match(r"- \[[ x]\] (.+?)(?: — | → | \(T\d|$)", line):
for name in m.group(1).split(" / "):
names[key(name)] = re.sub(r"\s*\([^)]*\)", "", name).strip()
return names
if m := re.match(r"- \[([ x])\] (.+?)(?: — | → | \(T\d|$)", line):
aliases = [re.sub(r"\s*\([^)]*\)", "", name).strip() for name in m.group(2).split(" / ")]
for name in aliases:
names[key(name)] = name
if m.group(1) == "x" and home(line, aliases):
homed.append(aliases)
return names, homed
def home(line, aliases):
"""False for a pointer: `A → b::B` lives at B, another entry."""
if " → " not in line:
return True
target = re.search(r" → `([\w:]+)", line)
return bool(target) and any(key(part) in map(key, aliases) for part in target.group(1).split("::"))
def key(name):
return re.sub(r"[^a-z0-9]", "", re.sub(r"\([^)]*\)", "", name).lower())
def components(title, known):
"""The task lists' names for what the title names: "Icon button" is IconButton."""
def components(title, extra, known):
"""The task lists' names for what the title and `extra` name."""
names = []
for part in re.sub(r"\s*\([^)]*\)", "", title).split(" · "):
for name in part.split(" → ")[0].split(" / "):
for name in part.split(" → ")[0].split(" / ") + extra:
if key(name) in known and known[key(name)] not in names:
names.append(known[key(name)])
return names
@@ -145,28 +201,40 @@ def components(title, known):
def pages():
listing = (PAGES / "mod.rs").read_text()
order = re.findall(r"^\s+(\w+)::PAGE,$", listing, re.M)
known = entries()
out = []
known, homed = entries()
for (page, lead), extra in SHOWN.items():
for name in extra:
if key(name) not in known:
sys.exit(f"stories.py: {name} in {page}/{lead} is no entry in tasks/*.md")
out, shown = [], dict(SHOWN)
for module in order:
folder = PAGES / module
head = (folder / "mod.rs").read_text()
field = lambda name: re.search(rf"^\s+{name}: (.+),$", head, re.M).group(1)
page = json.loads(field("slug"))
titles = sections(folder)
slugs = [slug(title) for title in titles]
for twice in {s for s in slugs if slugs.count(s) > 1}:
sys.exit(f"{module}: two stories answer to {twice}")
stories = []
for t, s in zip(titles, slugs):
extra = shown.pop((page, re.split(r" · | / ", t)[0]), [])
stories.append({"title": t, "slug": s, "components": components(t, extra, known)})
out.append(
{
"number": int(field("number")),
"slug": json.loads(field("slug")),
"slug": page,
"title": json.loads(field("title")),
"summary": json.loads(field("summary")),
"stories": [
{"title": t, "slug": s, "components": components(t, known)}
for t, s in zip(titles, slugs)
],
"stories": stories,
}
)
if shown:
sys.exit(f"stories.py: SHOWN names no story for {sorted(shown)}")
named = {key(name) for page in out for story in page["stories"] for name in story["components"]}
for aliases in homed:
if not any(key(name) in named for name in aliases):
sys.exit(f"no story shows {' / '.join(aliases)}: name it in a title or in SHOWN")
return out
+2 -5
View File
@@ -382,13 +382,10 @@ impl CodeEditor {
}
/// Pastes one line at each cursor when the counts match, else the whole text at every one.
pub(crate) fn paste(&mut self, cx: &mut Context<Self>) {
pub(crate) fn paste(&mut self, text: &str, cx: &mut Context<Self>) {
if !self.editable() {
return;
}
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
return;
};
let lines: Vec<&str> = text.split('\n').collect();
let edits = if lines.len() == self.selections.len() && lines.len() > 1 {
self.in_order()
@@ -399,7 +396,7 @@ impl CodeEditor {
} else {
self.selections
.iter()
.map(|selection| (selection.range(), text.clone()))
.map(|selection| (selection.range(), text.to_string()))
.collect()
};
self.apply(edits, false, cx);
+8 -2
View File
@@ -1,8 +1,8 @@
use std::ops::Range;
use gpui::{
App, Bounds, Context, EntityInputHandler, FontStyle, HighlightStyle, Pixels, Point,
UTF16Selection, Window, point,
App, Bounds, ClipboardItem, Context, EntityInputHandler, FontStyle, HighlightStyle, Pixels,
Point, UTF16Selection, Window, point,
};
use super::{
@@ -107,6 +107,12 @@ impl CodeEditor {
}
impl EntityInputHandler for CodeEditor {
fn paste(&mut self, item: ClipboardItem, _: &mut Window, cx: &mut Context<Self>) {
if let Some(text) = item.text() {
self.paste(&text, cx);
}
}
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
+9 -2
View File
@@ -91,13 +91,16 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-a", SelectAll, context),
KeyBinding::new("secondary-c", Copy, context),
KeyBinding::new("secondary-x", Cut, context),
KeyBinding::new("secondary-v", Paste, context),
KeyBinding::new("secondary-d", SelectNext, context),
KeyBinding::new("escape", SingleCursor, context),
KeyBinding::new("secondary-/", ToggleComment, context),
KeyBinding::new("secondary-alt-[", Fold, context),
KeyBinding::new("secondary-alt-]", Unfold, context),
];
// The browser's paste event carries the clipboard to the input handler.
if !cfg!(target_family = "wasm") {
bindings.push(KeyBinding::new("secondary-v", Paste, context));
}
if cfg!(target_os = "macos") {
bindings.extend([
KeyBinding::new("cmd-left", LineStart, context),
@@ -261,7 +264,11 @@ pub(crate) fn listen<E: InteractiveElement>(root: E, cx: &mut Context<CodeEditor
let root = on!(root, SelectAll, |e, _, cx| e.select_all(cx));
let root = on!(root, Copy, |e, _, cx| e.copy(cx));
let root = on!(root, Cut, |e, _, cx| e.cut(cx));
let root = on!(root, Paste, |e, _, cx| e.paste(cx));
let root = on!(root, Paste, |e, _, cx| {
if let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) {
e.paste(&text, cx)
}
});
let root = on!(root, AddCursorAbove, |e, _, cx| e.add_cursor(false, cx));
let root = on!(root, AddCursorBelow, |e, _, cx| e.add_cursor(true, cx));
let root = on!(root, SelectNext, |e, _, cx| e.select_next(cx));
+17 -1
View File
@@ -1,6 +1,9 @@
use std::time::Duration;
use gpui::{Entity, EntityInputHandler, KeyUpEvent, Keystroke, TestAppContext, VisualTestContext};
use gpui::{
ClipboardItem, Entity, EntityInputHandler, KeyUpEvent, Keystroke, TestAppContext,
VisualTestContext,
};
use super::{CodeEditor, GhostText, InlayHint, LineNumbers, layout::Row};
use crate::theme::Theme;
@@ -240,3 +243,16 @@ impl CodeEditor {
super::layout::rows(&self.buffer, &hide, &self.marks).contains(&Row::Line(line))
}
}
#[gpui::test]
fn a_browser_paste_gives_each_cursor_its_line(cx: &mut TestAppContext) {
let (editor, cx) = editor("a\nb", cx);
cx.update(|window, cx| {
editor.update(cx, |editor, cx| {
let (first, second) = (1..1, 3..3);
editor.select([first, second], cx);
EntityInputHandler::paste(editor, ClipboardItem::new_string("1\n2".into()), window, cx);
assert_eq!(editor.text(), "a1\nb2");
})
});
}
+18 -2
View File
@@ -7,8 +7,8 @@ mod tags;
mod upload;
use gpui::{
AppContext as _, Context, Entity, EntityInputHandler, IntoElement, KeyUpEvent, Keystroke,
ParentElement, Render, TestAppContext, VisualTestContext, Window, div, px,
AppContext as _, ClipboardItem, Context, Entity, EntityInputHandler, IntoElement, KeyUpEvent,
Keystroke, ParentElement, Render, TestAppContext, VisualTestContext, Window, div, px,
};
use super::{Input, MaskedInput, NumberInput, PinInput, TextInput, bind_keys};
@@ -146,6 +146,22 @@ fn a_committed_composition_obeys_the_filter_and_length(cx: &mut TestAppContext)
assert_eq!(text(field, cx), "");
}
#[gpui::test]
fn a_browser_paste_undoes_apart_from_the_typing(cx: &mut TestAppContext) {
let (fields, cx) = open(1, false, cx);
let field = &fields[0];
focus(field, cx);
cx.simulate_input("ab");
cx.update(|window, cx| {
field.update(cx, |input, cx| {
EntityInputHandler::paste(input, ClipboardItem::new_string("cd".into()), window, cx)
})
});
assert_eq!(text(field, cx), "abcd");
cx.simulate_keystrokes("cmd-z");
assert_eq!(text(field, cx), "ab");
}
#[gpui::test]
fn undo_leaves_a_disabled_field_alone(cx: &mut TestAppContext) {
let (fields, cx) = open(1, false, cx);
+4 -1
View File
@@ -82,7 +82,6 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-a", SelectAll, Some(CONTEXT)),
KeyBinding::new("secondary-c", Copy, Some(CONTEXT)),
KeyBinding::new("secondary-x", Cut, Some(CONTEXT)),
KeyBinding::new("secondary-v", Paste, Some(CONTEXT)),
KeyBinding::new("secondary-z", Undo, Some(CONTEXT)),
KeyBinding::new("secondary-shift-z", Redo, Some(CONTEXT)),
KeyBinding::new("enter", Enter, Some(CONTEXT)),
@@ -90,6 +89,10 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-enter", Submit, Some(CONTEXT)),
KeyBinding::new("secondary-enter", Submit, Some(FORM_CONTEXT)),
];
// The browser's paste event carries the clipboard to the input handler.
if !cfg!(target_family = "wasm") {
bindings.push(KeyBinding::new("secondary-v", Paste, Some(CONTEXT)));
}
if cfg!(target_os = "macos") {
bindings.extend([
KeyBinding::new("cmd-left", Home, Some(CONTEXT)),
+9 -1
View File
@@ -1,6 +1,8 @@
use std::ops::Range;
use gpui::{Bounds, Context, EntityInputHandler, Pixels, Point, UTF16Selection, Window};
use gpui::{
Bounds, ClipboardItem, Context, EntityInputHandler, Pixels, Point, UTF16Selection, Window,
};
use super::{
TextInput,
@@ -18,6 +20,12 @@ impl TextInput {
}
impl EntityInputHandler for TextInput {
fn paste(&mut self, item: ClipboardItem, _: &mut Window, cx: &mut Context<Self>) {
if let Some(text) = item.text() {
self.replace(self.selection.clone(), &text, false, cx);
}
}
fn text_for_range(
&mut self,
range_utf16: Range<usize>,
+1
View File
@@ -284,6 +284,7 @@ pub(crate) fn keys(
))),
("v", true) => {
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
log::info!("grid: the clipboard holds no text to paste");
return;
};
let edits = pasted(&text, (row, col), (rows, cols));