Modify checkboxes so that they can be indeterminate

To add the third state, we create our own checkboxes using font awesome
icons.
This commit is contained in:
René Pfeuffer
2020-06-24 15:20:28 +02:00
parent 149b6cbc2a
commit c5a2d04789
7 changed files with 119 additions and 21 deletions

View File

@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210)) - enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210))
### Changed
- Checkboxes can now be 'indeterminate' ([#1215](https://github.com/scm-manager/scm-manager/pull/1215))
## [2.1.1] - 2020-06-23 ## [2.1.1] - 2020-06-23
### Fixed ### Fixed
- Wait until recommended java installation is available for deb packages ([#1209](https://github.com/scm-manager/scm-manager/pull/1209)) - Wait until recommended java installation is available for deb packages ([#1209](https://github.com/scm-manager/scm-manager/pull/1209))

View File

@@ -30,6 +30,7 @@ type Props = {
name: string; name: string;
color: string; color: string;
className?: string; className?: string;
onClick?: () => void;
}; };
export default class Icon extends React.Component<Props> { export default class Icon extends React.Component<Props> {
@@ -39,12 +40,12 @@ export default class Icon extends React.Component<Props> {
}; };
render() { render() {
const { title, iconStyle, name, color, className } = this.props; const { title, iconStyle, name, color, className, onClick } = this.props;
if (title) { if (title) {
return ( return (
<i title={title} className={classNames(iconStyle, "fa-fw", "fa-" + name, `has-text-${color}`, className)} /> <i onClick={onClick} title={title} className={classNames(iconStyle, "fa-fw", "fa-" + name, `has-text-${color}`, className)} />
); );
} }
return <i className={classNames(iconStyle, "fa-" + name, `has-text-${color}`, className)} />; return <i onClick={onClick} className={classNames(iconStyle, "fa-" + name, `has-text-${color}`, className)} />;
} }
} }

View File

@@ -38182,14 +38182,13 @@ exports[`Storyshots Forms|Checkbox Default 1`] = `
> >
<div <div
className="control" className="control"
onClick={[Function]}
> >
<label <label
className="checkbox" className="checkbox"
> >
<input <i
checked={false} className="is-outlined fa-square has-text-black far"
onChange={[Function]}
type="checkbox"
/> />
Not checked Not checked
@@ -38201,20 +38200,37 @@ exports[`Storyshots Forms|Checkbox Default 1`] = `
> >
<div <div
className="control" className="control"
onClick={[Function]}
> >
<label <label
className="checkbox" className="checkbox"
> >
<input <i
checked={true} className="is-outlined fa-check-square has-text-blue fa"
onChange={[Function]}
type="checkbox"
/> />
Checked Checked
</label> </label>
</div> </div>
</div> </div>
<div
className="field"
>
<div
className="control"
onClick={[Function]}
>
<label
className="checkbox"
>
<i
className="is-outlined fa-minus-square has-text-blue far"
/>
Indeterminate
</label>
</div>
</div>
</div> </div>
`; `;
@@ -38227,16 +38243,14 @@ exports[`Storyshots Forms|Checkbox Disabled 1`] = `
> >
<div <div
className="control" className="control"
onClick={[Function]}
> >
<label <label
className="checkbox" className="checkbox"
disabled={true} disabled={true}
> >
<input <i
checked={true} className="is-outlined fa-check-square has-text-grey-light fa"
disabled={true}
onChange={[Function]}
type="checkbox"
/> />
Checked but disabled Checked but disabled

View File

@@ -35,6 +35,7 @@ storiesOf("Forms|Checkbox", module)
<Spacing> <Spacing>
<Checkbox label="Not checked" checked={false} /> <Checkbox label="Not checked" checked={false} />
<Checkbox label="Checked" checked={true} /> <Checkbox label="Checked" checked={true} />
<Checkbox label="Indeterminate" checked={true} indeterminate={true} />
</Spacing> </Spacing>
)) ))
.add("Disabled", () => ( .add("Disabled", () => (

View File

@@ -24,11 +24,13 @@
import React, { ChangeEvent } from "react"; import React, { ChangeEvent } from "react";
import { Help } from "../index"; import { Help } from "../index";
import LabelWithHelpIcon from "./LabelWithHelpIcon"; import LabelWithHelpIcon from "./LabelWithHelpIcon";
import TriStateCheckbox from "./TriStateCheckbox";
type Props = { type Props = {
label?: string; label?: string;
onChange?: (value: boolean, name?: string) => void; onChange?: (value: boolean, name?: string) => void;
checked: boolean; checked: boolean;
indeterminate?: boolean;
name?: string; name?: string;
title?: string; title?: string;
disabled?: boolean; disabled?: boolean;
@@ -36,9 +38,9 @@ type Props = {
}; };
export default class Checkbox extends React.Component<Props> { export default class Checkbox extends React.Component<Props> {
onCheckboxChange = (event: ChangeEvent<HTMLInputElement>) => { onCheckboxChange = () => {
if (this.props.onChange) { if (this.props.onChange) {
this.props.onChange(event.target.checked, this.props.name); this.props.onChange(!this.props.checked, this.props.name);
} }
}; };
@@ -57,18 +59,19 @@ export default class Checkbox extends React.Component<Props> {
}; };
render() { render() {
const { label, checked, disabled } = this.props; const { label, checked, indeterminate, disabled } = this.props;
return ( return (
<div className="field"> <div className="field">
{this.renderLabelWithHelp()} {this.renderLabelWithHelp()}
<div className="control"> <div className="control" onClick={this.onCheckboxChange}>
{/* {/*
we have to ignore the next line, we have to ignore the next line,
because jsx label does not the custom disabled attribute because jsx label does not the custom disabled attribute
but bulma does. but bulma does.
// @ts-ignore */} // @ts-ignore */}
<label className="checkbox" disabled={disabled}> <label className="checkbox" disabled={disabled}>
<input type="checkbox" checked={checked} onChange={this.onCheckboxChange} disabled={disabled} /> {label} <TriStateCheckbox checked={checked} indeterminate={indeterminate} disabled={disabled} />
{label}
{this.renderHelp()} {this.renderHelp()}
</label> </label>
</div> </div>

View File

@@ -0,0 +1,72 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC } from "react";
import Icon from "../Icon";
type Props = {
onChange?: (value: boolean) => void;
checked: boolean;
indeterminate?: boolean;
disabled?: boolean;
label?: string;
};
const TriStateCheckbox: FC<Props> = ({ onChange, checked, indeterminate, disabled, label }) => {
const onCheckboxChange = () => {
if (onChange) {
console.log("clickediclick");
onChange(!checked);
}
};
let icon;
if (indeterminate) {
icon = "minus-square";
} else if (checked) {
icon = "check-square";
} else {
icon = "square";
}
let className;
if (!checked || indeterminate) {
className = "far";
} else {
className = "fa";
}
let color;
if (disabled) {
color = "grey-light";
} else if (checked || indeterminate) {
color = "blue";
} else {
color = "black";
}
return <><Icon iconStyle={"is-outlined"} name={icon} className={className} color={color} />{" "}
{label}</>;
};
export default TriStateCheckbox;

View File

@@ -160,6 +160,10 @@ $danger-25: scale-color($danger, $lightness: 75%);
color: $blue-light !important; color: $blue-light !important;
} }
.has-text-blue {
color: $blue !important;
}
// border and background colors // border and background colors
.has-background-dark-75 { .has-background-dark-75 {
background-color: $dark-75; background-color: $dark-75;