@luscii-healthtech/web-ui 2.0.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/components/Accordion/Accordion.d.ts +10 -0
  2. package/dist/components/Accordion/AccordionItem.d.ts +9 -0
  3. package/dist/components/Form/Form.d.ts +9 -0
  4. package/dist/components/Form/FormFieldDecorator.d.ts +8 -0
  5. package/dist/components/Form/FormInput.d.ts +3 -0
  6. package/dist/components/Form/FormRadioGroup.d.ts +3 -0
  7. package/dist/components/Form/FormSelect.d.ts +3 -0
  8. package/dist/components/Form/form.transformer.d.ts +20 -0
  9. package/dist/components/Form/form.types.d.ts +54 -0
  10. package/dist/components/Input/Input.d.ts +8 -7
  11. package/dist/components/Input/SearchInput.d.ts +1 -1
  12. package/dist/components/List/List.d.ts +1 -1
  13. package/dist/components/List/List.types.d.ts +1 -0
  14. package/dist/components/List/ListItemSkeleton.d.ts +2 -0
  15. package/dist/components/List/ListSkeleton.d.ts +7 -0
  16. package/dist/components/Radio/Radio.d.ts +3 -0
  17. package/dist/components/Radio/RadioV2.d.ts +17 -0
  18. package/dist/components/RadioGroup/RadioGroup.d.ts +3 -0
  19. package/dist/components/RadioGroup/RadioGroupV2.d.ts +9 -0
  20. package/dist/components/Select/Select.d.ts +3 -0
  21. package/dist/components/Select/SelectV2.d.ts +31 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/web-ui-tailwind.css +50 -0
  24. package/dist/web-ui.cjs.development.js +604 -40
  25. package/dist/web-ui.cjs.development.js.map +1 -1
  26. package/dist/web-ui.cjs.production.min.js +1 -1
  27. package/dist/web-ui.cjs.production.min.js.map +1 -1
  28. package/dist/web-ui.esm.js +604 -41
  29. package/dist/web-ui.esm.js.map +1 -1
  30. package/package.json +6 -3
  31. package/src/components/Accordion/Accordion.tsx +33 -0
  32. package/src/components/Accordion/AccordionItem.tsx +50 -0
  33. package/src/components/Form/Form.tsx +106 -0
  34. package/src/components/Form/FormFieldDecorator.tsx +66 -0
  35. package/src/components/Form/FormInput.tsx +47 -0
  36. package/src/components/Form/FormRadioGroup.tsx +23 -0
  37. package/src/components/Form/FormSelect.tsx +32 -0
  38. package/src/components/Form/form.transformer.ts +9 -0
  39. package/src/components/Form/form.types.ts +132 -0
  40. package/src/components/Input/Input.tsx +160 -165
  41. package/src/components/Input/SearchInput.tsx +13 -3
  42. package/src/components/List/List.tsx +13 -9
  43. package/src/components/List/List.types.ts +1 -0
  44. package/src/components/List/ListItemSkeleton.tsx +26 -0
  45. package/src/components/List/ListSkeleton.scss +5 -0
  46. package/src/components/List/ListSkeleton.tsx +30 -0
  47. package/src/components/Radio/Radio.js +3 -0
  48. package/src/components/Radio/RadioV2.css +15 -0
  49. package/src/components/Radio/RadioV2.tsx +87 -0
  50. package/src/components/RadioGroup/RadioGroup.js +3 -0
  51. package/src/components/RadioGroup/RadioGroupV2.tsx +35 -0
  52. package/src/components/Select/Select.tsx +38 -12
  53. package/src/components/Select/SelectV2.tsx +171 -0
  54. package/src/index.tsx +3 -0
  55. package/src/styles/_skeleton.scss +63 -0
  56. package/src/types/general.types.ts +1 -1
  57. package/src/components/Select/Select.examples.md +0 -161
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0",
2
+ "version": "2.3.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -82,6 +82,7 @@
82
82
  "postcss": "^8.4.14",
