-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #294 from maryliag/column-selector
cluster-ui: show database info and ability to choose statement columns
- Loading branch information
Showing
23 changed files
with
673 additions
and
98 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
packages/cluster-ui/src/columnsSelector/columnsSelector.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
@import "../core/index.module"; | ||
|
||
.apply-btn { | ||
&__wrapper { | ||
text-align: end; | ||
} | ||
|
||
&__btn { | ||
height: $line-height--large; | ||
width: 67px; | ||
font-size: $font-size--small; | ||
} | ||
} | ||
|
||
.checkbox { | ||
&__input { | ||
margin-right: 5px; | ||
} | ||
&__label { | ||
cursor: pointer; | ||
} | ||
} | ||
|
||
.dropdown { | ||
position: relative; | ||
&__indicator { | ||
color: $colors--neutral-11; | ||
height: $line-height--medium; | ||
width: 32px; | ||
} | ||
} | ||
|
||
.float { | ||
float: left; | ||
margin-right: 7px; | ||
} | ||
|
||
.label { | ||
height: 16px; | ||
font-family: $font-family--base; | ||
font-weight: $font-weight--bold; | ||
font-size: $font-size--small; | ||
line-height: $line-height--small; | ||
letter-spacing: 0.3px; | ||
font-style: normal; | ||
margin-bottom: 8px; | ||
margin-left: 10px; | ||
} | ||
|
||
.menu { | ||
background-color: white; | ||
border-radius: 4px; | ||
box-shadow: 0px 0px 4px rgba(154, 161, 171, 33%); | ||
margin-top: 8px; | ||
position: absolute; | ||
z-index: 2; | ||
} |
253 changes: 253 additions & 0 deletions
253
packages/cluster-ui/src/columnsSelector/columnsSelector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
import Select, { components, OptionsType } from "react-select"; | ||
import React from "react"; | ||
import classNames from "classnames/bind"; | ||
import styles from "./columnsSelector.module.scss"; | ||
import { Button } from "../button"; | ||
import { | ||
dropdown, | ||
dropdownContentWrapper, | ||
hidden, | ||
} from "../queryFilter/filterClasses"; | ||
import { List } from "@cockroachlabs/icons"; | ||
import { | ||
DeselectOptionActionMeta, | ||
SelectOptionActionMeta, | ||
} from "react-select/src/types"; | ||
|
||
const cx = classNames.bind(styles); | ||
|
||
export interface SelectOption { | ||
label: string; | ||
value: string; | ||
isSelected: boolean; | ||
} | ||
|
||
export interface ColumnsSelectorProps { | ||
// options provides the list of available columns and their initial selection state | ||
options: SelectOption[]; | ||
onSubmitColumns: (selectedColumns: string[]) => void; | ||
} | ||
|
||
export interface ColumnsSelectorState { | ||
hide: boolean; | ||
selectionState: Map<string, boolean>; | ||
} | ||
|
||
/** | ||
* Create all options items using the values from options | ||
* on ColumnsSelector() | ||
* The options must have the parameters label and isSelected | ||
* @param props | ||
* @constructor | ||
*/ | ||
const CheckboxOption = (props: any) => { | ||
return ( | ||
<components.Option {...props}> | ||
<input | ||
type="checkbox" | ||
className={cx("checkbox__input")} | ||
checked={props.isSelected} | ||
onChange={() => null} | ||
/> | ||
<label className={cx("checkbox__label")}>{props.label}</label> | ||
</components.Option> | ||
); | ||
}; | ||
|
||
// customStyles uses the default styles provided from the | ||
// react-select component and add changes | ||
const customStyles = { | ||
container: (provided: any) => ({ | ||
...provided, | ||
border: "none", | ||
height: "fit-content", | ||
}), | ||
control: (provided: any) => ({ | ||
...provided, | ||
display: "none", | ||
}), | ||
menu: (provided: any) => ({ | ||
...provided, | ||
position: "relative", | ||
boxShadow: "none", | ||
}), | ||
option: (provided: any, state: any) => ({ | ||
...provided, | ||
backgroundColor: "white", | ||
color: "#394455", | ||
cursor: "pointer", | ||
padding: "4px 10px", | ||
}), | ||
multiValue: (provided: any) => ({ | ||
...provided, | ||
backgroundColor: "#E7ECF3", | ||
borderRadius: "3px", | ||
}), | ||
}; | ||
|
||
/** | ||
* Creates the ColumnsSelector from the props | ||
* @param props: | ||
* options (SelectOption[]): a list of options. Each option object must contain a | ||
* label, value and isSelected parameters | ||
* onSubmitColumns (callback function): receives the selected string | ||
* @constructor | ||
*/ | ||
export default class ColumnsSelector extends React.Component< | ||
ColumnsSelectorProps, | ||
ColumnsSelectorState | ||
> { | ||
constructor(props: ColumnsSelectorProps) { | ||
super(props); | ||
const allSelected = props.options.every(o => o.isSelected); | ||
// set initial state of selections based on props | ||
const selectionState = new Map( | ||
props.options.map(o => [o.value, allSelected || o.isSelected]), | ||
); | ||
selectionState.set("all", allSelected); | ||
this.state = { | ||
hide: true, | ||
selectionState, | ||
}; | ||
} | ||
dropdownRef: React.RefObject<HTMLDivElement> = React.createRef(); | ||
|
||
componentDidMount() { | ||
window.addEventListener("click", this.outsideClick, false); | ||
} | ||
componentWillUnmount() { | ||
window.removeEventListener("click", this.outsideClick, false); | ||
} | ||
|
||
toggleOpen = () => { | ||
this.setState({ | ||
hide: !this.state.hide, | ||
}); | ||
}; | ||
outsideClick = () => { | ||
this.setState({ hide: true }); | ||
}; | ||
insideClick = (event: any) => { | ||
event.stopPropagation(); | ||
}; | ||
|
||
handleChange = ( | ||
_selectedOptions: OptionsType<SelectOption>, | ||
// get actual selection of specific option and action type from "actionMeta" | ||
actionMeta: | ||
| SelectOptionActionMeta<SelectOption> | ||
| DeselectOptionActionMeta<SelectOption>, | ||
) => { | ||
const { option, action } = actionMeta; | ||
const selectionState = new Map(this.state.selectionState); | ||
// true - if option was selected, false - otherwise | ||
const isSelectedOption = action === "select-option"; | ||
|
||
// if "all" option was toggled - update all other options | ||
if (option.value === "all") { | ||
selectionState.forEach((_v, k) => | ||
selectionState.set(k, isSelectedOption), | ||
); | ||
} else { | ||
// check if all other options (except current changed and "all" options) are selected as well to select "all" option | ||
const allOtherOptionsSelected = [...selectionState.entries()] | ||
.filter(([k, _v]) => ![option.value, "all"].includes(k)) // filter all options except currently changed and "all" option | ||
.every(([_k, v]) => v); | ||
|
||
// update "all" option if other options are selected | ||
if (allOtherOptionsSelected) { | ||
selectionState.set("all", isSelectedOption); | ||
} | ||
|
||
selectionState.set(option.value, isSelectedOption); | ||
} | ||
this.setState({ | ||
selectionState, | ||
}); | ||
}; | ||
|
||
handleSubmit = () => { | ||
const { selectionState } = this.state; | ||
const selectedValues = this.props.options | ||
.filter(o => selectionState.get(o.value)) | ||
.filter(o => o.value !== "all") // do not include artificial option "all". It should live only inside this component. | ||
.map(o => o.value); | ||
this.props.onSubmitColumns(selectedValues); | ||
this.setState({ hide: true }); | ||
}; | ||
|
||
isAllSelected = (): boolean => { | ||
return this.state.selectionState.get("all"); | ||
}; | ||
|
||
// getOptions returns list of all options with updated selection states | ||
// and prepends "all" option as an artificial option | ||
getOptions = (): SelectOption[] => { | ||
const { options } = this.props; | ||
const { selectionState } = this.state; | ||
const isAllSelected = this.isAllSelected(); | ||
const allOption: SelectOption = { | ||
label: "All", | ||
value: "all", | ||
isSelected: isAllSelected, | ||
}; | ||
return [allOption, ...options].map(o => { | ||
let isSelected = o.isSelected; // default value; | ||
if (isAllSelected) { | ||
isSelected = true; | ||
} else if (selectionState.has(o.value)) { | ||
isSelected = selectionState.get(o.value); | ||
} | ||
return { | ||
...o, | ||
// if "all" is selected then every item in the list selected as well | ||
isSelected, | ||
}; | ||
}); | ||
}; | ||
|
||
render() { | ||
const { hide } = this.state; | ||
const dropdownArea = hide ? hidden : dropdown; | ||
const options = this.getOptions(); | ||
const columnsSelected = options.filter(o => o.isSelected); | ||
|
||
return ( | ||
<div | ||
onClick={this.insideClick} | ||
ref={this.dropdownRef} | ||
className={cx("float")} | ||
> | ||
<Button type="secondary" size="small" onClick={this.toggleOpen}> | ||
<List /> | ||
</Button> | ||
<div className={dropdownArea}> | ||
<div className={dropdownContentWrapper}> | ||
<div className={cx("label")}>Hide/show columns</div> | ||
<Select | ||
isMulti | ||
menuIsOpen={true} | ||
options={options} | ||
value={columnsSelected} | ||
onChange={this.handleChange} | ||
hideSelectedOptions={false} | ||
closeMenuOnSelect={false} | ||
components={{ Option: CheckboxOption }} | ||
styles={customStyles} | ||
controlShouldRenderValue={false} | ||
/> | ||
<div className={cx("apply-btn__wrapper")}> | ||
<Button | ||
className={cx("apply-btn__btn")} | ||
textAlign="center" | ||
onClick={this.handleSubmit} | ||
> | ||
Apply | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.