@patternfly/react-core 4.232.13 → 4.234.0

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.
Files changed (119) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts +17 -3
  3. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  4. package/dist/esm/components/ExpandableSection/ExpandableSection.js +39 -8
  5. package/dist/esm/components/ExpandableSection/ExpandableSection.js.map +1 -1
  6. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
  7. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  8. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
  9. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  10. package/dist/esm/components/InputGroup/InputGroup.d.ts.map +1 -1
  11. package/dist/esm/components/InputGroup/InputGroup.js +5 -3
  12. package/dist/esm/components/InputGroup/InputGroup.js.map +1 -1
  13. package/dist/esm/components/MenuToggle/MenuToggle.js +1 -1
  14. package/dist/esm/components/MenuToggle/MenuToggle.js.map +1 -1
  15. package/dist/esm/components/NumberInput/NumberInput.d.ts +5 -0
  16. package/dist/esm/components/NumberInput/NumberInput.d.ts.map +1 -1
  17. package/dist/esm/components/NumberInput/NumberInput.js +5 -4
  18. package/dist/esm/components/NumberInput/NumberInput.js.map +1 -1
  19. package/dist/esm/components/TimePicker/TimePicker.d.ts +1 -0
  20. package/dist/esm/components/TimePicker/TimePicker.d.ts.map +1 -1
  21. package/dist/esm/components/TimePicker/TimePicker.js +14 -2
  22. package/dist/esm/components/TimePicker/TimePicker.js.map +1 -1
  23. package/dist/esm/components/Timestamp/Timestamp.d.ts +72 -0
  24. package/dist/esm/components/Timestamp/Timestamp.d.ts.map +1 -0
  25. package/dist/esm/components/Timestamp/Timestamp.js +33 -0
  26. package/dist/esm/components/Timestamp/Timestamp.js.map +1 -0
  27. package/dist/esm/components/Timestamp/index.d.ts +2 -0
  28. package/dist/esm/components/Timestamp/index.d.ts.map +1 -0
  29. package/dist/esm/components/Timestamp/index.js +2 -0
  30. package/dist/esm/components/Timestamp/index.js.map +1 -0
  31. package/dist/esm/components/index.d.ts +1 -0
  32. package/dist/esm/components/index.d.ts.map +1 -1
  33. package/dist/esm/components/index.js +1 -0
  34. package/dist/esm/components/index.js.map +1 -1
  35. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts +17 -3
  36. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  37. package/dist/js/components/ExpandableSection/ExpandableSection.js +40 -9
  38. package/dist/js/components/ExpandableSection/ExpandableSection.js.map +1 -1
  39. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts +2 -0
  40. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  41. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js +4 -4
  42. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  43. package/dist/js/components/InputGroup/InputGroup.d.ts.map +1 -1
  44. package/dist/js/components/InputGroup/InputGroup.js +5 -3
  45. package/dist/js/components/InputGroup/InputGroup.js.map +1 -1
  46. package/dist/js/components/MenuToggle/MenuToggle.js +1 -1
  47. package/dist/js/components/MenuToggle/MenuToggle.js.map +1 -1
  48. package/dist/js/components/NumberInput/NumberInput.d.ts +5 -0
  49. package/dist/js/components/NumberInput/NumberInput.d.ts.map +1 -1
  50. package/dist/js/components/NumberInput/NumberInput.js +4 -3
  51. package/dist/js/components/NumberInput/NumberInput.js.map +1 -1
  52. package/dist/js/components/TimePicker/TimePicker.d.ts +1 -0
  53. package/dist/js/components/TimePicker/TimePicker.d.ts.map +1 -1
  54. package/dist/js/components/TimePicker/TimePicker.js +14 -2
  55. package/dist/js/components/TimePicker/TimePicker.js.map +1 -1
  56. package/dist/js/components/Timestamp/Timestamp.d.ts +72 -0
  57. package/dist/js/components/Timestamp/Timestamp.d.ts.map +1 -0
  58. package/dist/js/components/Timestamp/Timestamp.js +37 -0
  59. package/dist/js/components/Timestamp/Timestamp.js.map +1 -0
  60. package/dist/js/components/Timestamp/index.d.ts +2 -0
  61. package/dist/js/components/Timestamp/index.d.ts.map +1 -0
  62. package/dist/js/components/Timestamp/index.js +5 -0
  63. package/dist/js/components/Timestamp/index.js.map +1 -0
  64. package/dist/js/components/index.d.ts +1 -0
  65. package/dist/js/components/index.d.ts.map +1 -1
  66. package/dist/js/components/index.js +1 -0
  67. package/dist/js/components/index.js.map +1 -1
  68. package/dist/styles/base-no-reset.css +2 -2
  69. package/dist/styles/base.css +2 -2
  70. package/dist/umd/react-core.css +58 -12
  71. package/dist/umd/react-core.js +861 -770
  72. package/dist/umd/react-core.min.css +312 -266
  73. package/dist/umd/react-core.min.js +5 -5
  74. package/package.json +6 -6
  75. package/src/components/Button/examples/Button.md +55 -24
  76. package/src/components/ContextSelector/examples/ContextSelectorBasic.tsx +4 -4
  77. package/src/components/ContextSelector/examples/ContextSelectorPlainText.tsx +3 -3
  78. package/src/components/ContextSelector/examples/ContextSelectorWithFooter.tsx +4 -4
  79. package/src/components/DualListSelector/examples/DualListSelectorComposable.tsx +8 -2
  80. package/src/components/DualListSelector/examples/DualListSelectorComposableDragDrop.tsx +3 -9
  81. package/src/components/DualListSelector/examples/DualListSelectorComposableTree.tsx +3 -3
  82. package/src/components/DualListSelector/examples/DualListSelectorTreeExample.tsx +8 -5
  83. package/src/components/ExpandableSection/ExpandableSection.tsx +73 -19
  84. package/src/components/ExpandableSection/ExpandableSectionToggle.tsx +15 -9
  85. package/src/components/ExpandableSection/__tests__/ExpandableSection.test.tsx +27 -1
  86. package/src/components/ExpandableSection/examples/ExpandableSection.md +7 -0
  87. package/src/components/ExpandableSection/examples/ExpandableSectionTruncateExpansion.tsx +27 -0
  88. package/src/components/InputGroup/InputGroup.tsx +5 -3
  89. package/src/components/InputGroup/__tests__/InputGroup.test.tsx +30 -0
  90. package/src/components/MenuToggle/MenuToggle.tsx +2 -2
  91. package/src/components/NumberInput/NumberInput.tsx +14 -8
  92. package/src/components/NumberInput/__tests__/NumberInput.test.tsx +16 -0
  93. package/src/components/NumberInput/__tests__/__snapshots__/NumberInput.test.tsx.snap +292 -18
  94. package/src/components/NumberInput/examples/NumberInput.md +5 -0
  95. package/src/components/NumberInput/examples/WithStatus.tsx +59 -0
  96. package/src/components/TimePicker/TimePicker.tsx +19 -11
  97. package/src/components/Timestamp/Timestamp.tsx +134 -0
  98. package/src/components/Timestamp/__tests__/Timestamp.test.tsx +177 -0
  99. package/src/components/Timestamp/__tests__/__snapshots__/Timestamp.test.tsx.snap +17 -0
  100. package/src/components/Timestamp/examples/Timestamp.md +57 -0
  101. package/src/components/Timestamp/examples/TimestampBasicFormats.tsx +26 -0
  102. package/src/components/Timestamp/examples/TimestampCustomContent.tsx +20 -0
  103. package/src/components/Timestamp/examples/TimestampCustomFormat.tsx +6 -0
  104. package/src/components/Timestamp/examples/TimestampCustomTooltip.tsx +19 -0
  105. package/src/components/Timestamp/examples/TimestampDefault.tsx +4 -0
  106. package/src/components/Timestamp/examples/TimestampDefaultTooltip.tsx +18 -0
  107. package/src/components/Timestamp/index.ts +1 -0
  108. package/src/components/index.ts +1 -0
  109. package/src/demos/Button.md +5 -1
  110. package/src/demos/ComposableMenu/examples/ComposableActionsMenu.tsx +12 -8
  111. package/src/demos/ComposableMenu/examples/ComposableApplicationLauncher.tsx +12 -11
  112. package/src/demos/ComposableMenu/examples/ComposableContextSelector.tsx +10 -12
  113. package/src/demos/ComposableMenu/examples/ComposableDrilldownMenu.tsx +7 -7
  114. package/src/demos/ComposableMenu/examples/ComposableFlyout.tsx +8 -8
  115. package/src/demos/ComposableMenu/examples/ComposableSimpleCheckboxSelect.tsx +11 -8
  116. package/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx +7 -7
  117. package/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx +17 -8
  118. package/src/demos/ComposableMenu/examples/ComposableTreeViewMenu.tsx +11 -10
  119. package/src/demos/ComposableMenu/examples/ComposableTypeaheadSelect.tsx +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.232.13",
