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

View File

@@ -28,6 +28,17 @@ import useInnerRef from "./useInnerRef";
import { createFormFieldWrapper, FieldProps, FieldType, isLegacy, isUsingRef } from "./FormFieldTypes";
import classNames from "classnames";
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 {
value: boolean;
@@ -109,11 +120,11 @@ const InnerCheckbox: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({
because jsx label does not the custom disabled attribute
but bulma does.
// @ts-ignore */}
<label className="checkbox" disabled={disabled}>
<input
<StyledLabel className="checkbox is-align-items-center" disabled={disabled}>
<StyledInput
type="checkbox"
name={name}
className={classNames("checkbox", className)}
className={classNames("m-3", className)}
onChange={handleChange}
onBlur={handleBlur}
ref={field}
@@ -126,7 +137,7 @@ const InnerCheckbox: FC<FieldProps<BaseProps, HTMLInputElement, boolean>> = ({
/>{" "}
{label}
{renderHelp()}
</label>
</StyledLabel>
</div>
</fieldset>
);

View File

@@ -25,20 +25,52 @@
import React, { InputHTMLAttributes } from "react";
import { createAttributesForTesting } from "@scm-manager/ui-components";
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 = {
label: string;
helpText?: string;
testId?: string;
labelClassName?: string;
} & Omit<InputHTMLAttributes<HTMLInputElement>, "type">;
/**
* @see https://bulma.io/documentation/form/checkbox/
*/
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 ? (
<>
<input
@@ -50,9 +82,9 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
defaultChecked={defaultChecked}
readOnly
/>
<input
<StyledInput
type="checkbox"
className="mr-1"
className={classNames("m-3", className)}
ref={ref}
value={value}
defaultValue={defaultValue}
@@ -64,9 +96,9 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
/>
</>
) : (
<input
<StyledInput
type="checkbox"
className="mr-1"
className={classNames("m-3", className)}
ref={ref}
name={name}
value={value}
@@ -77,9 +109,10 @@ const Checkbox = React.forwardRef<HTMLInputElement, InputFieldProps>(
{...createAttributesForTesting(testId)}
/>
)}
{label}
{helpText ? <Help className="ml-1" text={helpText} /> : null}
</label>
</StyledLabel>
)
);
export default Checkbox;

View File

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

View File

@@ -22,7 +22,7 @@
* 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 Field from "../base/Field";
import Label from "../base/label/Label";
@@ -81,6 +81,7 @@ type InputFieldProps<T> = {
className?: string;
isLoading?: boolean;
isNewItemDuplicate?: (existingItem: Option<T>, newItem: Option<T>) => boolean;
ref?: Ref<HTMLInputElement>;
};
/**
@@ -105,7 +106,7 @@ const ChipInputField = function ChipInputField<T>(
isLoading,
isNewItemDuplicate,
...props
}: InputFieldProps<T>,
}: PropsWithRef<InputFieldProps<T>>,
ref: React.ForwardedRef<HTMLInputElement>
) {
const [t] = useTranslation("commons", { keyPrefix: "form.chipList" });

View File

@@ -24,6 +24,7 @@
import React, {
ButtonHTMLAttributes,
ComponentProps,
ComponentType,
Context,
createContext,
@@ -40,6 +41,7 @@ import React, {
import { Slot } from "@radix-ui/react-slot";
import { Option } from "@scm-manager/ui-types";
import { mergeRefs, withForwardRef } from "../helpers";
import { Button } from "@scm-manager/ui-buttons";
type ChipInputContextType<T> = {
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, {
Chip: Object.assign(Chip, {
Delete: ChipDelete,
}),
AddButton,
});

View File

@@ -36,17 +36,23 @@ import ControlledColumn from "./table/ControlledColumn";
import AddListEntryForm from "./AddListEntryForm";
import { ScmNestedFormPathContextProvider } from "./FormPathContext";
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 ConfigurationForm } from "./ConfigurationForm";
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 Input } from "./input/Input";
export { default as Select } from "./select/Select";
export * from "./resourceHooks";
export { default as Label } from "./base/label/Label";
export const ChipInputField = Object.assign(ChipInputFieldComponent, {
AddButton: ChipInput.AddButton,
});
export const Form = Object.assign(FormCmp, {
Row: FormRow,
Input: ControlledInputField,