83
83
  "postcss-loader": "4.2",
84
84
  "postcss-url": "^10.1.3",
85
+ "prettier": "^2.7.1",
85
86
  "react": "^18.2.0",
86
87
  "react-dom": "^18.2.0",
87
88
  "react-is": "^18.2.0",
@@ -103,6 +104,7 @@
103
104
  "dependencies": {
104
105
  "@fontsource/inter": "^4.5.11",
105
106
  "@fontsource/roboto": "^4.5.7",
107
+ "@hookform/error-message": "^2.0.0",
106
108
  "@reach/router": "^1.3.4",
107
109
  "@typescript-eslint/eslint-plugin": "^5.29.0",
108
110
  "classnames": "^2.3.1",
@@ -118,11 +120,12 @@
118
120
  "path": "^0.12.7",
119
121
  "react-datepicker": "^4.8.0",
120
122
  "react-draft-wysiwyg": "^1.14.7",
123
+ "react-dragula": "^1.1.17",
121
124
  "react-glider": "^3.1.0",
125
+ "react-hook-form": "^7.33.0",
122
126
  "react-modal": "^3.15.1",
123
127
  "react-quill": "^1.3.5",
124
- "react-select": "^5.3.2",
125
- "react-dragula": "^1.1.17"
128
+ "react-select": "^5.3.2"
126
129
  },
