LCOV - code coverage report
Current view: top level - pkg/lib - cockpit-components-simple-select.tsx Coverage Total Hit
Test: cockpit Lines: 92.0 % 75 69
Test Date: 2026-06-25 11:17:56

            Line data    Source code
       1              : /*
       2              : SPDX-License-Identifier: MIT
       3              : 
       4              : Copyright (c) 2019 Red Hat, Inc.
       5              : */
       6              : 
       7              : /* This is a copy of SimpleSelect.tsx from
       8              : 
       9              :        https://github.com/patternfly/patternfly-react/blob/v5/packages/react-templates/src/components/Select/SimpleSelect.tsx
      10              : 
      11              :    We don't use it directly from the @patternfly/react-templates node
      12              :    module since we might want to add features to it, and also to
      13              :    isolate us from gratuitous upstream changes.
      14              : 
      15              :    Our changes:
      16              : 
      17              :    - The selection is controlled from the outside and not maintained
      18              :      as internal state. This is how things should work with React.
      19              : 
      20              :    - The selected value can be any JavaScript value, not just a string
      21              :      or number. Normally, the (stringified) value is used as a React
      22              :      key. If your values are not suitable for this because they are
      23              :      neither strings nor numbers, you need to provide an explicit key.
      24              : 
      25              :      { key: 12, value: { param: "boot", value: 12 }, content: _("Boot number 12") }
      26              : 
      27              :    - Support for dividers.
      28              : 
      29              :      { decorator: "divider", key: "..." }
      30              : 
      31              :    - Support for headers.
      32              : 
      33              :      { decorator: "header", content: _("Also available"), key: "..." }
      34              : 
      35              : */
      36              : 
      37              : /* eslint-disable */
      38              : 
      39          138 : import React from 'react';
      40              : import {
      41              :   Select,
      42              :   SelectList,
      43              :   SelectOption,
      44              :   SelectOptionProps,
      45              :   SelectProps,
      46              : } from '@patternfly/react-core/dist/esm/components/Select';
      47              : import { Divider } from '@patternfly/react-core/dist/esm/components/Divider';
      48              : import { MenuToggle, MenuToggleElement, MenuToggleProps } from '@patternfly/react-core/dist/esm/components/MenuToggle';
      49              : 
      50              : import "cockpit-components-select.scss";
      51              : 
      52              : export interface SimpleSelectDividerOption {
      53              :   decorator: "divider";
      54              : 
      55              :   key: string | number;
      56              : };
      57              : 
      58              : export interface SimpleSelectHeaderOption {
      59              :   decorator: "header";
      60              : 
      61              :   key: string | number;
      62              :   content: React.ReactNode;
      63              : };
      64              : 
      65              : export interface SimpleSelectMenuOption<T> extends Omit<SelectOptionProps, 'content'> {
      66              :   decorator?: undefined;
      67              : 
      68              :   /** Content of the select option. */
      69              :   content: React.ReactNode;
      70              :   /** Value of the select option. */
      71              :   value: T;
      72              : }
      73              : 
      74              : export type SimpleSelectOption<T> = SimpleSelectMenuOption<T> |
      75              :                                     SimpleSelectDividerOption |
      76              :                                     SimpleSelectHeaderOption;
      77              : 
      78              : export interface SimpleSelectProps<T> extends Omit<SelectProps, 'toggle' | 'onSelect'> {
      79              :   /** Initial options of the select. */
      80              :   options: SimpleSelectOption<T>[];
      81              :   /** Selected option */
      82              :   selected: T;
      83              :   /** Callback triggered on selection. */
      84              :   onSelect: (selection: T) => void;
      85              :   /** Callback triggered when the select opens or closes. */
      86              :   onToggle?: (nextIsOpen: boolean) => void;
      87              :   /** Flag indicating the select should be disabled. */
      88              :   isDisabled?: boolean;
      89              :   /** Content of the toggle. Defaults to the selected option. */
      90              :   toggleContent?: React.ReactNode;
      91              :   /** Placeholder text for the select input. */
      92              :   placeholder?: string;
      93              :   /** Width of the toggle. */
      94              :   toggleWidth?: string;
      95              :   /** Additional props passed to the toggle. */
      96              :   toggleProps?: MenuToggleProps;
      97              : }
      98              : 
      99           29 : export function SimpleSelect<T>({
     100           29 :   options,
     101           29 :   selected,
     102           29 :   isDisabled = false,
     103           29 :   onSelect,
     104           29 :   onToggle,
     105           29 :   toggleContent,
     106           29 :   toggleWidth,
     107           29 :   toggleProps,
     108           29 :   placeholder = '',
     109           29 :   ...props
     110           29 : }: SimpleSelectProps<T>) {
     111           29 :   const [isOpen, setIsOpen] = React.useState(false);
     112              : 
     113           29 :   const simpleSelectOptions = options.map(option => {
     114           29 :     if (option.decorator == "divider")
     115            5 :       return <Divider key={option.key} component="li" />;
     116              : 
     117           29 :     if (option.decorator == "header")
     118           29 :       return (
     119            3 :         <SelectOption key={option.key}
     120            3 :             isDisabled
     121            3 :             className="ct-select-header">
     122            3 :             {option.content}
     123            3 :         </SelectOption>
     124              :       );
     125              : 
     126           29 :     const { content, value, key, ...props } = option;
     127           29 :     return (
     128           13 :       <SelectOption value={value} key={key !== undefined ? key : `${value}`} {...props}>
     129           29 :         {content}
     130           29 :       </SelectOption>
     131              :     );
     132           29 :   });
     133              : 
     134           11 :   const onToggleClick = () => {
     135            0 :     onToggle && onToggle(!isOpen);
     136           11 :     setIsOpen(!isOpen);
     137           11 :   };
     138              : 
     139           10 :   const _onSelect = (_event: React.MouseEvent<Element, MouseEvent> | undefined, value: T | undefined) => {
     140           10 :     if (value) {
     141           10 :       onSelect(value);
     142           10 :     }
     143            0 :     onToggle && onToggle(true);
     144           10 :     setIsOpen(false);
     145           10 :   };
     146              : 
     147           29 :   let content: React.ReactNode = placeholder;
     148           29 :   if (toggleContent)
     149            3 :     content = toggleContent;
     150           29 :   else if (selected)
     151           29 :     content = options.find((o): o is SimpleSelectMenuOption<T> => !o.decorator && o.value == selected)?.content;
     152              : 
     153           29 :   const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
     154           29 :     <MenuToggle
     155           29 :       ref={toggleRef}
     156           29 :       onClick={onToggleClick}
     157           29 :       isExpanded={isOpen}
     158           29 :       isDisabled={isDisabled}
     159           29 :       style={
     160           29 :         {
     161           29 :           width: toggleWidth
     162           29 :         } as React.CSSProperties
     163              :       }
     164           29 :       {...toggleProps}
     165              :     >
     166           29 :       {content}
     167           29 :     </MenuToggle>
     168              :   );
     169              : 
     170           29 :   return (
     171           29 :     <Select
     172           29 :       isOpen={isOpen}
     173           29 :       selected={selected}
     174           29 :       onSelect={_onSelect}
     175            0 :       onOpenChange={(isOpen) => {
     176            0 :         onToggle && onToggle(isOpen);
     177            0 :         setIsOpen(isOpen);
     178            0 :       }}
     179           29 :       toggle={toggle}
     180           29 :       shouldFocusToggleOnSelect
     181           29 :       {...props}
     182              :     >
     183           29 :       <SelectList>{simpleSelectOptions}</SelectList>
     184           29 :     </Select>
     185              :   );
     186           29 : };
     187              : 
     188          138 : SimpleSelect.displayName = 'SimpleSelect';
        

Generated by: LCOV version 2.0-1