@dbcdk/react-components 0.0.54 → 0.0.56

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.
@@ -1,7 +1,7 @@
1
1
  import type { JSX, ReactNode } from 'react';
2
2
  import type { Severity } from '../../constants/severity.types';
3
3
  import { CardMeta, CardMetaRow } from './components/CardMeta';
4
- type CardVariant = 'default' | 'subtle' | 'strong';
4
+ type CardVariant = 'default' | 'subtle';
5
5
  type CardSize = 'sm' | 'md' | 'lg';
6
6
  type CardImagePlacement = 'left' | 'right' | 'top';
7
7
  export interface CardProps {
@@ -19,8 +19,6 @@ function getVariantClass(variant, s) {
19
19
  switch (variant) {
20
20
  case 'subtle':
21
21
  return s.variantSubtle;
22
- case 'strong':
23
- return s.variantStrong;
24
22
  case 'default':
25
23
  default:
26
24
  return s.variantDefault;
@@ -31,11 +31,6 @@
31
31
  background-color: var(--card-bg-subtle, var(--color-bg-contextual-subtle));
32
32
  }
33
33
 
34
- .variantStrong {
35
- background-color: var(--card-bg-strong, var(--color-bg-surface-strong));
36
- color: var(--card-fg-on-strong, var(--color-fg-on-strong));
37
- }
38
-
39
34
  /* SIZE VARIANTS (define vars once; inner uses them) */
40
35
  .sm {
41
36
  --card-pad: var(--spacing-md);
@@ -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,
@@ -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,7 +7,7 @@ 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);
@@ -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) {
@@ -95,15 +95,10 @@
95
95
  cursor: pointer;
96
96
  }
97
97
 
98
- .row + .row .interactive,
99
- .row + .row > .interactiveChild {
100
- box-shadow: inset 0 1px 0 var(--color-border-subtle);
101
- }
102
-
103
98
  /* Hover: support both cases (interactive element, or wrapper child) */
104
99
  .interactive:hover:not(.selected),
105
100
  .row:hover > .interactiveChild:not(.selected) {
106
- background-color: var(--color-bg-hover-subtle);
101
+ background-color: var(--color-bg-toolbar-hover);
107
102
  }
108
103
 
109
104
  /* Focus ring: support both cases */
@@ -134,6 +129,19 @@
134
129
  color: var(--color-fg-default);
135
130
  }
136
131
 
132
+ .selected:hover,
133
+ .interactive.selected:hover,
134
+ .interactive[aria-selected='true']:hover,
135
+ .row:hover > .interactiveChild.selected,
136
+ .row:hover > .interactiveChild[aria-selected='true'],
137
+ .active.selected,
138
+ .interactive.active.selected,
139
+ .interactive.active[aria-selected='true'],
140
+ .row > .interactiveChild.active.selected,
141
+ .row > .interactiveChild.active[aria-selected='true'] {
142
+ background-color: var(--color-bg-selected-hover);
143
+ }
144
+
137
145
  /* Checked (legacy support; kept in case any interactive element still uses aria-checked) */
138
146
  .interactive[aria-checked='true'],
139
147
  .row > .interactiveChild[aria-checked='true'] {
@@ -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,7 +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
- triggerElRef.current = 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;
70
72
  if (isOpen)
71
73
  closePopover('trigger');
72
74
  else
@@ -78,19 +80,22 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
78
80
  isOpen: () => isOpen,
79
81
  }), [closePopover, openPopover, isOpen]);
80
82
  const computeAndSetPosition = useCallback(() => {
81
- var _a;
83
+ var _a, _b;
82
84
  const content = contentRef.current;
83
85
  if (!content)
84
86
  return;
85
- 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;
86
88
  if (!triggerEl)
87
89
  return;
88
90
  const triggerRect = triggerEl.getBoundingClientRect();
91
+ const overlayWidthBuffer = 8;
89
92
  // Only compute a forced width when requested.
90
93
  let forcedWidthPx = null;
91
94
  if (matchTriggerWidth) {
92
95
  const minWidthPx = parseMinWidthPx(minWidth, triggerEl);
93
- forcedWidthPx = Math.max(triggerRect.width, minWidthPx || 0);
96
+ // Make the overlay slightly wider than the trigger so it reads as a
97
+ // floating layer instead of blending into adjacent form fields.
98
+ forcedWidthPx = Math.max(triggerRect.width + overlayWidthBuffer, minWidthPx || 0);
94
99
  setTriggerWidth(forcedWidthPx);
95
100
  }
96
101
  else {
@@ -154,15 +159,16 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
154
159
  // eslint-disable-next-line react-hooks/exhaustive-deps
155
160
  }, [isOpen]);
156
161
  useEffect(() => {
157
- var _a;
162
+ var _a, _b, _c;
158
163
  if (!isOpen)
159
164
  return;
160
165
  const content = contentRef.current;
161
166
  if (!content)
162
167
  return;
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;
163
169
  if (autoFocusContent) {
164
170
  const focusables = getFocusable(content);
165
- (_a = focusables[0]) === null || _a === void 0 ? void 0 : _a.focus();
171
+ (_c = focusables[0]) === null || _c === void 0 ? void 0 : _c.focus();
166
172
  }
167
173
  const handlePointerDownCapture = (e) => {
168
174
  const container = containerRef.current;
@@ -179,17 +185,26 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
179
185
  closePopover('escape');
180
186
  };
181
187
  const handleReposition = () => computeAndSetPosition();
188
+ const resizeObserver = typeof ResizeObserver !== 'undefined'
189
+ ? new ResizeObserver(() => {
190
+ handleReposition();
191
+ })
192
+ : null;
182
193
  document.addEventListener('pointerdown', handlePointerDownCapture, true);
183
194
  document.addEventListener('keydown', handleEscape, true);
184
195
  window.addEventListener('resize', handleReposition);
185
196
  window.addEventListener('scroll', handleReposition, true);
197
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(content);
198
+ if (triggerEl)
199
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.observe(triggerEl);
186
200
  return () => {
187
201
  document.removeEventListener('pointerdown', handlePointerDownCapture, true);
188
202
  document.removeEventListener('keydown', handleEscape, true);
189
203
  window.removeEventListener('resize', handleReposition);
190
204
  window.removeEventListener('scroll', handleReposition, true);
205
+ resizeObserver === null || resizeObserver === void 0 ? void 0 : resizeObserver.disconnect();
191
206
  };
192
- }, [isOpen, closePopover, computeAndSetPosition, autoFocusContent]);
207
+ }, [isOpen, closePopover, computeAndSetPosition, autoFocusContent, anchorRef]);
193
208
  useEffect(() => {
194
209
  var _a, _b;
195
210
  if (isOpen)
@@ -19,7 +19,9 @@
19
19
  padding: 0;
20
20
  z-index: var(--z-popover);
21
21
  overflow: auto;
22
- box-shadow: var(--shadow-md);
22
+ box-shadow:
23
+ 0 0 0 1px color-mix(in srgb, var(--color-border-subtle) 55%, transparent),
24
+ var(--shadow-lg);
23
25
  }
24
26
 
25
27
  .content[hidden] {
@@ -37,6 +37,10 @@
37
37
  border-left-color: var(--color-status-info-border);
38
38
  }
39
39
 
40
+ .brand {
41
+ border-left-color: var(--color-brand);
42
+ }
43
+
40
44
  .success {
41
45
  border-left-color: var(--color-status-success-border);
42
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.54",
3
+ "version": "0.0.56",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",