• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

react-ui-org / react-ui / 14924998981

09 May 2025 08:39AM UTC coverage: 60.27%. First build
14924998981

Pull #613

github

web-flow
Merge af0cc3338 into 6b1c4e759
Pull Request #613: Add `writing-tests-guidelines.md` (#612)

413 of 897 branches covered (46.04%)

Branch coverage included in aggregate %.

614 of 807 relevant lines covered (76.08%)

29.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

5.71
/src/components/SelectField/SelectField.jsx
1
import PropTypes from 'prop-types';
2
import React, { useContext } from 'react';
3
import { withGlobalProps } from '../../providers/globalProps';
4
import { classNames } from '../../helpers/classNames/classNames';
5
import { transferProps } from '../../helpers/transferProps';
6
import { getRootSizeClassName } from '../_helpers/getRootSizeClassName';
7
import { getRootValidationStateClassName } from '../_helpers/getRootValidationStateClassName';
8
import { resolveContextOrProp } from '../_helpers/resolveContextOrProp';
9
import { FormLayoutContext } from '../FormLayout';
10
import { InputGroupContext } from '../InputGroup/InputGroupContext';
11
import { Option } from './_components/Option';
12
import styles from './SelectField.module.scss';
13

14
export const SelectField = React.forwardRef((props, ref) => {
2✔
15
  const {
16
    disabled,
17
    fullWidth,
18
    helpText,
19
    id,
20
    isLabelVisible,
21
    label,
22
    layout,
23
    options,
24
    renderAsRequired,
25
    required,
26
    size,
27
    validationState,
28
    validationText,
29
    variant,
30
    ...restProps
31
  } = props;
×
32

33
  const formLayoutContext = useContext(FormLayoutContext);
×
34
  const inputGroupContext = useContext(InputGroupContext);
×
35

36
  return (
×
37
    <label
38
      className={classNames(
39
        styles.root,
40
        fullWidth && styles.isRootFullWidth,
×
41
        formLayoutContext && styles.isRootInFormLayout,
×
42
        resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled) && styles.isRootDisabled,
×
43
        resolveContextOrProp(formLayoutContext && formLayoutContext.layout, layout) === 'horizontal'
×
44
          ? styles.isRootLayoutHorizontal
45
          : styles.isRootLayoutVertical,
46
        inputGroupContext && styles.isRootGrouped,
×
47
        (renderAsRequired || required) && styles.isRootRequired,
×
48
        getRootSizeClassName(
49
          resolveContextOrProp(inputGroupContext && inputGroupContext.size, size),
×
50
          styles,
51
        ),
52
        getRootValidationStateClassName(validationState, styles),
53
        variant === 'filled' ? styles.isRootVariantFilled : styles.isRootVariantOutline,
×
54
      )}
55
      htmlFor={id}
56
      id={id && `${id}__label`}
×
57
    >
58
      <div
59
        className={classNames(
60
          styles.label,
61
          (!isLabelVisible || inputGroupContext) && styles.isLabelHidden,
×
62
        )}
63
        id={id && `${id}__labelText`}
×
64
      >
65
        {label}
66
      </div>
67
      <div className={styles.field}>
68
        <div className={styles.inputContainer}>
69
          <select
70
            {...transferProps(restProps)}
71
            className={styles.input}
72
            disabled={resolveContextOrProp(inputGroupContext && inputGroupContext.disabled, disabled)}
×
73
            id={id}
74
            ref={ref}
75
            required={required}
76
          >
77
            {
78
              options.map((option) => {
79
                if ('options' in option) {
×
80
                  return (
×
81
                    <optgroup
82
                      key={option.key ?? option.label}
×
83
                      label={option.label}
84
                    >
85
                      {option.options.map((optgroupOption) => (
86
                        <Option
×
87
                          key={optgroupOption.key ?? optgroupOption.value}
×
88
                          {...optgroupOption}
89
                          {...(id && { id: `${id}__item__${optgroupOption.key ?? optgroupOption.value}` })}
×
90
                        />
91
                      ))}
92
                    </optgroup>
93
                  );
94
                }
95
                return (
×
96
                  <Option
97
                    key={option.key ?? option.value}
×
98
                    {...option}
99
                    {...(id && { id: `${id}__item__${option.key ?? option.value}` })}
×
100
                  />
101
                );
102
              })
103
            }
104
          </select>
105
          <div className={styles.caret}>
106
            <span className={styles.caretIcon} />
107
          </div>
108
          {variant === 'filled' && (
×
109
            <div className={styles.bottomLine} />
110
          )}
111
        </div>
112
        {helpText && (
×
113
          <div
114
            className={styles.helpText}
115
            id={id && `${id}__helpText`}
×
116
          >
117
            {helpText}
118
          </div>
119
        )}
120
        {(validationText && !inputGroupContext) && (
×
121
          <div
122
            className={styles.validationText}
123
            id={id && `${id}__validationText`}
×
124
          >
125
            {validationText}
126
          </div>
127
        )}
128
      </div>
129
    </label>
130
  );
131
});
132