127
130
  "browserslist": [
128
131
  "last 1 chrome version",
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+
3
+ import { AccordionItem, AccordionItemProps } from "./AccordionItem";
4
+
5
+ export interface AccordionProps {
6
+ dataTestId?: string;
7
+ items: AccordionItemProps[];
8
+ className?: string;
9
+ isCollapsedByDefault?: boolean;
10
+ }
11
+
12
+ const Accordion: React.VFC<AccordionProps> = ({
13
+ dataTestId,
14
+ isCollapsedByDefault = false,
15
+ items,
16
+ className,
17
+ }) => {
18
+ return (
19
+ <ul data-test-id={dataTestId} className={className}>
20
+ {items.map?.((item) => (
21
+ <AccordionItem
22
+ {...item}
23
+ key={item.id}
24
+ isCollapsedByDefault={
25
+ item.isCollapsedByDefault ?? isCollapsedByDefault
26
+ }
27
+ />
28
+ ))}
29
+ </ul>
30
+ );
31
+ };
32
+
33
+ export default Accordion;
@@ -0,0 +1,50 @@
1
+ import React, { useReducer } from "react";
2
+ import classNames from "classnames";
3
+
4
+ import { ChevronRightIcon } from "../Icons/ChevronRightIcon";
5
+ import { ChevronDownIcon } from "../Icons/ChevronDownIcon";
6
+ import Text from "../Text/Text";
7
+
8
+ export interface AccordionItemProps {
9
+ id: string;
10
+ title: string;
11
+ content: React.ReactNode;
12
+ className?: string;
13
+ isCollapsedByDefault?: boolean;
14
+ }
15
+
16
+ export const AccordionItem: React.VFC<AccordionItemProps> = ({
17
+ id,
18
+ title,
19
+ content,
20
+ isCollapsedByDefault = false,
21
+ }) => {
22
+ const [isCollapsed, toggleIsCollapsed] = useReducer(
23
+ (state) => !state,
24
+ isCollapsedByDefault
25
+ );
26
+ const Chevron = isCollapsed ? ChevronRightIcon : ChevronDownIcon;
27
+
28
+ return (
29
+ <li
30
+ className={"bg-white border-b last:border-b-0 border-slate-100"}
31
+ data-test-id={id}
32
+ >
33
+ <div
34
+ onClick={toggleIsCollapsed}
35
+ className={classNames(
36
+ "p-4 w-full flex flex-row space-x-4 select-none",
37
+ "cursor-pointer hover:bg-blue-50 transition-colors ease-in-out duration-300",
38
+ {
39
+ "border-b border-slate-100": !isCollapsed,
40
+ }
41
+ )}
42
+ >
43
+ <Chevron className={"text-slate-300"} />
44
+ <Text text={title} type={"lg-strong"} />
45
+ </div>
46
+
47
+ <div className={classNames({ hidden: isCollapsed })}>{content}</div>
48
+ </li>
49
+ );
50
+ };
@@ -0,0 +1,106 @@
1
+ import React from "react";
2
+ import { Control, useForm } from "react-hook-form";
3
+
4
+ import { PrimaryButton } from "../ButtonV2/PrimaryButton";
5
+ import { InputProps } from "../Input/Input";
6
+ import { RadioGroupProps } from "../RadioGroup/RadioGroupV2";
7
+ import { SelectProps } from "../Select/SelectV2";
8
+
9
+ import { FormInput } from "./FormInput";
10
+ import { FormFieldProps, FormFieldRowProps, FormProps } from "./form.types";
11
+ import { isRequired } from "./form.transformer";
12
+ import { FormRadioGroup } from "./FormRadioGroup";
13
+ import { FormSelect } from "./FormSelect";
14
+
15
+ /**
16
+ * Create a straight forward Form.
17
+ *
18
+ * TODO: wrap this in some Page component to style the div and buttons
19
+ * WARNING: don't use this component before some styling errors are resolved.
20
+ */
21
+ export function Form<TFieldValues>({
22
+ fields,
23
+ onValid,
24
+ onError,
25
+ defaultValues,
26
+ }: FormProps<TFieldValues>): JSX.Element {
27
+ const {
28
+ register,
29
+ handleSubmit,
30
+ control,
31
+ formState: { errors },
32
+ } = useForm<TFieldValues>({
33
+ criteriaMode: "all",
34
+ defaultValues: defaultValues,
35
+ });
36
+
37
+ const fieldMapper = ({
38
+ type,
39
+ name,
40
+ options,
41
+ fieldProps = {},
42
+ ...decoratorProps
43
+ }: FormFieldProps<TFieldValues>) => {
44
+ switch (type) {
45
+ case "text":
46
+ case "number":
47
+ case "email":
48
+ case "password":
49
+ return (
50
+ <FormInput
51
+ key={name}
52
+ {...decoratorProps}
53
+ fieldRequired={isRequired(options)}
54
+ fieldErrors={errors}
55
+ {...(fieldProps as InputProps)}
56
+ {...register(name, options)}
57
+ type={type || "text"}
58
+ />
59
+ );
60
+ case "select":
61
+ return (
62
+ <FormSelect
63
+ key={name}
64
+ {...decoratorProps}
65
+ fieldRequired={isRequired(options)}
66
+ fieldErrors={errors}
67
+ {...(fieldProps as SelectProps)}
68
+ control={control as Control}
69
+ rules={options}
70
+ name={name}
71
+ />
72
+ );
73
+ case "radioGroup":
74
+ return (
75
+ <FormRadioGroup
76
+ key={name}
77
+ {...decoratorProps}
78
+ fieldRequired={isRequired(options)}
79
+ fieldErrors={errors}
80
+ {...(fieldProps as RadioGroupProps)}
81
+ {...register(name, options)}
82
+ />
83
+ );
84
+ default:
85
+ return undefined;
86
+ }
87
+ };
88
+
89
+ return (
90
+ <div className="space-y-4">
91
+ {fields.map((props) => {
92
+ if (props.type === "row") {
93
+ const { fields: rowFields, key } =
94
+ props as FormFieldRowProps<TFieldValues>;
95
+ return (
96
+ <div className={"flex flex-row"} key={key}>
97
+ {rowFields.map(fieldMapper)}
98
+ </div>
99
+ );
100
+ }
101
+ return fieldMapper(props);
102
+ })}
103
+ <PrimaryButton onClick={handleSubmit(onValid, onError)} text={"submit"} />
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,66 @@
1
+ import React from "react";
2
+ import { ErrorMessage } from "@hookform/error-message";
3
+ import classNames from "classnames";
4
+
5
+ import { Text } from "../Text/Text";
6
+
7
+ import { FormFieldDecoratorWithGeneratedProps } from "./form.types";
8
+
9
+ /**
10
+ * Decorator for any input component. Adds a label and additional information to be shown.
11
+ *
12
+ * Includes the default error handling from react-hook-form.
13
+ */
14
+ export function FormFieldDecorator({
15
+ name,
16
+ children,
17
+ label,
18
+ fieldRequired,
19
+ info,
20
+ fieldErrors,
21
+ decoratorClassname,
22
+ }: FormFieldDecoratorWithGeneratedProps): JSX.Element {
23
+ return (
24
+ <div className={classNames(decoratorClassname)}>
25
+ {label && (
26
+ <label
27
+ className="cweb-form-field-label block mb-1"
28
+ htmlFor={name}
29
+ data-is-required={fieldRequired}
30
+ >
31
+ <Text
32
+ className="cweb-form-field-label-text"
33
+ inline={true}
34
+ text={label}
35
+ />
36
+ </label>
37
+ )}
38
+
39
+ <fieldset className="cweb-form-fieldset">{children}</fieldset>
40
+ {info && (
41
+ <Text
42
+ className="mt-1 cweb-form-info-text"
43
+ type="sm"
44
+ color="slate-500"
45
+ text={info}
46
+ />
47
+ )}
48
+
49
+ <ErrorMessage
50
+ errors={fieldErrors}
51
+ name={name}
52
+ render={({ messages }) =>
53
+ messages &&
54
+ Object.entries(messages).map(([key, message]) => (
55
+ <Text
56
+ key={key}
57
+ className="mt-1"
58
+ text={message as string}
59
+ color={"red"}
60
+ />
61
+ ))
62
+ }
63
+ />
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+
3
+ import Input from "../Input/Input";
4
+
5
+ import { FormFieldDecorator } from "./FormFieldDecorator";
6
+ import { hasError } from "./form.transformer";
7
+ import { FormInputProps } from "./form.types";
8
+
9
+ /**
10
+ * Input field that can be used in any react-hook-form context.
11
+ */
12
+ const FormInputInner = React.forwardRef<HTMLInputElement, FormInputProps>(
13
+ (
14
+ {
15
+ name,
16
+ fieldErrors,
17
+ fieldRequired,
18
+ label,
19
+ info,
20
+ decoratorClassname,
21
+ ...fieldProps
22
+ },
23
+ ref
24
+ ) => {
25
+ return (
26
+ <FormFieldDecorator
27
+ name={name}
28
+ fieldErrors={fieldErrors}
29
+ fieldRequired={fieldRequired}
30
+ label={label}
31
+ info={info}
32
+ decoratorClassname={decoratorClassname}
33
+ >
34
+ <Input
35
+ {...fieldProps}
36
+ isError={hasError(name, fieldErrors)}
37
+ ref={ref}
38
+ name={name}
39
+ />
40
+ </FormFieldDecorator>
41
+ );
42
+ }
43
+ );
44
+
45
+ export const FormInput = React.forwardRef<HTMLInputElement, FormInputProps>(
46
+ (props, ref) => <FormInputInner {...props} ref={ref} />
47
+ );
@@ -0,0 +1,23 @@
1
+ import React from "react";
2
+
3
+ import { RadioGroupV2 } from "../RadioGroup/RadioGroupV2";
4
+
5
+ import { FormFieldDecorator } from "./FormFieldDecorator";
6
+ import { hasError } from "./form.transformer";
7
+ import { FormRadioGroupProps } from "./form.types";
8
+
9
+ /**
10
+ * Input field that can be used in any react-hook-form context.
11
+ */
12
+ function FormRadioGroupInner(
13
+ { innerRef, name, fieldErrors, fieldRequired, label, info, decoratorClassname, ...fieldProps }: FormRadioGroupProps): JSX.Element {
14
+ return (
15
+ <FormFieldDecorator name={name} fieldErrors={fieldErrors} fieldRequired={fieldRequired} label={label} info={info} decoratorClassname={decoratorClassname}>
16
+ <RadioGroupV2 {...fieldProps} isError={hasError(name, fieldErrors)} ref={innerRef} name={name} />
17
+ </FormFieldDecorator>
18
+ );
19
+ }
20
+
21
+ export const FormRadioGroup = React.forwardRef<HTMLInputElement, FormRadioGroupProps>((props, ref) => (
22
+ <FormRadioGroupInner {...props} innerRef={ref} />
23
+ ));
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import { Controller } from "react-hook-form";
3
+
4
+ import { Select } from "../Select/SelectV2";
5
+
6
+ import { FormSelectProps } from "./form.types";
7
+ import { FormFieldDecorator } from "./FormFieldDecorator";
8
+ import { hasError } from "./form.transformer";
9
+
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ export const FormSelect = React.forwardRef<any, FormSelectProps>((
12
+ {
13
+ control, name, rules, fieldErrors, fieldRequired, label, info, decoratorClassname,
14
+ ...selectProps
15
+ }, innerRef) => {
16
+ return (
17
+ <FormFieldDecorator name={name} fieldErrors={fieldErrors} fieldRequired={fieldRequired} label={label} info={info} decoratorClassname={decoratorClassname}>
18
+ <Controller
19
+ name={name}
20
+ control={control}
21
+ rules={rules}
22
+ render={({ field }) => (
23
+ <Select
24
+ {...selectProps}
25
+ {...field}
26
+ isError={hasError(name, fieldErrors)}
27
+ ref={innerRef}
28
+ />)}
29
+ />
30
+ </FormFieldDecorator>
31
+ );
32
+ });
@@ -0,0 +1,9 @@
1
+ import { FieldErrors, RegisterOptions } from "react-hook-form";
2
+
3
+ export const hasError = (name?: string, errors?: FieldErrors): boolean => {
4
+ return !!(name && errors && name in errors);
5
+ };
6
+
7
+ export const isRequired = (options?: RegisterOptions): boolean => {
8
+ return !!(options && "required" in options);
9
+ };
@@ -0,0 +1,132 @@
1
+ import {
2
+ Control,
3
+ DeepPartial,
4
+ FieldErrors,
5
+ FieldPath,
6
+ FieldValues,
7
+ RegisterOptions,
8
+ SubmitErrorHandler,
9
+ SubmitHandler,
10
+ } from "react-hook-form";
11
+ import React, { HTMLInputTypeAttribute } from "react";
12
+
13
+ import { InputProps } from "../Input/Input";
14
+ import { RadioGroupProps } from "../RadioGroup/RadioGroupV2";
15
+ import { SelectProps } from "../Select/SelectV2";
16
+
17
+ export type AllowedTextInputTypes = Extract<
18
+ HTMLInputTypeAttribute,
19
+ "email" | "number" | "password" | "text"
20
+ >;
21
+
22
+ // --------------------------------------------
23
+ // The namespaces for props and components are structured as:
24
+ // <<type>>Props : usable in any context
25
+ // Form<<type>>Props : usable in any react-hook-form context
26
+ // FormField<<type>>Props: usable in specifically the Form component
27
+ // --------------------------------------------
28
+
29
+ // the input for the 'out-of-the-box' Form
30
+ export interface FormProps<TFieldValues extends FieldValues> {
31
+ // the fields to be rendered
32
+ fields: (FormFieldProps<TFieldValues> | FormFieldRowProps<TFieldValues>)[];
33
+ onValid: SubmitHandler<TFieldValues>;
34
+ onError?: SubmitErrorHandler<TFieldValues>;
35
+ // inject the form with already known data (for instance, when updating
36
+ defaultValues?: DeepPartial<TFieldValues>;
37
+ }
38
+
39
+ interface FormFieldGenericProps<TFieldType, TFieldValues>
40
+ extends FormFieldDecoratorProps {
41
+ // the name of the field, which is registered within react-hook-form
42
+ name: FieldPath<TFieldValues>;
43
+ // the options registered within react-hook-form
44
+ options?: RegisterOptions;
45
+ // any additional props to customise the component that renders the field
46
+ fieldProps?: TFieldType;
47
+ }
48
+
49
+ // these are the configuration options for the decorator
50
+ interface FormFieldDecoratorProps {
51
+ // the label shown to the user within the decorator
52
+ label?: string;
53
+ // the additional information shown to the user within the decorator
54
+ info?: string;
55
+ // allow for custom styling of the decorator component
56
+ decoratorClassname?: string;
57
+ }
58
+
59
+ // these are all the types that can be included in the 'out-of-the-box' Form
60
+ export type FormFieldProps<TFieldValues> =
61
+ | FormFieldInputProps<TFieldValues>
62
+ | FormFieldSelectProps<TFieldValues>
63
+ | FormFieldRadioGroupProps<TFieldValues>;
64
+
65
+ // allow a row of fields
66
+ export interface FormFieldRowProps<TFieldValues> {
67
+ type: "row";
68
+ // add a unique key string to ensure react can render the map in an optimized manner
69
+ key: string;
70
+ fields: FormFieldProps<TFieldValues>[];
71
+ }
72
+
73
+ interface FormFieldInputProps<TFieldValues>
74
+ extends FormFieldGenericProps<Omit<InputProps, "name">, TFieldValues> {
75
+ // the type of component rendered to display the field
76
+ type: AllowedTextInputTypes;
77
+ }
78
+
79
+ interface FormFieldRadioGroupProps<TFieldValues>
80
+ extends FormFieldGenericProps<Omit<RadioGroupProps, "name">, TFieldValues> {
81
+ type: "radioGroup";
82
+ }
83
+
84
+ export interface FormFieldSelectProps<TFieldValues>
85
+ extends FormFieldGenericProps<SelectProps, TFieldValues> {
86
+ type: "select";
87
+ // this makes it now required, because we need the options in there
88
+ fieldProps: SelectProps;
89
+ }
90
+
91
+ // --------------------------------------------
92
+ // The types below are used for components that are used in a react-hook-form context but not the Form context
93
+ // --------------------------------------------
94
+
95
+ // these are the inputs required to decorate fields
96
+ // they are generated within the Form component, but could also be manually inserted
97
+ export interface FormFieldDecoratorWithGeneratedProps
98
+ extends FormFieldDecoratorProps {
99
+ // coming from register react-hook-form
100
+ name: string;
101
+ // coming from useForms formState react-hook-form
102
+ fieldErrors: FieldErrors;
103
+ // automatically generated within Form from the RegisterOptions
104
+ // showing an asterisk to the label if true
105
+ fieldRequired: boolean;
106
+ // the field that is decorated
107
+ children?: React.ReactNode;
108
+ }
109
+
110
+ export interface FormInputProps
111
+ extends Omit<InputProps, "name">,
112
+ FormFieldDecoratorWithGeneratedProps {
113
+ type: Extract<
114
+ HTMLInputTypeAttribute,
115
+ "email" | "number" | "password" | "text"
116
+ >;
117
+ }
118
+
119
+ export interface FormRadioGroupProps
120
+ extends RadioGroupProps,
121
+ FormFieldDecoratorWithGeneratedProps {}
122
+
123
+ export interface FormSelectProps
124
+ extends Omit<SelectProps, "name">,
125
+ FormFieldDecoratorWithGeneratedProps {
126
+ control: Control;
127
+ // ref: https://react-hook-form.com/ts#UseControllerProps
128
+ rules?: Exclude<
129
+ RegisterOptions,
130
+ "valueAsNumber" | "valueAsDate" | "setValueAs"
131
+ >;
132
+ }