(core) Introduce tri-state checkboxes

Summary:
 - Takes advantage of native indeterminate state of html checkboxes
 - When an indeterminate checkbox is clicked it turns it into being not checked.

Test Plan: - Added test to projects/UI2018

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2846
This commit is contained in:
Cyprien P 2021-06-01 15:19:44 +02:00
parent 982711dbba
commit 5d3a4b5b5b

View File

@ -16,7 +16,7 @@
*/ */
import { colors } from 'app/client/ui2018/cssVars'; import { colors } from 'app/client/ui2018/cssVars';
import { dom, DomArg, styled } from 'grainjs'; import { Computed, dom, DomArg, styled } from 'grainjs';
import { Observable } from 'grainjs'; import { Observable } from 'grainjs';
export const cssLabel = styled('label', ` export const cssLabel = styled('label', `
@ -50,7 +50,7 @@ export const cssCheckboxSquare = styled('input', `
--radius: 3px; --radius: 3px;
&:checked:enabled { &:checked:enabled, &:indeterminate:enabled {
--color: ${colors.lightGreen}; --color: ${colors.lightGreen};
} }
@ -59,7 +59,8 @@ export const cssCheckboxSquare = styled('input', `
cursor: not-allowed; cursor: not-allowed;
} }
.${cssLabel.className}:hover > &:checked:enabled { .${cssLabel.className}:hover > &:checked:enabled,
.${cssLabel.className}:hover > &:indeterminate:enabled, {
--color: ${colors.darkGreen}; --color: ${colors.darkGreen};
} }
@ -79,11 +80,11 @@ export const cssCheckboxSquare = styled('input', `
border-radius: var(--radius); border-radius: var(--radius);
} }
&:checked::before, &:disabled::before { &:checked::before, &:disabled::before, &:indeterminate::before {
background-color: var(--color); background-color: var(--color);
} }
&:checked::after { &:checked::after, &:indeterminate::after {
content: ''; content: '';
position: absolute; position: absolute;
height: 16px; height: 16px;
@ -95,6 +96,10 @@ export const cssCheckboxSquare = styled('input', `
background-color: ${colors.light}; background-color: ${colors.light};
} }
&:not(:checked):indeterminate::after {
-webkit-mask-image: var(--icon-Minus);
}
&:not(:disabled)::after { &:not(:disabled)::after {
background-color: ${colors.light}; background-color: ${colors.light};
} }
@ -143,3 +148,27 @@ export function labeledSquareCheckbox(obs: Observable<boolean>, label: string, .
export function labeledCircleCheckbox(obs: Observable<boolean>, label: string, ...domArgs: CheckboxArg[]) { export function labeledCircleCheckbox(obs: Observable<boolean>, label: string, ...domArgs: CheckboxArg[]) {
return checkbox(obs, cssCheckboxCircle, label, ...domArgs); return checkbox(obs, cssCheckboxCircle, label, ...domArgs);
} }
export const Indeterminate = 'indeterminate';
export type TriState = boolean|'indeterminate';
function triStateCheckbox(
obs: Observable<TriState>, cssCheckbox: typeof cssCheckboxSquare, label: string = '', ...domArgs: CheckboxArg[]
) {
const checkboxObs = Computed.create(null, obs, (_use, state) => state === true)
.onWrite((checked) => obs.set(checked));
return checkbox(
checkboxObs, cssCheckbox, label,
dom.prop('indeterminate', (use) => use(obs) === 'indeterminate'),
dom.autoDispose(checkboxObs),
...domArgs
);
}
export function triStateSquareCheckbox(obs: Observable<TriState>, ...domArgs: CheckboxArg[]) {
return triStateCheckbox(obs, cssCheckboxSquare, '', ...domArgs);
}
export function labeledTriStateSquareCheckbox(obs: Observable<TriState>, label: string, ...domArgs: CheckboxArg[]) {
return triStateCheckbox(obs, cssCheckboxSquare, label, ...domArgs);
}