@patternfly/react-docs 7.6.0-prerelease.8 → 7.6.0-prerelease.9

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 (163) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/package.json +12 -11
  4. package/patternfly-docs/generated/components/about-modal/react.js +0 -149
  5. package/patternfly-docs/generated/components/accordion/react.js +0 -262
  6. package/patternfly-docs/generated/components/action-list/react.js +0 -144
  7. package/patternfly-docs/generated/components/alert/react-demos.js +0 -56
  8. package/patternfly-docs/generated/components/alert/react.js +0 -1433
  9. package/patternfly-docs/generated/components/avatar/react.js +0 -166
  10. package/patternfly-docs/generated/components/back-to-top/react-demos.js +0 -60
  11. package/patternfly-docs/generated/components/back-to-top/react.js +0 -77
  12. package/patternfly-docs/generated/components/backdrop/react.js +0 -64
  13. package/patternfly-docs/generated/components/background-image/react.js +0 -62
  14. package/patternfly-docs/generated/components/badge/react.js +0 -97
  15. package/patternfly-docs/generated/components/banner/react-demos.js +0 -57
  16. package/patternfly-docs/generated/components/banner/react.js +0 -148
  17. package/patternfly-docs/generated/components/brand/react.js +0 -142
  18. package/patternfly-docs/generated/components/breadcrumb/react.js +0 -206
  19. package/patternfly-docs/generated/components/button/react-demos.js +0 -57
  20. package/patternfly-docs/generated/components/button/react.js +0 -826
  21. package/patternfly-docs/generated/components/card/react-demos.js +0 -201
  22. package/patternfly-docs/generated/components/card/react.js +0 -1015
  23. package/patternfly-docs/generated/components/charts/area-chart/-Victory.js +0 -1350
  24. package/patternfly-docs/generated/components/charts/bar-chart/-Victory.js +0 -1334
  25. package/patternfly-docs/generated/components/charts/box-plot-chart/-Victory.js +0 -1282
  26. package/patternfly-docs/generated/components/charts/bullet-chart/-Victory.js +0 -848
  27. package/patternfly-docs/generated/components/charts/colors-for-charts/-Victory.js +0 -192
  28. package/patternfly-docs/generated/components/charts/donut-chart/-Victory.js +0 -426
  29. package/patternfly-docs/generated/components/charts/donut-utilization-chart/-Victory.js +0 -804
  30. package/patternfly-docs/generated/components/charts/legends/-Victory.js +0 -3230
  31. package/patternfly-docs/generated/components/charts/line-chart/-Victory.js +0 -1178
  32. package/patternfly-docs/generated/components/charts/line-chart/ECharts.js +0 -525
  33. package/patternfly-docs/generated/components/charts/patterns/-Victory.js +0 -3382
  34. package/patternfly-docs/generated/components/charts/pie-chart/-Victory.js +0 -377
  35. package/patternfly-docs/generated/components/charts/resize-observer/-Victory.js +0 -2475
  36. package/patternfly-docs/generated/components/charts/sankey-chart/ECharts.js +0 -538
  37. package/patternfly-docs/generated/components/charts/scatter-chart/-Victory.js +0 -1551
  38. package/patternfly-docs/generated/components/charts/skeletons/-Victory.js +0 -4115
  39. package/patternfly-docs/generated/components/charts/sparkline-chart/-Victory.js +0 -955
  40. package/patternfly-docs/generated/components/charts/stack-chart/-Victory.js +0 -1173
  41. package/patternfly-docs/generated/components/charts/threshold-chart/-Victory.js +0 -1166
  42. package/patternfly-docs/generated/components/charts/tooltips/-Victory.js +0 -413
  43. package/patternfly-docs/generated/components/chip/react-deprecated.js +0 -323
  44. package/patternfly-docs/generated/components/clipboard-copy/react.js +0 -373
  45. package/patternfly-docs/generated/components/code-block/react.js +0 -148
  46. package/patternfly-docs/generated/components/code-editor/react.js +0 -659
  47. package/patternfly-docs/generated/components/compass/react-demos.js +0 -147
  48. package/patternfly-docs/generated/components/compass/react.js +0 -440
  49. package/patternfly-docs/generated/components/content/react.js +0 -248
  50. package/patternfly-docs/generated/components/data-list/react-demos.js +0 -90
  51. package/patternfly-docs/generated/components/data-list/react.js +0 -709
  52. package/patternfly-docs/generated/components/date-and-time/calendar-month/react.js +0 -283
  53. package/patternfly-docs/generated/components/date-and-time/date-and-time-picker/react-demos.js +0 -64
  54. package/patternfly-docs/generated/components/date-and-time/date-picker/react-demos.js +0 -83
  55. package/patternfly-docs/generated/components/date-and-time/date-picker/react.js +0 -395
  56. package/patternfly-docs/generated/components/date-and-time/time-picker/react.js +0 -241
  57. package/patternfly-docs/generated/components/description-list/react-demos.js +0 -58
  58. package/patternfly-docs/generated/components/description-list/react.js +0 -743
  59. package/patternfly-docs/generated/components/divider/react.js +0 -126
  60. package/patternfly-docs/generated/components/drag-and-drop/react-demos.js +0 -351
  61. package/patternfly-docs/generated/components/drag-and-drop/react-deprecated.js +0 -184
  62. package/patternfly-docs/generated/components/drag-and-drop/react.js +0 -137
  63. package/patternfly-docs/generated/components/drawer/react.js +0 -598
  64. package/patternfly-docs/generated/components/dual-list-selector/react-deprecated.js +0 -772
  65. package/patternfly-docs/generated/components/dual-list-selector/react.js +0 -594
  66. package/patternfly-docs/generated/components/empty-state/react.js +0 -199
  67. package/patternfly-docs/generated/components/expandable-section/react-demos.js +0 -65
  68. package/patternfly-docs/generated/components/expandable-section/react.js +0 -408
  69. package/patternfly-docs/generated/components/file-upload/multiple-file-upload/react-demos.js +0 -52
  70. package/patternfly-docs/generated/components/file-upload/multiple-file-upload/react.js +0 -398
  71. package/patternfly-docs/generated/components/file-upload/simple-file-upload/react.js +0 -749
  72. package/patternfly-docs/generated/components/forms/checkbox/react.js +0 -222
  73. package/patternfly-docs/generated/components/forms/form/react.js +0 -1106
  74. package/patternfly-docs/generated/components/forms/form-select/react.js +0 -208
  75. package/patternfly-docs/generated/components/forms/radio/react.js +0 -212
  76. package/patternfly-docs/generated/components/forms/text-area/react.js +0 -160
  77. package/patternfly-docs/generated/components/forms/text-input/react.js +0 -216
  78. package/patternfly-docs/generated/components/helper-text/react-demos.js +0 -180
  79. package/patternfly-docs/generated/components/helper-text/react.js +0 -164
  80. package/patternfly-docs/generated/components/hero/react.js +0 -88
  81. package/patternfly-docs/generated/components/hint/react.js +0 -169
  82. package/patternfly-docs/generated/components/icon/react.js +0 -215
  83. package/patternfly-docs/generated/components/input-group/react.js +0 -182
  84. package/patternfly-docs/generated/components/jump-links/react-demos.js +0 -154
  85. package/patternfly-docs/generated/components/jump-links/react.js +0 -212
  86. package/patternfly-docs/generated/components/label/react-demos.js +0 -57
  87. package/patternfly-docs/generated/components/label/react.js +0 -417
  88. package/patternfly-docs/generated/components/list/react.js +0 -175
  89. package/patternfly-docs/generated/components/login-page/react.js +0 -587
  90. package/patternfly-docs/generated/components/masthead/react-demos.js +0 -79
  91. package/patternfly-docs/generated/components/masthead/react.js +0 -291
  92. package/patternfly-docs/generated/components/menus/application-launcher/react-demos.js +0 -769
  93. package/patternfly-docs/generated/components/menus/context-selector/react-demos.js +0 -665
  94. package/patternfly-docs/generated/components/menus/custom-menus/react-demos.js +0 -187
  95. package/patternfly-docs/generated/components/menus/dropdown/react-templates.js +0 -163
  96. package/patternfly-docs/generated/components/menus/dropdown/react.js +0 -998
  97. package/patternfly-docs/generated/components/menus/menu/react.js +0 -1540
  98. package/patternfly-docs/generated/components/menus/menu-toggle/react.js +0 -747
  99. package/patternfly-docs/generated/components/menus/options-menu/react-demos.js +0 -508
  100. package/patternfly-docs/generated/components/menus/select/react-templates.js +0 -257
  101. package/patternfly-docs/generated/components/menus/select/react.js +0 -998
  102. package/patternfly-docs/generated/components/modal/react-deprecated.js +0 -554
  103. package/patternfly-docs/generated/components/modal/react.js +0 -597
  104. package/patternfly-docs/generated/components/navigation/react-demos.js +0 -356
  105. package/patternfly-docs/generated/components/navigation/react.js +0 -409
  106. package/patternfly-docs/generated/components/notification-badge/react.js +0 -196
  107. package/patternfly-docs/generated/components/notification-drawer/react-demos.js +0 -107
  108. package/patternfly-docs/generated/components/notification-drawer/react.js +0 -394
  109. package/patternfly-docs/generated/components/number-input/react.js +0 -210
  110. package/patternfly-docs/generated/components/overflow-menu/react.js +0 -274
  111. package/patternfly-docs/generated/components/page/react-demos.js +0 -149
  112. package/patternfly-docs/generated/components/page/react.js +0 -1352
  113. package/patternfly-docs/generated/components/pagination/react.js +0 -492
  114. package/patternfly-docs/generated/components/panel/react.js +0 -236
  115. package/patternfly-docs/generated/components/popover/react.js +0 -390
  116. package/patternfly-docs/generated/components/progress/react-demos.js +0 -59
  117. package/patternfly-docs/generated/components/progress/react.js +0 -283
  118. package/patternfly-docs/generated/components/progress-stepper/react-demos.js +0 -45
  119. package/patternfly-docs/generated/components/progress-stepper/react.js +0 -219
  120. package/patternfly-docs/generated/components/search-input/react-demos.js +0 -113
  121. package/patternfly-docs/generated/components/search-input/react.js +0 -263
  122. package/patternfly-docs/generated/components/sidebar/react.js +0 -236
  123. package/patternfly-docs/generated/components/simple-list/react.js +0 -200
  124. package/patternfly-docs/generated/components/skeleton/react-demos.js +0 -44
  125. package/patternfly-docs/generated/components/skeleton/react.js +0 -122
  126. package/patternfly-docs/generated/components/skip-to-content/react.js +0 -73
  127. package/patternfly-docs/generated/components/slider/react.js +0 -309
  128. package/patternfly-docs/generated/components/spinner/react.js +0 -111
  129. package/patternfly-docs/generated/components/switch/react.js +0 -163
  130. package/patternfly-docs/generated/components/table/react-demos.js +0 -355
  131. package/patternfly-docs/generated/components/table/react-deprecated.js +0 -1350
  132. package/patternfly-docs/generated/components/table/react.js +0 -3241
  133. package/patternfly-docs/generated/components/tabs/react-demos.js +0 -108
  134. package/patternfly-docs/generated/components/tabs/react.js +0 -1359
  135. package/patternfly-docs/generated/components/text-input-group/react-demos.js +0 -152
  136. package/patternfly-docs/generated/components/text-input-group/react.js +0 -278
  137. package/patternfly-docs/generated/components/tile/react-deprecated.js +0 -242
  138. package/patternfly-docs/generated/components/timestamp/react.js +0 -283
  139. package/patternfly-docs/generated/components/title/react.js +0 -94
  140. package/patternfly-docs/generated/components/toggle-group/react.js +0 -299
  141. package/patternfly-docs/generated/components/toolbar/react-demos.js +0 -66
  142. package/patternfly-docs/generated/components/toolbar/react.js +0 -932
  143. package/patternfly-docs/generated/components/tooltip/react.js +0 -241
  144. package/patternfly-docs/generated/components/tree-view/react.js +0 -429
  145. package/patternfly-docs/generated/components/truncate/react.js +0 -211
  146. package/patternfly-docs/generated/components/wizard/react-demos.js +0 -87
  147. package/patternfly-docs/generated/components/wizard/react-deprecated.js +0 -788
  148. package/patternfly-docs/generated/components/wizard/react.js +0 -986
  149. package/patternfly-docs/generated/developer-guides/open-ui-automation/react.js +0 -285
  150. package/patternfly-docs/generated/foundations-and-styles/layouts/bullseye/react.js +0 -70
  151. package/patternfly-docs/generated/foundations-and-styles/layouts/flex/react.js +0 -506
  152. package/patternfly-docs/generated/foundations-and-styles/layouts/gallery/react.js +0 -94
  153. package/patternfly-docs/generated/foundations-and-styles/layouts/grid/react.js +0 -272
  154. package/patternfly-docs/generated/foundations-and-styles/layouts/level/react.js +0 -87
  155. package/patternfly-docs/generated/foundations-and-styles/layouts/split/react.js +0 -124
  156. package/patternfly-docs/generated/foundations-and-styles/layouts/stack/react.js +0 -112
  157. package/patternfly-docs/generated/index.js +0 -1769
  158. package/patternfly-docs/generated/patterns/card-view/react-demos.js +0 -78
  159. package/patternfly-docs/generated/patterns/filters/react-demos.js +0 -141
  160. package/patternfly-docs/generated/patterns/password-generator/react-demos.js +0 -51
  161. package/patternfly-docs/generated/patterns/password-strength/react-demos.js +0 -61
  162. package/patternfly-docs/generated/patterns/primary-detail/react-demos.js +0 -124
  163. package/patternfly-docs/generated/patterns/right-to-left/react-demos.js +0 -81
