Skip to main content

gpui_macos/
catalog.rs

1//! Complete, discoverable component inventory used by the native gallery.
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4pub enum ComponentCategory {
5    Explore,
6    Actions,
7    Forms,
8    Navigation,
9    Overlays,
10    Display,
11    Messages,
12    MacOsLayouts,
13    Charts,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct ComponentInfo {
18    pub id: &'static str,
19    pub name: &'static str,
20    pub category: ComponentCategory,
21    pub description: &'static str,
22    /// The GPUI Kit documentation slug, or `None` when there is no dedicated upstream page.
23    pub documentation: Option<&'static str>,
24}
25
26/// Ordered sidebar groups. Every identifier resolves through [`component`].
27pub const COMPONENT_GROUPS: &[(&str, &[&str])] = &[
28    ("Explore", &["overview", "theme", "root", "focus_trap"]),
29    (
30        "Actions",
31        &[
32            "button",
33            "button_group",
34            "dropdown_button",
35            "link",
36            "toggle",
37            "toggle_group",
38            "clipboard",
39            "command",
40        ],
41    ),
42    (
43        "Forms",
44        &[
45            "input",
46            "textarea",
47            "editor",
48            "number_input",
49            "input_group",
50            "select",
51            "combobox",
52            "calendar",
53            "date_picker",
54            "time_field",
55            "color_picker",
56            "otp_input",
57            "slider",
58            "checkbox",
59            "switch",
60            "radio",
61            "form",
62            "questionnaire",
63            "stepper",
64            "search_field",
65            "secure_field",
66        ],
67    ),
68    (
69        "Navigation",
70        &[
71            "menu",
72            "tabs",
73            "accordion",
74            "collapsible",
75            "nav_stack",
76            "pagination",
77            "breadcrumb",
78            "sidebar",
79        ],
80    ),
81    (
82        "Overlays",
83        &[
84            "sheet",
85            "dialog",
86            "alert_dialog",
87            "popover",
88            "tooltip",
89            "hover_card",
90            "toast",
91            "notification",
92        ],
93    ),
94    (
95        "Display",
96        &[
97            "icon",
98            "avatar",
99            "text_view",
100            "label",
101            "panel",
102            "group_box",
103            "description_list",
104            "list",
105            "virtual_list",
106            "scrollbar",
107            "scrollable",
108            "table",
109            "data_table",
110            "tree",
111            "resizable",
112            "dock",
113            "separator",
114            "keycap",
115            "badge",
116            "tag",
117            "alert",
118            "empty_state",
119            "progress",
120            "spinner",
121            "skeleton",
122            "shimmer",
123            "rating",
124            "carousel",
125            "image",
126            "diff",
127        ],
128    ),
129    (
130        "Messages",
131        &[
132            "message",
133            "bubble",
134            "marker",
135            "attachment",
136            "message_scroller",
137            "speech",
138        ],
139    ),
140    (
141        "macOS layouts",
142        &[
143            "title_bar",
144            "toolbar",
145            "status_bar",
146            "settings",
147            "inspector",
148            "inset_list",
149            "material",
150        ],
151    ),
152    (
153        "Charts",
154        &[
155            "plot",
156            "line_chart",
157            "bar_chart",
158            "area_chart",
159            "pie_chart",
160            "radar_chart",
161            "candlestick_chart",
162            "sankey_chart",
163        ],
164    ),
165];
166
167/// All gallery pages, including the library overview.
168pub const COMPONENTS: &[ComponentInfo] = &[
169    ComponentInfo {
170        id: "overview",
171        name: "Overview",
172        category: ComponentCategory::Explore,
173        description: "Explore the components together, then use the sidebar to inspect each one.",
174        documentation: Some("index"),
175    },
176    ComponentInfo {
177        id: "theme",
178        name: "Theme",
179        category: ComponentCategory::Explore,
180        description: "System appearance, macOS accent colors, and semantic light and dark tokens.",
181        documentation: Some("theme"),
182    },
183    ComponentInfo {
184        id: "root",
185        name: "Root",
186        category: ComponentCategory::Explore,
187        description: "The native window host coordinates overlays, focus, and text selection.",
188        documentation: Some("root"),
189    },
190    ComponentInfo {
191        id: "focus_trap",
192        name: "Focus trap",
193        category: ComponentCategory::Explore,
194        description: "Keep keyboard focus within the current group of related controls.",
195        documentation: Some("focus-trap"),
196    },
197    ComponentInfo {
198        id: "button",
199        name: "Button",
200        category: ComponentCategory::Actions,
201        description: "Content-sized actions with quiet hover, focus and pressed states.",
202        documentation: Some("button"),
203    },
204    ComponentInfo {
205        id: "button_group",
206        name: "Button group",
207        category: ComponentCategory::Actions,
208        description: "Choose one setting from a row of mutually exclusive options.",
209        documentation: Some("button"),
210    },
211    ComponentInfo {
212        id: "dropdown_button",
213        name: "Dropdown button",
214        category: ComponentCategory::Actions,
215        description: "Combine a primary command and its related actions in one compact control.",
216        documentation: Some("dropdown_button"),
217    },
218    ComponentInfo {
219        id: "link",
220        name: "Link",
221        category: ComponentCategory::Actions,
222        description: "Open a named destination.",
223        documentation: None,
224    },
225    ComponentInfo {
226        id: "toggle",
227        name: "Toggle",
228        category: ComponentCategory::Actions,
229        description: "Keep a command active until it is pressed again.",
230        documentation: Some("toggle"),
231    },
232    ComponentInfo {
233        id: "toggle_group",
234        name: "Toggle group",
235        category: ComponentCategory::Actions,
236        description: "Combine independent filters to show more than one status.",
237        documentation: Some("toggle"),
238    },
239    ComponentInfo {
240        id: "clipboard",
241        name: "Clipboard",
242        category: ComponentCategory::Actions,
243        description: "Copy a value with keyboard-accessible confirmation.",
244        documentation: Some("clipboard"),
245    },
246    ComponentInfo {
247        id: "command",
248        name: "Command",
249        category: ComponentCategory::Actions,
250        description: "Find and invoke commands with a searchable, keyboard-controlled palette.",
251        documentation: Some("command"),
252    },
253    ComponentInfo {
254        id: "input",
255        name: "Input",
256        category: ComponentCategory::Forms,
257        description: "Single-line editing with selection, clipboard and IME support.",
258        documentation: Some("input"),
259    },
260    ComponentInfo {
261        id: "textarea",
262        name: "Textarea",
263        category: ComponentCategory::Forms,
264        description: "Multi-line notes with native text editing.",
265        documentation: Some("textarea"),
266    },
267    ComponentInfo {
268        id: "editor",
269        name: "Editor",
270        category: ComponentCategory::Forms,
271        description: "Edit source with line numbers, folding, search, and multiple cursors.",
272        documentation: Some("editor"),
273    },
274    ComponentInfo {
275        id: "number_input",
276        name: "Number input",
277        category: ComponentCategory::Forms,
278        description: "A compact numeric field with keyboard and button stepping.",
279        documentation: Some("number-input"),
280    },
281    ComponentInfo {
282        id: "input_group",
283        name: "Input group",
284        category: ComponentCategory::Forms,
285        description: "Compose a text field with leading and trailing accessories.",
286        documentation: Some("input-group"),
287    },
288    ComponentInfo {
289        id: "select",
290        name: "Select",
291        category: ComponentCategory::Forms,
292        description: "Choose one value from a fixed set of options.",
293        documentation: Some("select"),
294    },
295    ComponentInfo {
296        id: "combobox",
297        name: "Combobox",
298        category: ComponentCategory::Forms,
299        description: "Search a collection, then choose a matching option.",
300        documentation: Some("combobox"),
301    },
302    ComponentInfo {
303        id: "calendar",
304        name: "Calendar",
305        category: ComponentCategory::Forms,
306        description: "Choose a date with month and year navigation.",
307        documentation: Some("calendar"),
308    },
309    ComponentInfo {
310        id: "date_picker",
311        name: "Date picker",
312        category: ComponentCategory::Forms,
313        description: "Choose a date from a calendar anchored to a field.",
314        documentation: Some("date-picker"),
315    },
316    ComponentInfo {
317        id: "time_field",
318        name: "Time field",
319        category: ComponentCategory::Forms,
320        description: "Edit hours and minutes as keyboard-navigable time segments.",
321        documentation: Some("time-field"),
322    },
323    ComponentInfo {
324        id: "color_picker",
325        name: "Color picker",
326        category: ComponentCategory::Forms,
327        description: "Choose a label color with Hex and HSLA controls.",
328        documentation: Some("color-picker"),
329    },
330    ComponentInfo {
331        id: "otp_input",
332        name: "OTP input",
333        category: ComponentCategory::Forms,
334        description: "Enter a six-digit verification code.",
335        documentation: Some("otp-input"),
336    },
337    ComponentInfo {
338        id: "slider",
339        name: "Slider",
340        category: ComponentCategory::Forms,
341        description: "Adjust one value or a range with the pointer or keyboard.",
342        documentation: Some("slider"),
343    },
344    ComponentInfo {
345        id: "checkbox",
346        name: "Checkbox",
347        category: ComponentCategory::Forms,
348        description: "Independent choices, including a mixed selection.",
349        documentation: Some("checkbox"),
350    },
351    ComponentInfo {
352        id: "switch",
353        name: "Switch",
354        category: ComponentCategory::Forms,
355        description: "An immediate on/off choice with a visible track and thumb.",
356        documentation: Some("switch"),
357    },
358    ComponentInfo {
359        id: "radio",
360        name: "Radio",
361        category: ComponentCategory::Forms,
362        description: "Choose one option from a group.",
363        documentation: Some("radio"),
364    },
365    ComponentInfo {
366        id: "form",
367        name: "Form",
368        category: ComponentCategory::Forms,
369        description: "Labeled, validated fields with consistent alignment and save actions.",
370        documentation: Some("form"),
371    },
372    ComponentInfo {
373        id: "questionnaire",
374        name: "Questionnaire",
375        category: ComponentCategory::Forms,
376        description: "Move through questions with retained answers, validation, and progress.",
377        documentation: Some("questionnaire"),
378    },
379    ComponentInfo {
380        id: "stepper",
381        name: "Stepper",
382        category: ComponentCategory::Forms,
383        description: "Walk through the steps of a task with visible completion state.",
384        documentation: Some("stepper"),
385    },
386    ComponentInfo {
387        id: "search_field",
388        name: "Search field",
389        category: ComponentCategory::Forms,
390        description: "Search using a native inset field with an inline search symbol.",
391        documentation: Some("input"),
392    },
393    ComponentInfo {
394        id: "secure_field",
395        name: "Secure field",
396        category: ComponentCategory::Forms,
397        description: "Enter a password in a masked text field.",
398        documentation: Some("input"),
399    },
400    ComponentInfo {
401        id: "menu",
402        name: "Menu",
403        category: ComponentCategory::Navigation,
404        description: "An anchored action menu with one pointer and keyboard cursor.",
405        documentation: Some("menu"),
406    },
407    ComponentInfo {
408        id: "tabs",
409        name: "Tabs",
410        category: ComponentCategory::Navigation,
411        description: "Switch between related views while keeping context.",
412        documentation: Some("tabs"),
413    },
414    ComponentInfo {
415        id: "accordion",
416        name: "Accordion",
417        category: ComponentCategory::Navigation,
418        description: "Reveal supporting content when it is needed.",
419        documentation: Some("accordion"),
420    },
421    ComponentInfo {
422        id: "collapsible",
423        name: "Collapsible",
424        category: ComponentCategory::Navigation,
425        description: "Expand a single region for additional settings.",
426        documentation: Some("collapsible"),
427    },
428    ComponentInfo {
429        id: "nav_stack",
430        name: "Navigation stack",
431        category: ComponentCategory::Navigation,
432        description: "Navigate between persistent pages and return to where you left off.",
433        documentation: None,
434    },
435    ComponentInfo {
436        id: "pagination",
437        name: "Pagination",
438        category: ComponentCategory::Navigation,
439        description: "Move through a paged collection.",
440        documentation: Some("pagination"),
441    },
442    ComponentInfo {
443        id: "breadcrumb",
444        name: "Breadcrumb",
445        category: ComponentCategory::Navigation,
446        description: "Return to an ancestor in a document or folder hierarchy.",
447        documentation: None,
448    },
449    ComponentInfo {
450        id: "sidebar",
451        name: "Sidebar",
452        category: ComponentCategory::Navigation,
453        description: "Source-list navigation with icons and rounded selected rows.",
454        documentation: Some("sidebar"),
455    },
456    ComponentInfo {
457        id: "sheet",
458        name: "Sheet",
459        category: ComponentCategory::Overlays,
460        description: "Inspect project details in an edge-attached panel.",
461        documentation: Some("sheet"),
462    },
463    ComponentInfo {
464        id: "dialog",
465        name: "Dialog",
466        category: ComponentCategory::Overlays,
467        description: "A focused task with confirmation, cancellation and focus return.",
468        documentation: Some("dialog"),
469    },
470    ComponentInfo {
471        id: "alert_dialog",
472        name: "Alert dialog",
473        category: ComponentCategory::Overlays,
474        description: "An explicit decision that cannot be dismissed by clicking the backdrop.",
475        documentation: Some("alert-dialog"),
476    },
477    ComponentInfo {
478        id: "popover",
479        name: "Popover",
480        category: ComponentCategory::Overlays,
481        description: "Adjust contextual settings while keeping the workspace in view.",
482        documentation: Some("popover"),
483    },
484    ComponentInfo {
485        id: "tooltip",
486        name: "Tooltip",
487        category: ComponentCategory::Overlays,
488        description: "A short explanation for an action, shown on hover.",
489        documentation: Some("tooltip"),
490    },
491    ComponentInfo {
492        id: "hover_card",
493        name: "Hover card",
494        category: ComponentCategory::Overlays,
495        description: "Preview supporting details without leaving the page.",
496        documentation: Some("hover-card"),
497    },
498    ComponentInfo {
499        id: "toast",
500        name: "Toast",
501        category: ComponentCategory::Overlays,
502        description: "Brief feedback that leaves your current task in place.",
503        documentation: Some("notification"),
504    },
505    ComponentInfo {
506        id: "notification",
507        name: "Notification",
508        category: ComponentCategory::Overlays,
509        description: "Window-hosted notifications with semantic status and dismissal.",
510        documentation: Some("notification"),
511    },
512    ComponentInfo {
513        id: "icon",
514        name: "Icon",
515        category: ComponentCategory::Display,
516        description: "Native SF Symbols on macOS with Lucide fallbacks that inherit text color.",
517        documentation: Some("icon"),
518    },
519    ComponentInfo {
520        id: "avatar",
521        name: "Avatar",
522        category: ComponentCategory::Display,
523        description: "Identify people and workspaces with an image or initials.",
524        documentation: Some("avatar"),
525    },
526    ComponentInfo {
527        id: "text_view",
528        name: "Text view",
529        category: ComponentCategory::Display,
530        description: "Read structured documents with selectable text and links.",
531        documentation: Some("text-view"),
532    },
533    ComponentInfo {
534        id: "label",
535        name: "Label",
536        category: ComponentCategory::Display,
537        description: "Readable labels with secondary text, masking, and search highlights.",
538        documentation: Some("label"),
539    },
540    ComponentInfo {
541        id: "panel",
542        name: "Panel",
543        category: ComponentCategory::Display,
544        description: "A surface for a related group of settings or information.",
545        documentation: None,
546    },
547    ComponentInfo {
548        id: "group_box",
549        name: "Group box",
550        category: ComponentCategory::Display,
551        description: "Group preferences in a titled, rounded container.",
552        documentation: Some("group-box"),
553    },
554    ComponentInfo {
555        id: "description_list",
556        name: "Description list",
557        category: ComponentCategory::Display,
558        description: "Compare labels and values in a structured inspector.",
559        documentation: Some("description-list"),
560    },
561    ComponentInfo {
562        id: "list",
563        name: "List",
564        category: ComponentCategory::Display,
565        description: "Selectable rows with icons, secondary content, and keyboard navigation.",
566        documentation: Some("list"),
567    },
568    ComponentInfo {
569        id: "virtual_list",
570        name: "Virtual list",
571        category: ComponentCategory::Display,
572        description: "Browse a large activity log with variable-height rows.",
573        documentation: Some("virtual-list"),
574    },
575    ComponentInfo {
576        id: "scrollbar",
577        name: "Scrollbar",
578        category: ComponentCategory::Display,
579        description: "Drag the scroll thumb to move through a long activity log.",
580        documentation: None,
581    },
582    ComponentInfo {
583        id: "scrollable",
584        name: "Scrollable",
585        category: ComponentCategory::Display,
586        description: "Scroll long content in a bounded native pane.",
587        documentation: Some("scrollable"),
588    },
589    ComponentInfo {
590        id: "table",
591        name: "Table",
592        category: ComponentCategory::Display,
593        description: "Aligned columns for comparing records.",
594        documentation: Some("table"),
595    },
596    ComponentInfo {
597        id: "data_table",
598        name: "Data table",
599        category: ComponentCategory::Display,
600        description: "Use the full table delegate for selection, columns, and large datasets.",
601        documentation: Some("data-table"),
602    },
603    ComponentInfo {
604        id: "tree",
605        name: "Tree",
606        category: ComponentCategory::Display,
607        description: "Explore nested folders and select a workspace document.",
608        documentation: Some("tree"),
609    },
610    ComponentInfo {
611        id: "resizable",
612        name: "Resizable",
613        category: ComponentCategory::Display,
614        description: "Drag the divider to adjust space between panes.",
615        documentation: Some("resizable"),
616    },
617    ComponentInfo {
618        id: "dock",
619        name: "Dock",
620        category: ComponentCategory::Display,
621        description: "Rearrange document panels by dragging their tabs.",
622        documentation: Some("dock"),
623    },
624    ComponentInfo {
625        id: "separator",
626        name: "Separator",
627        category: ComponentCategory::Display,
628        description: "A quiet boundary between distinct sections.",
629        documentation: None,
630    },
631    ComponentInfo {
632        id: "keycap",
633        name: "Keycap",
634        category: ComponentCategory::Display,
635        description: "Compact, readable keyboard hints.",
636        documentation: Some("kbd"),
637    },
638    ComponentInfo {
639        id: "badge",
640        name: "Badge",
641        category: ComponentCategory::Display,
642        description: "Short labels for neutral and semantic status.",
643        documentation: Some("badge"),
644    },
645    ComponentInfo {
646        id: "tag",
647        name: "Tag",
648        category: ComponentCategory::Display,
649        description: "Compact semantic labels with filled and outline presentations.",
650        documentation: Some("tag"),
651    },
652    ComponentInfo {
653        id: "alert",
654        name: "Alert",
655        category: ComponentCategory::Display,
656        description: "Inline feedback banners for neutral and semantic conditions.",
657        documentation: Some("alert"),
658    },
659    ComponentInfo {
660        id: "empty_state",
661        name: "Empty state",
662        category: ComponentCategory::Display,
663        description: "Explain an empty collection and its next step.",
664        documentation: Some("empty"),
665    },
666    ComponentInfo {
667        id: "progress",
668        name: "Progress",
669        category: ComponentCategory::Display,
670        description: "Show how much of a known task is complete.",
671        documentation: Some("progress"),
672    },
673    ComponentInfo {
674        id: "spinner",
675        name: "Spinner",
676        category: ComponentCategory::Display,
677        description: "Show an ongoing task with an animated progress indicator.",
678        documentation: Some("spinner"),
679    },
680    ComponentInfo {
681        id: "skeleton",
682        name: "Skeleton",
683        category: ComponentCategory::Display,
684        description: "Reserve stable layout while content is loading.",
685        documentation: Some("skeleton"),
686    },
687    ComponentInfo {
688        id: "shimmer",
689        name: "Shimmer",
690        category: ComponentCategory::Display,
691        description: "Animate a subtle highlight over text during background work.",
692        documentation: Some("shimmer"),
693    },
694    ComponentInfo {
695        id: "rating",
696        name: "Rating",
697        category: ComponentCategory::Display,
698        description: "Choose a star rating or display a read-only score.",
699        documentation: Some("rating"),
700    },
701    ComponentInfo {
702        id: "carousel",
703        name: "Carousel",
704        category: ComponentCategory::Display,
705        description: "Browse a collection with previous, next, and pagination controls.",
706        documentation: Some("carousel"),
707    },
708    ComponentInfo {
709        id: "image",
710        name: "Image",
711        category: ComponentCategory::Display,
712        description: "Draw bundled or local images with preserved aspect ratio and cropping.",
713        documentation: Some("image"),
714    },
715    ComponentInfo {
716        id: "diff",
717        name: "Diff",
718        category: ComponentCategory::Display,
719        description: "Review source changes in unified or side-by-side views.",
720        documentation: Some("diff"),
721    },
722    ComponentInfo {
723        id: "message",
724        name: "Message",
725        category: ComponentCategory::Messages,
726        description: "Compose avatars, headers, bodies, and footers in a conversation.",
727        documentation: Some("message"),
728    },
729    ComponentInfo {
730        id: "bubble",
731        name: "Bubble",
732        category: ComponentCategory::Messages,
733        description: "Display incoming and outgoing message bubbles.",
734        documentation: Some("bubble"),
735    },
736    ComponentInfo {
737        id: "marker",
738        name: "Marker",
739        category: ComponentCategory::Messages,
740        description: "Mark dates and events in a conversation timeline.",
741        documentation: Some("marker"),
742    },
743    ComponentInfo {
744        id: "attachment",
745        name: "Attachment",
746        category: ComponentCategory::Messages,
747        description: "Show files with progress, retry, and remove actions.",
748        documentation: Some("attachment"),
749    },
750    ComponentInfo {
751        id: "message_scroller",
752        name: "Message scroller",
753        category: ComponentCategory::Messages,
754        description: "Follow new messages while preserving a manually scrolled position.",
755        documentation: Some("message-scroller"),
756    },
757    ComponentInfo {
758        id: "speech",
759        name: "Speech",
760        category: ComponentCategory::Messages,
761        description: "Speech capture and waveform controls; system recognition is an optional feature.",
762        documentation: Some("speech"),
763    },
764    ComponentInfo {
765        id: "title_bar",
766        name: "Title bar",
767        category: ComponentCategory::MacOsLayouts,
768        description: "A native title-bar surface leaves room for the system window controls.",
769        documentation: Some("title-bar"),
770    },
771    ComponentInfo {
772        id: "toolbar",
773        name: "Toolbar",
774        category: ComponentCategory::MacOsLayouts,
775        description: "Compact command groups on a translucent macOS toolbar surface.",
776        documentation: Some("toolbar"),
777    },
778    ComponentInfo {
779        id: "status_bar",
780        name: "Status bar",
781        category: ComponentCategory::MacOsLayouts,
782        description: "Quiet document status and contextual controls along a window edge.",
783        documentation: Some("status-bar"),
784    },
785    ComponentInfo {
786        id: "settings",
787        name: "Settings",
788        category: ComponentCategory::MacOsLayouts,
789        description: "Grouped preference rows with descriptive labels and trailing controls.",
790        documentation: Some("settings"),
791    },
792    ComponentInfo {
793        id: "inspector",
794        name: "Inspector",
795        category: ComponentCategory::MacOsLayouts,
796        description: "A compact details pane with clear field alignment.",
797        documentation: None,
798    },
799    ComponentInfo {
800        id: "inset_list",
801        name: "Inset list",
802        category: ComponentCategory::MacOsLayouts,
803        description: "Rounded inset rows for grouped preferences and collections.",
804        documentation: None,
805    },
806    ComponentInfo {
807        id: "material",
808        name: "Material",
809        category: ComponentCategory::MacOsLayouts,
810        description: "Semantic surface fills and opt-in native system glass underlays.",
811        documentation: None,
812    },
813    ComponentInfo {
814        id: "plot",
815        name: "Plot",
816        category: ComponentCategory::Charts,
817        description: "Compose axes, marks, scales, and grids for custom visualizations.",
818        documentation: Some("plot"),
819    },
820    ComponentInfo {
821        id: "line_chart",
822        name: "Line chart",
823        category: ComponentCategory::Charts,
824        description: "Follow one measure over time. Hover a point for its value.",
825        documentation: Some("chart"),
826    },
827    ComponentInfo {
828        id: "bar_chart",
829        name: "Bar chart",
830        category: ComponentCategory::Charts,
831        description: "Compare totals across categories.",
832        documentation: Some("chart"),
833    },
834    ComponentInfo {
835        id: "area_chart",
836        name: "Area chart",
837        category: ComponentCategory::Charts,
838        description: "Compare related measures over time, such as download and upload.",
839        documentation: Some("chart"),
840    },
841    ComponentInfo {
842        id: "pie_chart",
843        name: "Pie chart",
844        category: ComponentCategory::Charts,
845        description: "Show how a whole divides into parts.",
846        documentation: Some("chart"),
847    },
848    ComponentInfo {
849        id: "radar_chart",
850        name: "Radar chart",
851        category: ComponentCategory::Charts,
852        description: "Compare profiles across several dimensions at once.",
853        documentation: Some("chart"),
854    },
855    ComponentInfo {
856        id: "candlestick_chart",
857        name: "Candlestick chart",
858        category: ComponentCategory::Charts,
859        description: "Read the open, high, low and close of each trading session.",
860        documentation: Some("chart"),
861    },
862    ComponentInfo {
863        id: "sankey_chart",
864        name: "Sankey chart",
865        category: ComponentCategory::Charts,
866        description: "Trace how an amount flows from its sources to where it goes.",
867        documentation: Some("chart"),
868    },
869];
870
871/// Find a component by its stable gallery identifier.
872pub fn component(id: &str) -> Option<&'static ComponentInfo> {
873    COMPONENTS.iter().find(|item| item.id == id)
874}
875
876impl ComponentInfo {
877    /// The explicit native counterpart or macOS workflow interpretation.
878    pub fn interpretation(&self) -> &'static crate::interpretation::ComponentInterpretation {
879        crate::interpretation::interpretation(self.id)
880            .expect("every catalog page has a presentation contract")
881    }
882}
883
884#[cfg(test)]
885mod tests {
886    use super::*;
887    #[test]
888    fn component_inventory_is_complete_and_unique() {
889        let ids: std::collections::HashSet<_> = COMPONENTS.iter().map(|item| item.id).collect();
890        assert_eq!(ids.len(), COMPONENTS.len());
891        assert!(COMPONENTS.len() >= 90);
892        let grouped: Vec<_> = COMPONENT_GROUPS
893            .iter()
894            .flat_map(|(_, ids)| ids.iter().copied())
895            .collect();
896        assert_eq!(grouped.len(), COMPONENTS.len());
897        assert_eq!(
898            grouped
899                .iter()
900                .copied()
901                .collect::<std::collections::HashSet<_>>(),
902            ids
903        );
904        assert!(
905            COMPONENTS
906                .iter()
907                .all(|item| !item.name.is_empty() && !item.description.is_empty())
908        );
909    }
910}