3
+ "version": "4.234.0",
4
4
  "description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -50,16 +50,16 @@
50
50
  "generate": "node scripts/copyStyles.js"
51
51
  },
52
52
  "dependencies": {
53
- "@patternfly/react-icons": "^4.83.13",
54
- "@patternfly/react-styles": "^4.82.13",
55
- "@patternfly/react-tokens": "^4.84.13",
53
+ "@patternfly/react-icons": "^4.85.0",
54
+ "@patternfly/react-styles": "^4.84.0",
55
+ "@patternfly/react-tokens": "^4.86.0",
56
56
  "focus-trap": "6.9.2",
57
57
  "react-dropzone": "9.0.0",
58
58
  "tippy.js": "5.1.2",
59
59
  "tslib": "^2.0.0"
60
60
  },
61
61
  "devDependencies": {
62
- "@patternfly/patternfly": "4.208.1",
62
+ "@patternfly/patternfly": "4.210.1",
63
63
  "@rollup/plugin-commonjs": "^22.0.2",
64
64
  "@rollup/plugin-node-resolve": "^13.3.0",
65
65
  "@rollup/plugin-replace": "^4.0.0",
@@ -76,5 +76,5 @@
76
76
  "react": "^16.8.0 || ^17.0.0",
77
77
  "react-dom": "^16.8.0 || ^17.0.0"
78
78
  },
