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

@@ -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,