mirror of
https://github.com/ansible/awx.git
synced 2026-02-01 09:38:10 -03:30
wip - pf4 data list and pagination
This commit is contained in:
226
src/components/DataListToolbar/DataListToolbar.jsx
Normal file
226
src/components/DataListToolbar/DataListToolbar.jsx
Normal file
@@ -0,0 +1,226 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dropdown,
|
||||
DropdownPosition,
|
||||
DropdownToggle,
|
||||
DropdownItem,
|
||||
FormGroup,
|
||||
KebabToggle,
|
||||
Level,
|
||||
LevelItem,
|
||||
TextInput,
|
||||
Toolbar,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
BarsIcon,
|
||||
EqualsIcon,
|
||||
SortAlphaDownIcon,
|
||||
SortAlphaUpIcon,
|
||||
SortNumericDownIcon,
|
||||
SortNumericUpIcon,
|
||||
TrashAltIcon,
|
||||
} from '@patternfly/react-icons';
|
||||
|
||||
import Tooltip from '../Tooltip';
|
||||
|
||||
class DataListToolbar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { sortedColumnKey } = this.props;
|
||||
|
||||
this.state = {
|
||||
isSearchDropdownOpen: false,
|
||||
isSortDropdownOpen: false,
|
||||
searchKey: sortedColumnKey,
|
||||
searchValue: '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSearchInputChange = searchValue => {
|
||||
this.setState({ searchValue });
|
||||
};
|
||||
|
||||
onSortDropdownToggle = isSortDropdownOpen => {
|
||||
this.setState({ isSortDropdownOpen });
|
||||
};
|
||||
|
||||
onSortDropdownSelect = ({ target }) => {
|
||||
const { columns, onSort, sortOrder } = this.props;
|
||||
|
||||
const [{ key }] = columns.filter(({ name }) => name === target.innerText);
|
||||
|
||||
this.setState({ isSortDropdownOpen: false });
|
||||
|
||||
onSort(key, sortOrder);
|
||||
};
|
||||
|
||||
onSearchDropdownToggle = isSearchDropdownOpen => {
|
||||
this.setState({ isSearchDropdownOpen });
|
||||
};
|
||||
|
||||
onSearchDropdownSelect = ({ target }) => {
|
||||
const { columns } = this.props;
|
||||
|
||||
const targetName = target.innerText;
|
||||
const [{ key }] = columns.filter(({ name }) => name === targetName);
|
||||
|
||||
this.setState({ isSearchDropdownOpen: false, searchKey: key });
|
||||
};
|
||||
|
||||
onActionToggle = isActionDropdownOpen => {
|
||||
this.setState({ isActionDropdownOpen });
|
||||
};
|
||||
|
||||
onActionSelect = ({ target }) => {
|
||||
this.setState({ isActionDropdownOpen: false });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { up } = DropdownPosition;
|
||||
const {
|
||||
columns,
|
||||
isAllSelected,
|
||||
onSearch,
|
||||
onSelectAll,
|
||||
onSort,
|
||||
sortedColumnKey,
|
||||
sortOrder,
|
||||
} = this.props;
|
||||
const {
|
||||
isActionDropdownOpen,
|
||||
isSearchDropdownOpen,
|
||||
isSortDropdownOpen,
|
||||
searchKey,
|
||||
searchValue,
|
||||
} = this.state;
|
||||
|
||||
const [searchColumn] = columns
|
||||
.filter(({ key }) => key === searchKey);
|
||||
const searchColumnName = searchColumn.name;
|
||||
|
||||
const [sortedColumn] = columns
|
||||
.filter(({ key }) => key === sortedColumnKey);
|
||||
const sortedColumnName = sortedColumn.name;
|
||||
const isSortNumeric = sortedColumn.isNumeric;
|
||||
|
||||
return (
|
||||
<div className="awx-toolbar">
|
||||
<Level>
|
||||
<LevelItem>
|
||||
<Toolbar style={{ marginLeft: "20px" }}>
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onChange={onSelectAll}
|
||||
aria-label="Select all"
|
||||
id="select-all"/>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
<div className="pf-c-input-group">
|
||||
<Dropdown
|
||||
onToggle={this.onSearchDropdownToggle}
|
||||
onSelect={this.onSearchDropdownSelect}
|
||||
direction={up}
|
||||
isOpen={isSearchDropdownOpen}
|
||||
toggle={(
|
||||
<DropdownToggle
|
||||
onToggle={this.onSearchDropdownToggle}>
|
||||
{ searchColumnName }
|
||||
</DropdownToggle>
|
||||
)}>
|
||||
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
|
||||
<DropdownItem key={key} component="button">
|
||||
{ name }
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
<TextInput
|
||||
type="search"
|
||||
aria-label="search text input"
|
||||
value={searchValue}
|
||||
onChange={this.handleSearchInputChange}/>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label="Search"
|
||||
onClick={() => onSearch(searchValue)}>
|
||||
<i className="fas fa-search" aria-hidden="true"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
<Dropdown
|
||||
onToggle={this.onSortDropdownToggle}
|
||||
onSelect={this.onSortDropdownSelect}
|
||||
direction={up}
|
||||
isOpen={isSortDropdownOpen}
|
||||
toggle={(
|
||||
<DropdownToggle
|
||||
onToggle={this.onSortDropdownToggle}>
|
||||
{ sortedColumnName }
|
||||
</DropdownToggle>
|
||||
)}>
|
||||
{columns
|
||||
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
||||
.map(({ key, name }) => (
|
||||
<DropdownItem key={key} component="button">
|
||||
{ name }
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Button
|
||||
onClick={() => onSort(sortedColumnKey, sortOrder === "ascending" ? "descending" : "ascending")}
|
||||
variant="plain"
|
||||
aria-label="Sort">
|
||||
{
|
||||
isSortNumeric ? (
|
||||
sortOrder === "ascending" ? <SortNumericUpIcon /> : <SortNumericDownIcon />
|
||||
) : (
|
||||
sortOrder === "ascending" ? <SortAlphaUpIcon /> : <SortAlphaDownIcon />
|
||||
)
|
||||
}
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
<ToolbarItem>
|
||||
<Button variant="plain" aria-label="Expand">
|
||||
<BarsIcon />
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem>
|
||||
<Button variant="plain" aria-label="Collapse">
|
||||
<EqualsIcon />
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</LevelItem>
|
||||
<LevelItem>
|
||||
<Tooltip message="Delete" position="top">
|
||||
<Button variant="plain" aria-label="Delete">
|
||||
<TrashAltIcon/>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button variant="primary" aria-label="Add">
|
||||
Add
|
||||
</Button>
|
||||
</LevelItem>
|
||||
</Level>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DataListToolbar;
|
||||
3
src/components/DataListToolbar/index.js
Normal file
3
src/components/DataListToolbar/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import DataListToolbar from './DataListToolbar';
|
||||
|
||||
export default DataListToolbar;
|
||||
89
src/components/DataListToolbar/styles.scss
Normal file
89
src/components/DataListToolbar/styles.scss
Normal file
@@ -0,0 +1,89 @@
|
||||
.awx-toolbar {
|
||||
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||
--awx-toolbar--BorderColor: var(--pf-global--Color--light-200);
|
||||
--awx-toolbar--BorderWidth: var(--pf-global--BorderWidth--sm);
|
||||
|
||||
border: var(--awx-toolbar--BorderWidth) solid var(--awx-toolbar--BorderColor);
|
||||
background-color: var(--awx-toolbar--BackgroundColor);
|
||||
height: 70px;
|
||||
padding-top: 5px;
|
||||
|
||||
--pf-global--target-size--MinHeight: 0px;
|
||||
--pf-global--target-size--MinWidth: 0px;
|
||||
--pf-global--FontSize--md: 14px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-c-button.pf-m-plain {
|
||||
--pf-c-button--m-plain--PaddingLeft: 0px;
|
||||
--pf-c-button--m-plain--PaddingRight: 0px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-l-toolbar__group {
|
||||
--pf-l-toolbar__group--MarginRight: 0px;
|
||||
--pf-l-toolbar__group--MarginLeft: 0px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-l-toolbar__group:after {
|
||||
content: "";
|
||||
background-color: #d7d7d7;
|
||||
width: 1px;
|
||||
height: 30px;
|
||||
display: block;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.awx-toolbar button {
|
||||
height: 30px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-c-input-group {
|
||||
min-height: 0px;
|
||||
height: 30px;
|
||||
|
||||
input {
|
||||
padding: 0px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.pf-m-tertiary {
|
||||
width: 34px;
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-c-dropdown__toggle {
|
||||
min-height: 30px;
|
||||
min-width: 70px;
|
||||
height: 30px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
.pf-c-dropdown__toggle-icon {
|
||||
margin: 0px;
|
||||
padding-top: 3px;
|
||||
padding-left: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-l-toolbar__item + .pf-l-toolbar__item button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-c-button.pf-m-primary {
|
||||
background-color: #5cb85c;
|
||||
min-width: 0px;
|
||||
width: 58px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.awx-toolbar .pf-l-toolbar__item .pf-c-button.pf-m-plain {
|
||||
font-size: 18px;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
class OrganizationCard extends Component {
|
||||
static title = 'Organization Card';
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { name } = props.organization;
|
||||
|
||||
this.state = { name };
|
||||
}
|
||||
|
||||
render () {
|
||||
const { name } = this.state;
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>{name}</CardHeader>
|
||||
<CardBody>Card Body</CardBody>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OrganizationCard;
|
||||
44
src/components/OrganizationListItem.jsx
Normal file
44
src/components/OrganizationListItem.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Badge,
|
||||
Checkbox,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
export default ({ itemId, name, userCount, teamCount, adminCount, isSelected, onSelect }) => (
|
||||
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
|
||||
<div className="pf-c-data-list__check">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
aria-label={`select organization ${itemId}`}
|
||||
id={`select-organization-${itemId}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell">
|
||||
<span id="check-action-item1">
|
||||
<a href={`#/organizations/${itemId}`}>{ name }</a>
|
||||
</span>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell">
|
||||
<a href="#/dashboard"> Users </a>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{userCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
<a href="#/dashboard"> Teams </a>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{teamCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
<a href="#/dashboard"> Admins </a>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{adminCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell" />
|
||||
</li>
|
||||
);
|
||||
220
src/components/Pagination/Pagination.jsx
Normal file
220
src/components/Pagination/Pagination.jsx
Normal file
@@ -0,0 +1,220 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownDirection,
|
||||
DropdownItem,
|
||||
DropdownToggle,
|
||||
Form,
|
||||
FormGroup,
|
||||
Level,
|
||||
LevelItem,
|
||||
TextInput,
|
||||
Toolbar,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
Split,
|
||||
SplitItem,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
class Pagination extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { page } = this.props;
|
||||
|
||||
this.state = { value: page, isOpen: false };
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { page } = this.props;
|
||||
|
||||
if (prevProps.page !== page) {
|
||||
this.setState({ value: page });
|
||||
}
|
||||
}
|
||||
|
||||
onPageChange = value => {
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
onSubmit = event => {
|
||||
const { onSetPage, page, pageCount, page_size } = this.props;
|
||||
const { value } = this.state;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const isPositiveInteger = value >>> 0 === parseFloat(value) && parseInt(value, 10) > 0;
|
||||
const isValid = isPositiveInteger && parseInt(value, 10) <= pageCount;
|
||||
|
||||
if (isValid) {
|
||||
onSetPage(value, page_size);
|
||||
} else{
|
||||
this.setState({ value: page });
|
||||
}
|
||||
};
|
||||
|
||||
onFirst = () => {
|
||||
const { onSetPage, page_size} = this.props;
|
||||
|
||||
onSetPage(1, page_size);
|
||||
};
|
||||
|
||||
onPrevious = () => {
|
||||
const { onSetPage, page, page_size } = this.props;
|
||||
const previousPage = page - 1;
|
||||
|
||||
if (previousPage >= 1) {
|
||||
onSetPage(previousPage, page_size)
|
||||
}
|
||||
};
|
||||
|
||||
onNext = () => {
|
||||
const { onSetPage, page, pageCount, page_size } = this.props;
|
||||
const nextPage = page + 1;
|
||||
|
||||
if (nextPage <= pageCount) {
|
||||
onSetPage(nextPage, page_size)
|
||||
}
|
||||
};
|
||||
|
||||
onLast = () => {
|
||||
const { onSetPage, pageCount, page_size } = this.props;
|
||||
|
||||
onSetPage(pageCount, page_size)
|
||||
};
|
||||
|
||||
onTogglePageSize = isOpen => {
|
||||
this.setState({ isOpen });
|
||||
};
|
||||
|
||||
onSelectPageSize = ({ target }) => {
|
||||
const { onSetPage } = this.props;
|
||||
|
||||
const page = 1;
|
||||
const page_size = parseInt(target.innerText, 10);
|
||||
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
onSetPage(page, page_size);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { up } = DropdownDirection;
|
||||
const {
|
||||
count,
|
||||
page,
|
||||
pageCount,
|
||||
page_size,
|
||||
pageSizeOptions,
|
||||
} = this.props;
|
||||
const { value, isOpen } = this.state;
|
||||
|
||||
const opts = pageSizeOptions.slice().reverse().filter(o => o !== page_size);
|
||||
const isOnFirst = page === 1;
|
||||
const isOnLast = page === pageCount;
|
||||
|
||||
const itemCount = isOnLast ? count % page_size : page_size;
|
||||
const itemMin = ((page - 1) * page_size) + 1;
|
||||
const itemMax = itemMin + itemCount - 1;
|
||||
|
||||
const disabledStyle = {
|
||||
backgroundColor: '#EDEDED',
|
||||
border: '1px solid #C2C2CA',
|
||||
borderRadius: '0px',
|
||||
color: '#C2C2CA',
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="awx-pagination">
|
||||
<Level>
|
||||
<LevelItem>
|
||||
<Dropdown
|
||||
onToggle={this.onTogglePageSize}
|
||||
onSelect={this.onSelectPageSize}
|
||||
direction={up}
|
||||
isOpen={isOpen}
|
||||
toggle={(
|
||||
<DropdownToggle
|
||||
onToggle={this.onTogglePageSize}>
|
||||
{ page_size }
|
||||
</DropdownToggle>
|
||||
)}>
|
||||
{opts.map(option => (
|
||||
<DropdownItem key={option} component="button">
|
||||
{ option }
|
||||
</DropdownItem>
|
||||
))}
|
||||
</Dropdown> Per Page
|
||||
</LevelItem>
|
||||
<LevelItem>
|
||||
<Split gutter="md" className="pf-u-display-flex pf-u-align-items-center">
|
||||
<SplitItem>{itemMin} - {itemMax } of { count }</SplitItem>
|
||||
<SplitItem>
|
||||
<div className="pf-c-input-group">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label="first"
|
||||
style={isOnFirst ? disabledStyle : {}}
|
||||
isDisabled={isOnFirst}
|
||||
onClick={this.onFirst}>
|
||||
<i className="fas fa-angle-double-left"></i>
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label="previous"
|
||||
style={isOnFirst ? disabledStyle : {}}
|
||||
isDisabled={isOnFirst}
|
||||
onClick={this.onPrevious}>
|
||||
<i className="fas fa-angle-left"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</SplitItem>
|
||||
<SplitItem isMain>
|
||||
<form onSubmit={this.onSubmit}>
|
||||
Page <TextInput
|
||||
isDisabled={pageCount === 1}
|
||||
aria-label="Page Number"
|
||||
style={{
|
||||
height: '30px',
|
||||
width: '30px',
|
||||
textAlign: 'center',
|
||||
padding: '0',
|
||||
margin: '0',
|
||||
...(pageCount === 1 ? disabledStyle : {})
|
||||
}}
|
||||
value={value}
|
||||
type="text"
|
||||
onChange={this.onPageChange}
|
||||
/> of { pageCount }
|
||||
</form>
|
||||
</SplitItem>
|
||||
<SplitItem>
|
||||
<div className="pf-c-input-group">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label="next"
|
||||
style={isOnLast ? disabledStyle : {}}
|
||||
isDisabled={isOnLast}
|
||||
onClick={this.onNext}>
|
||||
<i className="fas fa-angle-right"></i>
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary"
|
||||
aria-label="last"
|
||||
style={isOnLast ? disabledStyle : {}}
|
||||
isDisabled={isOnLast}
|
||||
onClick={this.onLast}>
|
||||
<i className="fas fa-angle-double-right"></i>
|
||||
</Button>
|
||||
</div>
|
||||
</SplitItem>
|
||||
</Split>
|
||||
</LevelItem>
|
||||
</Level>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
3
src/components/Pagination/index.js
Normal file
3
src/components/Pagination/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Pagination from './Pagination';
|
||||
|
||||
export default Pagination;
|
||||
39
src/components/Pagination/styles.scss
Normal file
39
src/components/Pagination/styles.scss
Normal file
@@ -0,0 +1,39 @@
|
||||
.awx-pagination {
|
||||
--awx-pagination--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||
--awx-pagination--BorderColor: var(--pf-global--Color--light-200);
|
||||
--awx-pagination--BorderWidth: var(--pf-global--BorderWidth--sm);
|
||||
|
||||
border: var(--awx-pagination--BorderWidth) solid var(--awx-pagination--BorderColor);
|
||||
background-color: var(--awx-pagination--BackgroundColor);
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
padding-top: 20px;
|
||||
height: 70px;
|
||||
|
||||
--pf-global--target-size--MinHeight: 30px;
|
||||
--pf-global--target-size--MinWidth: 30px;
|
||||
--pf-global--FontSize--md: 14px;
|
||||
|
||||
.pf-c-input-group button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.pf-c-dropdown button {
|
||||
width: 55px;
|
||||
height: 30px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
margin: 0px;
|
||||
margin-right: 10px;
|
||||
text-align: left;
|
||||
|
||||
.pf-c-dropdown__toggle-icon {
|
||||
margin: 0px;
|
||||
margin-top: 2px;
|
||||
padding: 0px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
src/components/Tooltip/Tooltip.jsx
Normal file
68
src/components/Tooltip/Tooltip.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
|
||||
class Tooltip extends React.Component {
|
||||
transforms = {
|
||||
top: {
|
||||
bottom: "100%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -25%)"
|
||||
},
|
||||
bottom: {
|
||||
top: "100%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, 25%)"
|
||||
},
|
||||
left: {
|
||||
top: "50%",
|
||||
right: "100%",
|
||||
transform: "translate(-25%, -50%)"
|
||||
},
|
||||
right: {
|
||||
bottom: "100%",
|
||||
left: "50%",
|
||||
transform: "translate(25%, 50%)"
|
||||
},
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isDisplayed: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
children,
|
||||
message,
|
||||
position,
|
||||
} = this.props;
|
||||
const {
|
||||
isDisplayed
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{ position: "relative"}}
|
||||
onMouseLeave={() => this.setState({ isDisplayed: false })}>
|
||||
{ isDisplayed &&
|
||||
<div
|
||||
style={{ position: "absolute", zIndex: "10", ...this.transforms[position] }}
|
||||
className={`pf-c-tooltip pf-m-${position}`}>
|
||||
<div className="pf-c-tooltip__arrow"></div>
|
||||
<div className="pf-c-tooltip__content">
|
||||
{ message }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<span
|
||||
onMouseOver={() => this.setState({ isDisplayed: true })}>
|
||||
{ children }
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
3
src/components/Tooltip/index.js
Normal file
3
src/components/Tooltip/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
export default Tooltip;
|
||||
Reference in New Issue
Block a user