@box/blueprint-web 7.24.0 → 7.24.1

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.
@@ -11,9 +11,9 @@ import { InlineError } from '../primitives/inline-error/inline-error.js';
11
11
  import { TextArea } from '../text-area/text-area.js';
12
12
  import { useForkRef } from '../utils/useForkRef.js';
13
13
  import { useUniqueId } from '../utils/useUniqueId.js';
14
- import { VisuallyHidden } from '../visually-hidden/visually-hidden.js';
15
14
  import { ChipsGroup } from './chips-group.js';
16
15
  import styles from './combobox.module.js';
16
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
17
17
 
18
18
  const getOptionValue = option => typeof option === 'string' ? option : option.value;
19
19
  const getOptionFromValue = (value, options) => options.find(option => typeof option === 'string' ? option === value : option.value === value);
@@ -236,15 +236,13 @@ const RootInner = ({
236
236
  const reference = useForkRef(inputRef, ref);
237
237
  const showChipsGroup = Array.isArray(selectedValue) && selectedValue.length > 0;
238
238
  const showComboboxCancelButton = clearButtonAriaLabel && (inputValue.length > 0 || (Array.isArray(selectedValue) ? selectedValue.length > 0 : !!selectedValue));
239
+ const Label = useLabelable(label, comboboxId);
239
240
  const inputComponent = isInput ? jsxs(Fragment, {
240
- children: [jsx("label", {
241
+ children: [jsx(Label, {
241
242
  className: clsx(styles.label, {
242
243
  [styles.hiddenLabel]: hideLabel
243
244
  }),
244
- htmlFor: comboboxId,
245
- children: hideLabel ? jsx(VisuallyHidden, {
246
- children: label
247
- }) : label
245
+ hideLabel: hideLabel
248
246
  }), jsx(PopoverAnchor, {
249
247
  store: comboboxStore,
250
248
  children: jsxs("div", {
@@ -2,6 +2,7 @@
2
2
  import { type SelectItemProps } from '@ariakit/react-core/select/select-item';
3
3
  import { type SelectRendererProps } from '@ariakit/react-core/select/select-renderer';
4
4
  import { type TextAreaProps } from '../text-area/types';
5
+ import { type Labelable } from '../util-components/labelable/types';
5
6
  export type OptionValue = {
6
7
  value: string;
7
8
  disabled?: boolean;
@@ -9,11 +10,7 @@ export type OptionValue = {
9
10
  export type SingleSelectComboboxValue<FreeInput, T> = FreeInput extends true ? string : T;
10
11
  export type MultiSelectComboboxValue<FreeInput, T> = FreeInput extends true ? string[] : T[];
11
12
  export type ComboboxValue<Multiple, FreeInput, T> = Multiple extends true ? MultiSelectComboboxValue<FreeInput, T> : SingleSelectComboboxValue<FreeInput, T>;
12
- export interface ComboboxBaseProps<Multiple extends boolean, FreeInput extends boolean, T extends OptionValue> {
13
- /**
14
- * The label for the combobox.
15
- */
16
- label: string;
13
+ export interface ComboboxBaseProps<Multiple extends boolean, FreeInput extends boolean, T extends OptionValue> extends Labelable {
17
14
  /**
18
15
  * The list of values for the combobox items.
19
16
  */
@@ -4,12 +4,13 @@ import { Gray65, Size4 } from '@box/blueprint-web-assets/tokens/tokens';
4
4
  import * as RadixPopover from '@radix-ui/react-popover';
5
5
  import clsx from 'clsx';
6
6
  import { forwardRef, useState, useEffect, useRef, useCallback } from 'react';
7
- import { DatePicker as DatePicker$1, Label, Group, DateInput, DateSegment, Button } from 'react-aria-components';
7
+ import { DatePicker as DatePicker$1, Group, DateInput, DateSegment, Button } from 'react-aria-components';
8
8
  import { Calendar as Calendar$1 } from '../primitives/calendar/calendar.js';
9
9
  import '@internationalized/date';
10
10
  import { IconButton } from '../primitives/icon-button/icon-button.js';
11
11
  import { InlineError } from '../primitives/inline-error/inline-error.js';
12
12
  import styles from './date-picker.module.js';
13
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
13
14
 
14
15
  const XMarkIcon = label => () => jsx(XMark, {
15
16
  "aria-label": label,
@@ -23,6 +24,7 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
23
24
  onChange,
24
25
  value,
25
26
  defaultValue,
27
+ hideLabel = false,
26
28
  label,
27
29
  openCalendarDropdownAriaLabel,
28
30
  clearDatePickerAriaLabel,
@@ -108,6 +110,7 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
108
110
  }
109
111
  }, []);
110
112
  const displayValue = value !== undefined ? value : internalValue;
113
+ const Label = useLabelable(label, '');
111
114
  return jsx(RadixPopover.Root, {
112
115
  onOpenChange: setIsCalendarOpen,
113
116
  open: isCalendarOpen,
@@ -124,7 +127,7 @@ const DatePicker = /*#__PURE__*/forwardRef((props, forwardedRef) => {
124
127
  className: clsx(styles.label, {
125
128
  [styles.disabled]: isDisabled
126
129
  }),
127
- children: label
130
+ hideLabel: hideLabel
128
131
  }), jsx(RadixPopover.Anchor, {
129
132
  asChild: true,
130
133
  children: jsxs(Group, {
@@ -3,11 +3,14 @@ import { type DatePickerProps as RACDatePickerProps, type DateValue } from 'reac
3
3
  import { type CalendarBase } from '../primitives/calendar/types';
4
4
  import { type InputValueProps } from '../types/input-value-props';
5
5
  import { type Modify } from '../types/modify';
6
- interface DatePickerPropsBase extends Omit<RACDatePickerProps<DateValue>, 'children'> {
6
+ import { type Labelable } from '../util-components/labelable/types';
7
+ interface DatePickerPropsBase extends Omit<RACDatePickerProps<DateValue>, 'children'>, Labelable {
7
8
  /**
8
- * Label displayed above date-picker input
9
+ * When `true`, label text is hidden.
10
+ *
11
+ * @default false
9
12
  */
10
- label: string;
13
+ hideLabel?: boolean;
11
14
  /**
12
15
  * Aria label for button opening calendar in DatePicker
13
16
  */
@@ -2,9 +2,9 @@ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
3
  import { forwardRef, useMemo, cloneElement } from 'react';
4
4
  import { useUniqueId } from '../../utils/useUniqueId.js';
5
- import { VisuallyHidden } from '../../visually-hidden/visually-hidden.js';
6
5
  import { InlineError } from '../inline-error/inline-error.js';
7
6
  import styles from './base-text-input.module.js';
7
+ import { useLabelable } from '../../util-components/labelable/useLabelable.js';
8
8
 
9
9
  /**
10
10
  * This extends a default HTML &lt;input/&gt; and accepts the same props as well as some custom ones listed below.<br/>
@@ -34,20 +34,18 @@ const BaseTextInput = /*#__PURE__*/forwardRef((props, forwardedRef) => {
34
34
  const IconComponent = useMemo(() => icon && /*#__PURE__*/cloneElement(icon, {
35
35
  className: clsx(icon.props?.className, styles.iconShared)
36
36
  }), [icon]);
37
+ const Label = useLabelable(label, inputId);
37
38
  return jsxs(Fragment, {
38
39
  children: [jsxs("div", {
39
40
  className: clsx([styles.textInputContainer], {
40
41
  [styles.disabled]: disabled,
41
42
  [styles.error]: shouldMarkError
42
43
  }, className),
43
- children: [jsx("label", {
44
+ children: [jsx(Label, {
44
45
  className: clsx([styles.label], {
45
46
  [styles.hidden]: hideLabel
46
47
  }),
47
- htmlFor: inputId,
48
- children: hideLabel ? jsx(VisuallyHidden, {
49
- children: label
50
- }) : label
48
+ hideLabel: hideLabel
51
49
  }), jsx("input", {
52
50
  ...rest,
53
51
  ...(hasError && {
@@ -1,7 +1,6 @@
1
1
  /// <reference types="react" />
2
- export type BaseTextInputProps = React.ComponentPropsWithRef<'input'> & {
3
- /** The label for text input */
4
- label: string;
2
+ import { type Labelable } from '../../util-components/labelable/types';
3
+ export type BaseTextInputProps = React.ComponentPropsWithRef<'input'> & Labelable & {
5
4
  /** Input type, defaults to 'text' */
6
5
  type?: 'text' | 'password';
7
6
  /** When true label text is hidden */
@@ -1,6 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { Caret, Checkmark } from '@box/blueprint-web-assets/icons/Fill';
3
- import { Label } from '@radix-ui/react-label';
4
3
  import * as ScrollArea from '@radix-ui/react-scroll-area';
5
4
  import * as SelectPrimitive from '@radix-ui/react-select';
6
5
  import clsx from 'clsx';
@@ -8,6 +7,7 @@ import { forwardRef, useCallback } from 'react';
8
7
  import { InlineError } from '../primitives/inline-error/inline-error.js';
9
8
  import { useUniqueId } from '../utils/useUniqueId.js';
10
9
  import styles from './select.module.js';
10
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
11
11
 
12
12
  const Root = /*#__PURE__*/forwardRef((props, forwardedRef) => {
13
13
  const {
@@ -50,21 +50,19 @@ const Root = /*#__PURE__*/forwardRef((props, forwardedRef) => {
50
50
  onValueChange,
51
51
  onOpenChange: handleOnChange
52
52
  };
53
+ const Label = useLabelable(label, selectId);
53
54
  return jsxs("div", {
54
55
  className: clsx(styles.container, {
55
56
  [styles.disabled]: disabled
56
57
  }, className),
57
58
  children: [jsx(Label, {
58
59
  className: styles.label,
59
- hidden: hideLabel,
60
- htmlFor: selectId,
61
- children: label
60
+ hideLabel: hideLabel
62
61
  }), jsxs(SelectPrimitive.Root, {
63
62
  ...selectProps,
64
63
  children: [jsxs(SelectPrimitive.Trigger, {
65
64
  ...triggerProps,
66
65
  ref: forwardedRef,
67
- "aria-label": label,
68
66
  ...(hasError && {
69
67
  'aria-invalid': 'true'
70
68
  }),
@@ -1,11 +1,8 @@
1
1
  import { type SelectItemProps as RadixItemProps, type SelectContentProps as RadixSelectContentProps, type SelectProps as RadixSelectProps, type SelectTriggerProps as RadixSelectTriggerProps } from '@radix-ui/react-select';
2
2
  import { type ReactElement } from 'react';
3
3
  import { type Modify } from '../types/modify';
4
- export interface ExtraProps {
5
- /**
6
- * The label for the select.
7
- */
8
- label: string;
4
+ import { type Labelable } from '../util-components/labelable/types';
5
+ export interface ExtraProps extends Labelable {
9
6
  /**
10
7
  * The placeholder value displayed within the select trigger button when no values has been selected yet.
11
8
  */
@@ -1,8 +1,8 @@
1
- import { type HTMLAttributes } from 'react';
1
+ import { type HTMLAttributes, type LabelHTMLAttributes } from 'react';
2
2
  import { type StyledText } from '../utils/commonTypes';
3
3
  type TypographyVariant = 'titleMondo' | 'titleXLarge' | 'titleLarge' | 'titleMedium' | 'titleSmall' | 'subtitle' | 'bodyLargeBold' | 'bodyLarge' | 'bodyDefaultBold' | 'bodyDefaultSemibold' | 'bodyDefault' | 'caption' | 'labelBold' | 'label';
4
4
  type TypographyColors = 'textOnLightDefault' | 'textOnLightSecondary' | 'textOnLightLink' | 'textOnDarkDefault';
5
- type Element = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'legend';
5
+ type Element = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'legend' | 'label';
6
6
  interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
7
7
  as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
8
8
  }
@@ -15,8 +15,11 @@ interface SpanProps extends HTMLAttributes<HTMLSpanElement> {
15
15
  interface LegendProps extends HTMLAttributes<HTMLLegendElement> {
16
16
  as: 'legend';
17
17
  }
18
+ interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {
19
+ as: 'label';
20
+ }
18
21
  /** Supported HTML elements for text */
19
- export type ElementTypes = HeadingProps | ParagraphProps | LegendProps | SpanProps;
22
+ export type ElementTypes = HeadingProps | ParagraphProps | LegendProps | SpanProps | LabelProps;
20
23
  export type TextProps = {
21
24
  /** The HTML element type which will render */
22
25
  as: Element;
@@ -1,12 +1,10 @@
1
1
  /// <reference types="react" />
2
- export declare const TextArea: import("react").ForwardRefExoticComponent<(Omit<Omit<import("./text-area-autosize/types").TextareaAutosizeProps, "hasError"> & {
3
- label: string;
2
+ export declare const TextArea: import("react").ForwardRefExoticComponent<(Omit<import("../util-components/labelable/types").Labelable & Omit<import("./text-area-autosize/types").TextareaAutosizeProps, "hasError"> & {
4
3
  hideLabel?: boolean | undefined;
5
4
  disabled?: boolean | undefined;
6
5
  required?: boolean | undefined;
7
6
  error?: import("react").ReactNode;
8
- } & Required<Pick<import("./types").Loading, keyof import("./types").Loading>> & Omit<import("./types").Loading, keyof import("./types").Loading>, "ref"> | Omit<Omit<import("./text-area-autosize/types").TextareaAutosizeProps, "hasError"> & {
9
- label: string;
7
+ } & Required<Pick<import("./types").Loading, keyof import("./types").Loading>> & Omit<import("./types").Loading, keyof import("./types").Loading>, "ref"> | Omit<import("../util-components/labelable/types").Labelable & Omit<import("./text-area-autosize/types").TextareaAutosizeProps, "hasError"> & {
10
8
  hideLabel?: boolean | undefined;
11
9
  disabled?: boolean | undefined;
12
10
  required?: boolean | undefined;
@@ -3,9 +3,9 @@ import clsx from 'clsx';
3
3
  import { forwardRef } from 'react';
4
4
  import { InlineError } from '../primitives/inline-error/inline-error.js';
5
5
  import { useUniqueId } from '../utils/useUniqueId.js';
6
- import { VisuallyHidden } from '../visually-hidden/visually-hidden.js';
7
6
  import { TextAreaAutosize } from './text-area-autosize/text-area-autosize.js';
8
7
  import styles from './text-area.module.js';
8
+ import { useLabelable } from '../util-components/labelable/useLabelable.js';
9
9
 
10
10
  const TextArea = /*#__PURE__*/forwardRef((props, forwardedRef) => {
11
11
  const {
@@ -22,19 +22,17 @@ const TextArea = /*#__PURE__*/forwardRef((props, forwardedRef) => {
22
22
  } = props;
23
23
  const textAreaId = useUniqueId('text-area-');
24
24
  const hasError = !!error && !disabled;
25
+ const Label = useLabelable(label, textAreaId);
25
26
  return jsxs("div", {
26
27
  className: clsx([styles.textAreaContainer], {
27
28
  [styles.disabled]: disabled,
28
29
  [styles.error]: hasError
29
30
  }, className),
30
- children: [jsx("label", {
31
+ children: [jsx(Label, {
31
32
  className: clsx([styles.label], {
32
33
  [styles.hidden]: hideLabel
33
34
  }),
34
- htmlFor: textAreaId,
35
- children: hideLabel ? jsx(VisuallyHidden, {
36
- children: label
37
- }) : label
35
+ hideLabel: hideLabel
38
36
  }), jsx(TextAreaAutosize, {
39
37
  ...rest,
40
38
  ref: forwardedRef,
@@ -1,15 +1,14 @@
1
1
  /// <reference types="react" />
2
2
  import { type RequireAllOrNone } from 'type-fest';
3
3
  import { type TextareaAutosizeProps } from './text-area-autosize/types';
4
+ import { type Labelable } from '../util-components/labelable/types';
4
5
  export interface Loading {
5
6
  /** When `true` TextArea is rendered with loading indicator. When this is true `loadingAriaLabel` must also be provided. */
6
7
  loading?: boolean;
7
8
  /** The aria-label for the loading indicator. */
8
9
  loadingAriaLabel?: string;
9
10
  }
10
- export type TextAreaProps = Omit<TextareaAutosizeProps, 'hasError'> & {
11
- /** The label for text area */
12
- label: string;
11
+ export type TextAreaProps = Labelable & Omit<TextareaAutosizeProps, 'hasError'> & {
13
12
  /** When true label text is hidden */
14
13
  hideLabel?: boolean;
15
14
  /** When true prevents user interaction with text area */
@@ -0,0 +1 @@
1
+ export * from './useLabelable';
@@ -0,0 +1,15 @@
1
+ import type React from 'react';
2
+ export type RenderProp<P = React.HTMLAttributes<never> & {
3
+ ref?: React.Ref<never>;
4
+ }> = (props: P) => React.ReactElement;
5
+ type HtmlForable = Pick<React.LabelHTMLAttributes<HTMLLabelElement>, 'htmlFor'>;
6
+ export interface Labelable {
7
+ label: string | React.ReactElement<HtmlForable> | RenderProp<HtmlForable>;
8
+ }
9
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
10
+ htmlFor: string;
11
+ hideLabel?: boolean;
12
+ ref: React.ForwardedRef<HTMLLabelElement>;
13
+ elementType?: 'span' | 'label';
14
+ }
15
+ export {};
@@ -0,0 +1,3 @@
1
+ /// <reference types="react" />
2
+ import { type Labelable, type LabelProps } from './types';
3
+ export declare const useLabelable: (label: Labelable['label'], inputId: string) => import("react").ForwardRefExoticComponent<Omit<Omit<LabelProps, "htmlFor">, "ref"> & import("react").RefAttributes<HTMLLabelElement>>;
@@ -0,0 +1,69 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { useMemo, forwardRef } from 'react';
3
+ import { Role } from '@ariakit/react';
4
+ import { useContextProps, LabelContext } from 'react-aria-components';
5
+ import { VisuallyHidden } from '../../visually-hidden/visually-hidden.js';
6
+
7
+ const useLabelable = (label, inputId) => {
8
+ return useMemo(() => {
9
+ const Label = /*#__PURE__*/forwardRef((props, ref) => {
10
+ // If this component is rendered within LabelContext from RAC, we can have props like htmlFor, id, elementType.
11
+ [props, ref] = useContextProps(props, ref, LabelContext);
12
+ const {
13
+ hideLabel,
14
+ elementType = 'label',
15
+ ...rest
16
+ } = props;
17
+ const labelProps = {
18
+ ...rest,
19
+ htmlFor: inputId,
20
+ ref
21
+ };
22
+ if (!label) {
23
+ return null;
24
+ }
25
+ if (typeof label === 'string') {
26
+ if (elementType === 'label') {
27
+ return hideLabel ? jsx(VisuallyHidden, {
28
+ render: jsx(Role.label, {
29
+ ...labelProps,
30
+ children: label
31
+ })
32
+ }) : jsx(Role.label, {
33
+ ...labelProps,
34
+ children: label
35
+ });
36
+ }
37
+ return hideLabel ? jsx(VisuallyHidden, {
38
+ render: jsx(Role.span, {
39
+ ...rest,
40
+ ref: ref,
41
+ children: label
42
+ })
43
+ }) : jsx(Role.span, {
44
+ ...rest,
45
+ ref: ref,
46
+ children: label
47
+ });
48
+ }
49
+ return elementType === 'label' ? jsx(Role.label, {
50
+ ...labelProps,
51
+ // @ts-expect-error: Otherwise we have a conflict of HTMLAttributes and our Blueprint components props.
52
+ render: hideLabel ? jsx(VisuallyHidden, {
53
+ render: label
54
+ }) : label
55
+ }) : jsx(Role.span, {
56
+ ...rest,
57
+ ref: ref,
58
+ // @ts-expect-error: Otherwise we have a conflict of HTMLAttributes and our Blueprint components props.
59
+ render: hideLabel ? jsx(VisuallyHidden, {
60
+ render: label
61
+ }) : label
62
+ });
63
+ });
64
+ Label.displayName = 'Label';
65
+ return Label;
66
+ }, [label, inputId]);
67
+ };
68
+
69
+ export { useLabelable };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@box/blueprint-web",
3
- "version": "7.24.0",
3
+ "version": "7.24.1",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@ariakit/react": "0.4.5",
26
26
  "@ariakit/react-core": "0.4.5",
27
- "@box/blueprint-web-assets": "^4.21.4",
27
+ "@box/blueprint-web-assets": "^4.21.5",
28
28
  "@internationalized/date": "^3.5.4",
29
29
  "@radix-ui/react-accordion": "1.1.2",
30
30
  "@radix-ui/react-checkbox": "1.0.4",
@@ -32,7 +32,6 @@
32
32
  "@radix-ui/react-context-menu": "2.1.5",
33
33
  "@radix-ui/react-dialog": "1.0.5",
34
34
  "@radix-ui/react-dropdown-menu": "2.0.6",
35
- "@radix-ui/react-label": "2.0.2",
36
35
  "@radix-ui/react-popover": "1.0.7",
37
36
  "@radix-ui/react-radio-group": "1.1.3",
38
37
  "@radix-ui/react-scroll-area": "1.0.5",
@@ -55,7 +54,7 @@
55
54
  "type-fest": "^3.2.0"
56
55
  },
57
56
  "devDependencies": {
58
- "@box/storybook-utils": "^0.6.0",
57
+ "@box/storybook-utils": "^0.6.1",
59
58
  "@types/react": "^18.0.0",
60
59
  "@types/react-dom": "^18.0.0",
61
60
  "react": "^18.3.0",
@@ -63,7 +62,7 @@
63
62
  "react-stately": "^3.31.1",
64
63
  "tsx": "^4.16.5"
65
64
  },
66
- "gitHead": "c45d13964d3b60b860c3606a0eb55fb86bc2e057",
65
+ "gitHead": "bb2a831c3e3b11c1af207a693263069f26f7076a",
67
66
  "module": "lib-esm/index.js",
68
67
  "main": "lib-esm/index.js",
69
68
  "exports": {