LeafyGreen UI Kit

repository·main·Indexed 42 days ago

https://github.com/mongodb/leafygreen-ui

A library of React components, CSS patterns, and CLI tools for MongoDB's LeafyGreen Design System. It includes a comprehensive charting ecosystem featuring @lg-charts/core for canvas-rendered interactive charts, @lg-charts/chart-card for expandable chart wrappers, @lg-charts/legend for series management, and @lg-charts/drag-provider for draggable chart layouts.

Tokens
351.9K
Snippets
1.3K
Records
2K
Agent score
53%

What's inside LeafyGreen UI

  1. What is the Box component?

    main

    The Box component is a polymorphic component used to simplify components that require different behaviors or HTML elements while maintaining a consistent interface. It renders a specific component or HTML tag based on the props provided.

    Key behaviors:

    • Default Rendering: By default, Box renders as a div.
    • Link Behavior: Providing an href prop causes Box to render as an anchor (<a>) tag.
    • Polymorphism: Providing the as prop allows you to specify exactly which component or HTML tag should be rendered. Note that the as prop takes precedence over other props like href.
    import Box from '@leafygreen-ui/box';
    
    // Renders as a link (anchor tag)
    <Box href="https://mongodb.design">I shall render as a link</Box>;
    
    // Renders as a specific component or tag
    <Box as="section">Content</Box>;
  2. How the Rating and Feedback flow works

    main

    The MessageActions component manages a multi-step interaction for user feedback:

    1. Rating Only: If the user clicks a thumbs up or thumbs down (and onRatingChange is provided), onRatingChange is called with the selected rating. No feedback form appears.
    2. Rating + Feedback: If onSubmitFeedback is also provided, clicking a rating button triggers the appearance of a feedback form containing a textarea.
      • The user can optionally enter text.
      • Upon clicking submit, onSubmitFeedback is called with the rating and the optional feedback string.
      • The component displays a success message (defined by submittedMessage) or an error message (defined by errorMessage) based on the outcome.

    Internal State Management:

    • Message rating: 'unselected' | 'disliked' | 'liked'
    • Form state: 'unset' | 'submitting' | 'submitted' | 'error'
    • The form state is reset when the feedback form is closed or a new rating is selected.
  3. How Modal initial focus works

    main

    The Modal component manages focus automatically for accessibility. When the modal opens, it follows this priority order to determine which element receives focus:

    1. initialFocus prop: The element specified via a string selector (e.g., initialFocus="#submit-button") or a React ref (recommended for type safety).
    2. autoFocus attribute: If no initialFocus is provided, the first child element with the autoFocus attribute receives focus.
    3. First focusable element: If initialFocus is set to "auto" and no child has autoFocus, the first focusable element in the DOM tree is focused.
    4. No focus: If initialFocus is explicitly set to null, no automatic focus occurs.
  4. How the ProgressBar component works

    main

    The ProgressBar component supports two primary modes of operation:

    1. Indeterminate: Shows an infinite looping animation. Use this when the duration or total work is unknown (e.g., a generic loading state). Set the isIndeterminate prop to true.
    2. Determinate: Shows measurable progress toward a known maximum using value and maxValue. Determinate bars can be used in two ways via the role prop:
      • progressbar (Default): A dynamic bar that fills as a task progresses toward completion (e.g., a file upload).
      • meter: A snapshot of a current value, often used for status or health indications (e.g., disk space used).

    Note: isIndeterminate is only available for info and success variants.

    // Indeterminate example
    <ProgressBar isIndeterminate label="Loading..." />
    
    // Determinate progressbar example
    <ProgressBar value={50} maxValue={100} role="progressbar" />
    
    // Determinate meter example
    <ProgressBar value={90} maxValue={100} role="meter" />
  5. How useLeafygreenTable works with the Table component

    main

    The useLeafygreenTable hook is the recommended way to implement advanced table features. It wraps TanStack's react-table and react-virtual to provide styled support for features like virtualized scrolling, nested rows, and expandable rows.

    When using this hook, you pass the returned table instance to the Table component and use the hook's methods (like getHeaderGroups() and getRowModel()) to render the UI components.

    import {
      Table,
      TableHead,
      HeaderRow,
      TableBody,
      Row,
      Cell,
    } from '@leafygreen-ui/table';
    
    const tableContainerRef = React.useRef<HTMLDivElement>(null);
    const data = React.useState(() => makeData(false, 10_000))[0];
    
    const columns = React.useMemo<Array<ColumnDef<Person>>>(()
      () => [
        { accessorKey: 'id', header: 'ID', size: 60 },
        { accessorKey: 'firstName', header: 'First Name', cell: info => info.getValue() },
      ],
      [],
    );
    
    const table = useLeafygreenTable<Person>({
      containerRef: tableContainerRef,
      data,
      columns,
    });
    
    const { rows } = table.getRowModel();
    
    <Table
      {...args}
      table={table}
      ref={tableContainerRef}
    >
      <TableHead>
        {table.getHeaderGroups().map((headerGroup: HeaderGroup<Person>) => (
          <HeaderRow key={headerGroup.id}>
            {headerGroup.headers.map(header => (
              <HeaderCell key={header.id} header={header}>
                {flexRender(header.column.columnDef.header, header.getContext())}
              </HeaderCell>
            ))}
          </HeaderRow>
        ))}
      </TableHead>
      <TableBody>
        {rows.map((row: LeafyGreenTableRow<Person>) => (
          <Row key={row.id}>
            {row.getVisibleCells().map((cell: LeafyGreenTableCell<Person>) => (
              <Cell key={cell.id}>
                {flexRender(cell.column.columnDef.cell, cell.getContext())}
              </Cell>
            ))}
          </Row>
        ))}
      </TableBody>
    </Table>
  6. How useRenderData works

    main

    The useRenderData hook is used by iframe-based UI components to receive data from an MCP client via postMessage.

    Lifecycle:

    1. On Mount: The hook automatically sends a ui-lifecycle-iframe-ready message to the parent window.
    2. Listening: It listens for ui-lifecycle-iframe-render-data messages containing the payload.
    3. Dark Mode: It provides a computed darkMode boolean that prioritizes the darkMode field from the received render data, falling back to the browser's prefers-color-scheme if the field is unavailable.

    Use this hook when your component needs to react to data provided by the host environment.

    import { useRenderData } from '@lg-mcp/hooks';
    
    interface DatabaseData {
      databases: Array<{ name: string; size: number }>;
    }
    
    function MyComponent() {
      const { data, darkMode, isLoading, error } = useRenderData<DatabaseData>();
    
      if (isLoading) return <div>Loading...</div>;
      if (error) return <div>Error: {error}</div>;
    
      return (
        <div style={{ background: darkMode ? '#1a1a1a' : '#ffffff' }}>
          {data?.databases.length} databases found
        </div>
      );
    }
  7. How Wizard and Wizard.Step work together

    main

    The Wizard component manages the internal state of a multi-step workflow and renders only the currently activeStep.

    Basic Usage

    Each step is defined using Wizard.Step. You can include a Wizard.Footer within each step to provide navigation controls like primary, back, or cancel buttons.

    <Wizard>
      <Wizard.Step>
        <div>Step 1 contents</div>
        <Wizard.Footer
          primaryButtonProps={{
            children: 'Continue',
            variant: 'primary'
          }}
        />
      </Wizard.Step>
    
      <Wizard.Step requiresAcknowledgement>
        <div>Step 2 contents</div>
        <Wizard.Footer
          primaryButtonProps={{
            children: 'Delete',
            variant: 'danger'
          }}
        />
      </Wizard.Step>
    </Wizard>

    Controlled Mode

    You can control the wizard externally by providing activeStep and an onStepChange callback.

    Warning: When controlling the index externally, ensure the activeStep index is valid. If the zero-indexed value is negative or exceeds the number of available steps (e.g., passing activeStep={2} to a Wizard with only 2 steps), nothing will render.

    <Wizard
      activeStep={0}
      onStepChange={() => {
        /* do something */
      }}
    />
  8. Important layout considerations for DrawerLayout

    main

    When using DrawerLayout, keep these two requirements in mind:

    1. Explicit Height: If you render DrawerLayout inside another element, ensure that parent element has an explicit height. Otherwise, the layout may not size itself correctly.
    2. Display Modes:
      • Use 'overlay' for a floating <dialog> effect.
      • Use 'embedded' for a layout where the drawer pushes content aside. Note that resizable is only supported in 'embedded' mode.
  9. Use Password Input states

    main

    The PasswordInput component supports four distinct visual and semantic states for validation feedback. You can use these values to control the icon and text color of the input's feedback elements.

    Available states:

    • error: Displays a red 'x' icon and red text.
    • warning: Displays a red warning icon and red text.
    • valid: Displays a green checkmark and black text.
    • none: Displays a gray checkmark and gray text.
    const State = {
      Error: 'error',
      Warning: 'warning',
      Valid: 'valid',
      None: 'none',
    } as const;
  10. Use the Internal Input Option component

    main
    The Internal Input Option is a specialized component designed for use within dropdown menu option components. It is not intended for direct use in general application layouts, but rather as a building block for components that represent selectable items in a list, such as ComboboxOption, SelectOption, or SearchInputResult.