passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
330 lines (305 loc) • 11.4 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) 2023 Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) 2023 Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 4.2.0
*/
import React, { Component } from "react";
import PropTypes from "prop-types";
import CellHeaderDefault from "./CellHeaderDefault";
import { withTable } from "./Context/TableContext";
import ColumnModel from "../../models/column/ColumnModel";
import ArrowDownSVG from "../../../img/svg/arrow_down.svg";
import ArrowUpSVG from "../../../img/svg/arrow_up.svg";
/**
* This component represents a table cell header wrapper
*/
class CellHeaderWrapper extends Component {
/**
* Default constructor
* @param props Component props
*/
constructor(props) {
super(props);
this.state = this.defaultState;
this.bindCallbacks();
this.createRefs();
}
/**
* Returns the component default state
*/
get defaultState() {
return {
mouseXPosition: 0,
move: 0,
columnToResizeWidth: 0,
resizing: false,
};
}
/**
* Bind callbacks methods
* @return {void}
*/
bindCallbacks() {
this.handleSortByColumnClick = this.handleSortByColumnClick.bind(this);
this.handleReorderColumnMouseMoveEvent = this.handleReorderColumnMouseMoveEvent.bind(this);
this.handleReorderColumnMouseUpEvent = this.handleReorderColumnMouseUpEvent.bind(this);
this.handleResizeColumnMouseDown = this.handleResizeColumnMouseDown.bind(this);
this.handleResizeColumnMouseMoveEvent = this.handleResizeColumnMouseMoveEvent.bind(this);
this.handleResizeColumnMouseUpEvent = this.handleResizeColumnMouseUpEvent.bind(this);
this.handleResizeDefaultByColumnDoubleClick = this.handleResizeDefaultByColumnDoubleClick.bind(this);
this.startDragging = this.startDragging.bind(this);
}
/**
* Create DOM nodes or React elements references in order to be able to access them programmatically.
*/
createRefs() {
this.columnRef = React.createRef();
}
/**
* Handle the drag start on the column
* @param event The DOM event
* @returns {Promise<void>}
*/
handleReorderColumnMouseDownEvent(event) {
// Get the current mouse position
const mouseXPosition = event.clientX;
this.setState({ mouseXPosition });
// Get width of the previous and next column
this.updatePreviousAndNextColumnWidth(
this.columnRef.current.previousSibling?.offsetWidth,
this.columnRef.current.nextSibling?.offsetWidth,
);
// Add listener to handle the first move
this.columnRef.current.addEventListener("mousemove", this.startDragging, { once: true });
// Add listener to handle mouse move event on document
document.addEventListener("mousemove", this.handleReorderColumnMouseMoveEvent, { capture: true });
document.addEventListener("mouseup", this.handleReorderColumnMouseUpEvent, { capture: true, once: true });
}
/**
* Start dragging
*/
startDragging() {
// Call props to inform the column is dragging
this.props.tableContext.onStartDraggingColumn();
}
/**
* Handle the reorder column mouse move event
* @param event The DOM event
*/
handleReorderColumnMouseMoveEvent(event) {
// Determine how far the mouse has been moved and the move from the beginning
const dx = event.clientX - this.state.mouseXPosition - this.state.move;
// Check if the column can move
const canMoveColumn = this.props.tableContext.canMoveColumn(this.props.index, dx);
if (canMoveColumn) {
this.columnRef.current.style.translate = `${dx}px`;
if (this.previousColumnWidth && dx < -this.previousColumnWidth / 2) {
// Move the column to the previous position
this.props.tableContext.onReorderColumns(this.props.index, this.props.index - 1);
this.updateColumnPosition(-this.previousColumnWidth, dx);
this.updatePreviousAndNextColumnWidth(
this.columnRef.current.previousSibling?.offsetWidth,
this.previousColumnWidth,
);
} else if (this.nextColumnWidth && dx > this.nextColumnWidth / 2) {
// Move the column to the next position
this.props.tableContext.onReorderColumns(this.props.index, this.props.index + 1);
this.updateColumnPosition(this.nextColumnWidth, dx);
this.updatePreviousAndNextColumnWidth(this.nextColumnWidth, this.columnRef.current.nextSibling?.offsetWidth);
}
} else {
// The column can not be moved
this.columnRef.current.style.translate = "0px";
}
}
/**
* Handle the reorder column mouse up event
*/
handleReorderColumnMouseUpEvent() {
// Reset all value to null
this.columnRef.current.style.translate = null;
this.updatePreviousAndNextColumnWidth(null, null);
// Remove listener on mouse move
this.columnRef.current.removeEventListener("mousemove", this.startDragging);
document.removeEventListener("mousemove", this.handleReorderColumnMouseMoveEvent, { capture: true });
// Call props to inform of the column is not dragging anymore
this.props.tableContext.onEndDraggingColumn();
this.setState({ mouseXPosition: 0, move: 0 });
this.handleChangeColumn();
}
/**
* Handle change column
*/
handleChangeColumn() {
this.props.tableContext.onChangeColumns();
}
/**
* Handle the change of sorter ( on property or direction )
* @param event
*/
handleSortByColumnClick(event) {
event.stopPropagation();
this.props.tableContext.onSortChange(this.column.field);
}
/**
* Update the column position
* @param columnPermutedWidth
* @param dx
*/
updateColumnPosition(columnPermutedWidth, dx) {
const move = this.state.move + columnPermutedWidth;
this.columnRef.current.style.translate = `${dx - columnPermutedWidth}px`;
this.setState({ move });
}
/**
* Update the width of the previous and next column
* @param previousColumnWidth
* @param nextColumnWidth
*/
updatePreviousAndNextColumnWidth(previousColumnWidth, nextColumnWidth) {
this.previousColumnWidth = previousColumnWidth;
this.nextColumnWidth = nextColumnWidth;
}
/**
* Handle Mouse down event to prepare the resize
* @param event
*/
async handleResizeColumnMouseDown(event) {
// Stop propagation to not fire parents events
event.stopPropagation();
// Get the actual width of the column to resize
const columnToResizeWidth = this.column.width;
// Get the current mouse position
const mouseXPosition = event.clientX;
this.setState({ resizing: true, mouseXPosition, columnToResizeWidth });
// Add listener to handle mouse move event on document
document.addEventListener("mousemove", this.handleResizeColumnMouseMoveEvent, { capture: true });
// Add once listener to handle mouse up event on document
document.addEventListener("mouseup", this.handleResizeColumnMouseUpEvent, { capture: true, once: true });
}
/**
* Handle Mouse move on document to resize a column
* @param event
*/
handleResizeColumnMouseMoveEvent(event) {
// Determine how far the mouse has been moved
const dx = event.clientX - this.state.mouseXPosition;
// Update the width of column
const width = this.state.columnToResizeWidth + dx > 50 ? this.state.columnToResizeWidth + dx : 50;
// Update the width
this.props.tableContext.onResizeColumn(this.props.index, width);
}
/**
* Handle Mouse up on document to end the resize
*/
handleResizeColumnMouseUpEvent() {
// Remove listener on mouse move
document.removeEventListener("mousemove", this.handleResizeColumnMouseMoveEvent, { capture: true });
this.setState({ resizing: null, mouseXPosition: 0, columnToResizeWidth: 0 });
this.handleChangeColumn();
}
/**
* Handle resize default by column double click
*/
handleResizeDefaultByColumnDoubleClick() {
this.props.tableContext.onResizeColumn(this.props.index, this.column.defaultWidth);
this.handleChangeColumn();
}
/**
* Check if the grid is sorted for a given column
* @param columnName The column name
*/
isSortedColumn(columnName) {
return this.props.tableContext.isSortedColumn(columnName);
}
/**
* Check if the sort is ascendant.
* @returns {boolean}
*/
isSortedAsc() {
return this.props.tableContext.isSortedAsc();
}
/**
* Get column
* @return {Object}
*/
get column() {
return this.props.column;
}
/**
* Get props for custom cell
* @return {object}
*/
get propsCellHeader() {
const props = this.column.headerCellRenderer?.props || {};
// For column checkbox add a checked props value else use the one already defined or add the label
if (this.column.id === "checkbox") {
props.checked = this.props.tableContext.isSelectAllChecked();
} else {
props.label = props.label || this.column.label;
}
return props;
}
/**
* Get the column width style by column name
* @return {{width: string} | null}
*/
get columnWidthStyle() {
// Get the column width
return this.column?.width ? { width: `${this.column.width}px` } : null;
}
/**
* Render the component
* @return {JSX}
*/
render() {
const CellHeader = this.column.headerCellRenderer?.component || CellHeaderDefault;
return (
<th
key={this.column.id}
className={`cell-${this.column.id} selections ${this.column.draggable ? "draggable" : ""} ${this.column.sortable ? "sortable" : ""}`}
style={this.columnWidthStyle}
ref={this.columnRef}
onMouseDown={(event) => (this.column.draggable ? this.handleReorderColumnMouseDownEvent(event) : undefined)}
>
{!this.column.sortable && (
<div className="cell-header">
<CellHeader {...this.propsCellHeader} />
</div>
)}
{this.column.sortable && (
<button className="no-border" type="button" onClick={this.handleSortByColumnClick}>
<div className="cell-header">
<CellHeader {...this.propsCellHeader} />
<span className="cell-header-icon-sort">
{this.isSortedColumn(this.column.field) && this.isSortedAsc() && <ArrowDownSVG />}
{this.isSortedColumn(this.column.field) && !this.isSortedAsc() && <ArrowUpSVG />}
</span>
</div>
</button>
)}
{this.column.resizable && (
<div
className={`resizer ${this.state.resizing ? "resizing" : ""}`}
onMouseDown={this.handleResizeColumnMouseDown}
onDoubleClick={this.handleResizeDefaultByColumnDoubleClick}
></div>
)}
</th>
);
}
}
CellHeaderWrapper.propTypes = {
tableContext: PropTypes.any, // The table context
column: PropTypes.instanceOf(ColumnModel).isRequired, // The columns to display
index: PropTypes.number.isRequired, // The index of the column
};
export default withTable(CellHeaderWrapper);