@centreon/ui 24.4.29 → 24.4.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "24.4.29",
3
+ "version": "24.4.31",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -0,0 +1,85 @@
1
+ import NumberField, { NumberProps } from './Number';
2
+
3
+ const initialize = (props: NumberProps): void => {
4
+ cy.mount({
5
+ Component: <NumberField {...props} />
6
+ });
7
+ };
8
+
9
+ describe('Number field', () => {
10
+ it('displays the placeholder when the field has no default or fallback values', () => {
11
+ initialize({ dataTestId: 'test', onChange: cy.stub() });
12
+
13
+ cy.get('input').should('have.value', '');
14
+ cy.get('input').should('have.attr', 'placeholder', '0');
15
+
16
+ cy.makeSnapshot();
17
+ });
18
+
19
+ it('displays the fallback value as placeholder when the field as no default value', () => {
20
+ initialize({ dataTestId: 'test', fallbackValue: 25, onChange: cy.stub() });
21
+
22
+ cy.get('input').should('have.value', '');
23
+ cy.get('input').should('have.attr', 'placeholder', '25');
24
+
25
+ cy.makeSnapshot();
26
+ });
27
+
28
+ it('displays the default value as placeholder when the field as default value', () => {
29
+ initialize({ dataTestId: 'test', defaultValue: 25, onChange: cy.stub() });
30
+
31
+ cy.get('input').should('have.value', '25');
32
+
33
+ cy.makeSnapshot();
34
+ });
35
+
36
+ it('displays the fallback value when the field is cleared out', () => {
37
+ initialize({
38
+ dataTestId: 'test',
39
+ defaultValue: 25,
40
+ fallbackValue: 10,
41
+ onChange: cy.stub()
42
+ });
43
+
44
+ cy.get('input').should('have.value', '25');
45
+ cy.get('input').clear();
46
+ cy.get('input').should('have.value', '');
47
+ cy.get('input').should('have.attr', 'placeholder', '10');
48
+
49
+ cy.makeSnapshot();
50
+ });
51
+
52
+ it('displays the field using auto size', () => {
53
+ initialize({
54
+ autoSize: true,
55
+ dataTestId: 'test',
56
+ defaultValue: 25,
57
+ onChange: cy.stub()
58
+ });
59
+
60
+ cy.get('input').should('have.value', '25');
61
+
62
+ cy.get('input').type('0');
63
+
64
+ cy.makeSnapshot();
65
+ });
66
+
67
+ it('sets the value to the minimum value configured when a value less than the minimum value is typed in the field', () => {
68
+ initialize({
69
+ dataTestId: 'test',
70
+ defaultValue: 25,
71
+ inputProps: {
72
+ min: 2
73
+ },
74
+ onChange: cy.stub()
75
+ });
76
+
77
+ cy.get('input').should('have.value', '25');
78
+
79
+ cy.get('input').clear().type('-1');
80
+
81
+ cy.get('input').should('have.value', '2');
82
+
83
+ cy.makeSnapshot();
84
+ });
85
+ });
@@ -0,0 +1,66 @@
1
+ /* eslint-disable no-console */
2
+ import { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import NumberField from './Number';
5
+
6
+ const meta: Meta<typeof NumberField> = {
7
+ argTypes: {
8
+ defaultValue: {
9
+ defaultValue: 0,
10
+ description:
11
+ 'The initial value which will be used by the input for the first render',
12
+ type: 'number'
13
+ },
14
+ fallbackValue: {
15
+ defaultValue: 0,
16
+ description: 'This value will be used when the input is cleared',
17
+ type: 'number'
18
+ },
19
+ onChange: {
20
+ description:
21
+ 'The change function with the actual value as parameter. This parameter will be the value when the input is filled but it will be the fallbackValue when the input is cleared out',
22
+ type: 'function'
23
+ }
24
+ },
25
+ component: NumberField
26
+ };
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof NumberField>;
30
+
31
+ export const Default: Story = {
32
+ args: {
33
+ onChange: console.log
34
+ }
35
+ };
36
+
37
+ export const WithDefaultValue: Story = {
38
+ args: {
39
+ defaultValue: 25,
40
+ onChange: console.log
41
+ }
42
+ };
43
+
44
+ export const WithFallbackValue: Story = {
45
+ args: {
46
+ fallbackValue: 25,
47
+ onChange: console.log
48
+ }
49
+ };
50
+
51
+ export const WithFallbackValueAndDefaultValue: Story = {
52
+ args: {
53
+ defaultValue: 10,
54
+ fallbackValue: 25,
55
+ onChange: console.log
56
+ }
57
+ };
58
+
59
+ export const WithFallbackValueAndDefaultValueAutoSize: Story = {
60
+ args: {
61
+ autoSize: true,
62
+ defaultValue: 10,
63
+ fallbackValue: 25,
64
+ onChange: console.log
65
+ }
66
+ };
@@ -0,0 +1,74 @@
1
+ import { ChangeEvent, useState } from 'react';
2
+
3
+ import { T, always, cond, isEmpty, isNil } from 'ramda';
4
+
5
+ import TextField, { TextProps } from '../Text';
6
+
7
+ export interface NumberProps
8
+ extends Omit<TextProps, 'defaultValue' | 'onChange'> {
9
+ /**
10
+ * The initial value which will be used by the input for the first render
11
+ */
12
+ defaultValue?: number;
13
+ /**
14
+ * This value will be used when the input is cleared
15
+ */
16
+ fallbackValue?: number;
17
+ /**
18
+ * The change function with the actual value as parameter. This parameter will be the value when the input is filled but it will be the fallbackValue when the input is cleared out
19
+ */
20
+ onChange: (actualValue: number) => void;
21
+ }
22
+
23
+ const NumberField = ({
24
+ fallbackValue = 0,
25
+ defaultValue,
26
+ onChange,
27
+ ...props
28
+ }: NumberProps): JSX.Element => {
29
+ const [placeholder, setPlaceholder] = useState<string | undefined>();
30
+ const [actualValue, setActualValue] = useState(
31
+ defaultValue ? `${defaultValue}` : ''
32
+ );
33
+
34
+ const { inputProps } = props;
35
+
36
+ const changeValue = (event: ChangeEvent<HTMLInputElement>): void => {
37
+ const inputValue = event.target.value;
38
+
39
+ const number = Number(inputValue);
40
+ const campledNumber = cond([
41
+ [() => isEmpty(inputValue), always(fallbackValue)],
42
+ [() => Number.isNaN(number), always(number)],
43
+ [
44
+ T,
45
+ always(
46
+ Math.max(
47
+ !isNil(inputProps?.min) ? inputProps?.min : -Infinity,
48
+ number
49
+ )
50
+ )
51
+ ]
52
+ ])();
53
+
54
+ onChange(campledNumber);
55
+ setPlaceholder(isEmpty(inputValue) ? `${fallbackValue}` : undefined);
56
+ setActualValue(isEmpty(inputValue) ? inputValue : `${campledNumber}`);
57
+ };
58
+
59
+ return (
60
+ <TextField
61
+ defaultValue={defaultValue}
62
+ type="number"
63
+ value={actualValue}
64
+ onChange={changeValue}
65
+ {...props}
66
+ inputProps={inputProps}
67
+ placeholder={
68
+ placeholder || (!defaultValue ? `${fallbackValue}` : undefined)
69
+ }
70
+ />
71
+ );
72
+ };
73
+
74
+ export default NumberField;
@@ -1,8 +1,8 @@
1
1
  import IconSearch from '@mui/icons-material/Search';
2
2
 
3
- import TextField, { Props as TextFieldProps } from '../Text';
3
+ import TextField, { TextProps } from '../Text';
4
4
 
5
- type Props = Omit<TextFieldProps, 'StartAdornment'>;
5
+ type Props = Omit<TextProps, 'StartAdornment'>;
6
6
 
7
7
  const SearchAdornment = (): JSX.Element => <IconSearch />;
8
8
 
@@ -17,38 +17,36 @@ import { getNormalizedId } from '../../utils';
17
17
 
18
18
  import useAutoSize from './useAutoSize';
19
19
 
20
- const useStyles = makeStyles<{ displayAsBlock: boolean }>()(
21
- (theme: Theme, { displayAsBlock }) => ({
22
- autoSizeCompact: {
23
- paddingRight: theme.spacing(1),
24
- paddingTop: theme.spacing(0.6)
25
- },
26
- hiddenText: {
27
- display: 'table',
28
- lineHeight: 0,
29
- transform: 'scaleY(0)'
30
- },
31
- input: {
32
- fontSize: theme.typography.body1.fontSize
33
- },
34
- inputBase: {
35
- display: displayAsBlock ? 'block' : 'inline-flex',
36
- justifyItems: 'start',
37
- paddingRight: theme.spacing(1)
38
- },
39
- noLabelInput: {
40
- padding: theme.spacing(1)
41
- },
42
- textField: {
43
- transition: theme.transitions.create(['width'], {
44
- duration: theme.transitions.duration.shortest
45
- })
46
- },
47
- transparent: {
48
- backgroundColor: 'transparent'
49
- }
50
- })
51
- );
20
+ const useStyles = makeStyles()((theme: Theme) => ({
21
+ autoSizeCompact: {
22
+ paddingRight: theme.spacing(1),
23
+ paddingTop: theme.spacing(0.6)
24
+ },
25
+ hiddenText: {
26
+ display: 'table',
27
+ lineHeight: 0,
28
+ transform: 'scaleY(0)'
29
+ },
30
+ input: {
31
+ fontSize: theme.typography.body1.fontSize
32
+ },
33
+ inputBase: {
34
+ display: 'inline-flex',
35
+ justifyItems: 'start',
36
+ paddingRight: theme.spacing(1)
37
+ },
38
+ noLabelInput: {
39
+ padding: theme.spacing(1)
40
+ },
41
+ textField: {
42
+ transition: theme.transitions.create(['width'], {
43
+ duration: theme.transitions.duration.shortest
44
+ })
45
+ },
46
+ transparent: {
47
+ backgroundColor: 'transparent'
48
+ }
49
+ }));
52
50
 
53
51
  interface OptionalLabelInputAdornmentProps {
54
52
  children: React.ReactNode;
@@ -72,7 +70,7 @@ const OptionalLabelInputAdornment = ({
72
70
 
73
71
  type SizeVariant = 'large' | 'medium' | 'small' | 'compact';
74
72
 
75
- export type Props = {
73
+ export type TextProps = {
76
74
  EndAdornment?: React.FC;
77
75
  StartAdornment?: React.FC;
78
76
  ariaLabel?: string;
@@ -114,17 +112,18 @@ const TextField = forwardRef(
114
112
  defaultValue,
115
113
  required = false,
116
114
  containerClassName,
115
+ type,
117
116
  ...rest
118
- }: Props,
117
+ }: TextProps,
119
118
  ref: React.ForwardedRef<HTMLDivElement>
120
119
  ): JSX.Element => {
121
- const { classes, cx } = useStyles({
122
- displayAsBlock: autoSize && isNil(StartAdornment) && isNil(EndAdornment)
123
- });
120
+ const { classes, cx } = useStyles();
124
121
 
125
122
  const { inputRef, width, changeInputValue, innerValue } = useAutoSize({
126
123
  autoSize,
127
- autoSizeCustomPadding,
124
+ autoSizeCustomPadding: equals(type, 'number')
125
+ ? Math.max(6, autoSizeCustomPadding || 0)
126
+ : autoSizeCustomPadding,
128
127
  autoSizeDefaultWidth,
129
128
  value: externalValueForAutoSize || rest.value
130
129
  });
@@ -196,6 +195,7 @@ const TextField = forwardRef(
196
195
  width: autoSize ? width : undefined,
197
196
  ...rest?.sx
198
197
  }}
198
+ type={type}
199
199
  />
200
200
  </Tooltip>
201
201
  {autoSize && (
@@ -93,9 +93,11 @@ export interface Props<TRow> {
93
93
  columnConfiguration?: ColumnConfiguration;
94
94
  columns: Array<Column>;
95
95
  currentPage?: number;
96
+ customListingComponent?: JSX.Element;
96
97
  customPaginationClassName?: string;
97
98
  disableRowCheckCondition?: (row) => boolean;
98
99
  disableRowCondition?: (row) => boolean;
100
+ displayCustomListing?: boolean;
99
101
  getHighlightRowCondition?: (row: TRow) => boolean;
100
102
  getId?: (row: TRow) => RowId;
101
103
  headerMemoProps?: Array<unknown>;
@@ -138,6 +140,8 @@ const defaultColumnConfiguration = {
138
140
  export const performanceRowsLimit = 60;
139
141
 
140
142
  const Listing = <TRow extends { id: RowId }>({
143
+ customListingComponent,
144
+ displayCustomListing,
141
145
  limit = 10,
142
146
  visualizationActions,
143
147
  columns,
@@ -494,6 +498,7 @@ const Listing = <TRow extends { id: RowId }>({
494
498
  onSelectColumns={onSelectColumns}
495
499
  />
496
500
  </div>
501
+
497
502
  <ParentSize
498
503
  parentSizeStyles={{
499
504
  height: '100%',
@@ -509,132 +514,141 @@ const Listing = <TRow extends { id: RowId }>({
509
514
  height: innerScrollDisabled ? '100%' : `calc(${height}px - 4px)`
510
515
  }}
511
516
  >
512
- <Table
513
- stickyHeader
514
- className={classes.table}
515
- component="div"
516
- role={undefined}
517
- size="small"
518
- >
519
- <ListingHeader
520
- areColumnsEditable={areColumnsEditable}
521
- checkable={checkable}
522
- columnConfiguration={columnConfiguration}
523
- columns={columns}
524
- listingVariant={listingVariant}
525
- memoProps={headerMemoProps}
526
- predefinedRowsSelection={predefinedRowsSelection}
527
- rowCount={limit - emptyRows}
528
- selectedRowCount={selectedRows.length}
529
- sortField={sortField}
530
- sortOrder={sortOrder}
531
- onSelectAllClick={selectAllRows}
532
- onSelectColumns={onSelectColumns}
533
- onSelectRowsWithCondition={onSelectRowsWithCondition}
534
- onSort={onSort}
535
- />
536
-
537
- <TableBody
538
- className={classes.tableBody}
517
+ {displayCustomListing ? (
518
+ customListingComponent
519
+ ) : (
520
+ <Table
521
+ stickyHeader
522
+ className={classes.table}
539
523
  component="div"
540
- onMouseLeave={clearHoveredRow}
524
+ role={undefined}
525
+ size="small"
541
526
  >
542
- {rowsToDisplay.map((row, index) => {
543
- const isRowSelected = isSelected(row);
544
- const isRowHovered = equals(hoveredRowId, getId(row));
545
- const isSubItem = allSubItemIds.includes(row.id);
546
-
547
- return (
548
- <ListingRow
549
- checkable={
550
- checkable && (!isSubItem || subItems.canCheckSubItems)
551
- }
552
- columnConfiguration={columnConfiguration}
553
- columnIds={columns.map(prop('id'))}
554
- disableRowCondition={disableRowCondition}
555
- isHovered={isRowHovered}
556
- isSelected={isRowSelected}
557
- isShiftKeyDown={isShiftKeyDown}
558
- key={
559
- gte(limit, performanceRowsLimit)
560
- ? `row_${index}`
561
- : getId(row)
562
- }
563
- lastSelectionIndex={lastSelectionIndex}
564
- limit={limit}
565
- listingVariant={listingVariant}
566
- row={row}
567
- rowColorConditions={rowColorConditions}
568
- shiftKeyDownRowPivot={shiftKeyDownRowPivot}
569
- subItemsPivots={subItemsPivots}
570
- tabIndex={-1}
571
- visibleColumns={visibleColumns}
572
- onClick={(): void => {
573
- onRowClick(row);
574
- }}
575
- onFocus={(): void => hoverRow(row)}
576
- onMouseOver={(): void => hoverRow(row)}
577
- >
578
- {checkable &&
579
- (!isSubItem || subItems.canCheckSubItems ? (
580
- <Cell
581
- align="left"
582
- className={classes.checkbox}
583
- disableRowCondition={disableRowCondition}
584
- isRowHovered={isRowHovered}
585
- row={row}
586
- rowColorConditions={rowColorConditions}
587
- onClick={(event): void => selectRow(event, row)}
588
- >
589
- <Checkbox
590
- checked={isRowSelected}
591
- disabled={
592
- disableRowCheckCondition(row) ||
593
- disableRowCondition(row)
594
- }
595
- inputProps={{
596
- 'aria-label': `Select row ${getId(row)}`
597
- }}
527
+ <ListingHeader
528
+ areColumnsEditable={areColumnsEditable}
529
+ checkable={checkable}
530
+ columnConfiguration={columnConfiguration}
531
+ columns={columns}
532
+ listingVariant={listingVariant}
533
+ memoProps={headerMemoProps}
534
+ predefinedRowsSelection={predefinedRowsSelection}
535
+ rowCount={limit - emptyRows}
536
+ selectedRowCount={selectedRows.length}
537
+ sortField={sortField}
538
+ sortOrder={sortOrder}
539
+ onSelectAllClick={selectAllRows}
540
+ onSelectColumns={onSelectColumns}
541
+ onSelectRowsWithCondition={onSelectRowsWithCondition}
542
+ onSort={onSort}
543
+ />
544
+
545
+ <TableBody
546
+ className={classes.tableBody}
547
+ component="div"
548
+ onMouseLeave={clearHoveredRow}
549
+ >
550
+ {rowsToDisplay.map((row, index) => {
551
+ const isRowSelected = isSelected(row);
552
+ const isRowHovered = equals(hoveredRowId, getId(row));
553
+ const isSubItem = allSubItemIds.includes(row.id);
554
+
555
+ return (
556
+ <ListingRow
557
+ checkable={
558
+ checkable &&
559
+ (!isSubItem || subItems.canCheckSubItems)
560
+ }
561
+ columnConfiguration={columnConfiguration}
562
+ columnIds={columns.map(prop('id'))}
563
+ disableRowCondition={disableRowCondition}
564
+ isHovered={isRowHovered}
565
+ isSelected={isRowSelected}
566
+ isShiftKeyDown={isShiftKeyDown}
567
+ key={
568
+ gte(limit, performanceRowsLimit)
569
+ ? `row_${index}`
570
+ : getId(row)
571
+ }
572
+ lastSelectionIndex={lastSelectionIndex}
573
+ limit={limit}
574
+ listingVariant={listingVariant}
575
+ row={row}
576
+ rowColorConditions={rowColorConditions}
577
+ shiftKeyDownRowPivot={shiftKeyDownRowPivot}
578
+ subItemsPivots={subItemsPivots}
579
+ tabIndex={-1}
580
+ visibleColumns={visibleColumns}
581
+ onClick={(): void => {
582
+ onRowClick(row);
583
+ }}
584
+ onFocus={(): void => hoverRow(row)}
585
+ onMouseOver={(): void => hoverRow(row)}
586
+ >
587
+ {checkable &&
588
+ (!isSubItem || subItems.canCheckSubItems ? (
589
+ <Cell
590
+ align="left"
591
+ className={classes.checkbox}
592
+ disableRowCondition={disableRowCondition}
593
+ isRowHovered={isRowHovered}
594
+ row={row}
595
+ rowColorConditions={rowColorConditions}
596
+ onClick={(event): void => selectRow(event, row)}
597
+ >
598
+ <Checkbox
599
+ checked={isRowSelected}
600
+ disabled={
601
+ disableRowCheckCondition(row) ||
602
+ disableRowCondition(row)
603
+ }
604
+ inputProps={{
605
+ 'aria-label': `Select row ${getId(row)}`
606
+ }}
607
+ />
608
+ </Cell>
609
+ ) : (
610
+ <Cell
611
+ align="left"
612
+ disableRowCondition={disableRowCondition}
613
+ isRowHovered={isRowHovered}
614
+ row={row}
615
+ rowColorConditions={rowColorConditions}
598
616
  />
599
- </Cell>
600
- ) : (
601
- <Cell
602
- align="left"
617
+ ))}
618
+
619
+ {visibleColumns.map((column) => (
620
+ <DataCell
621
+ column={column}
603
622
  disableRowCondition={disableRowCondition}
623
+ getHighlightRowCondition={
624
+ getHighlightRowCondition
625
+ }
604
626
  isRowHovered={isRowHovered}
627
+ isRowSelected={isRowSelected}
628
+ key={`${getId(row)}-${column.id}`}
629
+ labelCollapse={subItems.labelCollapse}
630
+ labelExpand={subItems.labelExpand}
631
+ listingVariant={listingVariant}
605
632
  row={row}
606
633
  rowColorConditions={rowColorConditions}
634
+ subItemsRowProperty={subItems?.getRowProperty(
635
+ row
636
+ )}
607
637
  />
608
638
  ))}
609
-
610
- {visibleColumns.map((column) => (
611
- <DataCell
612
- column={column}
613
- disableRowCondition={disableRowCondition}
614
- getHighlightRowCondition={getHighlightRowCondition}
615
- isRowHovered={isRowHovered}
616
- isRowSelected={isRowSelected}
617
- key={`${getId(row)}-${column.id}`}
618
- labelCollapse={subItems.labelCollapse}
619
- labelExpand={subItems.labelExpand}
620
- listingVariant={listingVariant}
621
- row={row}
622
- rowColorConditions={rowColorConditions}
623
- subItemsRowProperty={subItems?.getRowProperty(row)}
624
- />
625
- ))}
626
- </ListingRow>
627
- );
628
- })}
629
-
630
- {rows.length < 1 &&
631
- (loading ? (
632
- <SkeletonLoader rows={limit} />
633
- ) : (
634
- <EmptyResult label={t(labelNoResultFound)} />
635
- ))}
636
- </TableBody>
637
- </Table>
639
+ </ListingRow>
640
+ );
641
+ })}
642
+
643
+ {rows.length < 1 &&
644
+ (loading ? (
645
+ <SkeletonLoader rows={limit} />
646
+ ) : (
647
+ <EmptyResult label={t(labelNoResultFound)} />
648
+ ))}
649
+ </TableBody>
650
+ </Table>
651
+ )}
638
652
  </Box>
639
653
  )}
640
654
  </ParentSize>
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export { default as RegexpHelpTooltip } from './InputField/Search/RegexpHelpTool
23
23
 
24
24
  export { default as TextField } from './InputField/Text';
25
25
  export type { Props as TextFieldProps } from './InputField/Text';
26
+ export { default as NumberField } from './InputField/Number/Number';
26
27
 
27
28
  export type { SelectEntry } from './InputField/Select';
28
29
  export { default as SelectField } from './InputField/Select';