Checkbox and Chipinput have been improved, and a button has been added

Pushed-by: Konstantin Schaper<konstantin.schaper@cloudogu.com>
Pushed-by: Tarik Gürsoy<tarik.guersoy@cloudogu.com>
Co-authored-by: Konstantin Schaper<konstantin.schaper@cloudogu.com>
Co-authored-by: Tarik Gürsoy<tarik.guersoy@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2023-10-09 11:56:24 +02:00
parent 2ca68c43b3
commit 37fbce1496
8 changed files with 125 additions and 49 deletions

View File

@@ -0,0 +1,4 @@
- type: changed
description: The checkbox now has a bigger click target
- type: changed
description: The chip input api now provides an external add button

View File

@@ -2478,13 +2478,13 @@ exports[`Storyshots Forms/Checkbox Default 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_11" aria-describedby="checkbox_11"
aria-labelledby="checkbox_10" aria-labelledby="checkbox_10"
checked={false} checked={false}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2501,13 +2501,13 @@ exports[`Storyshots Forms/Checkbox Default 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_13" aria-describedby="checkbox_13"
aria-labelledby="checkbox_12" aria-labelledby="checkbox_12"
checked={true} checked={true}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2524,13 +2524,13 @@ exports[`Storyshots Forms/Checkbox Default 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_15" aria-describedby="checkbox_15"
aria-labelledby="checkbox_14" aria-labelledby="checkbox_14"
checked={true} checked={true}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2554,14 +2554,14 @@ exports[`Storyshots Forms/Checkbox Disabled 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
disabled={true} disabled={true}
> >
<input <input
aria-describedby="checkbox_17" aria-describedby="checkbox_17"
aria-labelledby="checkbox_16" aria-labelledby="checkbox_16"
checked={true} checked={true}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
disabled={true} disabled={true}
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -2584,13 +2584,13 @@ Array [
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_25" aria-describedby="checkbox_25"
aria-labelledby="checkbox_24" aria-labelledby="checkbox_24"
checked={false} checked={false}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2618,12 +2618,12 @@ exports[`Storyshots Forms/Checkbox ReactHookForm 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_27" aria-describedby="checkbox_27"
aria-labelledby="checkbox_26" aria-labelledby="checkbox_26"
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
name="rememberMe" name="rememberMe"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -2641,12 +2641,12 @@ exports[`Storyshots Forms/Checkbox ReactHookForm 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_29" aria-describedby="checkbox_29"
aria-labelledby="checkbox_28" aria-labelledby="checkbox_28"
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
name="scramblePassword" name="scramblePassword"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -2664,13 +2664,13 @@ exports[`Storyshots Forms/Checkbox ReactHookForm 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
disabled={true} disabled={true}
> >
<input <input
aria-describedby="checkbox_31" aria-describedby="checkbox_31"
aria-labelledby="checkbox_30" aria-labelledby="checkbox_30"
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
disabled={true} disabled={true}
name="disabled" name="disabled"
onBlur={[Function]} onBlur={[Function]}
@@ -2690,12 +2690,12 @@ exports[`Storyshots Forms/Checkbox ReactHookForm 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_33" aria-describedby="checkbox_33"
aria-labelledby="checkbox_32" aria-labelledby="checkbox_32"
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
name="readonly" name="readonly"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
@@ -2735,13 +2735,13 @@ Array [
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_23" aria-describedby="checkbox_23"
aria-labelledby="checkbox_22" aria-labelledby="checkbox_22"
checked={false} checked={false}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2776,13 +2776,13 @@ exports[`Storyshots Forms/Checkbox With HelpText 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_19" aria-describedby="checkbox_19"
aria-labelledby="checkbox_18" aria-labelledby="checkbox_18"
checked={false} checked={false}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -2811,13 +2811,13 @@ exports[`Storyshots Forms/Checkbox With HelpText 1`] = `
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_21" aria-describedby="checkbox_21"
aria-labelledby="checkbox_20" aria-labelledby="checkbox_20"
checked={true} checked={true}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"
@@ -18413,13 +18413,13 @@ Array [
className="control" className="control"
> >
<label <label
className="checkbox" className="Checkbox__StyledLabel-sc-r42kwe-1 ZZdrN checkbox is-align-items-center"
> >
<input <input
aria-describedby="checkbox_130" aria-describedby="checkbox_130"
aria-labelledby="checkbox_129" aria-labelledby="checkbox_129"
checked={true} checked={true}
className="checkbox" className="Checkbox__StyledInput-sc-r42kwe-0 kCgKZT m-3"
onBlur={[Function]} onBlur={[Function]}
onChange={[Function]} onChange={[Function]}
type="checkbox" type="checkbox"

View File

@@ -28,6 +28,17 @@ import useInnerRef from "./useInnerRef";
import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes"; import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
import classNames from "classnames"; import classNames from "classnames";
import { createA11yId } from "../createA11yId"; import { createA11yId } from "../createA11yId";
import styled from "styled-components";
const StyledInput = styled.input`
height: 1rem;
width: 1rem;
`;
const StyledLabel = styled.label`
margin-left: -0.75rem;
display: inline-flex;
`;
export interface CheckboxElement extends HTMLElement { export interface CheckboxElement extends HTMLElement {
value: boolean; value: boolean;
@@ -109,11 +120,11 @@ const InnerCheckbox: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({
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}> <StyledLabel className="checkbox is-align-items-center" disabled={disabled}>
<input <StyledInput
type="checkbox" type="checkbox"
name={name} name={name}
className={classNames("checkbox", className)} className={classNames("m-3", className)}
onChange={handleChange} onChange={handleChange}
onBlur={handleBlur} onBlur={handleBlur}
ref={field} ref={field}
@@ -126,7 +137,7 @@ const InnerCheckbox: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({
/>{" "} />{" "}
{label} {label}
{renderHelp()} {renderHelp()}
</label> </StyledLabel>
</div> </div>
</fieldset> </fieldset>
); );

View File

@@ -25,20 +25,52 @@
import React, { InputHTMLAttributes } from "react"; import React, { InputHTMLAttributes } from "react";
import { createAttributesForTesting } from "@scm-manager/ui-components"; import { createAttributesForTesting } from "@scm-manager/ui-components";
import Help from "../base/help/Help"; import Help from "../base/help/Help";
import styled from "styled-components";
import classNames from "classnames";
const StyledInput = styled.input`
height: 1rem;
width: 1rem;
`;
const StyledLabel = styled.label`
margin-left: -0.75rem;
display: inline-flex;
`;
type InputFieldProps = { type InputFieldProps = {
label: string; label: string;
helpText?: string; helpText?: string;
testId?: string; testId?: string;
labelClassName?: string;
} & Omit<InputHTMLAttributes<HTMLInputElement>, "type">; } & Omit<InputHTMLAttributes<HTMLInputElement>, "type">;
/** /**
* @see https://bulma.io/documentation/form/checkbox/ * @see https://bulma.io/documentation/form/checkbox/
*/ */
const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>( const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
({ readOnly, label, value, name, checked, defaultChecked, defaultValue, testId, helpText, ...props }, ref) => ( (
// @ts-ignore bulma uses the disabled attribute on labels, although it is not part of the html spec {
<label className="checkbox" disabled={readOnly || props.disabled}> readOnly,
label,
className,
labelClassName,
value,
name,
checked,
defaultChecked,
defaultValue,
testId,
helpText,
...props
},
ref
) => (
<StyledLabel
className={classNames("checkbox is-align-items-center", labelClassName)}
// @ts-ignore bulma uses the disabled attribute on labels, although it is not part of the html spec
disabled={readOnly || props.disabled}
>
{readOnly ? ( {readOnly ? (
<> <>
<input <input
@@ -50,9 +82,9 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
defaultChecked={defaultChecked} defaultChecked={defaultChecked}
readOnly readOnly
/> />
<input <StyledInput
type="checkbox" type="checkbox"
className="mr-1" className={classNames("m-3", className)}
ref={ref} ref={ref}
value={value} value={value}
defaultValue={defaultValue} defaultValue={defaultValue}
@@ -64,9 +96,9 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
/> />
</> </>
) : ( ) : (
<input <StyledInput
type="checkbox" type="checkbox"
className="mr-1" className={classNames("m-3", className)}
ref={ref} ref={ref}
name={name} name={name}
value={value} value={value}
@@ -77,9 +109,10 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
{...createAttributesForTesting(testId)} {...createAttributesForTesting(testId)}
/> />
)} )}
{label} {label}
{helpText ? <Help className="ml-1" text={helpText} /> : null} {helpText ? <Help className="ml-1" text={helpText} /> : null}
</label> </StyledLabel>
) )
); );
export default Checkbox; export default Checkbox;

View File

@@ -23,22 +23,28 @@
*/ */
import { storiesOf } from "@storybook/react"; import { storiesOf } from "@storybook/react";
import React, { useState } from "react"; import React, { useRef, useState } from "react";
import ChipInputField from "./ChipInputField"; import ChipInputField from "./ChipInputField";
import Combobox from "../combobox/Combobox"; import Combobox from "../combobox/Combobox";
import { Option } from "@scm-manager/ui-types"; import { Option } from "@scm-manager/ui-types";
import ChipInput from "../headless-chip-input/ChipInput";
storiesOf("Chip Input Field", module) storiesOf("Chip Input Field", module)
.add("Default", () => { .add("Default", () => {
const [value, setValue] = useState<Option<string>[]>([]); const [value, setValue] = useState<Option<string>[]>([]);
const ref = useRef<HTMLInputElement>(null);
return ( return (
<ChipInputField <>
value={value} <ChipInputField
onChange={setValue} value={value}
label="Test Chips" onChange={setValue}
placeholder="Type a new chip name and press enter to add" label="Test Chips"
aria-label="My personal chip list" placeholder="Type a new chip name and press enter to add"
/> aria-label="My personal chip list"
ref={ref}
/>
<ChipInput.AddButton inputRef={ref}>Add</ChipInput.AddButton>
</>
); );
}) })
.add("With Autocomplete", () => { .add("With Autocomplete", () => {

View File

@@ -22,7 +22,7 @@
* SOFTWARE. * SOFTWARE.
*/ */
import React, { KeyboardEventHandler, ReactElement, useCallback } from "react"; import React, { KeyboardEventHandler, PropsWithRef, ReactElement, Ref, RefObject, useCallback } from "react";
import { createAttributesForTesting, useGeneratedId } from "@scm-manager/ui-components"; import { createAttributesForTesting, useGeneratedId } from "@scm-manager/ui-components";
import Field from "../base/Field"; import Field from "../base/Field";
import Label from "../base/label/Label"; import Label from "../base/label/Label";
@@ -81,6 +81,7 @@ type InputFieldProps<T> = {
className?: string; className?: string;
isLoading?: boolean; isLoading?: boolean;
isNewItemDuplicate?: (existingItem: Option<T>, newItem: Option<T>) => boolean; isNewItemDuplicate?: (existingItem: Option<T>, newItem: Option<T>) => boolean;
ref?: Ref<HTMLInputElement>;
}; };
/** /**
@@ -105,7 +106,7 @@ const ChipInputField = function ChipInputField<T>(
isLoading, isLoading,
isNewItemDuplicate, isNewItemDuplicate,
...props ...props
}: InputFieldProps<T>, }: PropsWithRef<InputFieldProps<T>>,
ref: React.ForwardedRef<HTMLInputElement> ref: React.ForwardedRef<HTMLInputElement>
) { ) {
const [t] = useTranslation("commons", { keyPrefix: "form.chipList" }); const [t] = useTranslation("commons", { keyPrefix: "form.chipList" });

View File

@@ -24,6 +24,7 @@
import React, { import React, {
ButtonHTMLAttributes, ButtonHTMLAttributes,
ComponentProps,
ComponentType, ComponentType,
Context, Context,
createContext, createContext,
@@ -40,6 +41,7 @@ import React, {
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot";
import { Option } from "@scm-manager/ui-types"; import { Option } from "@scm-manager/ui-types";
import { mergeRefs, withForwardRef } from "../helpers"; import { mergeRefs, withForwardRef } from "../helpers";
import { Button } from "@scm-manager/ui-buttons";
type ChipInputContextType<T> = { type ChipInputContextType<T> = {
add(newValue: Option<T>): void; add(newValue: Option<T>): void;
@@ -215,8 +217,21 @@ const ChipInput = withForwardRef(function ChipInput<T>(
); );
}); });
const AddButton = React.forwardRef<
HTMLButtonElement,
Omit<ComponentProps<typeof Button>, "onClick"> & { inputRef: RefObject<HTMLInputElement | null> }
>(({ inputRef, children, ...props }, ref) => (
<Button
{...props}
onClick={() => inputRef.current?.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }))}
>
{children}
</Button>
));
export default Object.assign(ChipInput, { export default Object.assign(ChipInput, {
Chip: Object.assign(Chip, { Chip: Object.assign(Chip, {
Delete: ChipDelete, Delete: ChipDelete,
}), }),
AddButton,
}); });

View File

@@ -36,17 +36,23 @@ import ControlledColumn from "./table/ControlledColumn";
import AddListEntryForm from "./AddListEntryForm"; import AddListEntryForm from "./AddListEntryForm";
import { ScmNestedFormPathContextProvider } from "./FormPathContext"; import { ScmNestedFormPathContextProvider } from "./FormPathContext";
import ControlledComboboxField from "./combobox/ControlledComboboxField"; import ControlledComboboxField from "./combobox/ControlledComboboxField";
import ChipInputFieldComponent from "./chip-input/ChipInputField";
import ChipInput from "./headless-chip-input/ChipInput";
export { default as Checkbox } from "./checkbox/Checkbox";
export { default as Combobox } from "./combobox/Combobox"; export { default as Combobox } from "./combobox/Combobox";
export { default as ConfigurationForm } from "./ConfigurationForm"; export { default as ConfigurationForm } from "./ConfigurationForm";
export { default as SelectField } from "./select/SelectField"; export { default as SelectField } from "./select/SelectField";
export { default as ChipInputField } from "./chip-input/ChipInputField";
export { default as ComboboxField } from "./combobox/ComboboxField"; export { default as ComboboxField } from "./combobox/ComboboxField";
export { default as Input } from "./input/Input"; export { default as Input } from "./input/Input";
export { default as Select } from "./select/Select"; export { default as Select } from "./select/Select";
export * from "./resourceHooks"; export * from "./resourceHooks";
export { default as Label } from "./base/label/Label"; export { default as Label } from "./base/label/Label";
export const ChipInputField = Object.assign(ChipInputFieldComponent, {
AddButton: ChipInput.AddButton,
});
export const Form = Object.assign(FormCmp, { export const Form = Object.assign(FormCmp, {
Row: FormRow, Row: FormRow,
Input: ControlledInputField, Input: ControlledInputField,