@dbcdk/react-components 0.0.55 → 0.0.57

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.
@@ -71,6 +71,7 @@ function isFilterActive(value) {
71
71
  }
72
72
  export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, minWidth, width, maxWidth, debounceTime = INPUT_DEBOUNCE_MS, ...inputProps }) {
73
73
  var _a, _b, _c, _d, _e, _f;
74
+ const filterFieldRef = useRef(null);
74
75
  const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
75
76
  const [selectedOperator, setSelectedOperator] = useState(operator);
76
77
  const active = isFilterActive(value);
@@ -156,7 +157,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
156
157
  clearDebounce();
157
158
  };
158
159
  }, []);
159
- return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: [styles.filterField, styles[size], styles[variant], active ? styles.active : '']
160
+ return (_jsxs("div", { ref: filterFieldRef, ...(dataCy ? { 'data-cy': dataCy } : {}), className: [styles.filterField, styles[size], styles[variant], active ? styles.active : '']
160
161
  .filter(Boolean)
161
162
  .join(' '), children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: handleOperatorChange, operators: ops, size: size, disabled: disabled }), _jsx("div", { className: [styles.valueWrapper, control === 'input' ? 'dbc-flex dbc-flex-grow' : '']
162
163
  .filter(Boolean)
@@ -171,7 +172,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
171
172
  pendingValueRef.current = '';
172
173
  setLocalValue('');
173
174
  emit({ value: '' });
174
- } })) : (_jsx(Typeahead, { options: options, mode: single ? 'single' : 'multi', selectedValue: single ? ((_f = value) !== null && _f !== void 0 ? _f : null) : Array.isArray(value) ? value : [], onChange: v => emit({ value: v }), minWidth: minWidth, placeholder: placeholder, variant: "embedded", inputProps: {
175
+ } })) : (_jsx(Typeahead, { options: options, mode: single ? 'single' : 'multi', selectedValue: single ? ((_f = value) !== null && _f !== void 0 ? _f : null) : Array.isArray(value) ? value : [], onChange: v => emit({ value: v }), minWidth: minWidth, popoverAnchorRef: filterFieldRef, placeholder: placeholder, variant: "embedded", inputProps: {
175
176
  inputSize: size,
176
177
  fieldClassName: styles.embeddedInputField,
177
178
  inputClassName: styles.embeddedInputElement,
@@ -28,6 +28,8 @@ export const Input = forwardRef(function Input({ label, error, helpText, orienta
28
28
  (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
29
29
  }, [autoFocus]);
30
30
  const hasButton = Boolean(onButtonClick || buttonLabel || buttonIcon);
31
+ const hasVisibleClear = Boolean(onClear && inputProps.value);
32
+ const hasInlineClear = Boolean(hasVisibleClear && endAdornment);
31
33
  const rootStyle = {
32
34
  ...(style !== null && style !== void 0 ? style : {}),
33
35
  ...(minWidth ? { ['--input-min-width']: minWidth } : null),
@@ -43,7 +45,8 @@ export const Input = forwardRef(function Input({ label, error, helpText, orienta
43
45
  return (_jsx(InputContainer, { label: label, htmlFor: inputId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: _jsxs("div", { style: rootStyle, className: [
44
46
  styles.container,
45
47
  fullWidth ? styles.fullWidth : '',
46
- onClear ? styles.withClear : '',
48
+ hasVisibleClear ? styles.withClear : '',
49
+ hasInlineClear ? styles.withInlineClear : '',
47
50
  hasButton ? styles.withButton : '',
48
51
  className !== null && className !== void 0 ? className : '',
49
52
  ]
@@ -59,6 +62,6 @@ export const Input = forwardRef(function Input({ label, error, helpText, orienta
59
62
  .filter(Boolean)
60
63
  .join(' '), "data-forminput": "field", "data-modified": modified ? 'true' : undefined, "aria-disabled": inputProps.disabled ? 'true' : undefined, ...(tooltip ? triggerProps : {}), children: [icon && _jsx("span", { className: styles.icon, children: icon }), startAdornment && _jsx("span", { className: styles.startAdornment, children: startAdornment }), _jsx("input", { ...inputProps, id: inputId, ref: mergeRefs(inputRef, ref), className: [styles.input, inputSize ? styles[inputSize] : '', inputClassName !== null && inputClassName !== void 0 ? inputClassName : '']
61
64
  .filter(Boolean)
62
- .join(' ') }), endAdornment && _jsx("span", { className: styles.endAdornment, children: endAdornment }), onClear && inputProps.value && _jsx(ClearButton, { onClick: onClear, absolute: true })] }), hasButton && (_jsxs(Button, { onClick: onButtonClick, className: styles.trailingButton, type: "button", variant: trailingButtonVariant, size: inputSize, children: [buttonIcon !== null && buttonIcon !== void 0 ? buttonIcon : null, buttonLabel !== null && buttonLabel !== void 0 ? buttonLabel : null] }))] }) }));
65
+ .join(' ') }), (hasInlineClear || endAdornment) && (_jsxs("span", { className: styles.endAdornment, children: [hasInlineClear && onClear ? _jsx(ClearButton, { onClick: onClear }) : null, endAdornment] })), hasVisibleClear && !hasInlineClear && onClear ? (_jsx(ClearButton, { onClick: onClear, absolute: true })) : null] }), hasButton && (_jsxs(Button, { onClick: onButtonClick, className: styles.trailingButton, type: "button", variant: trailingButtonVariant, size: inputSize, children: [buttonIcon !== null && buttonIcon !== void 0 ? buttonIcon : null, buttonLabel !== null && buttonLabel !== void 0 ? buttonLabel : null] }))] }) }));
63
66
  });
