Passed
Push — task/improve-find-skills-modal... ( fe8ff3...87dfae )
by Yonathan
08:18
created

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

Complexity

Total Complexity 2
Complexity/F 0

Size

Lines of Code 157
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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