Passed
Push — feature/azure-webapp-pipeline-... ( 9e9b64...fdb227 )
by Grant
07:49 queued 11s
created

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

Complexity

Total Complexity 3
Complexity/F 0

Size

Lines of Code 161
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 3
eloc 121
mnd 3
bc 3
fnc 0
dl 0
loc 161
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import * as React from "react";
2
import { useIntl } from "react-intl";
3
import { inputMessages } from "../Form/Messages";
4
5
interface SelectContext {
6
  /** HTML id of the input element */
7
  id?: string;
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?: any;
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
    id,
67
    additionalInfo,
68
    defaultValue,
69
    errorMessage,
70
    label,
71
    name,
72
    nullSelection,
73
    register,
74
    required,
75
    supplementaryInfo,
76
    onChange,
77
    children,
78
    ...rest
79
  } = props;
80
  const registerProps = register ? register(name) : {};
81
  return (
82
    // The select context provider doesn't provide any props to its children (Option),
83
    // however it does ensure the Option component can only be used within a Select component.
84
    <SelectContext.Provider value={props}>
85
      <div data-h2-form-item="select" {...rest}>
86
        <div data-h2-input-title-wrapper>
87
          <div data-h2-input-label-wrapper>
88
            <label data-h2-input-label htmlFor={id || name}>
89
              {label}
90
            </label>
91
          </div>
92
          <div data-h2-input-data-wrapper>
93
            <span data-h2-input-required>
94
              ({intl.formatMessage(inputMessages.required)})
95
            </span>
96
            <span data-h2-input-optional>
97
              ({intl.formatMessage(inputMessages.optional)})
98
            </span>
99
            <span data-h2-input-data>{supplementaryInfo}</span>
100
          </div>
101
        </div>
102
        <div data-h2-input-wrapper>
103
          <span data-h2-input-select-icon>▼</span>
104
          <select
105
            id={id || name}
106
            name={name}
107
            data-h2-input
108
            required={required}
109
            onChange={onChange}
110
            defaultValue={defaultValue}
111
            {...registerProps}
112
          >
113
            {nullSelection ? (
114
              <option value="" disabled>
115
                {nullSelection}
116
              </option>
117
            ) : (
118
              <option value="" disabled>
119
                {intl.formatMessage(inputMessages.nullSelectOption)}
120
              </option>
121
            )}
122
            {children}
123
          </select>
124
        </div>
125
        <div data-h2-input-context-wrapper>
126
          <div data-h2-input-error-wrapper>
127
            <span data-h2-input-error>{errorMessage}</span>
128
          </div>
129
          <div data-h2-input-info-trigger-wrapper>
130
            {additionalInfo && (
131
              <button
132
                aria-expanded="false"
133
                data-h2-input-info-trigger
134
                type="button"
135
              >
136
                <span data-h2-input-info-trigger-more-label>
137
                  {intl.formatMessage(inputMessages.moreInfo)}
138
                </span>
139
                <span data-h2-input-info-trigger-less-label>
140
                  {intl.formatMessage(inputMessages.lessInfo)}
141
                </span>{" "}
142
                {intl.formatMessage(inputMessages.info)}
143
              </button>
144
            )}
145
          </div>
146
        </div>
147
        <div aria-hidden="true" data-h2-input-info-wrapper>
148
          <p data-h2-focus>{additionalInfo}</p>
149
        </div>
150
      </div>
151
    </SelectContext.Provider>
152
  );
153
};
154
155
// We expose the children components here, as properties.
156
// Using the dot notation we explicitly set the composition relationships,
157
// btw the Dialog component and its sub components.
158
Select.Option = Option;
159
160
export default Select;
161