64
67
  Input.displayName = 'Input';
@@ -77,6 +77,10 @@
77
77
  padding-inline-end: calc(var(--spacing-xxs) + 16px + var(--spacing-xxs));
78
78
  }
79
79
 
80
+ .withInlineClear .input {
81
+ padding-inline-end: var(--spacing-xs);
82
+ }
83
+
80
84
  /* Global focus reset - variants own visible focus treatment */
81
85
  .input:focus-visible {
82
86
  outline: none;
@@ -395,6 +399,6 @@
395
399
  .endAdornment {
396
400
  display: flex;
397
401
  align-items: center;
398
- gap: 4px;
399
- margin-right: 4px;
402
+ margin-right: var(--spacing-xxs);
403
+ color: var(--color-fg-subtle);
400
404
  }
@@ -101,7 +101,7 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
101
101
  else {
102
102
  setSearchQuery('');
103
103
  }
104
- }, dataCy: dataCy, fullWidth: fullWidth, autoFocusContent: false, returnFocus: true, trigger: (onClick, icon, isOpen) => (_jsx(Button, { variant: variant, onClick: onClick, onKeyDown: handleCombinedKeyDown, size: size, fullWidth: fullWidth, disabled: disabled, "aria-haspopup": "menu", "aria-expanded": !!isOpen, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx("span", { children: children !== null && children !== void 0 ? children : placeholder }), selectedValues.length > 0 ? _jsx(Chip, { size: "sm", children: selectedValues.length }) : null] }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [onClear && selectedValues.length > 0 ? _jsx(ClearButton, { onClick: onClear }) : null, icon] })] }) })), children: _jsxs(Menu, { onKeyDown: handleCombinedKeyDown, children: [searchable ? (_jsx(Menu.Item, { children: _jsx(Input, { ref: searchInputRef, value: searchQuery, onChange: e => setSearchQuery(e.target.value), onKeyDown: handleCombinedKeyDown, placeholder: searchPlaceholder, icon: _jsx(Search, { size: 16 }), fullWidth: true }) })) : null, filteredOptions.map((option, index) => {
104
+ }, dataCy: dataCy, fullWidth: fullWidth, autoFocusContent: false, returnFocus: true, trigger: (onClick, icon, isOpen) => (_jsx(Button, { variant: variant, onClick: onClick, onKeyDown: handleCombinedKeyDown, size: size, fullWidth: fullWidth, disabled: disabled, "aria-haspopup": "menu", "aria-expanded": !!isOpen, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx("span", { children: children !== null && children !== void 0 ? children : placeholder }), selectedValues.length > 0 ? _jsx(Chip, { size: "sm", children: selectedValues.length }) : null] }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [onClear && selectedValues.length > 0 ? _jsx(ClearButton, { onClick: onClear }) : null, _jsx("span", { style: { color: 'var(--color-fg-subtle)', display: 'inline-flex' }, children: icon })] })] }) })), children: _jsxs(Menu, { onKeyDown: handleCombinedKeyDown, children: [searchable ? (_jsx(Menu.Item, { children: _jsx(Input, { ref: searchInputRef, value: searchQuery, onChange: e => setSearchQuery(e.target.value), onKeyDown: handleCombinedKeyDown, placeholder: searchPlaceholder, icon: _jsx(Search, { size: 16 }), fullWidth: true }) })) : null, filteredOptions.map((option, index) => {
105
105
  const isSelected = selectedSet.has(option.value);
106
106
  const isActive = index === activeIndex;
107
107
  return (_jsx(Menu.Item, { active: isActive, children: _jsxs("button", { ref: el => {
@@ -168,7 +168,7 @@ export function Select({ label, error, helpText, orientation = 'vertical', label
168
168
  returnFocus: true, trigger: (toggle, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
169
169
  resetActiveToSelected();
170
170
  toggle(e);
171
- }, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : _jsx("span", { className: "dbc-muted-text", children: placeholder }) }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] })] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
171
+ }, size: size, type: "button", "data-forminput": true, "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsx("span", { children: selected ? selected.label : _jsx("span", { className: "dbc-muted-text", children: placeholder }) }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [onClear && selected && _jsx(ClearButton, { onClick: onClear }), _jsx("span", { style: { color: 'var(--color-fg-subtle)', display: 'inline-flex' }, children: icon })] })] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
172
172
  const isSelected = typeof opt.value === 'object' && typeof selectedValue === 'object' && datakey
173
173
  ? (selectedValue === null || selectedValue === void 0 ? void 0 : selectedValue[datakey]) === opt.value[datakey]
174
174
  : opt.value === selectedValue;
@@ -30,6 +30,7 @@ interface TypeaheadProps<T> {
30
30
  autoCorrect?: InputProps['autoCorrect'];
31
31
  autoCapitalize?: InputProps['autoCapitalize'];
32
32
  spellCheck?: InputProps['spellCheck'];
33
+ popoverAnchorRef?: React.RefObject<HTMLElement | null>;
33
34
  }
34
- export declare function Typeahead<T extends string | number>({ options, mode, multiValueDisplayMode, multiSelectedValuesDisplayMode, multiSelectedValueChipContent, selectedValue, onChange, placeholder, variant, disabled, fullWidth, onClear, emptyMessage, filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, }: TypeaheadProps<T>): React.ReactElement;
35
+ export declare function Typeahead<T extends string | number>({ options, mode, multiValueDisplayMode, multiSelectedValuesDisplayMode, multiSelectedValueChipContent, selectedValue, onChange, placeholder, variant, disabled, fullWidth, onClear, emptyMessage, filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, popoverAnchorRef, }: TypeaheadProps<T>): React.ReactElement;
35
36
  export {};
@@ -7,14 +7,14 @@ import { Input } from '../../../components/forms/input/Input';
7
7
  import { Menu } from '../../../components/menu/Menu';
8
8
  import { Popover } from '../../../components/popover/Popover';
9
9
  import styles from './Typeahead.module.css';
10
- export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'chips', multiSelectedValuesDisplayMode = 'hidden', multiSelectedValueChipContent = 'label', selectedValue = null, onChange, placeholder, variant = 'outlined', disabled = false, fullWidth = false, onClear, emptyMessage = 'Ingen resultater', filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, }) {
10
+ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'chips', multiSelectedValuesDisplayMode = 'hidden', multiSelectedValueChipContent = 'label', selectedValue = null, onChange, placeholder, variant = 'outlined', disabled = false, fullWidth = false, onClear, emptyMessage = 'Ingen resultater', filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, popoverAnchorRef, }) {
11
11
  var _a;
12
12
  const inputRef = useRef(null);
13
13
  const listboxRef = useRef(null);
14
14
  const optionRefs = useRef([]);
15
15
  const interactingWithOptionsRef = useRef(false);
16
16
  const listboxId = useId();
17
- const { onFocus: inputPropsOnFocus, onBlur: inputPropsOnBlur, onKeyDown: inputPropsOnKeyDown, onMouseDown: inputPropsOnMouseDown, onClear: inputPropsOnClear, startAdornment: inputPropsStartAdornment, ...passthroughInputProps } = inputProps !== null && inputProps !== void 0 ? inputProps : {};
17
+ const { onFocus: inputPropsOnFocus, onBlur: inputPropsOnBlur, onKeyDown: inputPropsOnKeyDown, onMouseDown: inputPropsOnMouseDown, onClear: inputPropsOnClear, startAdornment: inputPropsStartAdornment, endAdornment: inputPropsEndAdornment, ...passthroughInputProps } = inputProps !== null && inputProps !== void 0 ? inputProps : {};
18
18
  const selectedOption = useMemo(() => {
19
19
  var _a;
20
20
  if (mode === 'multi')
@@ -270,7 +270,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
270
270
  ? 8
271
271
  : 0,
272
272
  width: fullWidth ? '100%' : undefined,
273
- }, children: [_jsx(Popover, { open: open, minWidth: minWidth, onOpenChange: nextOpen => {
273
+ }, children: [_jsx(Popover, { open: open, minWidth: minWidth, anchorRef: popoverAnchorRef, onOpenChange: nextOpen => {
274
274
  setOpen(nextOpen);
275
275
  if (nextOpen) {
276
276
  if (mode === 'single' && selectedOption) {
@@ -284,9 +284,9 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
284
284
  else {
285
285
  setActiveIndex(-1);
286
286
  }
287
- }, fullWidth: fullWidth, autoFocusContent: false, returnFocus: false, trigger: openPopover => {
287
+ }, fullWidth: fullWidth, autoFocusContent: false, returnFocus: false, trigger: (openPopover, icon) => {
288
288
  var _a, _b, _c, _d, _e;
289
- return (_jsx(Input, { ...passthroughInputProps, ref: inputRef, value: inputValue, startAdornment: multiSelectionAdornment || inputPropsStartAdornment ? (_jsxs(_Fragment, { children: [multiSelectionAdornment, inputPropsStartAdornment] })) : undefined, onFocus: e => {
289
+ return (_jsx(Input, { ...passthroughInputProps, ref: inputRef, value: inputValue, startAdornment: multiSelectionAdornment || inputPropsStartAdornment ? (_jsxs(_Fragment, { children: [multiSelectionAdornment, inputPropsStartAdornment] })) : undefined, endAdornment: inputPropsEndAdornment || icon ? (_jsxs(_Fragment, { children: [inputPropsEndAdornment, icon] })) : undefined, onFocus: e => {
290
290
  inputPropsOnFocus === null || inputPropsOnFocus === void 0 ? void 0 : inputPropsOnFocus(e);
291
291
  if (e.defaultPrevented)
292
292
  return;
@@ -22,6 +22,7 @@ export interface PopoverProps {
22
22
  fullWidth?: boolean;
23
23
  autoFocusContent?: boolean;
24
24
  returnFocus?: boolean;
25
+ anchorRef?: React.RefObject<HTMLElement | null>;
25
26
  }
26
27
  export interface PopoverHandle {
27
28
  close: () => void;
@@ -37,7 +37,7 @@ function parseMinWidthPx(minWidth, elForEm) {
37
37
  }
38
38
  return 0;
39
39
  }
40
- export const Popover = forwardRef(function Popover({ trigger: Trigger, children, open, defaultOpen = false, onOpenChange, contentId, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, fullWidth = false, autoFocusContent = false, returnFocus = true, }, ref) {
40
+ export const Popover = forwardRef(function Popover({ trigger: Trigger, children, open, defaultOpen = false, onOpenChange, contentId, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, fullWidth = false, autoFocusContent = false, returnFocus = true, anchorRef, }, ref) {
41
41
  const internalId = useId();
42
42
  const resolvedContentId = contentId !== null && contentId !== void 0 ? contentId : `popover-${internalId}`;
43
43
  const isControlled = open !== undefined;
@@ -66,8 +66,9 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
66
66
  setOpen(false);
67
67
  }, [setOpen]);
68
68
  const togglePopover = useCallback((e) => {
69
- var _a;
70
- triggerElRef.current = (_a = containerRef.current) !== null && _a !== void 0 ? _a : e.currentTarget;
69
+ var _a, _b;
70
+ triggerElRef.current =
71
+ (_b = (_a = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) !== null && _a !== void 0 ? _a : containerRef.current) !== null && _b !== void 0 ? _b : e.currentTarget;
71
72
  if (isOpen)
72
73
  closePopover('trigger');
73
74
  else
@@ -79,11 +80,11 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
79
80
  isOpen: () => isOpen,
80
81
  }), [closePopover, openPopover, isOpen]);
81
82
  const computeAndSetPosition = useCallback(() => {
82
- var _a;
83
+ var _a, _b;
83
84
  const content = contentRef.current;
84
85
  if (!content)
85
86
  return;
86
- const triggerEl = (_a = triggerElRef.current) !== null && _a !== void 0 ? _a : containerRef.current;
87
+ const triggerEl = (_b = (_a = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) !== null && _a !== void 0 ? _a : triggerElRef.current) !== null && _b !== void 0 ? _b : containerRef.current;
87
88
  if (!triggerEl)
88
89
  return;
89
90
  const triggerRect = triggerEl.getBoundingClientRect();
@@ -158,16 +159,16 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
158
159
  // eslint-disable-next-line react-hooks/exhaustive-deps
159
160
  }, [isOpen]);
160
161
  useEffect(() => {
161
- var _a, _b;
162
+ var _a, _b, _c;
162
163
  if (!isOpen)
163
164
  return;
164
165
  const content = contentRef.current;
165
166
  if (!content)
166
167
  return;
167
- const triggerEl = (_a = triggerElRef.current) !== null && _a !== void 0 ? _a : containerRef.current;
168
+ const triggerEl = (_b = (_a = anchorRef === null || anchorRef === void 0 ? void 0 : anchorRef.current) !== null && _a !== void 0 ? _a : triggerElRef.current) !== null && _b !== void 0 ? _b : containerRef.current;
168
169
  if (autoFocusContent) {
169
170
  const focusables = getFocusable(content);
170
- (_b = focusables[0]) === null || _b === void 0 ? void 0 : _b.focus();
171
+ (_c = focusables[0]) === null || _c === void 0 ? void 0 : _c.focus();
171
172
  }
172
173
  const handlePointerDownCapture = (e) => {
173
174
  const container = containerRef.current;
@@ -203,7 +204,7 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
203
204
  window.removeEventListener('scroll', handleReposition, true);
204
205
  resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
205
206
  };
206
- }, [isOpen, closePopover, computeAndSetPosition, autoFocusContent]);
207
+ }, [isOpen, closePopover, computeAndSetPosition, autoFocusContent, anchorRef]);
207
208
  useEffect(() => {
208
209
  var _a, _b;
209
210
  if (isOpen)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.55",
3
+ "version": "0.0.57",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",