133
SelectField.defaultProps = {
2✔
134
  disabled: false,
135
  fullWidth: false,
136
  helpText: null,
137
  id: undefined,
138
  isLabelVisible: true,
139
  layout: 'vertical',
140
  renderAsRequired: false,
141
  required: false,
142
  size: 'medium',
143
  validationState: null,
144
  validationText: null,
145
  variant: 'outline',
146
};
147

148
SelectField.propTypes = {
2✔
149
  /**
150
   * If `true`, the input will be disabled.
151
   */
152
  disabled: PropTypes.bool,
153
  /**
154
   * If `true`, the field will span the full width of its parent.
155
   */
156
  fullWidth: PropTypes.bool,
157
  /**
158
   * Optional help text.
159
   */
160
  helpText: PropTypes.node,
161
  /**
162
   * ID of the input HTML element.
163
   *
164
   * Also serves as a prefix for important inner elements:
165
   * * `<ID>__label`
166
   * * `<ID>__labelText`,
167
   * * `<ID>__helpText`
168
   * * `<ID>__validationText`
169
   *
170
   * and of individual options:
171
   * * `<ID>__item__<VALUE>`
172
   *
173
   * If `key` in the option definition object is set,
174
   * then `option.key` is used instead of `option.value` in place of `<VALUE>`.
175
   */
176
  id: PropTypes.string,
177
  /**
178
   * If `false`, the label will be visually hidden (but remains accessible by assistive
179
   * technologies).
180
   *
181
   * Automatically set to `false` when the component is rendered within `InputGroup` component.
182
   */
183
  isLabelVisible: PropTypes.bool,
184
  /**
185
   * Select field label.
186
   */
187
  label: PropTypes.node.isRequired,
188
  /**
189
   * Layout of the field.
190
   *
191
   * Ignored if the component is rendered within `FormLayout` component
192
   * as the value is inherited in such case.
193
   */
194
  layout: PropTypes.oneOf(['horizontal', 'vertical']),
195
  /**
196
   * Set of options to be chosen from.
197
   *
198
   * Either set of individual or grouped options is acceptable.
199
   *
200
   * For generating unique IDs the `option.value` is normally used. For cases when this is not practical or
201
   * the `option.value` values are not unique the `option.key` attribute can be set manually.
202
   * The same applies for the `label` value of grouped options which is supposed to be unique.
203
   * To ensure uniqueness `key` attribute can be set manually.
204
   */
205
  options: PropTypes.oneOfType([
206
    PropTypes.arrayOf(
207
      PropTypes.shape({
208
        key: PropTypes.string,
209
        label: PropTypes.string.isRequired,
210
        options: PropTypes.arrayOf(PropTypes.shape({
211
          disabled: PropTypes.bool,
212
          key: PropTypes.string,
213
          label: PropTypes.string.isRequired,
214
          value: PropTypes.oneOfType([
215
            PropTypes.string,
216
            PropTypes.number,
217
          ]),
218
        })),
219
      }),
220
    ),
221
    PropTypes.arrayOf(PropTypes.shape({
222
      disabled: PropTypes.bool,
223
      key: PropTypes.string,
224
      label: PropTypes.string.isRequired,
225
      value: PropTypes.oneOfType([
226
        PropTypes.string,
227
        PropTypes.number,
228
      ]),
229
    })),
230
  ]).isRequired,
231
  /**
232
   * If `true`, the input will be rendered as if it was required.
233
   */
234
  renderAsRequired: PropTypes.bool,
235
  /**
236
   * If `true`, the input will be made and rendered as required, regardless of the `renderAsRequired` prop.
237
   */
238
  required: PropTypes.bool,
239
  /**
240
   * Size of the field.
241
   *
242
   * Ignored if the component is rendered within `InputGroup` component as the value is inherited in such case.
243
   */
244
  size: PropTypes.oneOf(['small', 'medium', 'large']),
245
  /**
246
   * Alter the field to provide feedback based on validation result.
247
   */
248
  validationState: PropTypes.oneOf(['invalid', 'valid', 'warning']),
249
  /**
250
   * Validation message to be displayed.
251
   *
252
   * Validation text is never rendered when the component is placed into `InputGroup`. Instead, the `InputGroup`
253
   * component itself renders all validation texts of its nested components.
254
   */
255
  validationText: PropTypes.node,
256
  /**
257
   * Design variant of the field, further customizable with CSS custom properties.
258
   */
259
  variant: PropTypes.oneOf(['filled', 'outline']),
260
};
261

262
export const SelectFieldWithGlobalProps = withGlobalProps(SelectField, 'SelectField');
2✔
263

264
export default SelectFieldWithGlobalProps;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc