diff --git a/packages/react-core/src/demos/Card/Card.md b/packages/react-core/src/demos/Card/Card.md index c5669cfbd54..5c3cfb3316e 100644 --- a/packages/react-core/src/demos/Card/Card.md +++ b/packages/react-core/src/demos/Card/Card.md @@ -27,701 +27,5 @@ This demonstrates how you can assemble a full page view that contains a grid of ### Card view -```js isFullscreen -import React from 'react'; -import { - Badge, - Bullseye, - Button, - Card, - CardHeader, - CardTitle, - CardBody, - Checkbox, - Divider, - Dropdown, - DropdownItem, - DropdownList, - EmptyState, - EmptyStateHeader, - EmptyStateIcon, - EmptyStateFooter, - EmptyStateVariant, - EmptyStateActions, - Gallery, - MenuToggle, - MenuToggleCheckbox, - OverflowMenu, - OverflowMenuControl, - OverflowMenuDropdownItem, - OverflowMenuItem, - PageSection, - PageSectionVariants, - Pagination, - TextContent, - Text, - Title, - Toolbar, - ToolbarItem, - ToolbarFilter, - ToolbarContent, - Select, - SelectList, - SelectOption -} from '@patternfly/react-core'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import DashboardWrapper from '@patternfly/react-core/src/demos/examples/DashboardWrapper'; - -import FilterIcon from '@patternfly/react-icons/dist/esm/icons/filter-icon'; -import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon'; -import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; -import pfIcon from './pf-logo-small.svg'; -import activeMQIcon from './activemq-core_200x150.png'; -import avroIcon from './camel-avro_200x150.png'; -import dropBoxIcon from './camel-dropbox_200x150.png'; -import infinispanIcon from './camel-infinispan_200x150.png'; -import saxonIcon from './camel-saxon_200x150.png'; -import sparkIcon from './camel-spark_200x150.png'; -import swaggerIcon from './camel-swagger-java_200x150.png'; -import azureIcon from './FuseConnector_Icons_AzureServices.png'; -import restIcon from './FuseConnector_Icons_REST.png'; -import { data } from './CardData.jsx'; - -class CardViewBasic extends React.Component { - constructor(props) { - super(props); - - this.state = { - filters: { - products: [] - }, - cardData: data, - isChecked: false, - selectedItems: [], - areAllSelected: false, - isUpperToolbarDropdownOpen: false, - isUpperToolbarKebabDropdownOpen: false, - isLowerToolbarDropdownOpen: false, - isLowerToolbarKebabDropdownOpen: false, - isCardKebabDropdownOpen: false, - activeItem: 0, - splitButtonDropdownIsOpen: false, - page: 1, - perPage: 10, - totalItemCount: 10 - }; - - this.checkAllSelected = (selected, total) => { - if (selected && selected < total) { - return null; - } - return selected === total; - }; - - this.onToolbarDropdownToggle = () => { - this.setState((prevState) => ({ - isLowerToolbarDropdownOpen: !prevState.isLowerToolbarDropdownOpen - })); - }; - - this.onToolbarKebabDropdownToggle = () => { - this.setState({ - isOpen: !this.state.isLowerToolbarKebabDropdownOpen - }); - }; - - this.onToolbarKebabDropdownSelect = (event) => { - this.setState({ - isLowerToolbarKebabDropdownOpen: !this.state.isLowerToolbarKebabDropdownOpen - }); - }; - - this.onCardKebabDropdownToggle = (key, event) => { - event?.stopPropagation(); - this.setState((prevState) => ({ - [key]: !prevState[key] - })); - }; - - this.onCardKebabDropdownSelect = (key, event) => { - this.setState({ - [key]: !this.state[key] - }); - }; - - this.deleteItem = (item) => (event) => { - const filter = (getter) => (val) => getter(val) !== item.id; - this.setState({ - cardData: this.state.cardData.filter(filter(({ id }) => id)), - selectedItems: this.state.selectedItems.filter(filter((id) => id)) - }); - }; - - this.onSetPage = (_event, pageNumber) => { - this.setState({ - page: pageNumber - }); - }; - - this.onPerPageSelect = (_event, perPage) => { - this.setState({ - perPage, - page: 1 - }); - }; - - this.onSplitButtonToggle = () => { - this.setState((prevState) => ({ - splitButtonDropdownIsOpen: !prevState.splitButtonDropdownIsOpen - })); - }; - - this.onSplitButtonSelect = () => { - this.setState({ - splitButtonDropdownIsOpen: false - }); - }; - - this.onNameSelect = (event, selection) => { - const checked = event.target.checked; - this.setState((prevState) => { - const prevSelections = prevState.filters['products']; - return { - filters: { - ...prevState.filters, - ['products']: checked - ? [...prevSelections, selection] - : prevSelections.filter((value) => value !== selection) - } - }; - }); - }; - - this.onDelete = (type = '', id = '') => { - if (type) { - this.setState((prevState) => { - prevState.filters[type.toLowerCase()] = prevState.filters[type.toLowerCase()].filter((s) => s !== id); - return { - filters: prevState.filters - }; - }); - } else { - this.setState({ - filters: { - products: [] - } - }); - } - }; - - this.onKeyDown = (event, productId) => { - if (event.target !== event.currentTarget) { - return; - } - if ([' ', 'Enter'].includes(event.key)) { - event.preventDefault(); - this.setState((prevState) => { - return prevState.selectedItems.includes(productId * 1) - ? { - selectedItems: [...prevState.selectedItems.filter((id) => productId * 1 != id)], - areAllSelected: this.checkAllSelected(prevState.selectedItems.length - 1, prevState.totalItemCount) - } - : { - selectedItems: [...prevState.selectedItems, productId * 1], - areAllSelected: this.checkAllSelected(prevState.selectedItems.length + 1, prevState.totalItemCount) - }; - }); - } - }; - - this.onClick = (productId) => { - this.setState((prevState) => { - return prevState.selectedItems.includes(productId * 1) - ? { - selectedItems: [...prevState.selectedItems.filter((id) => productId * 1 != id)], - areAllSelected: this.checkAllSelected(prevState.selectedItems.length - 1, prevState.totalItemCount) - } - : { - selectedItems: [...prevState.selectedItems, productId * 1], - areAllSelected: this.checkAllSelected(prevState.selectedItems.length + 1, prevState.totalItemCount) - }; - }); - }; - } - - selectedItems(e) { - const { value, checked } = e.target; - let { selectedItems } = this.state; - - if (checked) { - selectedItems = [...selectedItems, value]; - } else { - selectedItems = selectedItems.filter((el) => el !== value); - if (this.state.areAllSelected) { - this.setState({ - areAllSelected: !this.state.areAllSelected - }); - } - } - this.setState({ selectedItems }); - } - - splitCheckboxSelectAll(e) { - const { checked } = e.target; - const { isChecked, cardData } = this.state; - let collection = []; - - if (checked) { - for (var i = 0; i <= 9; i++) collection = [...collection, i]; - } - - this.setState( - { - selectedItems: collection, - isChecked: isChecked, - areAllSelected: checked - }, - this.updateSelected - ); - } - - selectPage(e) { - const { checked } = e.target; - const { isChecked, totalItemCount, perPage } = this.state; - let collection = []; - - collection = this.getAllItems(); - - this.setState( - { - selectedItems: collection, - isChecked: checked, - areAllSelected: totalItemCount === perPage ? true : false - }, - this.updateSelected - ); - } - - selectAll(e) { - const { checked } = e.target; - const { isChecked } = this.state; - - let collection = []; - for (var i = 0; i <= 9; i++) collection = [...collection, i]; - - this.setState( - { - selectedItems: collection, - isChecked: true, - areAllSelected: true - }, - this.updateSelected - ); - } - - selectNone(e) { - const { checked } = e.target; - const { isChecked, selectedItems } = this.state; - this.setState( - { - selectedItems: [], - isChecked: false, - areAllSelected: false - }, - this.updateSelected - ); - } - - getAllItems() { - const { cardData } = this.state; - const collection = []; - for (const items of cardData) { - collection.push(items.id); - } - - return collection; - } - - updateSelected() { - const { cardData, selectedItems } = this.state; - let rows = cardData.map((post) => { - post.selected = selectedItems.includes(post.id); - return post; - }); - - this.setState({ - cardData: rows - }); - } - - renderPagination() { - const { page, perPage, totalItemCount, cardData } = this.state; - - const defaultPerPageOptions = [ - { - title: '1', - value: 1 - }, - { - title: '5', - value: 5 - }, - { - title: '10', - value: 10 - } - ]; - - return ( - - ); - } - - buildSelectDropdown() { - const { splitButtonDropdownIsOpen, selectedItems, areAllSelected, filters, cardData } = this.state; - const numSelected = selectedItems.length; - const allSelected = areAllSelected; - const anySelected = numSelected > 0; - const someChecked = anySelected ? null : false; - const isChecked = allSelected ? true : someChecked; - const splitButtonDropdownItems = ( - <> - - Select none (0 items) - - - Select page ({this.state.perPage} items) - - - Select all ({this.state.totalItemCount} items) - - - ); - return ( - this.setState({ splitButtonDropdownIsOpen: isOpen })} - toggle={(toggleRef) => ( - - {numSelected !== 0 && `${numSelected} selected`} - - ] - }} - > - )} - > - {splitButtonDropdownItems} - - ); - } - - buildFilterDropdown() { - const { isLowerToolbarDropdownOpen, filters } = this.state; - - const filterDropdownItems = ( - - - PatternFly - - - ActiveMQ - - - Apache Spark - - - Avro - - - Azure Services - - - Crypto - - - DropBox - - - JBoss Data Grid - - - REST - - - SWAGGER - - - ); - - return ( - - - - ); - } - - render() { - const { - isUpperToolbarDropdownOpen, - isLowerToolbarDropdownOpen, - isUpperToolbarKebabDropdownOpen, - isLowerToolbarKebabDropdownOpen, - isCardKebabDropdownOpen, - splitButtonDropdownIsOpen, - activeItem, - filters, - cardData, - checked, - selectedItems, - areAllSelected, - isChecked, - page, - perPage - } = this.state; - - const toolbarKebabDropdownItems = [ - - Link - , - - Action - , - - Disabled Link - , - - Disabled Action - , - , - - Separated Link - , - - Separated Action - - ]; - - const toolbarItems = ( - - {this.buildSelectDropdown()} - {this.buildFilterDropdown()} - - - - - - - ( - - - - )} - isOpen={isLowerToolbarKebabDropdownOpen} - onOpenChange={(isOpen) => this.setState({ isLowerToolbarKebabDropdownOpen: isOpen })} - > - {toolbarKebabDropdownItems} - - - - - - {this.renderPagination()} - - - ); - - const icons = { - pfIcon, - activeMQIcon, - sparkIcon, - avroIcon, - azureIcon, - saxonIcon, - dropBoxIcon, - infinispanIcon, - restIcon, - swaggerIcon - }; - - const filtered = - filters.products.length > 0 - ? data.filter((card) => { - return filters.products.length === 0 || filters.products.includes(card.name); - }) - : cardData.slice((page - 1) * perPage, perPage === 1 ? page * perPage : page * perPage - 1); - - return ( - - - - - Projects - This is a demo that showcases PatternFly cards. - - - {toolbarItems} - - - - - - - - } - /> - - - - - - - - - {filtered.map((product, key) => ( - this.onKeyDown(e, product.id)} - onClick={() => this.onClick(product.id)} - onSelectableInputChange={() => this.onClick(product.id)} - isSelected={selectedItems.includes(product.id)} - > - - this.setState({ [key]: isOpen })} - toggle={(toggleRef: React.Ref) => ( - this.onCardKebabDropdownToggle(key, e)} - isExpanded={this.state[key]} - > - - - )} - popperProps={{ position: 'right' }} - > - - - - Delete - - - - - - ) - }} - > - {`${product.name} - - {product.name} - {product.description} - - ))} - - - - - - - - ); - } -} +```js file="../examples/Card/CardView.tsx" isFullscreen ``` diff --git a/packages/react-core/src/demos/PrimaryDetail.md b/packages/react-core/src/demos/PrimaryDetail.md index 32be47f1363..fad31736e70 100644 --- a/packages/react-core/src/demos/PrimaryDetail.md +++ b/packages/react-core/src/demos/PrimaryDetail.md @@ -534,10 +534,9 @@ class PrimaryDetailCardView extends React.Component { id={'card-view-' + key} onKeyDown={this.onKeyDown} onClick={this.onCardClick} - onSelectableInputChange={this.onChange} - isSelectableRaised - isSelected={activeCard === 'card-view-' + key} - hasSelectableInput + isClickable + isSelectable + isSelected={activeCard?.charAt(activeCard.length - 1) === key.toString()} > - this.onCheckboxClick(event, product.id)} - value={product.id} - isChecked={selectedItems.includes(product.id)} - aria-label="card checkbox example" - id={`check-${product.id}`} - /> ) }} + selectableActions={{ + isChecked: selectedItems.includes(product.id), + selectableActionAriaLabelledby: `${'card-view-' + key}`, + selectableActionId: `selectable-actions-item-${product.id}`, + name: `check-${product.id}`, + onChange: this.state.onCardClick + }} > {`${product.name} diff --git a/packages/react-core/src/demos/examples/Card/CardView.tsx b/packages/react-core/src/demos/examples/Card/CardView.tsx new file mode 100644 index 00000000000..b2e0b6d0c59 --- /dev/null +++ b/packages/react-core/src/demos/examples/Card/CardView.tsx @@ -0,0 +1,583 @@ +import React from 'react'; +import DashboardWrapper from '@patternfly/react-core/src/demos/examples/DashboardWrapper'; +import { + Badge, + Bullseye, + Button, + Card, + CardHeader, + CardTitle, + CardBody, + Divider, + Dropdown, + DropdownItem, + DropdownList, + EmptyState, + EmptyStateHeader, + EmptyStateIcon, + EmptyStateFooter, + EmptyStateVariant, + EmptyStateActions, + Gallery, + MenuToggle, + MenuToggleCheckbox, + OverflowMenu, + OverflowMenuControl, + OverflowMenuDropdownItem, + OverflowMenuItem, + PageSection, + PageSectionVariants, + Pagination, + TextContent, + Text, + Toolbar, + ToolbarItem, + ToolbarFilter, + ToolbarContent, + Select, + SelectList, + SelectOption, + MenuToggleElement +} from '@patternfly/react-core'; +import { data } from '../../Card/CardData'; +import TrashIcon from '@patternfly/react-icons/dist/esm/icons/trash-icon'; +import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon'; +import pfIcon from './pf-logo-small.svg'; +import activeMQIcon from './../Card/activemq-core_200x150.png'; +import avroIcon from './../camel-avro_200x150.png'; +import dropBoxIcon from './../camel-dropbox_200x150.png'; +import infinispanIcon from './../camel-infinispan_200x150.png'; +import saxonIcon from './../camel-saxon_200x150.png'; +import sparkIcon from './../camel-spark_200x150.png'; +import swaggerIcon from './../camel-swagger-java_200x150.png'; +import azureIcon from './../FuseConnector_Icons_AzureServices.png'; +import restIcon from './../FuseConnector_Icons_REST.png'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; + +export const CardViewBasic: React.FunctionComponent = () => { + const totalItemCount = 10; + + const [cardData, setCardData] = React.useState(data); + const [isChecked, setIsChecked] = React.useState(false); + const [selectedItems, setSelectedItems] = React.useState([]); + const [areAllSelected, setAreAllSelected] = React.useState(false); + const [splitButtonDropdownIsOpen, setSplitButtonDropdownIsOpen] = React.useState(false); + const [isLowerToolbarDropdownOpen, setIsLowerToolbarDropdownOpen] = React.useState(false); + const [isLowerToolbarKebabDropdownOpen, setIsLowerToolbarKebabDropdownOpen] = React.useState(false); + const [page, setPage] = React.useState(1); + const [perPage, setPerPage] = React.useState(10); + const [filters, setFilters] = React.useState>({ products: [] }); + const [state, setState] = React.useState({}); + + interface ProductType { + id: number; + name: string; + icon: string; + description: string; + } + const checkAllSelected = (selected: number, total: number) => { + if (selected && selected < total) { + return null; + } + return selected === total; + }; + + const onToolbarDropdownToggle = () => { + setIsLowerToolbarDropdownOpen(!isLowerToolbarDropdownOpen); + }; + + const onToolbarKebabDropdownToggle = () => { + setIsLowerToolbarKebabDropdownOpen(!isLowerToolbarKebabDropdownOpen); + }; + + const onToolbarKebabDropdownSelect = () => { + setIsLowerToolbarKebabDropdownOpen(!isLowerToolbarKebabDropdownOpen); + }; + + const onCardKebabDropdownToggle = ( + event: React.MouseEvent | React.MouseEvent, + key: string + ) => { + event?.stopPropagation(); + setState({ + [key]: !state[key as keyof Object] + }); + }; + + const deleteItem = (item: ProductType) => { + const filter = (getter) => (val) => getter(val) !== item.id; + + setCardData(cardData.filter(filter(({ id }) => id))); + + setSelectedItems(selectedItems.filter(filter((id) => id))); + }; + + const onSetPage = (_event: any, pageNumber: number) => { + setPage(pageNumber); + }; + + const onPerPageSelect = (_event: any, perPage: number) => { + setPerPage(perPage); + setPage(1); + }; + + const onSplitButtonToggle = () => { + setSplitButtonDropdownIsOpen(!splitButtonDropdownIsOpen); + }; + + const onSplitButtonSelect = () => { + setSplitButtonDropdownIsOpen(false); + }; + + const onNameSelect = (event: any, selection = '') => { + const checked = event.target.checked; + const prevSelections = filters.products; + + setFilters({ + ...filters, + products: checked ? [...prevSelections, selection] : prevSelections.filter((value) => value !== selection) + }); + }; + + const onDelete = (type = '', _id = '') => { + if (type) { + setFilters(filters); + } else { + setFilters({ products: [] }); + } + }; + + const onChange = (event: React.FormEvent) => { + const name = event.currentTarget.name; + const productId = Number(name.charAt(name.length - 1)); + + if (selectedItems.includes(productId * 1)) { + setSelectedItems(selectedItems.filter((id) => productId * 1 !== id)); + + const checkAll = checkAllSelected(selectedItems.length - 1, totalItemCount); + setAreAllSelected(!!checkAll); + } else { + setSelectedItems([...selectedItems, productId * 1]); + const checkAll = checkAllSelected(selectedItems.length + 1, totalItemCount); + setAreAllSelected(!!checkAll); + } + }; + + const updateSelected = () => { + const rows = cardData.map((post) => { + post.selected = selectedItems.includes(post.id); + return post; + }); + + setCardData(rows); + }; + + const getAllItems = () => { + const collection: number[] = []; + for (const items of cardData) { + collection.push(items.id); + } + + return collection; + }; + + const splitCheckboxSelectAll = (e: any) => { + let collection: number[] = []; + + if (e.target.checked) { + for (let i = 0; i <= 9; i++) { + collection = [...collection, i]; + } + } + + setSelectedItems(collection); + setIsChecked(isChecked); + setAreAllSelected(e.target.checked); + + updateSelected(); + }; + + const selectPage = (e: { target: { checked: any } }) => { + const { checked } = e.target; + let collection: number[] = []; + + collection = getAllItems(); + + setSelectedItems(collection); + setIsChecked(checked); + setAreAllSelected(totalItemCount === perPage ? true : false); + + updateSelected(); + }; + + const selectAll = () => { + let collection: number[] = []; + for (let i = 0; i <= 9; i++) { + collection = [...collection, i]; + } + + setSelectedItems(collection); + setIsChecked(true); + setAreAllSelected(true); + + updateSelected(); + }; + + const selectNone = () => { + setSelectedItems([]); + setIsChecked(false); + setAreAllSelected(false); + + updateSelected(); + }; + + const renderPagination = () => { + const defaultPerPageOptions = [ + { + title: '1', + value: 1 + }, + { + title: '5', + value: 5 + }, + { + title: '10', + value: 10 + } + ]; + + return ( + + ); + }; + + const buildSelectDropdown = () => { + const numSelected = selectedItems.length; + const anySelected = numSelected > 0; + const splitButtonDropdownItems = ( + <> + + Select none (0 items) + + + Select page ({perPage} items) + + + Select all ({totalItemCount} items) + + + ); + return ( + setSplitButtonDropdownIsOpen(isOpen)} + toggle={(toggleRef) => ( + splitCheckboxSelectAll(e)} + > + {numSelected !== 0 && `${numSelected} selected`} + + ] + }} + > + )} + > + {splitButtonDropdownItems} + + ); + }; + + const buildFilterDropdown = () => { + const filterDropdownItems = ( + + + PatternFly + + + ActiveMQ + + + Apache Spark + + + Avro + + + Azure Services + + + Crypto + + + DropBox + + + JBoss Data Grid + + + REST + + + SWAGGER + + + ); + + return ( + onDelete(type as string, id as string)} + > + + + ); + }; + + const toolbarKebabDropdownItems = [ + + Link + , + + Action + , + + Disabled Link + , + + Disabled Action + , + , + + Separated Link + , + + Separated Action + + ]; + + const toolbarItems = ( + + {buildSelectDropdown()} + {buildFilterDropdown()} + + + + + + + ( + + + + )} + isOpen={isLowerToolbarKebabDropdownOpen} + onOpenChange={(isOpen) => setIsLowerToolbarDropdownOpen(isOpen)} + > + {toolbarKebabDropdownItems} + + + + + + {renderPagination()} + + + ); + + const icons = { + pfIcon, + activeMQIcon, + sparkIcon, + avroIcon, + azureIcon, + saxonIcon, + dropBoxIcon, + infinispanIcon, + restIcon, + swaggerIcon + }; + + const filtered = + filters.products.length > 0 + ? data.filter((card: { name: string }) => filters.products.length === 0 || filters.products.includes(card.name)) + : cardData.slice((page - 1) * perPage, perPage === 1 ? page * perPage : page * perPage - 1); + + return ( + + + + + Projects + This is a demo that showcases PatternFly cards. + + + {toolbarItems} + + + + + + + + } + /> + + + + + + + + + {filtered.map((product, key) => ( + + + setState({ [key]: isOpen })} + toggle={(toggleRef: React.Ref) => ( + { + onCardKebabDropdownToggle(e, key.toString()); + }} + isExpanded={!!state[key]} + > + + + )} + popperProps={{ position: 'right' }} + > + + { + deleteItem(product); + }} + > + + Delete + + + + + ) + }} + > + {`${product.name} + + {product.name} + {product.description} + + ))} + + + + + + + + ); +};