Passed
Push — task/application-profile-react... ( 95a834...f06e77 )
by Yonathan
08:39
created

resources/assets/js/components/H2Components/Select.tsx   A

Complexity

Total Complexity 2
Complexity/F 0

Size

Lines of Code 159
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 2
eloc 121
dl 0
loc 159
rs 10
c 0
b 0
f 0
mnd 2
bc 2
fnc 0
bpm 0
cpm 0
noi 0
1
import * as React from "react";
2
import { useIntl } from "react-intl";
3
import { FieldName, FieldValues, RegisterOptions } from "react-hook-form";
4
import { GeneralProps } from "./utils";
5
import { inputMessages } from "../Form/Messages";
6
7
interface SelectContext extends GeneralProps {
8
  /** Additional information displayed under the input */
9
  additionalInfo?: string;
10
  /** The default value of the select input when it is rendered. */
11
  defaultValue?: string | number;
12
  /** The error message displayed if the form validation fails. */
13
  errorMessage?: string;
14
  /** The text for label associated with select input. */
15
  label: string;
16
  /** A string specifying a name for the input control. This name is submitted along with the control's value when the form data is submitted. */
17
  name: string;
18
  /** Provides a null value with instructions to user (eg. Select one of the following...). */
19
  nullSelection?: string;
20
  /** Supplementary information about the purpose of the select input. */
21
  supplementaryInfo?: string;
22
  /** Boolean indicating if input must have a value, or not */
23
  required?: boolean;
24
  /** Event listener which fires when a change event occurs (varies on input type) */
25
  onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
26
  /** This method allows you to register an input/select Ref and apply validation rules into React Hook Form. https://react-hook-form.com/api#register */
27
  register?: (name: FieldName<FieldValues>, rules?: RegisterOptions) => void;
28
}
29
30
const SelectContext = React.createContext<SelectContext | undefined>(undefined);
31
32
/**
33
 * This Context hook allows our child components to easily reach
34
 * into the Tabs context and get the pieces it needs.
35
 *
36
 * Bonus: it even makes sure the component is used within a
37
 * Select component!
38
 */
39
const useSelectContext = (): Partial<SelectContext> => {
40
  const context = React.useContext(SelectContext);
41
  if (!context) {
42
    throw new Error("This component must be used within a <Select> component.");
43
  }
44
  return context;
45
};
46
47
interface OptionProps {
48
  /** option value */
49
  value: string | number;
50
}
51
52
const Option: React.FunctionComponent<OptionProps> = ({ value, children }) => {
53
  useSelectContext(); // Ensures sub-component can only be used within the Option component.
54
  return <option value={value}>{children}</option>;
55
};
56
57
interface SelectComposition {
58
  Option: React.FunctionComponent<OptionProps>;
59
}
60
61
const Select: React.FunctionComponent<SelectContext> & SelectComposition = (
62
  props,
63
) => {
64
  const intl = useIntl();
65
  const {
66
    additionalInfo,
67
    defaultValue,
68
    errorMessage,
69
    label,
70
    name,
71
    nullSelection,
72
    register,
73
    required,
74
    supplementaryInfo,
75
    onChange,
76
    children,
77
    ...rest
78
  } = props;
79
  return (
80
    // The select context provider doesn't provide any props to its children (Option),
81
    // however it does ensure the Option component can only be used within a Select component.
82
    <SelectContext.Provider value={props}>
83
      <div data-h2-form-item="select" {...rest}>
84
        <div data-h2-input-title-wrapper>
85
          <div data-h2-input-label-wrapper>
86
            <label data-h2-input-label htmlFor="selectInput">
87
              {label}
88
            </label>
89
          </div>
90
          <div data-h2-input-data-wrapper>
91
            <span data-h2-input-required>
92
              ({intl.formatMessage(inputMessages.required)})
93
            </span>
94
            <span data-h2-input-optional>
95
              ({intl.formatMessage(inputMessages.optional)})
96
            </span>
97
            <span data-h2-input-data>{supplementaryInfo}</span>
98
          </div>
99
        </div>
100
        <div data-h2-input-wrapper>
101
          <span data-h2-input-select-icon>▼</span>
102
          <select
103
            name={name}
104
            ref={register}
105
            data-h2-input
106
            id="selectInput"
107
            required={required}
108
            onChange={onChange}
109
            defaultValue={defaultValue}
110
          >
111
            {nullSelection ? (
112
              <option value="" disabled>
113
                {nullSelection}
114
              </option>
115
            ) : (
116
              <option value="" disabled>
117
                {intl.formatMessage(inputMessages.nullSelectOption)}
118
              </option>
119
            )}
120
            {children}
121
          </select>
122
        </div>
123
        <div data-h2-input-context-wrapper>
124
          <div data-h2-input-error-wrapper>
125
            <span data-h2-input-error>{errorMessage}</span>
126
          </div>
127
          <div data-h2-input-info-trigger-wrapper>
128
            {additionalInfo && (
129
              <button
130
                aria-expanded="false"
131
                data-h2-input-info-trigger
132
                type="button"
133
              >
134
                <span data-h2-input-info-trigger-more-label>
135
                  {intl.formatMessage(inputMessages.moreInfo)}
136
                </span>
137
                <span data-h2-input-info-trigger-less-label>
138
                  {intl.formatMessage(inputMessages.lessInfo)}
139
                </span>{" "}
140
                {intl.formatMessage(inputMessages.info)}
141
              </button>
142
            )}
143
          </div>
144
        </div>
145
        <div aria-hidden="true" data-h2-input-info-wrapper>
146
          <p data-h2-focus>{additionalInfo}</p>
147
        </div>
148
      </div>
149
    </SelectContext.Provider>
150
  );
151
};
152
153
// We expose the children components here, as properties.
154
// Using the dot notation we explicitly set the composition relationships,
155
// btw the Dialog component and its sub components.
156
Select.Option = Option;
157
158
export default Select;
159