Passed
Push — dev ( e37f55...168365 )
by Tristan
05:02 queued 11s
created

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

Complexity

Total Complexity 2
Complexity/F 0

Size

Lines of Code 158
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

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