79
- "gitHead": "0dd257e3f6ec904a5aa670c4c8364e75ced77404"
79
+ "gitHead": "fa6869951efc44e52696e065322c40478aa3fad3"
80
80
  }
@@ -16,68 +16,99 @@ import { Link } from '@reach/router';
16
16
 
17
17
  ## Examples
18
18
 
19
- ### Variations
19
+ ### Variant examples
20
+
21
+ PatternFly supports several button styling variants to be used in different scenarios as needed. The following button variants can be assigned using the `variant` property.
20
22
 
21
23
  ```ts file="./ButtonVariations.tsx"
22
24
  ```
23
25
 
24
- ### Disabled
26
+ | Variant | Description|
27
+ | --- | ---|
28
+ | Primary | Primary buttons are the most visually prominent variant. Use for the most important call to action.|
29
+ | Secondary | Secondary buttons are less visually prominant than primary buttons. Use for general actions on a page that require less emphasis than primary buttons. |
30
+ | Tertiary | Tertiary buttons are the least visually prominent variant. Use to retain a classic button format with less emphasis than primary or secondary buttons. |
31
+ | Danger | Danger buttons may be used for actions that are potentially destructive or difficult/impossible to undo. Danger is an independent variant, but all button variants may use the the `isDanger` property to apply similar styling. |
32
+ | Warning | Warning buttons may be used for actions that change an important setting or behavior, but not in a destructive or irreversible way. |
33
+ | Link | Links are labeled, but have no background or border. Use for actions that require less emphasis, actions that navigate users to another page within the same window, and/or actions that navigate to external pages in a new window. Links may be placed inline with text using the `isInline` property.|
34
+ | Plain | Plain buttons have no styling and are intended to be labeled with icons. |
35
+ | Control | Control buttons can be labeled with text or icons. Primarily intended to be paired with other controls in an [input group](https://www.patternfly.org/v4/components/input-group). |
36
+
37
+ ### Disabled buttons
38
+
39
+ To indicate that an action is currently unavailable, all button variations can be disabled using the `isDisabled` property.
25
40
 
26
41
  ```ts file="./ButtonDisabled.tsx"
27
42
  ```
28
43
 
29
- ### Aria disabled
44
+ ### Small buttons
30
45
 
31
- ```ts file="./ButtonAriaDisabled.tsx"
46
+ To fit into tight spaces, primary, secondary, tertiary, danger, and warning button variations can be made smaller using the `isSmall` property.
47
+
48
+ ```ts file="./ButtonSmall.tsx"
32
49
  ```
33
50
 
34
- ### Aria disabled button with tooltip
51
+ ### Call to action (CTA) buttons
35
52
 
36
- ```ts file="./ButtonAriaDisabledTooltip.tsx"
53
+ CTA buttons and links direct users to complete an action. Primary, secondary, tertiary, and link button variants can be styled as CTAs using the `isLarge` property.
54
+
55
+ ```ts file="./ButtonCallToAction.tsx"
37
56
  ```
38
57
 
39
- ### Aria disabled link as button with tooltip
58
+ ### Block level buttons
40
59
 
41
- ```ts file="./ButtonAriaDisabledLinkTooltip.tsx"
60
+ Block level buttons span the full width of the parent element and can be enabled using the `isBlock` property.
61
+
62
+ ```ts file="./ButtonBlock.tsx"
63
+ ```
64
+
65
+ ### Progress indicators
66
+
67
+ Progress indicators can be added to buttons to identify that an action is in progress after a click.
68
+
69
+ ```ts file="./ButtonProgress.tsx"
42
70
  ```
43
71
 
44
72
  ### Links as buttons
45
73
 
74
+ Buttons that link to another resource may take the form of primary, secondary, tertiary, or link variants. Use `component="a"` and an `href` property to designate the button's target link.
75
+
46
76
  ```ts file="./ButtonLinks.tsx"
47
77
  ```
48
78
 
49
79
  ### Inline link as span
50
80
 
81
+ Inline links should use `component="span"` and the `isInline` property to wrap inline with surrounding text.
82
+
51
83
  ```ts file="./ButtonInlineSpanLink.tsx"
52
84
  ```
53
85
 
54
- ### Block level
55
-
56
- ```ts file="./ButtonBlock.tsx"
57
- ```
86
+ ### Router link
58
87
 
59
- ### Types
88
+ Router links can be used for in-app linking in React environments to prevent page reloading.
60
89
 
61
- ```ts file="./ButtonTypes.tsx"
90
+ ```ts file="./ButtonRouterLink.tsx"
62
91
  ```
63
92
 
64
- ### Small
93
+ ### Aria-disabled examples
65
94
 
66
- ```ts file="./ButtonSmall.tsx"
67
- ```
95
+ [Accessible Rich Internet Applications (ARIA)](https://www.w3.org/WAI/standards-guidelines/aria/) is a set of roles and attributes specified by the World Wide Web Consortium. ARIA defines ways to make web content and web applications more accessible to people with disabilities.
68
96
 
69
- ### Call to action
97
+ Buttons that are aria-disabled are similar to normal disabled buttons, except they can receive focus. Every button variant can be aria-disabled using the `isAriaDisabled` property.
70
98
 
71
- ```ts file="./ButtonCallToAction.tsx"
99
+ ```ts file="./ButtonAriaDisabled.tsx"
72
100
  ```
73
101
 
74
- ### Progress
102
+ ### Aria-disabled with tooltip
75
103
 
76
- ```ts file="./ButtonProgress.tsx"
104
+ Unlike normal disabled buttons, aria-disabled buttons can support tooltips.
105
+
106
+ ```ts file="./ButtonAriaDisabledTooltip.tsx"
77
107
  ```
78
108
 
79
- ### Router link
109
+ ### Aria-disabled link as button with tooltip
80
110
 
81
- ```ts file="./ButtonRouterLink.tsx"
82
- ```
111
+ Aria-disabled buttons can operate as links, which also support tooltips.
83
112
 
113
+ ```ts file="./ButtonAriaDisabledLinkTooltip.tsx"
114
+ ```
@@ -48,7 +48,7 @@ export const ContextSelectorBasic: React.FunctionComponent = () => {
48
48
  setSearchValue(value);
49
49
  };
50
50
 
51
- const onSearchButtonClick = (_event: React.SyntheticEvent<HTMLButtonElement>) => {
51
+ const onSearchButtonClick = () => {
52
52
  const filtered =
53
53
  searchValue === ''
54
54
  ? items
@@ -71,11 +71,11 @@ export const ContextSelectorBasic: React.FunctionComponent = () => {
71
71
  screenReaderLabel="Selected Project:"
72
72
  >
73
73
  {filteredItems.map((item, index) => {
74
- const [text = null, href = null, isDisabled] =
75
- typeof item === 'string' ? [item, null, false] : [item.text, item.href, item.isDisabled];
74
+ const [text = null, href = undefined, isDisabled] =
75
+ typeof item === 'string' ? [item, undefined, false] : [item.text, item.href, item.isDisabled];
76
76
  return (
77
77
  <ContextSelectorItem key={index} href={href} isDisabled={isDisabled}>
78
- {text || item}
78
+ {text}
79
79
  </ContextSelectorItem>
80
80
  );
81
81
  })}
@@ -47,7 +47,7 @@ export const ContextSelectorPlainText: React.FunctionComponent = () => {
47
47
  setSearchValue(value);
48
48
  };
49
49
 
50
- const onSearchButtonClick = (_event: React.SyntheticEvent<HTMLButtonElement>) => {
50
+ const onSearchButtonClick = () => {
51
51
  const filtered =
52
52
  searchValue === ''
53
53
  ? items
@@ -72,8 +72,8 @@ export const ContextSelectorPlainText: React.FunctionComponent = () => {
72
72
  isText
73
73
  >
74
74
  {filteredItems.map((item, index) => {
75
- const [text, href = null, isDisabled = false] =
76
- typeof item === 'string' ? [item, null, false] : [item.text, item.href, item.isDisabled];
75
+ const [text, href = undefined, isDisabled = false] =
76
+ typeof item === 'string' ? [item, undefined, false] : [item.text, item.href, item.isDisabled];
77
77
  return (
78
78
  <ContextSelectorItem key={index} href={href} isDisabled={isDisabled}>
79
79
  {text}
@@ -47,7 +47,7 @@ export const ContextSelectorWithFooter: React.FunctionComponent = () => {
47
47
  setSearchValue(value);
48
48
  };
49
49
 
50
- const onSearchButtonClick = (_event: React.SyntheticEvent<HTMLButtonElement>) => {
50
+ const onSearchButtonClick = () => {
51
51
  const filtered =
52
52
  searchValue === ''
53
53
  ? items
@@ -78,11 +78,11 @@ export const ContextSelectorWithFooter: React.FunctionComponent = () => {
78
78
  }
79
79
  >
80
80
  {filteredItems.map((item, index) => {
81
- const [text = null, href = null, isDisabled] =
82
- typeof item === 'string' ? [item, null, false] : [item.text, item.href, item.isDisabled];
81
+ const [text = null, href = undefined, isDisabled] =
82
+ typeof item === 'string' ? [item, undefined, false] : [item.text, item.href, item.isDisabled];
83
83
  return (
84
84
  <ContextSelectorItem key={index} href={href} isDisabled={isDisabled}>
85
- {text || item}
85
+ {text}
86
86
  </ContextSelectorItem>
87
87
  );
88
88
  })}
@@ -16,8 +16,14 @@ import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-d
16
16
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
17
17
  import PficonSortCommonAscIcon from '@patternfly/react-icons/dist/esm/icons/pficon-sort-common-asc-icon';
18
18
 
19
+ interface Option {
20
+ text: string;
21
+ selected: boolean;
22
+ isVisible: boolean;
23
+ }
24
+
19
25
  export const DualListSelectorComposable: React.FunctionComponent = () => {
20
- const [availableOptions, setAvailableOptions] = React.useState([
26
+ const [availableOptions, setAvailableOptions] = React.useState<Option[]>([
21
27
  { text: 'Apple', selected: false, isVisible: true },
22
28
  { text: 'Banana', selected: false, isVisible: true },
23
29
  { text: 'Pineapple', selected: false, isVisible: true },
@@ -26,7 +32,7 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
26
32
  { text: 'Peach', selected: false, isVisible: true },
27
33
  { text: 'Strawberry', selected: false, isVisible: true }
28
34
  ]);
29
- const [chosenOptions, setChosenOptions] = React.useState([]);
35
+ const [chosenOptions, setChosenOptions] = React.useState<Option[]>([]);
30
36
  const [availableFilter, setAvailableFilter] = React.useState('');
31
37
  const [chosenFilter, setChosenFilter] = React.useState('');
32
38
 
@@ -8,20 +8,14 @@ import {
8
8
  DualListSelectorList,
9
9
  DualListSelectorListItem,
10
10
  DualListSelectorControlsWrapper,
11
- DualListSelectorControl
11
+ DualListSelectorControl,
12
+ DraggableItemPosition
12
13
  } from '@patternfly/react-core';
13
14
  import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
14
15
  import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
15
16
  import AngleDoubleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-right-icon';
16
17
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
17
18
 
18
- interface SourceType {
19
- droppableId: string;
20
- index: number;
21
- }
22
-
23
- interface DestinationType extends SourceType {}
24
-
25
19
  export const DualListSelectorComposableDragDrop: React.FunctionComponent = () => {
26
20
  const [ignoreNextOptionSelect, setIgnoreNextOptionSelect] = React.useState(false);
27
21
  const [availableOptions, setAvailableOptions] = React.useState([
@@ -87,7 +81,7 @@ export const DualListSelectorComposableDragDrop: React.FunctionComponent = () =>
87
81
  }
88
82
  };
89
83
 
90
- const onDrop = (source: SourceType, dest: DestinationType) => {
84
+ const onDrop = (source: DraggableItemPosition, dest?: DraggableItemPosition) => {
91
85
  if (dest) {
92
86
  const newList = [...chosenOptions];
93
87
  const [removed] = newList.splice(source.index, 1);
@@ -52,7 +52,7 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
52
52
  if (!node.children || !node.children.length) {
53
53
  return [node.id];
54
54
  } else {
55
- let childrenIds = [];
55
+ let childrenIds: string[] = [];
56
56
  node.children.forEach(child => {
57
57
  childrenIds = [...childrenIds, ...getDescendantLeafIds(child)];
58
58
  });
@@ -77,7 +77,7 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
77
77
  // Builds a map of child leaf nodes by node id - memoized so that it only rebuilds the list if the data changes.
78
78
  const { memoizedLeavesById, memoizedAllLeaves, memoizedNodeText } = React.useMemo(() => {
79
79
  let leavesById = {};
80
- let allLeaves = [];
80
+ let allLeaves: string[] = [];
81
81
  let nodeTexts = {};
82
82
  data.forEach(foodNode => {
83
83
  nodeTexts = { ...nodeTexts, ...buildTextById(foodNode) };
@@ -129,7 +129,7 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
129
129
  return true;
130
130
  }
131
131
  if (areSomeDescendantsSelected(node, isChosen)) {
132
- return null;
132
+ return false;
133
133
  }
134
134
  return false;
135
135
  };
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { DualListSelector } from '@patternfly/react-core';
2
+ import { DualListSelector, DualListSelectorTreeItemData } from '@patternfly/react-core';
3
3
 
4
4
  export const DualListSelectorTreeExample: React.FunctionComponent = () => {
5
- const [availableOptions, setAvailableOptions] = React.useState<React.ReactNode[]>([
5
+ const [availableOptions, setAvailableOptions] = React.useState<DualListSelectorTreeItemData[]>([
6
6
  {
7
7
  id: 'F1',
8
8
  text: 'Folder 1',
@@ -38,7 +38,7 @@ export const DualListSelectorTreeExample: React.FunctionComponent = () => {
38
38
  }
39
39
  ]);
40
40
 
41
- const [chosenOptions, setChosenOptions] = React.useState<React.ReactNode[]>([
41
+ const [chosenOptions, setChosenOptions] = React.useState<DualListSelectorTreeItemData[]>([
42
42
  {
43
43
  id: 'CF1',
44
44
  text: 'Chosen Folder 1',
@@ -73,7 +73,10 @@ export const DualListSelectorTreeExample: React.FunctionComponent = () => {
73
73
  }
74
74
  ]);
75
75
 
76
- const onListChange = (newAvailableOptions: React.ReactNode[], newChosenOptions: React.ReactNode[]) => {
76
+ const onListChange = (
77
+ newAvailableOptions: DualListSelectorTreeItemData[],
78
+ newChosenOptions: DualListSelectorTreeItemData[]
79
+ ) => {
77
80
  setAvailableOptions(newAvailableOptions.sort());
78
81
  setChosenOptions(newChosenOptions.sort());
79
82
  };
@@ -84,7 +87,7 @@ export const DualListSelectorTreeExample: React.FunctionComponent = () => {
84
87
  isTree
85
88
  availableOptions={availableOptions}
86
89
  chosenOptions={chosenOptions}
87
- onListChange={onListChange}
90
+ onListChange={onListChange as any}
88
91
  id="dual-list-selector-tree"
89
92
  />
90
93
  );
@@ -1,13 +1,19 @@
1
1
  import * as React from 'react';
2
2
  import styles from '@patternfly/react-styles/css/components/ExpandableSection/expandable-section';
3
3
  import { css } from '@patternfly/react-styles';
4
+ import lineClamp from '@patternfly/react-tokens/dist/esm/c_expandable_section_m_truncate__content_LineClamp';
4
5
  import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
5
6
  import { PickOptional } from '../../helpers/typeUtils';
6
7
 
8
+ export enum ExpandableSectionVariant {
9
+ default = 'default',
10
+ truncate = 'truncate'
11
+ }
12
+
7
13
  export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement> {
8
- /** Content rendered inside the Expandable Component */
14
+ /** Content rendered inside the expandable section. */
9
15
  children?: React.ReactNode;
10
- /** Additional classes added to the Expandable Component */
16
+ /** Additional classes added to the expandable section. */
11
17
  className?: string;
12
18
  /** Flag to indicate if the content is expanded */
13
19
  isExpanded?: boolean;
@@ -19,7 +25,7 @@ export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement>
19
25
  toggleTextCollapsed?: string;
20
26
  /** React node that appears in the attached toggle in place of toggle text */
21
27
  toggleContent?: React.ReactNode;
22
- /** Callback function to toggle the expandable content. Detached expandable sections should use the onToggle property of ExpandableSectionToggle. */
28
+ /** Callback function to toggle the expandable section. Detached expandable sections should use the onToggle property of the expandable section toggle sub-component. */
23
29
  onToggle?: (isExpanded: boolean) => void;
24
30
  /** Forces active state */
25
31
  isActive?: boolean;
@@ -33,12 +39,27 @@ export interface ExpandableSectionProps extends React.HTMLProps<HTMLDivElement>
33
39
  isWidthLimited?: boolean;
34
40
  /** Flag to indicate if the content is indented */
35
41
  isIndented?: boolean;
42
+ /** @beta Determines the variant of the expandable section. When passing in "truncate" as the
43
+ * variant, the expandable content will be truncated after 3 lines by default.
44
+ */
45
+ variant?: 'default' | 'truncate';
46
+ /** @beta Truncates the expandable content to the specified number of lines when using the
47
+ * "truncate" variant. */
48
+ truncateMaxLines?: number;
36
49
  }
37
50
 
38
51
  interface ExpandableSectionState {
39
52
  isExpanded: boolean;
40
53
  }
41
54
 
55
+ const setLineClamp = (lines: number, element: HTMLDivElement) => {
56
+ if (!element || lines < 1) {
57
+ return;
58
+ }
59
+
60
+ element.style.setProperty(lineClamp.name, lines.toString());
61
+ };
62
+
42
63
  export class ExpandableSection extends React.Component<ExpandableSectionProps, ExpandableSectionState> {
43
64
  static displayName = 'ExpandableSection';
44
65
  constructor(props: ExpandableSectionProps) {
@@ -49,6 +70,7 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
49
70
  };
50
71
  }
51
72
 
73
+ expandableContentRef = React.createRef<HTMLDivElement>();
52
74
  static defaultProps: PickOptional<ExpandableSectionProps> = {
53
75
  className: '',
54
76
  toggleText: '',
@@ -61,7 +83,8 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
61
83
  displaySize: 'default',
62
84
  isWidthLimited: false,
63
85
  isIndented: false,
64
- contentId: ''
86
+ contentId: '',
87
+ variant: 'default'
65
88
  };
66
89
 
67
90
  private calculateToggleText(
@@ -79,6 +102,23 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
79
102
  return toggleText;
80
103
  }
81
104
 
105
+ componentDidMount() {
106
+ if (this.props.variant === ExpandableSectionVariant.truncate && this.props.truncateMaxLines) {
107
+ const expandableContent = this.expandableContentRef.current;
108
+ setLineClamp(this.props.truncateMaxLines, expandableContent);
109
+ }
110
+ }
111
+
112
+ componentDidUpdate(prevProps: ExpandableSectionProps) {
113
+ if (
114
+ this.props.variant === ExpandableSectionVariant.truncate &&
115
+ prevProps.truncateMaxLines !== this.props.truncateMaxLines
116
+ ) {
117
+ const expandableContent = this.expandableContentRef.current;
118
+ setLineClamp(this.props.truncateMaxLines, expandableContent);
119
+ }
120
+ }
121
+
82
122
  render() {
83
123
  const {
84
124
  onToggle: onToggleProp,
@@ -96,6 +136,9 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
96
136
  isWidthLimited,
97
137
  isIndented,
98
138
  contentId,
139
+ variant,
140
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
141
+ truncateMaxLines,
99
142
  ...props
100
143
  } = this.props;
101
144
  let onToggle = onToggleProp;
@@ -116,9 +159,24 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
116
159
  propOrStateIsExpanded
117
160
  );
118
161
 
162
+ const expandableToggle = !isDetached && (
163
+ <button
164
+ className={css(styles.expandableSectionToggle)}
165
+ type="button"
166
+ aria-expanded={propOrStateIsExpanded}
167
+ onClick={() => onToggle(!propOrStateIsExpanded)}
168
+ >
169
+ {variant !== ExpandableSectionVariant.truncate && (
170
+ <span className={css(styles.expandableSectionToggleIcon)}>
171
+ <AngleRightIcon aria-hidden />
172
+ </span>
173
+ )}
174
+ <span className={css(styles.expandableSectionToggleText)}>{toggleContent || computedToggleText}</span>
175
+ </button>
176
+ );
177
+
119
178
  return (
120
179
  <div
121
- {...props}
122
180
  className={css(
123
181
  styles.expandableSection,
124
182
  propOrStateIsExpanded && styles.modifiers.expanded,
@@ -127,25 +185,21 @@ export class ExpandableSection extends React.Component<ExpandableSectionProps, E
127
185
  displaySize === 'large' && styles.modifiers.displayLg,
128
186
  isWidthLimited && styles.modifiers.limitWidth,
129
187
  isIndented && styles.modifiers.indented,
188
+ variant === ExpandableSectionVariant.truncate && styles.modifiers.truncate,
130
189
  className
131
190
  )}
191
+ {...props}
132
192
  >
133
- {!isDetached && (
134
- <button
135
- className={css(styles.expandableSectionToggle)}
136
- type="button"
137
- aria-expanded={propOrStateIsExpanded}
138
- onClick={() => onToggle(!propOrStateIsExpanded)}
139
- >
140
- <span className={css(styles.expandableSectionToggleIcon)}>
141
- <AngleRightIcon aria-hidden />
142
- </span>
143
- <span className={css(styles.expandableSectionToggleText)}>{toggleContent || computedToggleText}</span>
144
- </button>
145
- )}
146
- <div className={css(styles.expandableSectionContent)} hidden={!propOrStateIsExpanded} id={contentId}>
193
+ {variant === ExpandableSectionVariant.default && expandableToggle}
194
+ <div
195
+ ref={this.expandableContentRef}
196
+ className={css(styles.expandableSectionContent)}
197
+ hidden={variant !== ExpandableSectionVariant.truncate && !propOrStateIsExpanded}
198
+ id={contentId}
199
+ >
147
200
  {children}
148
201
  </div>
202
+ {variant === ExpandableSectionVariant.truncate && expandableToggle}
149
203
  </div>
150
204
  );
151
205
  }
@@ -16,6 +16,8 @@ export interface ExpandableSectionToggleProps extends React.HTMLProps<HTMLDivEle
16
16
  contentId?: string;
17
17
  /** Direction the toggle arrow should point when the expandable section is expanded. */
18
18
  direction?: 'up' | 'down';
19
+ /** @beta Flag to determine toggle styling when the expandable content is truncated. */
20
+ hasTruncatedContent?: boolean;
19
21
  }
20
22
 
21
23
  export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionToggleProps> = ({
@@ -25,16 +27,18 @@ export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionT
25
27
  onToggle,
26
28
  contentId,
27
29
  direction = 'down',
30
+ hasTruncatedContent = false,
28
31
  ...props
29
32
  }: ExpandableSectionToggleProps) => (
30
33
  <div
31
- {...props}
32
34
  className={css(
33
35
  styles.expandableSection,
34
36
  isExpanded && styles.modifiers.expanded,
35
37
  styles.modifiers.detached,
38
+ hasTruncatedContent && styles.modifiers.truncate,
36
39
  className
37
40
  )}
41
+ {...props}
38
42
  >
39
43
  <button
40
44
  className={css(styles.expandableSectionToggle)}
@@ -43,14 +47,16 @@ export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionT
43
47
  aria-controls={contentId}
44
48
  onClick={() => onToggle(!isExpanded)}
45
49
  >
46
- <span
47
- className={css(
48
- styles.expandableSectionToggleIcon,
49
- isExpanded && direction === 'up' && styles.modifiers.expandTop
50
- )}
51
- >
52
- <AngleRightIcon aria-hidden />
53
- </span>
50
+ {!hasTruncatedContent && (
51
+ <span
52
+ className={css(
53
+ styles.expandableSectionToggleIcon,
54
+ isExpanded && direction === 'up' && styles.modifiers.expandTop
55
+ )}
56
+ >
57
+ <AngleRightIcon aria-hidden />
58
+ </span>
59
+ )}
54
60
  <span className={css(styles.expandableSectionToggleText)}>{children}</span>
55
61
  </button>
56
62
  </div>
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { render, screen } from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
5
 
6
- import { ExpandableSection } from '../ExpandableSection';
6
+ import { ExpandableSection, ExpandableSectionVariant } from '../ExpandableSection';
7
7
  import { ExpandableSectionToggle } from '../ExpandableSectionToggle';
8
8
 
9
9
  const props = {};
@@ -64,3 +64,29 @@ test('Renders ExpandableSection indented', () => {
64
64
  );
65
65
  expect(asFragment()).toMatchSnapshot();
66
66
  });
67
+
68
+ test('Does not render with pf-m-truncate class when variant is not passed', () => {
69
+ render(<ExpandableSection {...props}>test</ExpandableSection>);
70
+
71
+ expect(screen.getByText('test').parentElement).not.toHaveClass('pf-m-truncate');
72
+ });
73
+
74
+ test('Does not render with pf-m-truncate class when variant is not truncate', () => {
75
+ render(
76
+ <ExpandableSection variant={ExpandableSectionVariant.default} {...props}>
77
+ test
78
+ </ExpandableSection>
79
+ );
80
+
81
+ expect(screen.getByText('test').parentElement).not.toHaveClass('pf-m-truncate');
82
+ });
83
+
84
+ test('Renders with pf-m-truncate class when variant is truncate', () => {
85
+ render(
86
+ <ExpandableSection variant={ExpandableSectionVariant.truncate} {...props}>
87
+ test
88
+ </ExpandableSection>
89
+ );
90
+
91
+ expect(screen.getByText('test').parentElement).toHaveClass('pf-m-truncate');
92
+ });
@@ -45,3 +45,10 @@ By using the `toggleContent` prop, you can pass in content other than a simple s
45
45
 
46
46
  ```ts file="ExpandableSectionCustomToggle.tsx"
47
47
  ```
48
+
49
+ ### Truncate expansion
50
+
51
+ By passing in `variant="truncate"`, the expandable content will be visible up to a maximum number of lines before being truncated, with the toggle revealing or hiding the truncated content. By default the expandable content will truncate after 3 lines, and this can be customized by also passing in the `truncateMaxLines` prop.
52
+
53
+ ```ts file="ExpandableSectionTruncateExpansion.tsx" isBeta
54
+ ```
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { ExpandableSection, ExpandableSectionVariant } from '@patternfly/react-core';
3
+
4
+ export const ExpandableSectionTruncateExpansion: React.FunctionComponent = () => {
5
+ const [isExpanded, setIsExpanded] = React.useState(false);
6
+
7
+ const onToggle = (isExpanded: boolean) => {
8
+ setIsExpanded(isExpanded);
9
+ };
10
+
11
+ return (
12
+ <ExpandableSection
13
+ variant={ExpandableSectionVariant.truncate}
14
+ toggleText={isExpanded ? 'Show less' : 'Show more'}
15
+ onToggle={onToggle}
16
+ isExpanded={isExpanded}
17
+ >
18
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque nec dignissim turpis, et tristique purus.
19
+ Phasellus efficitur ante quis dolor viverra imperdiet. Orci varius natoque penatibus et magnis dis parturient
20
+ montes, nascetur ridiculus mus. Pellentesque laoreet, sem ac elementum semper, lectus mauris vestibulum nulla,
21
+ eget volutpat massa neque vel turpis. Donec finibus enim eu leo accumsan consectetur. Praesent massa diam,
22
+ tincidunt eu dui ac, ullamcorper elementum est. Phasellus metus felis, venenatis vitae semper nec, porta a metus.
23
+ Vestibulum justo nisi, imperdiet id eleifend at, varius nec lorem. Fusce porttitor mollis nibh, ut elementum ante
24
+ commodo tincidunt. Integer tincidunt at ipsum non aliquet.
25
+ </ExpandableSection>
26
+ );
27
+ };