@@ -1,78 +0,0 @@
1
- import React from 'react';
2
- import { AutoLinkHeader, Example, Link as PatternflyThemeLink } from '@patternfly/documentation-framework/components';
3
- import { Fragment, useState } from 'react';
4
- import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon';
5
- import RhUiAddCircleFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-circle-fill-icon';
6
- import pfIcon from '../../../../../react-core/src/demos/CardView/../assets/pf-logo-small.svg';
7
- import activeMQIcon from '../../../../../react-core/src/demos/CardView/../assets/activemq-core_200x150.png';
8
- import avroIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-avro_200x150.png';
9
- import dropBoxIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-dropbox_200x150.png';
10
- import infinispanIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-infinispan_200x150.png';
11
- import saxonIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-saxon_200x150.png';
12
- import sparkIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-spark_200x150.png';
13
- import swaggerIcon from '../../../../../react-core/src/demos/CardView/../assets/camel-swagger-java_200x150.png';
14
- import azureIcon from '../../../../../react-core/src/demos/CardView/../assets/FuseConnector_Icons_AzureServices.png';
15
- import restIcon from '../../../../../react-core/src/demos/CardView/../assets/FuseConnector_Icons_REST.png';
16
- import RhUiEllipsisVerticalFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ellipsis-vertical-fill-icon';
17
- import { data } from '@patternfly/react-core/src/demos/CardView/examples/CardViewData.jsx';
18
- import { DashboardWrapper } from '@patternfly/react-core/dist/js/demos/DashboardWrapper';
19
- import srcImportcardview from './react-demos/card-view.png';
20
- const pageData = {
21
- "id": "Card view",
22
- "section": "patterns",
23
- "subsection": "",
24
- "deprecated": false,
25
- "template": false,
26
- "beta": false,
27
- "demo": false,
28
- "newImplementationLink": false,
29
- "source": "react-demos",
30
- "tabName": null,
31
- "slug": "/patterns/card-view/react-demos",
32
- "sourceLink": "https://github.com/patternfly/patternfly-react/blob/main/packages/react-core/src/demos/CardView/CardView.md",
33
- "relPath": "packages/react-core/src/demos/CardView/CardView.md",
34
- "fullscreenExamples": [
35
- "Card view"
36
- ]
37
- };
38
- pageData.liveContext = {
39
- Fragment,
40
- useState,
41
- TrashIcon,
42
- RhUiAddCircleFillIcon,
43
- pfIcon,
44
- activeMQIcon,
45
- avroIcon,
46
- dropBoxIcon,
47
- infinispanIcon,
48
- saxonIcon,
49
- sparkIcon,
50
- swaggerIcon,
51
- azureIcon,
52
- restIcon,
53
- RhUiEllipsisVerticalFillIcon,
54
- data,
55
- DashboardWrapper
56
- };
57
- pageData.examples = {
58
- 'Card view': props =>
59
- <Example {...pageData} {...props} thumbnail={srcImportcardview} {...{"code":"import { Fragment, useState } from 'react';\nimport {\n Badge,\n Bullseye,\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardBody,\n Content,\n Divider,\n Dropdown,\n DropdownItem,\n DropdownList,\n EmptyState,\n EmptyStateFooter,\n EmptyStateVariant,\n EmptyStateActions,\n Gallery,\n MenuToggle,\n MenuToggleCheckbox,\n OverflowMenu,\n OverflowMenuControl,\n OverflowMenuDropdownItem,\n OverflowMenuItem,\n PageSection,\n Pagination,\n Toolbar,\n ToolbarItem,\n ToolbarFilter,\n ToolbarContent,\n Select,\n SelectList,\n SelectOption,\n MenuToggleElement\n} from '@patternfly/react-core';\nimport TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon';\nimport RhUiAddCircleFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-circle-fill-icon';\nimport pfIcon from './assets/pf-logo-small.svg';\nimport activeMQIcon from './assets/activemq-core_200x150.png';\nimport avroIcon from './assets/camel-avro_200x150.png';\nimport dropBoxIcon from './assets/camel-dropbox_200x150.png';\nimport infinispanIcon from './assets/camel-infinispan_200x150.png';\nimport saxonIcon from './assets/camel-saxon_200x150.png';\nimport sparkIcon from './assets/camel-spark_200x150.png';\nimport swaggerIcon from './assets/camel-swagger-java_200x150.png';\nimport azureIcon from './assets/FuseConnector_Icons_AzureServices.png';\nimport restIcon from './assets/FuseConnector_Icons_REST.png';\nimport RhUiEllipsisVerticalFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ellipsis-vertical-fill-icon';\nimport { DashboardWrapper } from '@patternfly/react-core/dist/js/demos/DashboardWrapper';\nimport { data } from '@patternfly/react-core/src/demos/CardView/examples/CardViewData.jsx';\n\nexport const CardViewBasic: React.FunctionComponent = () => {\n const totalItemCount = 10;\n\n const [cardData, setCardData] = useState(data);\n const [isChecked, setIsChecked] = useState(false);\n const [selectedItems, setSelectedItems] = useState<number[]>([]);\n const [areAllSelected, setAreAllSelected] = useState<boolean>(false);\n const [splitButtonDropdownIsOpen, setSplitButtonDropdownIsOpen] = useState(false);\n const [isLowerToolbarDropdownOpen, setIsLowerToolbarDropdownOpen] = useState(false);\n const [isLowerToolbarKebabDropdownOpen, setIsLowerToolbarKebabDropdownOpen] = useState(false);\n const [page, setPage] = useState(1);\n const [perPage, setPerPage] = useState(10);\n const [filters, setFilters] = useState<Record<string, string[]>>({ products: [] });\n const [state, setState] = useState({});\n\n interface ProductType {\n id: number;\n name: string;\n icon: string;\n description: string;\n }\n const checkAllSelected = (selected: number, total: number) => {\n if (selected && selected < total) {\n return null;\n }\n return selected === total;\n };\n\n const onToolbarDropdownToggle = () => {\n setIsLowerToolbarDropdownOpen(!isLowerToolbarDropdownOpen);\n };\n\n const onToolbarKebabDropdownToggle = () => {\n setIsLowerToolbarKebabDropdownOpen(!isLowerToolbarKebabDropdownOpen);\n };\n\n const onToolbarKebabDropdownSelect = () => {\n setIsLowerToolbarKebabDropdownOpen(!isLowerToolbarKebabDropdownOpen);\n };\n\n const onCardKebabDropdownToggle = (\n event: React.MouseEvent<HTMLButtonElement, MouseEvent> | React.MouseEvent<HTMLDivElement, MouseEvent>,\n key: string\n ) => {\n setState({\n [key]: !state[key as keyof Object]\n });\n };\n\n const deleteItem = (item: ProductType) => {\n const filter = (getter) => (val) => getter(val) !== item.id;\n\n setCardData(cardData.filter(filter(({ id }) => id)));\n\n setSelectedItems(selectedItems.filter(filter((id) => id)));\n };\n\n const onSetPage = (_event: any, pageNumber: number) => {\n setPage(pageNumber);\n };\n\n const onPerPageSelect = (_event: any, perPage: number) => {\n setPerPage(perPage);\n setPage(1);\n };\n\n const onSplitButtonToggle = () => {\n setSplitButtonDropdownIsOpen(!splitButtonDropdownIsOpen);\n };\n\n const onSplitButtonSelect = () => {\n setSplitButtonDropdownIsOpen(false);\n };\n\n const onNameSelect = (event: any, selection = '') => {\n const checked = event.target.checked;\n const prevSelections = filters.products;\n\n setFilters({\n ...filters,\n products: checked ? [...prevSelections, selection] : prevSelections.filter((value) => value !== selection)\n });\n };\n\n const onDelete = (type = '', _id = '') => {\n if (type) {\n setFilters(filters);\n } else {\n setFilters({ products: [] });\n }\n };\n\n const onChange = (event: React.FormEvent<HTMLInputElement>) => {\n const name = event.currentTarget.name;\n const productId = Number(name.charAt(name.length - 1));\n\n if (selectedItems.includes(productId * 1)) {\n setSelectedItems(selectedItems.filter((id) => productId * 1 !== id));\n\n const checkAll = checkAllSelected(selectedItems.length - 1, totalItemCount);\n setAreAllSelected(!!checkAll);\n } else {\n setSelectedItems([...selectedItems, productId * 1]);\n const checkAll = checkAllSelected(selectedItems.length + 1, totalItemCount);\n setAreAllSelected(!!checkAll);\n }\n };\n\n const updateSelected = () => {\n const rows = cardData.map((post) => {\n post.selected = selectedItems.includes(post.id);\n return post;\n });\n\n setCardData(rows);\n };\n\n const getAllItems = () => {\n const collection: number[] = [];\n for (const items of cardData) {\n collection.push(items.id);\n }\n\n return collection;\n };\n\n const splitCheckboxSelectAll = (e: any) => {\n let collection: number[] = [];\n\n if (e.target.checked) {\n for (let i = 0; i <= 9; i++) {\n collection = [...collection, i];\n }\n }\n\n setSelectedItems(collection);\n setIsChecked(isChecked);\n setAreAllSelected(e.target.checked);\n\n updateSelected();\n };\n\n const selectPage = (e: { target: { checked: any } }) => {\n const { checked } = e.target;\n let collection: number[] = [];\n\n collection = getAllItems();\n\n setSelectedItems(collection);\n setIsChecked(checked);\n setAreAllSelected(totalItemCount === perPage ? true : false);\n\n updateSelected();\n };\n\n const selectAll = () => {\n let collection: number[] = [];\n for (let i = 0; i <= 9; i++) {\n collection = [...collection, i];\n }\n\n setSelectedItems(collection);\n setIsChecked(true);\n setAreAllSelected(true);\n\n updateSelected();\n };\n\n const selectNone = () => {\n setSelectedItems([]);\n setIsChecked(false);\n setAreAllSelected(false);\n\n updateSelected();\n };\n\n const renderPagination = () => {\n const defaultPerPageOptions = [\n {\n title: '1',\n value: 1\n },\n {\n title: '5',\n value: 5\n },\n {\n title: '10',\n value: 10\n }\n ];\n\n return (\n <Pagination\n itemCount={totalItemCount}\n page={page}\n perPage={perPage}\n perPageOptions={defaultPerPageOptions}\n onSetPage={onSetPage}\n onPerPageSelect={onPerPageSelect}\n variant=\"top\"\n isCompact\n />\n );\n };\n\n const buildSelectDropdown = () => {\n const numSelected = selectedItems.length;\n const anySelected = numSelected > 0;\n const splitButtonDropdownItems = (\n <>\n <DropdownItem key=\"item-1\" onClick={selectNone}>\n Select none (0 items)\n </DropdownItem>\n <DropdownItem key=\"item-2\" onClick={selectPage}>\n Select page ({perPage} items)\n </DropdownItem>\n <DropdownItem key=\"item-3\" onClick={selectAll}>\n Select all ({totalItemCount} items)\n </DropdownItem>\n </>\n );\n return (\n <Dropdown\n onSelect={onSplitButtonSelect}\n isOpen={splitButtonDropdownIsOpen}\n onOpenChange={(isOpen) => setSplitButtonDropdownIsOpen(isOpen)}\n toggle={(toggleRef) => (\n <MenuToggle\n ref={toggleRef}\n isExpanded={splitButtonDropdownIsOpen}\n onClick={onSplitButtonToggle}\n aria-label=\"Select cards\"\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"split-dropdown-checkbox\"\n key=\"split-dropdown-checkbox\"\n aria-label={anySelected ? 'Deselect all cards' : 'Select all cards'}\n isChecked={areAllSelected}\n onClick={(e) => splitCheckboxSelectAll(e)}\n >\n {numSelected !== 0 && `${numSelected} selected`}\n </MenuToggleCheckbox>\n ]}\n ></MenuToggle>\n )}\n >\n <DropdownList>{splitButtonDropdownItems}</DropdownList>\n </Dropdown>\n );\n };\n\n const buildFilterDropdown = () => {\n const filterDropdownItems = (\n <SelectList>\n <SelectOption\n hasCheckbox\n key=\"patternfly\"\n value=\"PatternFly\"\n isSelected={filters.products.includes('PatternFly')}\n >\n PatternFly\n </SelectOption>\n <SelectOption hasCheckbox key=\"activemq\" value=\"ActiveMQ\" isSelected={filters.products.includes('ActiveMQ')}>\n ActiveMQ\n </SelectOption>\n <SelectOption\n hasCheckbox\n key=\"apachespark\"\n value=\"Apache Spark\"\n isSelected={filters.products.includes('Apache Spark')}\n >\n Apache Spark\n </SelectOption>\n <SelectOption hasCheckbox key=\"avro\" value=\"Avro\" isSelected={filters.products.includes('Avro')}>\n Avro\n </SelectOption>\n <SelectOption\n hasCheckbox\n key=\"azureservices\"\n value=\"Azure Services\"\n isSelected={filters.products.includes('Azure Services')}\n >\n Azure Services\n </SelectOption>\n <SelectOption hasCheckbox key=\"crypto\" value=\"Crypto\" isSelected={filters.products.includes('Crypto')}>\n Crypto\n </SelectOption>\n <SelectOption hasCheckbox key=\"dropbox\" value=\"DropBox\" isSelected={filters.products.includes('DropBox')}>\n DropBox\n </SelectOption>\n <SelectOption\n hasCheckbox\n key=\"jbossdatagrid\"\n value=\"JBoss Data Grid\"\n isSelected={filters.products.includes('JBoss Data Grid')}\n >\n JBoss Data Grid\n </SelectOption>\n <SelectOption hasCheckbox key=\"rest\" value=\"REST\" isSelected={filters.products.includes('REST')}>\n REST\n </SelectOption>\n <SelectOption hasCheckbox key=\"swagger\" value=\"SWAGGER\" isSelected={filters.products.includes('SWAGGER')}>\n SWAGGER\n </SelectOption>\n </SelectList>\n );\n\n return (\n <ToolbarFilter\n categoryName=\"Products\"\n labels={filters.products}\n deleteLabel={(type, id) => onDelete(type as string, id as string)}\n >\n <Select\n aria-label=\"Products\"\n role=\"menu\"\n toggle={(toggleRef) => (\n <MenuToggle ref={toggleRef} onClick={onToolbarDropdownToggle} isExpanded={isLowerToolbarDropdownOpen}>\n Filter by creator name\n {filters.products.length > 0 && <Badge isRead>{filters.products.length}</Badge>}\n </MenuToggle>\n )}\n onSelect={(event, selection) => onNameSelect(event, selection.toString())}\n onOpenChange={(isOpen) => {\n setIsLowerToolbarDropdownOpen(isOpen);\n }}\n selected={filters.products}\n isOpen={isLowerToolbarDropdownOpen}\n >\n {filterDropdownItems}\n </Select>\n </ToolbarFilter>\n );\n };\n\n const toolbarKebabDropdownItems = [\n <OverflowMenuDropdownItem itemId={0} key=\"link\">\n Link\n </OverflowMenuDropdownItem>,\n <OverflowMenuDropdownItem itemId={1} key=\"action\" component=\"button\">\n Action\n </OverflowMenuDropdownItem>,\n <OverflowMenuDropdownItem itemId={2} key=\"disabled link\" isDisabled>\n Disabled Link\n </OverflowMenuDropdownItem>,\n <OverflowMenuDropdownItem itemId={3} key=\"disabled action\" isDisabled component=\"button\">\n Disabled Action\n </OverflowMenuDropdownItem>,\n <Divider key=\"separator\" />,\n <OverflowMenuDropdownItem itemId={5} key=\"separated link\">\n Separated Link\n </OverflowMenuDropdownItem>,\n <OverflowMenuDropdownItem itemId={6} key=\"separated action\" component=\"button\">\n Separated Action\n </OverflowMenuDropdownItem>\n ];\n\n const toolbarItems = (\n <Fragment>\n <ToolbarItem>{buildSelectDropdown()}</ToolbarItem>\n <ToolbarItem>{buildFilterDropdown()}</ToolbarItem>\n <ToolbarItem>\n <OverflowMenu breakpoint=\"md\">\n <OverflowMenuItem>\n <Button variant=\"primary\">Create a project</Button>\n </OverflowMenuItem>\n <OverflowMenuControl hasAdditionalOptions>\n <Dropdown\n onSelect={onToolbarKebabDropdownSelect}\n toggle={(toggleRef) => (\n <MenuToggle\n ref={toggleRef}\n aria-label=\"Toolbar kebab overflow menu\"\n variant=\"plain\"\n onClick={onToolbarKebabDropdownToggle}\n isExpanded={isLowerToolbarKebabDropdownOpen}\n icon={<RhUiEllipsisVerticalFillIcon />}\n />\n )}\n isOpen={isLowerToolbarKebabDropdownOpen}\n onOpenChange={(isOpen) => setIsLowerToolbarDropdownOpen(isOpen)}\n >\n <DropdownList>{toolbarKebabDropdownItems}</DropdownList>\n </Dropdown>\n </OverflowMenuControl>\n </OverflowMenu>\n </ToolbarItem>\n <ToolbarItem variant=\"pagination\" align={{ default: 'alignEnd' }}>\n {renderPagination()}\n </ToolbarItem>\n </Fragment>\n );\n\n const icons = {\n pfIcon,\n activeMQIcon,\n sparkIcon,\n avroIcon,\n azureIcon,\n saxonIcon,\n dropBoxIcon,\n infinispanIcon,\n restIcon,\n swaggerIcon\n };\n\n const filtered =\n filters.products.length > 0\n ? data.filter((card: { name: string }) => filters.products.length === 0 || filters.products.includes(card.name))\n : cardData.slice((page - 1) * perPage, perPage === 1 ? page * perPage : page * perPage - 1);\n\n return (\n <Fragment>\n <DashboardWrapper mainContainerId=\"main-content-card-view-default-nav\" breadcrumb={null}>\n <PageSection aria-labelledby=\"projects\">\n <Content>\n <h1 id=\"projects\">Projects</h1>\n <p>This is a demo that showcases PatternFly cards.</p>\n </Content>\n <Toolbar id=\"toolbar-group-types\" clearAllFilters={onDelete}>\n <ToolbarContent>{toolbarItems}</ToolbarContent>\n </Toolbar>\n </PageSection>\n <PageSection isFilled aria-label=\"Selectable card gallery\">\n <Gallery hasGutter aria-label=\"Selectable card container\">\n <Card isCompact>\n <Bullseye>\n <EmptyState\n headingLevel=\"h2\"\n titleText=\"Add a new card to your page\"\n icon={RhUiAddCircleFillIcon}\n variant={EmptyStateVariant.xs}\n >\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button variant=\"link\">Add card</Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n </Bullseye>\n </Card>\n {filtered.map((product, key) => (\n <Card isCompact isClickable isSelectable key={product.name} id={product.name.replace(/ /g, '-')}>\n <CardHeader\n selectableActions={{\n isChecked: selectedItems.includes(product.id),\n selectableActionId: `selectable-actions-item-${product.id}`,\n selectableActionAriaLabelledby: product.name.replace(/ /g, '-'),\n name: `check-${product.id}`,\n onChange\n }}\n actions={{\n actions: (\n <>\n <Dropdown\n isOpen={!!state[key] ?? false}\n onOpenChange={(isOpen) => setState({ [key]: isOpen })}\n toggle={(toggleRef: React.Ref<MenuToggleElement>) => (\n <MenuToggle\n ref={toggleRef}\n aria-label={`${product.name} actions`}\n variant=\"plain\"\n onClick={(e) => {\n onCardKebabDropdownToggle(e, key.toString());\n }}\n isExpanded={!!state[key]}\n icon={<RhUiEllipsisVerticalFillIcon />}\n />\n )}\n popperProps={{ position: 'right' }}\n >\n <DropdownList>\n <DropdownItem\n key=\"trash\"\n onClick={() => {\n deleteItem(product);\n }}\n >\n <TrashIcon />\n Delete\n </DropdownItem>\n </DropdownList>\n </Dropdown>\n </>\n )\n }}\n >\n <img src={icons[product.icon]} alt={`${product.name} icon`} style={{ maxWidth: '60px' }} />\n </CardHeader>\n <CardTitle>{product.name}</CardTitle>\n <CardBody>{product.description}</CardBody>\n </Card>\n ))}\n </Gallery>\n </PageSection>\n <PageSection\n isFilled={false}\n stickyOnBreakpoint={{ default: 'bottom' }}\n padding={{ default: 'noPadding' }}\n aria-label=\"Pagination controls\"\n >\n <Pagination\n itemCount={totalItemCount}\n page={page}\n perPage={perPage}\n onPerPageSelect={onPerPageSelect}\n onSetPage={onSetPage}\n variant=\"bottom\"\n />\n </PageSection>\n </DashboardWrapper>\n </Fragment>\n );\n};\n","title":"Card view","lang":"ts","isFullscreen":true,"className":""}}>
60
-
61
- </Example>
62
- };
63
-
64
- const Component = () => (
65
- <React.Fragment>
66
- <AutoLinkHeader {...{"id":"demos","headingLevel":"h2","className":"ws-title ws-h2"}}>
67
- {`Demos`}
68
- </AutoLinkHeader>
69
- <p {...{"className":"pf-v6-c-content--p pf-m-editorial ws-p "}}>
70
- {`This demonstrates how you can assemble a full page view that contains a grid of equal sized cards that includes a toolbar for managing card grid contents.`}
71
- </p>
72
- {React.createElement(pageData.examples["Card view"])}
73
- </React.Fragment>
74
- );
75
- Component.displayName = 'PatternsCardViewReactDemosDocs';
76
- Component.pageData = pageData;
77
-
78
- export default Component;
@@ -1,141 +0,0 @@
1
- import React from 'react';
2
- import { AutoLinkHeader, Example, Link as PatternflyThemeLink } from '@patternfly/documentation-framework/components';
3
- import { Fragment, useEffect, useRef, useState } from 'react';
4
- import {
5
- Popper,
6
- SearchInput,
7
- Toolbar,
8
- ToolbarContent,
9
- ToolbarItem,
10
- Menu,
11
- MenuContent,
12
- MenuList,
13
- MenuItem,
14
- MenuToggle,
15
- MenuToggleCheckbox,
16
- ToolbarGroup,
17
- ToolbarFilter,
18
- Badge,
19
- Pagination,
20
- EmptyState,
21
- EmptyStateBody,
22
- EmptyStateFooter,
23
- EmptyStateActions,
24
- Title,
25
- Button,
26
- Bullseye,
27
- ToolbarToggleGroup
28
- } from '@patternfly/react-core';
29
- import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';
30
- import SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';
31
- import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
32
- const pageData = {
33
- "id": "Filters",
34
- "section": "patterns",
35
- "subsection": "",
36
- "deprecated": false,
37
- "template": false,
38
- "beta": false,
39
- "demo": false,
40
- "newImplementationLink": false,
41
- "source": "react-demos",
42
- "tabName": null,
43
- "slug": "/patterns/filters/react-demos",
44
- "sourceLink": "https://github.com/patternfly/patternfly-react/blob/main/packages/react-core/src/demos/Filters/FilterDemos.md",
45
- "relPath": "packages/react-core/src/demos/Filters/FilterDemos.md",
46
- "examples": [
47
- "Search input",
48
- "Single select",
49
- "Checkbox select",
50
- "Attribute search",
51
- "Mixed select filter group",
52
- "Single select filter group",
53
- "Faceted filter"
54
- ]
55
- };
56
- pageData.liveContext = {
57
- Fragment,
58
- useEffect,
59
- useRef,
60
- useState,
61
- Popper,
62
- SearchInput,
63
- Toolbar,
64
- ToolbarContent,
65
- ToolbarItem,
66
- Menu,
67
- MenuContent,
68
- MenuList,
69
- MenuItem,
70
- MenuToggle,
71
- MenuToggleCheckbox,
72
- ToolbarGroup,
73
- ToolbarFilter,
74
- Badge,
75
- Pagination,
76
- EmptyState,
77
- EmptyStateBody,
78
- EmptyStateFooter,
79
- EmptyStateActions,
80
- Title,
81
- Button,
82
- Bullseye,
83
- ToolbarToggleGroup,
84
- FilterIcon,
85
- SearchIcon,
86
- Table,
87
- Thead,
88
- Tr,
89
- Th,
90
- Tbody,
91
- Td
92
- };
93
- pageData.examples = {
94
- 'Search input': props =>
95
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n SearchInput,\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Pagination,\n EmptyState,\n EmptyStateFooter,\n EmptyStateBody,\n Button,\n Bullseye,\n EmptyStateActions\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterSearchInput: React.FunctionComponent = () => {\n // Set up repo filtering\n const [searchValue, setSearchValue] = useState('');\n\n const onSearchChange = (value: string) => {\n setSearchValue(value);\n };\n\n const onFilter = (repo: Repository) => {\n if (searchValue === '') {\n return true;\n }\n\n let input: RegExp;\n try {\n input = new RegExp(searchValue, 'i');\n } catch (err) {\n input = new RegExp(searchValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i');\n }\n return repo.name.search(input) >= 0;\n };\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table row selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []); // Selecting all should only select all currently filtered rows\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"search-input-bulk-select\"\n key=\"search-input-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n id=\"search-input-bulk-select\"\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n const searchInput = (\n <SearchInput\n placeholder=\"Filter by server name\"\n value={searchValue}\n onChange={(_event, value) => onSearchChange(value)}\n onClear={() => onSearchChange('')}\n />\n );\n\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Search filter pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"search-input-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar id=\"search-input-filter-toolbar\">\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarItem>{searchInput}</ToolbarItem>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n const emptyState = (\n <EmptyState headingLevel=\"h4\" titleText=\"No results found\" icon={SearchIcon}>\n <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button\n variant=\"link\"\n onClick={() => {\n setSearchValue('');\n }}\n >\n Clear all filters\n </Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.length > 0 &&\n filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n {filteredRepos.length === 0 && (\n <Tr>\n <Td colSpan={8}>\n <Bullseye>{emptyState}</Bullseye>\n </Td>\n </Tr>\n )}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Search input","lang":"ts","className":""}}>
96
-
97
- </Example>,
98
- 'Single select': props =>
99
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Pagination\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterSingleSelect: React.FunctionComponent = () => {\n // Set up repo filtering\n const [searchValue, setSearchValue] = useState('');\n const onFilter = (repo: Repository) => {\n if (searchValue === 'all') {\n return true;\n }\n\n let input: RegExp;\n try {\n input = new RegExp(searchValue, 'i');\n } catch (err) {\n input = new RegExp(searchValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i');\n }\n return repo.status.search(input) >= 0;\n };\n\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []);\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"single-bulk-select\"\n key=\"single-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up single select menu & state\n const [isOpen, setIsOpen] = useState<boolean>(false);\n const [menuSelection, setMenuSelection] = useState<string>('all');\n const toggleRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleMenuKeys = (event: KeyboardEvent) => {\n if (isOpen && menuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsOpen(!isOpen);\n toggleRef.current?.focus();\n }\n }\n };\n\n const handleClickOutside = (event: MouseEvent) => {\n if (isOpen && !menuRef.current?.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleMenuKeys);\n window.addEventListener('click', handleClickOutside);\n return () => {\n window.removeEventListener('keydown', handleMenuKeys);\n window.removeEventListener('click', handleClickOutside);\n };\n }, [isOpen, menuRef]);\n\n const onToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (menuRef.current) {\n const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsOpen(!isOpen);\n };\n\n function onSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n setMenuSelection(itemId.toString());\n setSearchValue(itemId.toString());\n setIsOpen(!isOpen);\n }\n\n const menuToggleDisplay = {\n all: 'All statuses',\n down: 'Down',\n stopped: 'Stopped',\n degraded: 'Degraded',\n running: 'Running',\n maint: 'Needs maintenance'\n };\n\n const toggle = (\n <MenuToggle\n ref={toggleRef}\n onClick={onToggleClick}\n isExpanded={isOpen}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n {menuToggleDisplay[menuSelection]}\n </MenuToggle>\n );\n\n const menu = (\n <Menu ref={menuRef} id=\"single-select-menu\" onSelect={onSelect} selected={menuSelection}>\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"all\">All statuses</MenuItem>\n <MenuItem itemId=\"stopped\">Stopped</MenuItem>\n <MenuItem itemId=\"down\">Down</MenuItem>\n <MenuItem itemId=\"degraded\">Degraded</MenuItem>\n <MenuItem itemId=\"running\">Running</MenuItem>\n <MenuItem itemId=\"maint\">Needs maintenance</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const select = (\n <div ref={containerRef}>\n <Popper\n trigger={toggle}\n triggerRef={toggleRef}\n popper={menu}\n popperRef={menuRef}\n appendTo={containerRef.current || undefined}\n isVisible={isOpen}\n />\n </div>\n );\n\n // Set up pagination\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Single select filter pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"single-select-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar id=\"single-select-filter-toolbar\">\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarItem>{select}</ToolbarItem>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Single select","lang":"ts","className":""}}>
100
-
101
- </Example>,
102
- 'Checkbox select': props =>
103
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Toolbar,\n ToolbarContent,\n ToolbarGroup,\n ToolbarFilter,\n ToolbarItem,\n Badge,\n Pagination\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterCheckboxSelect: React.FunctionComponent = () => {\n // Set up repo filtering\n const [selections, setSelections] = useState<string[]>([]);\n const onFilter = (repo: Repository) => {\n if (selections.length === 0) {\n return true;\n }\n\n return selections.some((searchValue) => {\n let input: RegExp;\n try {\n input = new RegExp(searchValue, 'i');\n } catch (err) {\n input = new RegExp(searchValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i');\n }\n return repo.location.search(input) >= 0;\n });\n };\n\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []);\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"checkbox-bulk-select\"\n key=\"checkbox-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up checkbox select menu\n const [isOpen, setIsOpen] = useState<boolean>(false);\n const toggleRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleMenuKeys = (event: KeyboardEvent) => {\n if (isOpen && menuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsOpen(!isOpen);\n toggleRef.current?.focus();\n }\n }\n };\n\n const handleClickOutside = (event: MouseEvent) => {\n if (isOpen && !menuRef.current?.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleMenuKeys);\n window.addEventListener('click', handleClickOutside);\n return () => {\n window.removeEventListener('keydown', handleMenuKeys);\n window.removeEventListener('click', handleClickOutside);\n };\n }, [isOpen, menuRef]);\n\n const onToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (menuRef.current) {\n const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsOpen(!isOpen);\n };\n\n function onSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n const itemStr = itemId.toString();\n setSelections(\n selections.includes(itemStr) ? selections.filter((selection) => selection !== itemStr) : [itemStr, ...selections]\n );\n }\n\n const toggle = (\n <MenuToggle\n ref={toggleRef}\n onClick={onToggleClick}\n isExpanded={isOpen}\n icon={<FilterIcon />}\n {...(selections.length > 0 && { badge: <Badge isRead>{selections.length}</Badge> })}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Location\n </MenuToggle>\n );\n\n const menu = (\n <Menu ref={menuRef} id=\"checkbox-select-menu\" onSelect={onSelect} selected={selections}>\n <MenuContent>\n <MenuList>\n <MenuItem hasCheckbox isSelected={selections.includes('Bangalore')} itemId=\"Bangalore\">\n Bangalore\n </MenuItem>\n <MenuItem hasCheckbox isSelected={selections.includes('Boston')} itemId=\"Boston\">\n Boston\n </MenuItem>\n <MenuItem hasCheckbox isSelected={selections.includes('Brno')} itemId=\"Brno\">\n Brno\n </MenuItem>\n <MenuItem hasCheckbox isSelected={selections.includes('Raleigh')} itemId=\"Raleigh\">\n Raleigh\n </MenuItem>\n <MenuItem hasCheckbox isSelected={selections.includes('Westford')} itemId=\"Westford\">\n Westford\n </MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const select = (\n <div ref={containerRef}>\n <Popper\n trigger={toggle}\n triggerRef={toggleRef}\n popper={menu}\n popperRef={menuRef}\n appendTo={containerRef.current || undefined}\n isVisible={isOpen}\n />\n </div>\n );\n\n // Set up pagination\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Checkbox filter pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"checkbox-select-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar id=\"checkbox-filter-toolbar\" clearAllFilters={() => setSelections([])}>\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarGroup variant=\"filter-group\">\n <ToolbarFilter\n labels={selections}\n deleteLabel={(category, label) => onSelect(undefined, label as string)}\n deleteLabelGroup={() => setSelections([])}\n categoryName=\"Location\"\n >\n {select}\n </ToolbarFilter>\n </ToolbarGroup>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Checkbox select","lang":"ts","className":""}}>
104
-
105
- </Example>,
106
- 'Attribute search': props =>
107
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n SearchInput,\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Pagination,\n EmptyState,\n EmptyStateFooter,\n EmptyStateBody,\n Button,\n Bullseye,\n Badge,\n ToolbarGroup,\n ToolbarFilter,\n ToolbarToggleGroup,\n EmptyStateActions\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterAttributeSearch: React.FunctionComponent = () => {\n // Set up repo filtering\n const [searchValue, setSearchValue] = useState('');\n const [locationSelections, setLocationSelections] = useState<string[]>([]);\n const [statusSelection, setStatusSelection] = useState('');\n\n const onSearchChange = (value: string) => {\n setSearchValue(value);\n };\n\n const onFilter = (repo: Repository) => {\n // Search name with search value\n let searchValueInput: RegExp;\n try {\n searchValueInput = new RegExp(searchValue, 'i');\n } catch (err) {\n searchValueInput = new RegExp(searchValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'), 'i');\n }\n const matchesSearchValue = repo.name.search(searchValueInput) >= 0;\n\n // Search status with status selection\n const matchesStatusValue = repo.status.toLowerCase() === statusSelection.toLowerCase();\n\n // Search location with location selections\n const matchesLocationValue = locationSelections.includes(repo.location);\n\n return (\n (searchValue === '' || matchesSearchValue) &&\n (statusSelection === '' || matchesStatusValue) &&\n (locationSelections.length === 0 || matchesLocationValue)\n );\n };\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table row selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []); // Selecting all should only select all currently filtered rows\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"attribute-search-input-bulk-select\"\n key=\"attribute-search-input-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n id=\"attribute-search-input-bulk-select\"\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up name search input\n const searchInput = (\n <SearchInput\n placeholder=\"Filter by server name\"\n value={searchValue}\n onChange={(_event, value) => onSearchChange(value)}\n onClear={() => onSearchChange('')}\n />\n );\n\n // Set up status single select\n const [isStatusMenuOpen, setIsStatusMenuOpen] = useState<boolean>(false);\n const statusToggleRef = useRef<HTMLButtonElement>(null);\n const statusMenuRef = useRef<HTMLDivElement>(null);\n const statusContainerRef = useRef<HTMLDivElement>(null);\n\n const handleStatusMenuKeys = (event: KeyboardEvent) => {\n if (isStatusMenuOpen && statusMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsStatusMenuOpen(!isStatusMenuOpen);\n statusToggleRef.current?.focus();\n }\n }\n };\n\n const handleStatusClickOutside = (event: MouseEvent) => {\n if (isStatusMenuOpen && !statusMenuRef.current?.contains(event.target as Node)) {\n setIsStatusMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleStatusMenuKeys);\n window.addEventListener('click', handleStatusClickOutside);\n return () => {\n window.removeEventListener('keydown', handleStatusMenuKeys);\n window.removeEventListener('click', handleStatusClickOutside);\n };\n }, [isStatusMenuOpen, statusMenuRef]);\n\n const onStatusToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (statusMenuRef.current) {\n const firstElement = statusMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsStatusMenuOpen(!isStatusMenuOpen);\n };\n\n function onStatusSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n setStatusSelection(itemId.toString());\n setIsStatusMenuOpen(!isStatusMenuOpen);\n }\n\n const statusToggle = (\n <MenuToggle\n ref={statusToggleRef}\n onClick={onStatusToggleClick}\n isExpanded={isStatusMenuOpen}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Filter by status\n </MenuToggle>\n );\n\n const statusMenu = (\n <Menu ref={statusMenuRef} id=\"attribute-search-status-menu\" onSelect={onStatusSelect} selected={statusSelection}>\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"Degraded\">Degraded</MenuItem>\n <MenuItem itemId=\"Down\">Down</MenuItem>\n <MenuItem itemId=\"Needs maintenance\">Needs maintenance</MenuItem>\n <MenuItem itemId=\"Running\">Running</MenuItem>\n <MenuItem itemId=\"Stopped\">Stopped</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const statusSelect = (\n <div ref={statusContainerRef}>\n <Popper\n trigger={statusToggle}\n triggerRef={statusToggleRef}\n popper={statusMenu}\n popperRef={statusMenuRef}\n appendTo={statusContainerRef.current || undefined}\n isVisible={isStatusMenuOpen}\n />\n </div>\n );\n\n // Set up location checkbox select\n const [isLocationMenuOpen, setIsLocationMenuOpen] = useState<boolean>(false);\n const locationToggleRef = useRef<HTMLButtonElement>(null);\n const locationMenuRef = useRef<HTMLDivElement>(null);\n const locationContainerRef = useRef<HTMLDivElement>(null);\n\n const handleLocationMenuKeys = (event: KeyboardEvent) => {\n if (isLocationMenuOpen && locationMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsLocationMenuOpen(!isLocationMenuOpen);\n locationToggleRef.current?.focus();\n }\n }\n };\n\n const handleLocationClickOutside = (event: MouseEvent) => {\n if (isLocationMenuOpen && !locationMenuRef.current?.contains(event.target as Node)) {\n setIsLocationMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleLocationMenuKeys);\n window.addEventListener('click', handleLocationClickOutside);\n return () => {\n window.removeEventListener('keydown', handleLocationMenuKeys);\n window.removeEventListener('click', handleLocationClickOutside);\n };\n }, [isLocationMenuOpen, locationMenuRef]);\n\n const onLocationMenuToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (locationMenuRef.current) {\n const firstElement = locationMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsLocationMenuOpen(!isLocationMenuOpen);\n };\n\n function onLocationMenuSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n const itemStr = itemId.toString();\n\n setLocationSelections(\n locationSelections.includes(itemStr)\n ? locationSelections.filter((selection) => selection !== itemStr)\n : [itemStr, ...locationSelections]\n );\n }\n\n const locationToggle = (\n <MenuToggle\n ref={locationToggleRef}\n onClick={onLocationMenuToggleClick}\n isExpanded={isLocationMenuOpen}\n {...(locationSelections.length > 0 && { badge: <Badge isRead>{locationSelections.length}</Badge> })}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Filter by location\n </MenuToggle>\n );\n\n const locationMenu = (\n <Menu\n ref={locationMenuRef}\n id=\"attribute-search-location-menu\"\n onSelect={onLocationMenuSelect}\n selected={locationSelections}\n >\n <MenuContent>\n <MenuList>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Bangalore')} itemId=\"Bangalore\">\n Bangalore\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Boston')} itemId=\"Boston\">\n Boston\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Brno')} itemId=\"Brno\">\n Brno\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Raleigh')} itemId=\"Raleigh\">\n Raleigh\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Westford')} itemId=\"Westford\">\n Westford\n </MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const locationSelect = (\n <div ref={locationContainerRef}>\n <Popper\n trigger={locationToggle}\n triggerRef={locationToggleRef}\n popper={locationMenu}\n popperRef={locationMenuRef}\n appendTo={locationContainerRef.current || undefined}\n isVisible={isLocationMenuOpen}\n />\n </div>\n );\n\n // Set up attribute selector\n const [activeAttributeMenu, setActiveAttributeMenu] = useState<'Servers' | 'Status' | 'Location'>('Servers');\n const [isAttributeMenuOpen, setIsAttributeMenuOpen] = useState(false);\n const attributeToggleRef = useRef<HTMLButtonElement>(null);\n const attributeMenuRef = useRef<HTMLDivElement>(null);\n const attributeContainerRef = useRef<HTMLDivElement>(null);\n\n const handleAttribueMenuKeys = (event: KeyboardEvent) => {\n if (!isAttributeMenuOpen) {\n return;\n }\n if (\n attributeMenuRef.current?.contains(event.target as Node) ||\n attributeToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsAttributeMenuOpen(!isAttributeMenuOpen);\n attributeToggleRef.current?.focus();\n }\n }\n };\n\n const handleAttributeClickOutside = (event: MouseEvent) => {\n if (isAttributeMenuOpen && !attributeMenuRef.current?.contains(event.target as Node)) {\n setIsAttributeMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleAttribueMenuKeys);\n window.addEventListener('click', handleAttributeClickOutside);\n return () => {\n window.removeEventListener('keydown', handleAttribueMenuKeys);\n window.removeEventListener('click', handleAttributeClickOutside);\n };\n }, [isAttributeMenuOpen, attributeMenuRef]);\n\n const onAttributeToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (attributeMenuRef.current) {\n const firstElement = attributeMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsAttributeMenuOpen(!isAttributeMenuOpen);\n };\n\n const attributeToggle = (\n <MenuToggle\n ref={attributeToggleRef}\n onClick={onAttributeToggleClick}\n isExpanded={isAttributeMenuOpen}\n icon={<FilterIcon />}\n >\n {activeAttributeMenu}\n </MenuToggle>\n );\n const attributeMenu = (\n <Menu\n ref={attributeMenuRef}\n onSelect={(_ev, itemId) => {\n setActiveAttributeMenu(itemId?.toString() as 'Servers' | 'Status' | 'Location');\n setIsAttributeMenuOpen(!isAttributeMenuOpen);\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"Servers\">Servers</MenuItem>\n <MenuItem itemId=\"Status\">Status</MenuItem>\n <MenuItem itemId=\"Location\">Location</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const attributeDropdown = (\n <div ref={attributeContainerRef}>\n <Popper\n trigger={attributeToggle}\n triggerRef={attributeToggleRef}\n popper={attributeMenu}\n popperRef={attributeMenuRef}\n appendTo={attributeContainerRef.current || undefined}\n isVisible={isAttributeMenuOpen}\n />\n </div>\n );\n\n // Set up pagination and toolbar\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Attribute search pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"attribute-search-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar\n id=\"attribute-search-filter-toolbar\"\n clearAllFilters={() => {\n setSearchValue('');\n setStatusSelection('');\n setLocationSelections([]);\n }}\n >\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint=\"xl\">\n <ToolbarGroup variant=\"filter-group\">\n <ToolbarItem>{attributeDropdown}</ToolbarItem>\n <ToolbarFilter\n labels={searchValue !== '' ? [searchValue] : ([] as string[])}\n deleteLabel={() => setSearchValue('')}\n deleteLabelGroup={() => setSearchValue('')}\n categoryName=\"Name\"\n showToolbarItem={activeAttributeMenu === 'Servers'}\n >\n {searchInput}\n </ToolbarFilter>\n <ToolbarFilter\n labels={statusSelection !== '' ? [statusSelection] : ([] as string[])}\n deleteLabel={() => setStatusSelection('')}\n deleteLabelGroup={() => setStatusSelection('')}\n categoryName=\"Status\"\n showToolbarItem={activeAttributeMenu === 'Status'}\n >\n {statusSelect}\n </ToolbarFilter>\n <ToolbarFilter\n labels={locationSelections}\n deleteLabel={(category, label) => onLocationMenuSelect(undefined, label as string)}\n deleteLabelGroup={() => setLocationSelections([])}\n categoryName=\"Location\"\n showToolbarItem={activeAttributeMenu === 'Location'}\n >\n {locationSelect}\n </ToolbarFilter>\n </ToolbarGroup>\n </ToolbarToggleGroup>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n const emptyState = (\n <EmptyState headingLevel=\"h4\" titleText=\"No results found\" icon={SearchIcon}>\n <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button\n variant=\"link\"\n onClick={() => {\n setSearchValue('');\n setStatusSelection('');\n setLocationSelections([]);\n }}\n >\n Clear all filters\n </Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.length > 0 &&\n filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n {filteredRepos.length === 0 && (\n <Tr>\n <Td colSpan={8}>\n <Bullseye>{emptyState}</Bullseye>\n </Td>\n </Tr>\n )}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Attribute search","lang":"ts","className":""}}>
108
-
109
- </Example>,
110
- 'Mixed select filter group': props =>
111
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Pagination,\n EmptyState,\n EmptyStateFooter,\n EmptyStateBody,\n Button,\n Bullseye,\n Badge,\n ToolbarFilter,\n ToolbarToggleGroup,\n EmptyStateActions\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterMixedSelectGroup: React.FunctionComponent = () => {\n // Set up repo filtering\n const [locationSelections, setLocationSelections] = useState<string[]>([]);\n const [statusSelection, setStatusSelection] = useState('');\n\n const onFilter = (repo: Repository) => {\n // Search status with status selection\n const matchesStatusValue = repo.status.toLowerCase() === statusSelection.toLowerCase();\n\n // Search location with location selections\n const matchesLocationValue = locationSelections.includes(repo.location);\n\n return (statusSelection === '' || matchesStatusValue) && (locationSelections.length === 0 || matchesLocationValue);\n };\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table row selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []); // Selecting all should only select all currently filtered rows\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"mixed-group-input-bulk-select\"\n key=\"mixed-group-input-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n id=\"mixed-group-input-bulk-select\"\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up status single select\n const [isStatusMenuOpen, setIsStatusMenuOpen] = useState<boolean>(false);\n const statusToggleRef = useRef<HTMLButtonElement>(null);\n const statusMenuRef = useRef<HTMLDivElement>(null);\n const statusContainerRef = useRef<HTMLDivElement>(null);\n\n const handleStatusMenuKeys = (event: KeyboardEvent) => {\n if (isStatusMenuOpen && statusMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsStatusMenuOpen(!isStatusMenuOpen);\n statusToggleRef.current?.focus();\n }\n }\n };\n\n const handleStatusClickOutside = (event: MouseEvent) => {\n if (isStatusMenuOpen && !statusMenuRef.current?.contains(event.target as Node)) {\n setIsStatusMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleStatusMenuKeys);\n window.addEventListener('click', handleStatusClickOutside);\n return () => {\n window.removeEventListener('keydown', handleStatusMenuKeys);\n window.removeEventListener('click', handleStatusClickOutside);\n };\n }, [isStatusMenuOpen, statusMenuRef]);\n\n const onStatusToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (statusMenuRef.current) {\n const firstElement = statusMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsStatusMenuOpen(!isStatusMenuOpen);\n };\n\n function onStatusSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n setStatusSelection(itemId.toString());\n setIsStatusMenuOpen(!isStatusMenuOpen);\n }\n\n const statusToggle = (\n <MenuToggle\n ref={statusToggleRef}\n onClick={onStatusToggleClick}\n isExpanded={isStatusMenuOpen}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Status\n </MenuToggle>\n );\n\n const statusMenu = (\n <Menu ref={statusMenuRef} id=\"mixed-group-status-menu\" onSelect={onStatusSelect} selected={statusSelection}>\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"Degraded\">Degraded</MenuItem>\n <MenuItem itemId=\"Down\">Down</MenuItem>\n <MenuItem itemId=\"Needs maintenance\">Needs maintenance</MenuItem>\n <MenuItem itemId=\"Running\">Running</MenuItem>\n <MenuItem itemId=\"Stopped\">Stopped</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const statusSelect = (\n <div ref={statusContainerRef}>\n <Popper\n trigger={statusToggle}\n triggerRef={statusToggleRef}\n popper={statusMenu}\n popperRef={statusMenuRef}\n appendTo={statusContainerRef.current || undefined}\n isVisible={isStatusMenuOpen}\n />\n </div>\n );\n\n // Set up location checkbox select\n const [isLocationMenuOpen, setIsLocationMenuOpen] = useState<boolean>(false);\n const locationToggleRef = useRef<HTMLButtonElement>(null);\n const locationMenuRef = useRef<HTMLDivElement>(null);\n const locationContainerRef = useRef<HTMLDivElement>(null);\n\n const handleLocationMenuKeys = (event: KeyboardEvent) => {\n if (isLocationMenuOpen && locationMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsLocationMenuOpen(!isLocationMenuOpen);\n locationToggleRef.current?.focus();\n }\n }\n };\n\n const handleLocationClickOutside = (event: MouseEvent) => {\n if (isLocationMenuOpen && !locationMenuRef.current?.contains(event.target as Node)) {\n setIsLocationMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleLocationMenuKeys);\n window.addEventListener('click', handleLocationClickOutside);\n return () => {\n window.removeEventListener('keydown', handleLocationMenuKeys);\n window.removeEventListener('click', handleLocationClickOutside);\n };\n }, [isLocationMenuOpen, locationMenuRef]);\n\n const onLocationMenuToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (locationMenuRef.current) {\n const firstElement = locationMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsLocationMenuOpen(!isLocationMenuOpen);\n };\n\n function onLocationMenuSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n const itemStr = itemId.toString();\n\n setLocationSelections(\n locationSelections.includes(itemStr)\n ? locationSelections.filter((selection) => selection !== itemStr)\n : [itemStr, ...locationSelections]\n );\n }\n\n const locationToggle = (\n <MenuToggle\n ref={locationToggleRef}\n onClick={onLocationMenuToggleClick}\n isExpanded={isLocationMenuOpen}\n {...(locationSelections.length > 0 && { badge: <Badge isRead>{locationSelections.length}</Badge> })}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Location\n </MenuToggle>\n );\n\n const locationMenu = (\n <Menu\n ref={locationMenuRef}\n id=\"mixed-group-location-menu\"\n onSelect={onLocationMenuSelect}\n selected={locationSelections}\n >\n <MenuContent>\n <MenuList>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Bangalore')} itemId=\"Bangalore\">\n Bangalore\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Boston')} itemId=\"Boston\">\n Boston\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Brno')} itemId=\"Brno\">\n Brno\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Raleigh')} itemId=\"Raleigh\">\n Raleigh\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Westford')} itemId=\"Westford\">\n Westford\n </MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const locationSelect = (\n <div ref={locationContainerRef}>\n <Popper\n trigger={locationToggle}\n triggerRef={locationToggleRef}\n popper={locationMenu}\n popperRef={locationMenuRef}\n appendTo={locationContainerRef.current || undefined}\n isVisible={isLocationMenuOpen}\n />\n </div>\n );\n\n // Set up pagination and toolbar\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Mixed filter group pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"mixed-group-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar\n id=\"mixed-group-toolbar\"\n clearAllFilters={() => {\n setStatusSelection('');\n setLocationSelections([]);\n }}\n >\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint=\"xl\">\n <ToolbarFilter\n labels={statusSelection !== '' ? [statusSelection] : ([] as string[])}\n deleteLabel={() => setStatusSelection('')}\n deleteLabelGroup={() => setStatusSelection('')}\n categoryName=\"Status\"\n >\n {statusSelect}\n </ToolbarFilter>\n <ToolbarFilter\n labels={locationSelections}\n deleteLabel={(category, label) => onLocationMenuSelect(undefined, label as string)}\n deleteLabelGroup={() => setLocationSelections([])}\n categoryName=\"Location\"\n >\n {locationSelect}\n </ToolbarFilter>\n </ToolbarToggleGroup>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n const emptyState = (\n <EmptyState headingLevel=\"h4\" titleText=\"No results found\" icon={SearchIcon}>\n <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button\n variant=\"link\"\n onClick={() => {\n setStatusSelection('');\n setLocationSelections([]);\n }}\n >\n Clear all filters\n </Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.length > 0 &&\n filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n {filteredRepos.length === 0 && (\n <Tr>\n <Td colSpan={8}>\n <Bullseye>{emptyState}</Bullseye>\n </Td>\n </Tr>\n )}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Mixed select filter group","lang":"ts","className":""}}>
112
-
113
- </Example>,
114
- 'Single select filter group': props =>
115
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Pagination,\n EmptyState,\n EmptyStateFooter,\n EmptyStateBody,\n Button,\n Bullseye,\n ToolbarToggleGroup,\n EmptyStateActions\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterSameSelectGroup: React.FunctionComponent = () => {\n // Set up repo filtering\n const [locationSelection, setLocationSelection] = useState('All locations');\n const [statusSelection, setStatusSelection] = useState('All statuses');\n\n const onFilter = (repo: Repository) => {\n // Search status with status selection\n const matchesStatusValue = repo.status.toLowerCase() === statusSelection.toLowerCase();\n\n // Search location with location selections\n const matchesLocationValue = repo.location.toLowerCase() === locationSelection.toLowerCase();\n\n return (\n (statusSelection === 'All statuses' || matchesStatusValue) &&\n (locationSelection === 'All locations' || matchesLocationValue)\n );\n };\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table row selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []); // Selecting all should only select all currently filtered rows\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"same-select-group-input-bulk-select\"\n key=\"same-select-group-input-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n id=\"same-select-group-input-bulk-select\"\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up status single select\n const [isStatusMenuOpen, setIsStatusMenuOpen] = useState<boolean>(false);\n const statusToggleRef = useRef<HTMLButtonElement>(null);\n const statusMenuRef = useRef<HTMLDivElement>(null);\n const statusContainerRef = useRef<HTMLDivElement>(null);\n\n const handleStatusMenuKeys = (event: KeyboardEvent) => {\n if (isStatusMenuOpen && statusMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsStatusMenuOpen(!isStatusMenuOpen);\n statusToggleRef.current?.focus();\n }\n }\n };\n\n const handleStatusClickOutside = (event: MouseEvent) => {\n if (isStatusMenuOpen && !statusMenuRef.current?.contains(event.target as Node)) {\n setIsStatusMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleStatusMenuKeys);\n window.addEventListener('click', handleStatusClickOutside);\n return () => {\n window.removeEventListener('keydown', handleStatusMenuKeys);\n window.removeEventListener('click', handleStatusClickOutside);\n };\n }, [isStatusMenuOpen, statusMenuRef]);\n\n const onStatusToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (statusMenuRef.current) {\n const firstElement = statusMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsStatusMenuOpen(!isStatusMenuOpen);\n };\n\n function onStatusSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n setStatusSelection(itemId.toString());\n setIsStatusMenuOpen(!isStatusMenuOpen);\n }\n\n const statusToggle = (\n <MenuToggle\n ref={statusToggleRef}\n onClick={onStatusToggleClick}\n isExpanded={isStatusMenuOpen}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n {statusSelection}\n </MenuToggle>\n );\n\n const statusMenu = (\n <Menu ref={statusMenuRef} id=\"same-select-group-status-menu\" onSelect={onStatusSelect} selected={statusSelection}>\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"All statuses\">All statuses</MenuItem>\n <MenuItem itemId=\"Degraded\">Degraded</MenuItem>\n <MenuItem itemId=\"Down\">Down</MenuItem>\n <MenuItem itemId=\"Needs maintenance\">Needs maintenance</MenuItem>\n <MenuItem itemId=\"Running\">Running</MenuItem>\n <MenuItem itemId=\"Stopped\">Stopped</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const statusSelect = (\n <div ref={statusContainerRef}>\n <Popper\n trigger={statusToggle}\n triggerRef={statusToggleRef}\n popper={statusMenu}\n popperRef={statusMenuRef}\n appendTo={statusContainerRef.current || undefined}\n isVisible={isStatusMenuOpen}\n />\n </div>\n );\n\n // Set up location checkbox select\n const [isLocationMenuOpen, setIsLocationMenuOpen] = useState<boolean>(false);\n const locationToggleRef = useRef<HTMLButtonElement>(null);\n const locationMenuRef = useRef<HTMLDivElement>(null);\n const locationContainerRef = useRef<HTMLDivElement>(null);\n\n const handleLocationMenuKeys = (event: KeyboardEvent) => {\n if (isLocationMenuOpen && locationMenuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsLocationMenuOpen(!isLocationMenuOpen);\n locationToggleRef.current?.focus();\n }\n }\n };\n\n const handleLocationClickOutside = (event: MouseEvent) => {\n if (isLocationMenuOpen && !locationMenuRef.current?.contains(event.target as Node)) {\n setIsLocationMenuOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleLocationMenuKeys);\n window.addEventListener('click', handleLocationClickOutside);\n return () => {\n window.removeEventListener('keydown', handleLocationMenuKeys);\n window.removeEventListener('click', handleLocationClickOutside);\n };\n }, [isLocationMenuOpen, locationMenuRef]);\n\n const onLocationMenuToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (locationMenuRef.current) {\n const firstElement = locationMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsLocationMenuOpen(!isLocationMenuOpen);\n };\n\n function onLocationMenuSelect(event: React.MouseEvent | undefined, itemId: string | number | undefined) {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n setLocationSelection(itemId.toString());\n setIsLocationMenuOpen(!isLocationMenuOpen);\n }\n\n const locationToggle = (\n <MenuToggle\n ref={locationToggleRef}\n onClick={onLocationMenuToggleClick}\n isExpanded={isLocationMenuOpen}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n {locationSelection}\n </MenuToggle>\n );\n\n const locationMenu = (\n <Menu\n ref={locationMenuRef}\n id=\"same-select-group-location-menu\"\n onSelect={onLocationMenuSelect}\n selected={locationSelection}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId=\"All locations\">All locations</MenuItem>\n <MenuItem itemId=\"Bangalore\">Bangalore</MenuItem>\n <MenuItem itemId=\"Boston\">Boston</MenuItem>\n <MenuItem itemId=\"Brno\">Brno</MenuItem>\n <MenuItem itemId=\"Raleigh\">Raleigh</MenuItem>\n <MenuItem itemId=\"Westford\">Westford</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const locationSelect = (\n <div ref={locationContainerRef}>\n <Popper\n trigger={locationToggle}\n triggerRef={locationToggleRef}\n popper={locationMenu}\n popperRef={locationMenuRef}\n appendTo={locationContainerRef.current || undefined}\n isVisible={isLocationMenuOpen}\n />\n </div>\n );\n\n // Set up pagination and toolbar\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Same select group pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"same-select-group-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar\n id=\"same-select-group-toolbar\"\n clearAllFilters={() => {\n setStatusSelection('All statuses');\n setLocationSelection('All locations');\n }}\n >\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint=\"xl\">\n <ToolbarItem>{statusSelect}</ToolbarItem>\n <ToolbarItem>{locationSelect}</ToolbarItem>\n </ToolbarToggleGroup>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n const emptyState = (\n <EmptyState headingLevel=\"h4\" titleText=\"No results found\" icon={SearchIcon}>\n <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button\n variant=\"link\"\n onClick={() => {\n setStatusSelection('All statuses');\n setLocationSelection('All locations');\n }}\n >\n Clear all filters\n </Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.length > 0 &&\n filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n {filteredRepos.length === 0 && (\n <Tr>\n <Td colSpan={8}>\n <Bullseye>{emptyState}</Bullseye>\n </Td>\n </Tr>\n )}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Single select filter group","lang":"ts","className":""}}>
116
-
117
- </Example>,
118
- 'Faceted filter': props =>
119
- <Example {...pageData} {...props} {...{"code":"import { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n Toolbar,\n ToolbarContent,\n ToolbarItem,\n Menu,\n MenuContent,\n MenuList,\n MenuItem,\n MenuToggle,\n MenuToggleCheckbox,\n Popper,\n Pagination,\n EmptyState,\n EmptyStateFooter,\n EmptyStateBody,\n Button,\n Bullseye,\n Badge,\n ToolbarFilter,\n ToolbarToggleGroup,\n MenuGroup,\n EmptyStateActions\n} from '@patternfly/react-core';\nimport { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';\nimport SearchIcon from '@patternfly/react-icons/dist/esm/icons/search-icon';\nimport FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon';\n\ninterface Repository {\n name: string;\n threads: string;\n apps: string;\n workspaces: string;\n status: string;\n location: string;\n}\n\n// In real usage, this data would come from some external source like an API via props.\nconst repositories: Repository[] = [\n { name: 'US-Node 1', threads: '5', apps: '25', workspaces: '5', status: 'Stopped', location: 'Raleigh' },\n { name: 'US-Node 2', threads: '5', apps: '30', workspaces: '2', status: 'Down', location: 'Westford' },\n { name: 'US-Node 3', threads: '13', apps: '35', workspaces: '12', status: 'Degraded', location: 'Boston' },\n { name: 'US-Node 4', threads: '2', apps: '5', workspaces: '18', status: 'Needs Maintenance', location: 'Raleigh' },\n { name: 'US-Node 5', threads: '7', apps: '30', workspaces: '5', status: 'Running', location: 'Boston' },\n { name: 'US-Node 6', threads: '5', apps: '20', workspaces: '15', status: 'Stopped', location: 'Raleigh' },\n { name: 'CZ-Node 1', threads: '12', apps: '48', workspaces: '13', status: 'Down', location: 'Brno' },\n { name: 'CZ-Node 2', threads: '3', apps: '8', workspaces: '20', status: 'Running', location: 'Brno' },\n { name: 'CZ-Remote-Node 1', threads: '1', apps: '15', workspaces: '20', status: 'Down', location: 'Brno' },\n { name: 'Bangalore-Node 1', threads: '1', apps: '20', workspaces: '20', status: 'Running', location: 'Bangalore' }\n];\n\nconst columnNames = {\n name: 'Servers',\n threads: 'Threads',\n apps: 'Applications',\n workspaces: 'Workspaces',\n status: 'Status',\n location: 'Location'\n};\n\nexport const FilterFaceted: React.FunctionComponent = () => {\n // Set up repo filtering\n const [locationSelections, setLocationSelections] = useState<string[]>([]);\n const [statusSelections, setStatusSelections] = useState<string[]>([]);\n\n const onFilter = (repo: Repository) => {\n // Search status with status selection\n const matchesStatusValue = statusSelections.includes(repo.status);\n\n // Search location with location selections\n const matchesLocationValue = locationSelections.includes(repo.location);\n\n return (\n (statusSelections.length === 0 || matchesStatusValue) && (locationSelections.length === 0 || matchesLocationValue)\n );\n };\n const filteredRepos = repositories.filter(onFilter);\n\n // Set up table row selection\n // In this example, selected rows are tracked by the repo names from each row. This could be any unique identifier.\n // This is to prevent state from being based on row order index in case we later add sorting.\n const isRepoSelectable = (repo: Repository) => repo.name !== 'a'; // Arbitrary logic for this example\n const [selectedRepoNames, setSelectedRepoNames] = useState<string[]>([]);\n const setRepoSelected = (repo: Repository, isSelecting = true) =>\n setSelectedRepoNames((prevSelected) => {\n const otherSelectedRepoNames = prevSelected.filter((r) => r !== repo.name);\n return isSelecting && isRepoSelectable(repo) ? [...otherSelectedRepoNames, repo.name] : otherSelectedRepoNames;\n });\n const selectAllRepos = (isSelecting = true) =>\n setSelectedRepoNames(isSelecting ? filteredRepos.map((r) => r.name) : []); // Selecting all should only select all currently filtered rows\n const areAllReposSelected = selectedRepoNames.length === filteredRepos.length && filteredRepos.length > 0;\n const areSomeReposSelected = selectedRepoNames.length > 0;\n const isRepoSelected = (repo: Repository) => selectedRepoNames.includes(repo.name);\n\n // To allow shift+click to select/deselect multiple rows\n const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<number | null>(null);\n const [shifting, setShifting] = useState(false);\n\n const onSelectRepo = (repo: Repository, rowIndex: number, isSelecting: boolean) => {\n // If the user is shift + selecting the checkboxes, then all intermediate checkboxes should be selected\n if (shifting && recentSelectedRowIndex !== null) {\n const numberSelected = rowIndex - recentSelectedRowIndex;\n const intermediateIndexes =\n numberSelected > 0\n ? Array.from(new Array(numberSelected + 1), (_x, i) => i + recentSelectedRowIndex)\n : Array.from(new Array(Math.abs(numberSelected) + 1), (_x, i) => i + rowIndex);\n intermediateIndexes.forEach((index) => setRepoSelected(repositories[index], isSelecting));\n } else {\n setRepoSelected(repo, isSelecting);\n }\n setRecentSelectedRowIndex(rowIndex);\n };\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(true);\n }\n };\n const onKeyUp = (e: KeyboardEvent) => {\n if (e.key === 'Shift') {\n setShifting(false);\n }\n };\n\n document.addEventListener('keydown', onKeyDown);\n document.addEventListener('keyup', onKeyUp);\n\n return () => {\n document.removeEventListener('keydown', onKeyDown);\n document.removeEventListener('keyup', onKeyUp);\n };\n }, []);\n\n // Set up bulk selection menu\n const bulkSelectMenuRef = useRef<HTMLDivElement>(null);\n const bulkSelectToggleRef = useRef<any>(null);\n const bulkSelectContainerRef = useRef<HTMLDivElement>(null);\n\n const [isBulkSelectOpen, setIsBulkSelectOpen] = useState<boolean>(false);\n\n const handleBulkSelectClickOutside = (event: MouseEvent) => {\n if (isBulkSelectOpen && !bulkSelectMenuRef.current?.contains(event.target as Node)) {\n setIsBulkSelectOpen(false);\n }\n };\n\n const handleBulkSelectMenuKeys = (event: KeyboardEvent) => {\n if (!isBulkSelectOpen) {\n return;\n }\n if (\n bulkSelectMenuRef.current?.contains(event.target as Node) ||\n bulkSelectToggleRef.current?.contains(event.target as Node)\n ) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleBulkSelectMenuKeys);\n window.addEventListener('click', handleBulkSelectClickOutside);\n return () => {\n window.removeEventListener('keydown', handleBulkSelectMenuKeys);\n window.removeEventListener('click', handleBulkSelectClickOutside);\n };\n }, [isBulkSelectOpen, bulkSelectMenuRef]);\n\n const onBulkSelectToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (bulkSelectMenuRef.current) {\n const firstElement = bulkSelectMenuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n };\n\n let menuToggleCheckmark: boolean | null = false;\n if (areAllReposSelected) {\n menuToggleCheckmark = true;\n } else if (areSomeReposSelected) {\n menuToggleCheckmark = null;\n }\n\n const bulkSelectToggle = (\n <MenuToggle\n ref={bulkSelectToggleRef}\n onClick={onBulkSelectToggleClick}\n isExpanded={isBulkSelectOpen}\n splitButtonItems={[\n <MenuToggleCheckbox\n id=\"filter-faceted-input-bulk-select\"\n key=\"filter-faceted-input-bulk-select\"\n aria-label=\"Select all\"\n isChecked={menuToggleCheckmark}\n onChange={(checked, _event) => selectAllRepos(checked)}\n />\n ]}\n aria-label=\"Full table selection checkbox\"\n />\n );\n\n const bulkSelectMenu = (\n <Menu\n id=\"filter-faceted-input-bulk-select\"\n ref={bulkSelectMenuRef}\n onSelect={(_ev, itemId) => {\n selectAllRepos(itemId === 1 || itemId === 2);\n setIsBulkSelectOpen(!isBulkSelectOpen);\n bulkSelectToggleRef.current?.querySelector('button').focus();\n }}\n >\n <MenuContent>\n <MenuList>\n <MenuItem itemId={0}>Select none (0 items)</MenuItem>\n <MenuItem itemId={1}>Select page ({repositories.length} items)</MenuItem>\n <MenuItem itemId={2}>Select all ({repositories.length} items)</MenuItem>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const toolbarBulkSelect = (\n <div ref={bulkSelectContainerRef}>\n <Popper\n trigger={bulkSelectToggle}\n triggerRef={bulkSelectToggleRef}\n popper={bulkSelectMenu}\n popperRef={bulkSelectMenuRef}\n appendTo={bulkSelectContainerRef.current || undefined}\n isVisible={isBulkSelectOpen}\n />\n </div>\n );\n\n // Set up location checkbox select\n const [isOpen, setIsOpen] = useState<boolean>(false);\n const toggleRef = useRef<HTMLButtonElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n\n const handleKeys = (event: KeyboardEvent) => {\n if (isOpen && menuRef.current?.contains(event.target as Node)) {\n if (event.key === 'Escape' || event.key === 'Tab') {\n setIsOpen(!isOpen);\n toggleRef.current?.focus();\n }\n }\n };\n\n const handleClickOutside = (event: MouseEvent) => {\n if (isOpen && !menuRef.current?.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n useEffect(() => {\n window.addEventListener('keydown', handleKeys);\n window.addEventListener('click', handleClickOutside);\n return () => {\n window.removeEventListener('keydown', handleKeys);\n window.removeEventListener('click', handleClickOutside);\n };\n }, [isOpen, menuRef]);\n\n const onToggleClick = (ev: React.MouseEvent) => {\n ev.stopPropagation(); // Stop handleClickOutside from handling\n setTimeout(() => {\n if (menuRef.current) {\n const firstElement = menuRef.current.querySelector('li > button:not(:disabled)');\n firstElement && (firstElement as HTMLElement).focus();\n }\n }, 0);\n setIsOpen(!isOpen);\n };\n\n const onSelect = (event: React.MouseEvent | undefined, itemId: string | number | undefined) => {\n if (typeof itemId === 'undefined') {\n return;\n }\n\n const itemStr = itemId.toString();\n const category = ['Raleigh', 'Boston', 'Brno', 'Westford', 'Bangalore'].includes(itemStr) ? 'location' : 'status';\n\n if (category === 'status') {\n setStatusSelections(\n statusSelections.includes(itemStr)\n ? statusSelections.filter((selection) => selection !== itemStr)\n : [itemStr, ...statusSelections]\n );\n } else {\n setLocationSelections(\n locationSelections.includes(itemStr)\n ? locationSelections.filter((selection) => selection !== itemStr)\n : [itemStr, ...locationSelections]\n );\n }\n };\n\n const onLabelDelete = (category: string, label: string) => {\n if (category === 'status') {\n setStatusSelections(statusSelections.filter((selection) => selection !== label));\n } else {\n setLocationSelections(locationSelections.filter((selection) => selection !== label));\n }\n };\n\n const areSelectionsPresent = locationSelections.length > 0 || statusSelections.length > 0;\n\n const toggle = (\n <MenuToggle\n ref={toggleRef}\n onClick={onToggleClick}\n isExpanded={isOpen}\n {...(areSelectionsPresent && {\n badge: <Badge isRead>{locationSelections.length + statusSelections.length}</Badge>\n })}\n icon={<FilterIcon />}\n style={\n {\n width: '200px'\n } as React.CSSProperties\n }\n >\n Filter\n </MenuToggle>\n );\n\n const menu = (\n <Menu ref={menuRef} id=\"filter-faceted-location-menu\" onSelect={onSelect} selected={locationSelections}>\n <MenuContent>\n <MenuList>\n <MenuGroup label=\"Status\">\n <MenuItem hasCheckbox isSelected={statusSelections.includes('Degraded')} itemId=\"Degraded\">\n Degraded\n </MenuItem>\n <MenuItem hasCheckbox isSelected={statusSelections.includes('Down')} itemId=\"Down\">\n Down\n </MenuItem>\n <MenuItem\n hasCheckbox\n isSelected={statusSelections.includes('Needs Maintenance')}\n itemId=\"Needs Maintenance\"\n >\n Needs Maintenance\n </MenuItem>\n <MenuItem hasCheckbox isSelected={statusSelections.includes('Running')} itemId=\"Running\">\n Running\n </MenuItem>\n <MenuItem hasCheckbox isSelected={statusSelections.includes('Stopped')} itemId=\"Stopped\">\n Stopped\n </MenuItem>\n </MenuGroup>\n <MenuGroup label=\"Location\">\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Bangalore')} itemId=\"Bangalore\">\n Bangalore\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Boston')} itemId=\"Boston\">\n Boston\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Brno')} itemId=\"Brno\">\n Brno\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Raleigh')} itemId=\"Raleigh\">\n Raleigh\n </MenuItem>\n <MenuItem hasCheckbox isSelected={locationSelections.includes('Westford')} itemId=\"Westford\">\n Westford\n </MenuItem>\n </MenuGroup>\n </MenuList>\n </MenuContent>\n </Menu>\n );\n\n const select = (\n <div ref={containerRef}>\n <Popper\n trigger={toggle}\n triggerRef={toggleRef}\n popper={menu}\n popperRef={menuRef}\n appendTo={containerRef.current || undefined}\n isVisible={isOpen}\n />\n </div>\n );\n\n // Set up pagination and toolbar\n const toolbarPagination = (\n <Pagination\n titles={{ paginationAriaLabel: 'Faceted filter group pagination' }}\n itemCount={repositories.length}\n perPage={10}\n page={1}\n widgetId=\"filter-faceted-mock-pagination\"\n isCompact\n />\n );\n\n const toolbar = (\n <Toolbar\n id=\"filter-faceted-toolbar\"\n clearAllFilters={() => {\n setStatusSelections([]);\n setLocationSelections([]);\n }}\n >\n <ToolbarContent>\n <ToolbarItem>{toolbarBulkSelect}</ToolbarItem>\n <ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint=\"xl\">\n <ToolbarFilter\n labels={statusSelections}\n deleteLabel={(category, label) => onLabelDelete(category as string, label as string)}\n deleteLabelGroup={() => setStatusSelections([])}\n categoryName=\"Status\"\n showToolbarItem={false}\n >\n <div />\n </ToolbarFilter>\n <ToolbarFilter\n labels={locationSelections}\n deleteLabel={(category, label) => onLabelDelete(category as string, label as string)}\n deleteLabelGroup={() => setLocationSelections([])}\n categoryName=\"Location\"\n >\n {select}\n </ToolbarFilter>\n </ToolbarToggleGroup>\n <ToolbarItem variant=\"pagination\">{toolbarPagination}</ToolbarItem>\n </ToolbarContent>\n </Toolbar>\n );\n\n const emptyState = (\n <EmptyState headingLevel=\"h4\" titleText=\"No results found\" icon={SearchIcon}>\n <EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>\n <EmptyStateFooter>\n <EmptyStateActions>\n <Button\n variant=\"link\"\n onClick={() => {\n setStatusSelections([]);\n setLocationSelections([]);\n }}\n >\n Clear all filters\n </Button>\n </EmptyStateActions>\n </EmptyStateFooter>\n </EmptyState>\n );\n\n return (\n <Fragment>\n {toolbar}\n <Table aria-label=\"Selectable table\">\n <Thead>\n <Tr>\n <Th screenReaderText=\"Row select\" />\n <Th width={20}>{columnNames.name}</Th>\n <Th width={10}>{columnNames.threads}</Th>\n <Th width={10}>{columnNames.apps}</Th>\n <Th width={10}>{columnNames.workspaces}</Th>\n <Th width={20}>{columnNames.status}</Th>\n <Th width={20}>{columnNames.location}</Th>\n </Tr>\n </Thead>\n <Tbody>\n {filteredRepos.length > 0 &&\n filteredRepos.map((repo, rowIndex) => (\n <Tr key={repo.name}>\n <Td\n select={{\n rowIndex,\n onSelect: (_event, isSelecting) => onSelectRepo(repo, rowIndex, isSelecting),\n isSelected: isRepoSelected(repo),\n isDisabled: !isRepoSelectable(repo)\n }}\n />\n <Td dataLabel={columnNames.name} modifier=\"truncate\">\n {repo.name}\n </Td>\n <Td dataLabel={columnNames.threads} modifier=\"truncate\">\n {repo.threads}\n </Td>\n <Td dataLabel={columnNames.apps} modifier=\"truncate\">\n {repo.apps}\n </Td>\n <Td dataLabel={columnNames.workspaces} modifier=\"truncate\">\n {repo.workspaces}\n </Td>\n <Td dataLabel={columnNames.status} modifier=\"truncate\">\n {repo.status}\n </Td>\n <Td dataLabel={columnNames.location} modifier=\"truncate\">\n {repo.location}\n </Td>\n </Tr>\n ))}\n {filteredRepos.length === 0 && (\n <Tr>\n <Td colSpan={8}>\n <Bullseye>{emptyState}</Bullseye>\n </Td>\n </Tr>\n )}\n </Tbody>\n </Table>\n </Fragment>\n );\n};\n","title":"Faceted filter","lang":"ts","className":""}}>
120
-
121
- </Example>
122
- };
123
-
124
- const Component = () => (
125
- <React.Fragment>
126
- <AutoLinkHeader {...{"id":"filtering-demos","headingLevel":"h2","className":"ws-title ws-h2"}}>
127
- {`Filtering demos`}
128
- </AutoLinkHeader>
129
- {React.createElement(pageData.examples["Search input"])}
130
- {React.createElement(pageData.examples["Single select"])}
131
- {React.createElement(pageData.examples["Checkbox select"])}
132
- {React.createElement(pageData.examples["Attribute search"])}
133
- {React.createElement(pageData.examples["Mixed select filter group"])}
134
- {React.createElement(pageData.examples["Single select filter group"])}
135
- {React.createElement(pageData.examples["Faceted filter"])}
136
- </React.Fragment>
137
- );
138
- Component.displayName = 'PatternsFiltersReactDemosDocs';
139
- Component.pageData = pageData;
140
-
141
- export default Component;