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-07-13 10:00:01

            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          133 : 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           28 : export function SimpleSelect<T>({
     100           28 :   options,
     101           28 :   selected,
     102           28 :   isDisabled = false,
     103           28 :   onSelect,
     104           28 :   onToggle,
     105           28 :   toggleContent,
     106           28 :   toggleWidth,
     107           28 :   toggleProps,
     108           28 :   placeholder = '',
     109           28 :   ...props
     110           28 : }: SimpleSelectProps<T>) {
     111           28 :   const [isOpen, setIsOpen] = React.useState(false);
     112              : 
     113           28 :   const simpleSelectOptions = options.map(option => {
     114           28 :     if (option.decorator == "divider")
     115            5 :       return <Divider key={option.key} component="li" />;
     116              : 
     117           28 :     if (option.decorator == "header")
     118           28 :       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           28 :     const { content, value, key, ...props } = option;
     127           28 :     return (
     128           12 :       <SelectOption value={value} key={key !== undefined ? key : `${value}`} {...props}>
     129           28 :         {content}
     130           28 :       </SelectOption>
     131              :     );
     132           28 :   });
     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           28 :   let content: React.ReactNode = placeholder;
     148           28 :   if (toggleContent)
     149            3 :     content = toggleContent;
     150           28 :   else if (selected)
     151           28 :     content = options.find((o): o is SimpleSelectMenuOption<T> => !o.decorator && o.value == selected)?.content;
     152              : 
     153           28 :   const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
     154           28 :     <MenuToggle
     155           28 :       ref={toggleRef}
     156           28 :       onClick={onToggleClick}
     157           28 :       isExpanded={isOpen}
     158           28 :       isDisabled={isDisabled}
     159           28 :       style={
     160           28 :         {
     161           28 :           width: toggleWidth
     162           28 :         } as React.CSSProperties
     163              :       }
     164           28 :       {...toggleProps}
     165              :     >
     166           28 :       {content}
     167           28 :     </MenuToggle>
     168              :   );
     169              : 
     170           28 :   return (
     171           28 :     <Select
     172           28 :       isOpen={isOpen}
     173           28 :       selected={selected}
     174           28 :       onSelect={_onSelect}
     175            0 :       onOpenChange={(isOpen) => {
     176            0 :         onToggle && onToggle(isOpen);
     177            0 :         setIsOpen(isOpen);
     178            0 :       }}
     179           28 :       toggle={toggle}
     180           28 :       shouldFocusToggleOnSelect
     181           28 :       {...props}
     182              :     >
     183           28 :       <SelectList>{simpleSelectOptions}</SelectList>
     184           28 :     </Select>
     185              :   );
     186           28 : };
     187              : 
     188          133 : SimpleSelect.displayName = 'SimpleSelect';
        

Generated by: LCOV version 2.0-1