@box/blueprint-web 8.1.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,11 +9,12 @@ import { LoadingIndicator } from '../loading-indicator/loading-indicator.js';
9
9
  import { IconButton } from '../primitives/icon-button/icon-button.js';
10
10
  import { InlineError } from '../primitives/inline-error/inline-error.js';
11
11
  import { TextArea } from '../text-area/text-area.js';
12
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
13
+ import { composeEventHandlers } from '../utils/composeEventHandlers.js';
12
14
  import { useForkRef } from '../utils/useForkRef.js';
13
15
  import { useUniqueId } from '../utils/useUniqueId.js';
14
16
  import { ChipsGroup } from './chips-group.js';
15
17
  import styles from './combobox.module.js';
16
- import { useLabelable } from '../util-components/labelable/useLabelable.js';
17
18
 
18
19
  const getOptionValue = option => typeof option === 'string' ? option : option.value;
19
20
  const getOptionFromValue = (value, options) => options.find(option => typeof option === 'string' ? option === value : option.value === value);
@@ -45,6 +46,7 @@ const RootInner = ({
45
46
  open,
46
47
  onOpenChange,
47
48
  onFocus,
49
+ onBlur,
48
50
  defaultInputValue = '',
49
51
  inputValue: inputValueProp,
50
52
  onInputValueChange,
@@ -70,11 +72,12 @@ const RootInner = ({
70
72
  focusLoop,
71
73
  clearButtonAriaLabel,
72
74
  endComboboxIcon,
75
+ idForLabel,
73
76
  ...comboboxProps
74
77
  } = rest;
75
78
  const isInput = useMemo(() => as === 'input', [as]);
76
79
  const inputRef = useRef(null);
77
- const comboboxId = useUniqueId('combobox-');
80
+ const comboboxId = useUniqueId('combobox-', !idForLabel) || idForLabel || 'default-combobox-id';
78
81
  const popoverRef = useRef(null);
79
82
  const popoverRefCallback = useCallback(node => {
80
83
  popoverRef.current = node;
@@ -277,13 +280,15 @@ const RootInner = ({
277
280
  ...comboboxProps,
278
281
  ref: reference,
279
282
  "aria-describedby": ariaDescribedBy,
280
- "aria-invalid": hasError,
283
+ ...(hasError && {
284
+ 'aria-invalid': 'true'
285
+ }),
281
286
  "aria-required": required,
282
287
  autoSelect: false,
283
288
  className: styles.textInput,
284
289
  disabled: disabled,
285
290
  id: comboboxId,
286
- onBlur: handleOnBlur,
291
+ onBlur: composeEventHandlers(onBlur, handleOnBlur),
287
292
  onFocus: handleOnFocus,
288
293
  onKeyDown: handleKeyDown,
289
294
  required: required,
@@ -315,14 +320,16 @@ const RootInner = ({
315
320
  id: comboboxId,
316
321
  render: jsx(TextArea, {
317
322
  ref: reference,
318
- "aria-invalid": hasError,
323
+ ...(hasError && {
324
+ 'aria-invalid': 'true'
325
+ }),
319
326
  "aria-required": required,
320
327
  className: styles.textInput,
321
328
  disabled: disabled,
322
329
  error: error,
323
330
  hideLabel: hideLabel,
324
331
  label: label,
325
- onBlur: handleOnBlur,
332
+ onBlur: composeEventHandlers(onBlur, handleOnBlur),
326
333
  onFocus: handleOnFocus,
327
334
  onKeyDown: handleKeyDown,
328
335
  required: required
@@ -1,4 +1,4 @@
1
1
  import '../index.css';
2
- var styles = {"container":"bp_combobox_module_container--92da0","disabled":"bp_combobox_module_disabled--92da0","label":"bp_combobox_module_label--92da0","hiddenLabel":"bp_combobox_module_hiddenLabel--92da0","comboboxContainer":"bp_combobox_module_comboboxContainer--92da0","withComboboxButtons":"bp_combobox_module_withComboboxButtons--92da0","error":"bp_combobox_module_error--92da0","textInputWrapper":"bp_combobox_module_textInputWrapper--92da0","textInput":"bp_combobox_module_textInput--92da0","errorIcon":"bp_combobox_module_errorIcon--92da0","comboboxButtons":"bp_combobox_module_comboboxButtons--92da0","withChips":"bp_combobox_module_withChips--92da0","inlineError":"bp_combobox_module_inlineError--92da0","popover":"bp_combobox_module_popover--92da0","option":"bp_combobox_module_option--92da0","indicator":"bp_combobox_module_indicator--92da0","indicatorIcon":"bp_combobox_module_indicatorIcon--92da0","optionWithIndicator":"bp_combobox_module_optionWithIndicator--92da0","loadingIndicator":"bp_combobox_module_loadingIndicator--92da0","noResultOption":"bp_combobox_module_noResultOption--92da0"};
2
+ var styles = {"container":"bp_combobox_module_container--4d653","disabled":"bp_combobox_module_disabled--4d653","label":"bp_combobox_module_label--4d653","hiddenLabel":"bp_combobox_module_hiddenLabel--4d653","comboboxContainer":"bp_combobox_module_comboboxContainer--4d653","withComboboxButtons":"bp_combobox_module_withComboboxButtons--4d653","error":"bp_combobox_module_error--4d653","textInputWrapper":"bp_combobox_module_textInputWrapper--4d653","textInput":"bp_combobox_module_textInput--4d653","errorIcon":"bp_combobox_module_errorIcon--4d653","comboboxButtons":"bp_combobox_module_comboboxButtons--4d653","withChips":"bp_combobox_module_withChips--4d653","inlineError":"bp_combobox_module_inlineError--4d653","popover":"bp_combobox_module_popover--4d653","option":"bp_combobox_module_option--4d653","indicator":"bp_combobox_module_indicator--4d653","indicatorIcon":"bp_combobox_module_indicatorIcon--4d653","optionWithIndicator":"bp_combobox_module_optionWithIndicator--4d653","loadingIndicator":"bp_combobox_module_loadingIndicator--4d653","noResultOption":"bp_combobox_module_noResultOption--4d653"};
3
3
 
4
4
  export { styles as default };
@@ -185,6 +185,10 @@ export interface ComboboxBaseProps<Multiple extends boolean, FreeInput extends b
185
185
  * Callback used when combobox input/textarea is focused
186
186
  */
187
187
  onFocus?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
188
+ /**
189
+ * Callback used when combobox input/textarea loses focus
190
+ */
191
+ onBlur?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
188
192
  /**
189
193
  * aria-label passed to the Combobox clear button. If not provided, the clear button is not shown.
190
194
  */
@@ -194,6 +198,10 @@ export interface ComboboxBaseProps<Multiple extends boolean, FreeInput extends b
194
198
  * it disappears when the input is not empty
195
199
  */
196
200
  endComboboxIcon?: React.ReactNode;
201
+ /**
202
+ * `idForLabel` is used in the Combobox Group to define the id of the subcomponent associated with the Combobox Group label.
203
+ */
204
+ idForLabel?: string;
197
205
  }
198
206
  export type ComboboxTextArea = Pick<TextAreaProps, 'maxRows' | 'minRows' | 'maxLength' | 'aria-describedby'> & {
199
207
  as: 'textarea';
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import { type ComboboxGroupComboboxProps } from './types';
3
+ export declare const ComboboxGroupCombobox: import("react").ForwardRefExoticComponent<ComboboxGroupComboboxProps<true, true, import("../combobox").OptionValue> & import("react").RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,62 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import clsx from 'clsx';
3
+ import { forwardRef, useCallback, useEffect } from 'react';
4
+ import { Combobox } from '../combobox/combobox.js';
5
+ import { useComboboxGroupContext } from './combobox-group-context.js';
6
+ import styles from './combobox-group.module.js';
7
+
8
+ const ComboboxGroupCombobox = /*#__PURE__*/forwardRef((props, forwardedRef) => {
9
+ const {
10
+ position = 'leading',
11
+ error = false,
12
+ portalElement,
13
+ disabled,
14
+ ...rest
15
+ } = props;
16
+ const {
17
+ errors,
18
+ setError,
19
+ inlineErrorId,
20
+ isDisabled,
21
+ isSelectFocused,
22
+ setComboboxFocus,
23
+ comboboxId,
24
+ setSubcomponentId
25
+ } = useComboboxGroupContext();
26
+ const borderStyles = position === 'trailing' ? styles.trailing : styles.leading;
27
+ const handleFocus = useCallback(() => setComboboxFocus(true), [setComboboxFocus]);
28
+ const handleBlur = useCallback(() => setComboboxFocus(false), [setComboboxFocus]);
29
+ const selectHasError = !!errors.select;
30
+ const comboboxHasError = !!errors.combobox;
31
+ const isLeading = position === 'leading';
32
+ useEffect(() => {
33
+ if (isLeading) {
34
+ setSubcomponentId(comboboxId);
35
+ }
36
+ }, [isLeading, comboboxId, setSubcomponentId]);
37
+ useEffect(() => {
38
+ setError('combobox', error);
39
+ }, [error, setError]);
40
+ return jsx(Combobox, {
41
+ as: "input",
42
+ ...rest,
43
+ ref: forwardedRef,
44
+ "aria-describedby": comboboxHasError ? inlineErrorId : undefined,
45
+ "aria-invalid": comboboxHasError,
46
+ className: clsx(borderStyles, {
47
+ [styles.errorSelect]: selectHasError
48
+ }, {
49
+ [styles.errorCombobox]: comboboxHasError
50
+ }, {
51
+ [styles.selectHasFocus]: isSelectFocused
52
+ }),
53
+ disabled: isDisabled,
54
+ error: null,
55
+ idForLabel: comboboxId,
56
+ onBlur: handleBlur,
57
+ onFocus: handleFocus,
58
+ portalElement: portalElement
59
+ });
60
+ });
61
+
62
+ export { ComboboxGroupCombobox };
@@ -0,0 +1,24 @@
1
+ import React, { type ReactNode } from 'react';
2
+ interface ComboboxGroupContextType {
3
+ errors: {
4
+ combobox?: boolean;
5
+ select?: boolean;
6
+ };
7
+ setError: (type: 'combobox' | 'select', error: boolean) => void;
8
+ inlineErrorId: string;
9
+ selectId: string;
10
+ comboboxId: string;
11
+ subcomponentId: string;
12
+ setSubcomponentId: (id: string) => void;
13
+ isDisabled: boolean;
14
+ isComboboxFocused: boolean;
15
+ setComboboxFocus: (focus: boolean) => void;
16
+ isSelectFocused: boolean;
17
+ setSelectFocus: (focus: boolean) => void;
18
+ }
19
+ export declare const ComboboxGroupProvider: React.FC<{
20
+ children: ReactNode;
21
+ isDisabled: boolean;
22
+ }>;
23
+ export declare const useComboboxGroupContext: () => ComboboxGroupContextType;
24
+ export {};
@@ -0,0 +1,50 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useState, useCallback, useMemo, createContext, useContext } from 'react';
3
+ import { useUniqueId } from '../utils/useUniqueId.js';
4
+
5
+ const ComboboxGroupContext = /*#__PURE__*/createContext(undefined);
6
+ const ComboboxGroupProvider = ({
7
+ children,
8
+ isDisabled
9
+ }) => {
10
+ const [errors, setErrors] = useState({});
11
+ const [isComboboxFocused, setComboboxFocus] = useState(false);
12
+ const [isSelectFocused, setSelectFocus] = useState(false);
13
+ const [subcomponentId, setSubcomponentId] = useState('');
14
+ const inlineErrorId = useUniqueId('inline-error-');
15
+ const selectId = useUniqueId('select-');
16
+ const comboboxId = useUniqueId('combobox-');
17
+ const setError = useCallback((type, hasError) => {
18
+ setErrors(prev => ({
19
+ ...prev,
20
+ [type]: hasError
21
+ }));
22
+ }, []);
23
+ const contextValue = useMemo(() => ({
24
+ isDisabled,
25
+ errors,
26
+ setError,
27
+ inlineErrorId,
28
+ isComboboxFocused,
29
+ setComboboxFocus,
30
+ isSelectFocused,
31
+ setSelectFocus,
32
+ subcomponentId,
33
+ setSubcomponentId,
34
+ selectId,
35
+ comboboxId
36
+ }), [isDisabled, errors, setError, inlineErrorId, isComboboxFocused, setComboboxFocus, isSelectFocused, setSelectFocus, subcomponentId, setSubcomponentId, selectId, comboboxId]);
37
+ return jsx(ComboboxGroupContext.Provider, {
38
+ value: contextValue,
39
+ children: children
40
+ });
41
+ };
42
+ const useComboboxGroupContext = () => {
43
+ const context = useContext(ComboboxGroupContext);
44
+ if (!context) {
45
+ throw new Error('useComboboxGroupContext must be used within a component wrapped in ComboboxGroup.');
46
+ }
47
+ return context;
48
+ };
49
+
50
+ export { ComboboxGroupProvider, useComboboxGroupContext };
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import { type ComboboxGroupSelectProps } from './types';
3
+ export declare const ComboboxGroupSelect: import("react").ForwardRefExoticComponent<ComboboxGroupSelectProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,59 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import clsx from 'clsx';
3
+ import { forwardRef, useCallback, useEffect } from 'react';
4
+ import { Select } from '../select/select.js';
5
+ import { useComboboxGroupContext } from './combobox-group-context.js';
6
+ import styles from './combobox-group.module.js';
7
+
8
+ const ComboboxGroupSelect = /*#__PURE__*/forwardRef((props, forwardedRef) => {
9
+ const {
10
+ position = 'trailing',
11
+ error = false,
12
+ disabled,
13
+ ...rest
14
+ } = props;
15
+ const {
16
+ errors,
17
+ setError,
18
+ inlineErrorId,
19
+ isDisabled,
20
+ setSelectFocus,
21
+ isComboboxFocused,
22
+ setSubcomponentId,
23
+ selectId
24
+ } = useComboboxGroupContext();
25
+ const handleFocus = useCallback(() => setSelectFocus(true), [setSelectFocus]);
26
+ const handleBlur = useCallback(() => setSelectFocus(false), [setSelectFocus]);
27
+ const borderStyles = position === 'trailing' ? styles.trailing : styles.leading;
28
+ const selectHasError = !!errors.select;
29
+ const comboboxHasError = !!errors.combobox;
30
+ const isLeading = position === 'leading';
31
+ useEffect(() => {
32
+ if (isLeading) {
33
+ setSubcomponentId(selectId);
34
+ }
35
+ }, [isLeading, selectId, setSubcomponentId]);
36
+ useEffect(() => {
37
+ setError('select', error);
38
+ }, [error, setError]);
39
+ return jsx(Select, {
40
+ ...rest,
41
+ ref: forwardedRef,
42
+ "aria-describedby": selectHasError ? inlineErrorId : undefined,
43
+ "aria-invalid": selectHasError,
44
+ className: clsx(borderStyles, {
45
+ [styles.errorSelect]: selectHasError
46
+ }, {
47
+ [styles.errorCombobox]: comboboxHasError
48
+ }, {
49
+ [styles.comboboxHasFocus]: isComboboxFocused
50
+ }),
51
+ disabled: isDisabled,
52
+ error: null,
53
+ id: selectId,
54
+ onBlur: handleBlur,
55
+ onFocus: handleFocus
56
+ });
57
+ });
58
+
59
+ export { ComboboxGroupSelect };
@@ -0,0 +1,7 @@
1
+ /// <reference types="react" />
2
+ import { type ComboboxGroupProps } from './types';
3
+ export declare const ComboboxGroup: {
4
+ ({ children, disabled, ...props }: ComboboxGroupProps): import("react/jsx-runtime").JSX.Element;
5
+ Combobox: import("react").ForwardRefExoticComponent<import("./types").ComboboxGroupComboboxProps<true, true, import("../combobox").OptionValue> & import("react").RefAttributes<HTMLInputElement>>;
6
+ Select: import("react").ForwardRefExoticComponent<import("./types").ComboboxGroupSelectProps & import("react").RefAttributes<HTMLButtonElement>>;
7
+ };
@@ -0,0 +1,61 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import clsx from 'clsx';
3
+ import { forwardRef } from 'react';
4
+ import { InlineError } from '../primitives/inline-error/inline-error.js';
5
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
6
+ import { ComboboxGroupCombobox } from './combobox-group-combobox.js';
7
+ import { ComboboxGroupProvider, useComboboxGroupContext } from './combobox-group-context.js';
8
+ import { ComboboxGroupSelect } from './combobox-group-select.js';
9
+ import styles from './combobox-group.module.js';
10
+
11
+ const Root = /*#__PURE__*/forwardRef(({
12
+ children,
13
+ label,
14
+ errorMessage,
15
+ hideLabel,
16
+ ...props
17
+ }, forwardedRef) => {
18
+ const {
19
+ errors,
20
+ inlineErrorId,
21
+ isDisabled,
22
+ subcomponentId
23
+ } = useComboboxGroupContext();
24
+ const selectHasError = !!errors.select;
25
+ const comboboxHasError = !!errors.combobox;
26
+ const Label = useLabelable(label, subcomponentId);
27
+ return jsxs("div", {
28
+ ...props,
29
+ ref: forwardedRef,
30
+ className: clsx(styles.container, {
31
+ [styles.disabled]: isDisabled
32
+ }),
33
+ children: [jsx(Label, {
34
+ className: styles.label,
35
+ hideLabel: hideLabel
36
+ }), jsx("div", {
37
+ className: styles.comboboxGroupcontainer,
38
+ children: children
39
+ }), (comboboxHasError || selectHasError) && jsx(InlineError, {
40
+ id: inlineErrorId,
41
+ children: errorMessage
42
+ })]
43
+ });
44
+ });
45
+ const ComboboxGroup = ({
46
+ children,
47
+ disabled,
48
+ ...props
49
+ }) => {
50
+ return jsx(ComboboxGroupProvider, {
51
+ isDisabled: !!disabled,
52
+ children: jsx(Root, {
53
+ ...props,
54
+ children: children
55
+ })
56
+ });
57
+ };
58
+ ComboboxGroup.Combobox = ComboboxGroupCombobox;
59
+ ComboboxGroup.Select = ComboboxGroupSelect;
60
+
61
+ export { ComboboxGroup };
@@ -0,0 +1,4 @@
1
+ import '../index.css';
2
+ var styles = {"container":"bp_combobox_group_module_container--88b97","comboboxGroupcontainer":"bp_combobox_group_module_comboboxGroupcontainer--88b97","disabled":"bp_combobox_group_module_disabled--88b97","label":"bp_combobox_group_module_label--88b97","trailing":"bp_combobox_group_module_trailing--88b97","leading":"bp_combobox_group_module_leading--88b97","errorCombobox":"bp_combobox_group_module_errorCombobox--88b97","errorSelect":"bp_combobox_group_module_errorSelect--88b97","selectHasFocus":"bp_combobox_group_module_selectHasFocus--88b97","comboboxHasFocus":"bp_combobox_group_module_comboboxHasFocus--88b97"};
3
+
4
+ export { styles as default };
@@ -0,0 +1,2 @@
1
+ export * from './combobox-group';
2
+ export * from './types';
@@ -0,0 +1,22 @@
1
+ /// <reference types="react" />
2
+ import { type ComboboxBaseProps, type OptionValue } from '../combobox/types';
3
+ import { type SelectProps } from '../select/types';
4
+ export interface ComboboxGroupProps {
5
+ children: React.ReactNode;
6
+ disabled?: boolean;
7
+ errorMessage?: string;
8
+ label: string;
9
+ hideLabel?: boolean;
10
+ }
11
+ export interface ComboboxGroupSelectProps extends SelectProps {
12
+ disabled?: never;
13
+ error?: boolean;
14
+ label: never;
15
+ position?: 'leading' | 'trailing';
16
+ }
17
+ export interface ComboboxGroupComboboxProps<Multiple extends boolean = true, FreeInput extends boolean = true, T extends OptionValue = OptionValue> extends ComboboxBaseProps<Multiple, FreeInput, T> {
18
+ disabled?: never;
19
+ error?: boolean;
20
+ label: never;
21
+ position?: 'leading' | 'trailing';
22
+ }