@diabolic/hangover 0.1.1 → 0.1.3
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.
- package/README.md +54 -0
- package/dist/index.cjs.js +83 -38
- package/dist/index.esm.js +83 -38
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -77,6 +77,7 @@ Root provider. All state lives here.
|
|
|
77
77
|
| `darkMode` | `boolean` | `false` | Enable dark mode. Applies `hangoverDropdown--dark` CSS class which overrides all color tokens. |
|
|
78
78
|
| `searchQuery` | `string` | — | Controlled search query. When provided, the internal search state is kept in sync with this value. Use together with `onEvent` (`type: "search"`) to handle changes. |
|
|
79
79
|
| `defaultSearchQuery` | `string` | `""` | Uncontrolled initial search query. Only applied on first render. |
|
|
80
|
+
| `useTranslationFunction` | `(text, payload?) => string` | — | Translation hook. When provided, **every** user-facing string — including built-in defaults — is routed through this function. See [Translation](#translation). |
|
|
80
81
|
| `onEvent` | `(event) => any` | — | Central event handler. See [Events](#events). |
|
|
81
82
|
| `ref` | `React.Ref` | — | Exposes imperative API. See [Imperative API](#imperative-api). |
|
|
82
83
|
| `...rest` | `any` | — | Any additional props (e.g. `data-*`, `className`, `style`) are forwarded to the root `<div>`. |
|
|
@@ -274,6 +275,7 @@ const config = {
|
|
|
274
275
|
defaultGroupExpanded: boolean | 'first',
|
|
275
276
|
hideOnSelection: boolean,
|
|
276
277
|
onEvent: ({ type, payload, prev }) => any,
|
|
278
|
+
useTranslationFunction: (text, payload) => string,
|
|
277
279
|
// ...any extra props are spread onto <Dropdown>
|
|
278
280
|
|
|
279
281
|
// Trigger — required
|
|
@@ -488,6 +490,58 @@ trigger.addEventListener('HO:select', (e) => {
|
|
|
488
490
|
|
|
489
491
|
---
|
|
490
492
|
|
|
493
|
+
## Translation
|
|
494
|
+
|
|
495
|
+
Pass a `useTranslationFunction` to `<Dropdown>` to localize the UI. When
|
|
496
|
+
provided, **every** user-facing string is routed through it — including
|
|
497
|
+
built-in defaults like `"Search"`, `"Select all"`, `"No results"` and
|
|
498
|
+
`"Nothing to show here"`, as well as your own labels (group names, item
|
|
499
|
+
labels, nav items, section/panel titles).
|
|
500
|
+
|
|
501
|
+
The function receives the original string and returns the translated one:
|
|
502
|
+
|
|
503
|
+
```jsx
|
|
504
|
+
const translations = {
|
|
505
|
+
'Search': 'Ara',
|
|
506
|
+
'Select all': 'Tümünü seç',
|
|
507
|
+
'No results': 'Sonuç yok',
|
|
508
|
+
'Nothing to show here': 'Gösterilecek bir şey yok',
|
|
509
|
+
'Fruits': 'Meyveler',
|
|
510
|
+
'Apple': 'Elma',
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
<Dropdown useTranslationFunction={(text) => translations[text] ?? text}>
|
|
514
|
+
{/* ... */}
|
|
515
|
+
</Dropdown>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Dynamic values
|
|
519
|
+
|
|
520
|
+
When a string contains dynamic values, they are passed as a payload object
|
|
521
|
+
in the **second argument** instead of being concatenated into the string.
|
|
522
|
+
The default string uses `{placeholder}` tokens that map to payload keys:
|
|
523
|
+
|
|
524
|
+
```jsx
|
|
525
|
+
<Dropdown
|
|
526
|
+
useTranslationFunction={(text, payload) => {
|
|
527
|
+
// e.g. text = "{label} — {action}", payload = { label: "Fruits", action: "collapse" }
|
|
528
|
+
if (text === '{label} — {action}') {
|
|
529
|
+
return `${payload.label}: ${payload.action === 'collapse' ? 'kapat' : 'aç'}`
|
|
530
|
+
}
|
|
531
|
+
return translations[text] ?? text
|
|
532
|
+
}}
|
|
533
|
+
>
|
|
534
|
+
{/* ... */}
|
|
535
|
+
</Dropdown>
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
If no `useTranslationFunction` is provided, strings render as-is and any
|
|
539
|
+
`{placeholder}` tokens are interpolated from the payload automatically.
|
|
540
|
+
|
|
541
|
+
> Also available via [`fromConfig`](#fromconfig--config-driven-rendering) as `useTranslationFunction`.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
491
545
|
## Imperative API
|
|
492
546
|
|
|
493
547
|
Attach a `ref` to `<Dropdown>` to control it programmatically from **outside** the tree:
|
package/dist/index.cjs.js
CHANGED
|
@@ -34,7 +34,8 @@ function DropdownTrigger({
|
|
|
34
34
|
const {
|
|
35
35
|
triggerRef,
|
|
36
36
|
isOpen,
|
|
37
|
-
fireEvent
|
|
37
|
+
fireEvent,
|
|
38
|
+
t
|
|
38
39
|
} = useDropdownContext();
|
|
39
40
|
const child = react.Children.only(children);
|
|
40
41
|
function handleClick(e) {
|
|
@@ -49,12 +50,14 @@ function DropdownTrigger({
|
|
|
49
50
|
}
|
|
50
51
|
child.props.onClick?.(e);
|
|
51
52
|
}
|
|
53
|
+
const childChildren = child.props.children;
|
|
54
|
+
const translatedChildren = typeof childChildren === 'string' ? t(childChildren) : childChildren;
|
|
52
55
|
return /*#__PURE__*/react.cloneElement(child, {
|
|
53
56
|
ref: triggerRef,
|
|
54
57
|
onClick: handleClick,
|
|
55
58
|
'aria-expanded': isOpen,
|
|
56
59
|
'aria-haspopup': 'dialog'
|
|
57
|
-
});
|
|
60
|
+
}, translatedChildren);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -342,6 +345,7 @@ function useOutsideClick(refs, callback) {
|
|
|
342
345
|
function DropdownPanel({
|
|
343
346
|
placement = 'bottom-start',
|
|
344
347
|
offset = 8,
|
|
348
|
+
title,
|
|
345
349
|
anchor,
|
|
346
350
|
component: Comp,
|
|
347
351
|
children,
|
|
@@ -353,7 +357,8 @@ function DropdownPanel({
|
|
|
353
357
|
triggerRef,
|
|
354
358
|
fireEvent,
|
|
355
359
|
hasNav,
|
|
356
|
-
darkMode
|
|
360
|
+
darkMode,
|
|
361
|
+
t
|
|
357
362
|
} = useDropdownContext();
|
|
358
363
|
const panelRef = react.useRef(null);
|
|
359
364
|
const anchorRef = anchor ?? triggerRef;
|
|
@@ -391,20 +396,24 @@ function DropdownPanel({
|
|
|
391
396
|
placement: actualPlacement,
|
|
392
397
|
style: style,
|
|
393
398
|
className: classNames,
|
|
399
|
+
title: t(title),
|
|
394
400
|
...rest,
|
|
395
401
|
children: children
|
|
396
|
-
}) : /*#__PURE__*/jsxRuntime.
|
|
402
|
+
}) : /*#__PURE__*/jsxRuntime.jsxs("div", {
|
|
397
403
|
ref: panelRef,
|
|
398
404
|
className: classNames,
|
|
399
405
|
style: style,
|
|
400
406
|
role: "dialog",
|
|
401
407
|
"aria-modal": "true",
|
|
402
|
-
"aria-label":
|
|
408
|
+
"aria-label": t('Dropdown'),
|
|
403
409
|
...rest,
|
|
404
|
-
children: /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
410
|
+
children: [title && /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
411
|
+
className: "hangoverDropdown-panel-title",
|
|
412
|
+
children: t(title)
|
|
413
|
+
}), /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
405
414
|
className: "hangoverDropdown-panel-inner",
|
|
406
415
|
children: children
|
|
407
|
-
})
|
|
416
|
+
})]
|
|
408
417
|
});
|
|
409
418
|
return /*#__PURE__*/reactDom.createPortal(content, document.body);
|
|
410
419
|
}
|
|
@@ -437,7 +446,8 @@ function DropdownNavItem({
|
|
|
437
446
|
displayMode,
|
|
438
447
|
contentRef,
|
|
439
448
|
sectionRefs,
|
|
440
|
-
registerNavLabel
|
|
449
|
+
registerNavLabel,
|
|
450
|
+
t
|
|
441
451
|
} = useDropdownContext();
|
|
442
452
|
const isActive = activeNavId === id;
|
|
443
453
|
react.useEffect(() => {
|
|
@@ -493,7 +503,7 @@ function DropdownNavItem({
|
|
|
493
503
|
handleClick();
|
|
494
504
|
userOnClick?.();
|
|
495
505
|
},
|
|
496
|
-
title: typeof children === 'string' ? children : undefined,
|
|
506
|
+
title: typeof children === 'string' ? t(children) : undefined,
|
|
497
507
|
"data-ho-active": isActive,
|
|
498
508
|
...navItemRest,
|
|
499
509
|
children: [icon && /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
@@ -502,7 +512,7 @@ function DropdownNavItem({
|
|
|
502
512
|
children: renderIcon(icon)
|
|
503
513
|
}), /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
504
514
|
className: "hangoverDropdown-nav-item-label",
|
|
505
|
-
children: children
|
|
515
|
+
children: typeof children === 'string' ? t(children) : children
|
|
506
516
|
})]
|
|
507
517
|
});
|
|
508
518
|
}
|
|
@@ -615,12 +625,15 @@ function DefaultSearchIcon() {
|
|
|
615
625
|
*
|
|
616
626
|
* Props:
|
|
617
627
|
* searchPlaceholder string (default "Search")
|
|
628
|
+
* emptyText string (default "Nothing to show here") — shown when
|
|
629
|
+
* Content has no children; the search bar is hidden too
|
|
618
630
|
* title string — overrides active nav label as section title
|
|
619
631
|
* component custom wrapper component
|
|
620
632
|
* children DropdownSection / DropdownGroup / DropdownItem elements
|
|
621
633
|
*/
|
|
622
634
|
function DropdownContent({
|
|
623
635
|
searchPlaceholder = 'Search',
|
|
636
|
+
emptyText = 'Nothing to show here',
|
|
624
637
|
component: Comp,
|
|
625
638
|
children,
|
|
626
639
|
...rest
|
|
@@ -631,7 +644,8 @@ function DropdownContent({
|
|
|
631
644
|
contentRef,
|
|
632
645
|
displayMode,
|
|
633
646
|
activeNavId,
|
|
634
|
-
setScrollSpyActive
|
|
647
|
+
setScrollSpyActive,
|
|
648
|
+
t
|
|
635
649
|
} = useDropdownContext();
|
|
636
650
|
|
|
637
651
|
// Scroll spy: update active nav based on scroll position
|
|
@@ -686,8 +700,9 @@ function DropdownContent({
|
|
|
686
700
|
query: e.target.value
|
|
687
701
|
});
|
|
688
702
|
}
|
|
703
|
+
const isEmpty = react.Children.count(children) === 0;
|
|
689
704
|
const inner = /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
690
|
-
children: [/*#__PURE__*/jsxRuntime.jsxs("label", {
|
|
705
|
+
children: [!isEmpty && /*#__PURE__*/jsxRuntime.jsxs("label", {
|
|
691
706
|
className: "hangoverDropdown-search",
|
|
692
707
|
children: [/*#__PURE__*/jsxRuntime.jsx("span", {
|
|
693
708
|
className: "hangoverDropdown-search-icon",
|
|
@@ -695,8 +710,8 @@ function DropdownContent({
|
|
|
695
710
|
}), /*#__PURE__*/jsxRuntime.jsx("input", {
|
|
696
711
|
type: "text",
|
|
697
712
|
className: "hangoverDropdown-search-input",
|
|
698
|
-
placeholder: searchPlaceholder,
|
|
699
|
-
"aria-label": searchPlaceholder,
|
|
713
|
+
placeholder: t(searchPlaceholder),
|
|
714
|
+
"aria-label": t(searchPlaceholder),
|
|
700
715
|
value: searchQuery,
|
|
701
716
|
onChange: handleSearch
|
|
702
717
|
})]
|
|
@@ -704,7 +719,10 @@ function DropdownContent({
|
|
|
704
719
|
role: "listbox",
|
|
705
720
|
className: `hangoverDropdown-list${displayMode === 'tab' ? ' isTabMode' : ''}${displayMode === 'tab' && activeNavId === '__all__' ? ' isAllActive' : ''}`,
|
|
706
721
|
ref: contentRef,
|
|
707
|
-
children:
|
|
722
|
+
children: isEmpty ? /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
723
|
+
className: "hangoverDropdown-content-empty",
|
|
724
|
+
children: t(emptyText)
|
|
725
|
+
}) : children
|
|
708
726
|
})]
|
|
709
727
|
});
|
|
710
728
|
if (Comp) {
|
|
@@ -734,7 +752,8 @@ function DropdownSection({
|
|
|
734
752
|
activeNavId,
|
|
735
753
|
displayMode,
|
|
736
754
|
registerSectionRef,
|
|
737
|
-
hasNav
|
|
755
|
+
hasNav,
|
|
756
|
+
t
|
|
738
757
|
} = useDropdownContext();
|
|
739
758
|
const sectionRef = react.useRef(null);
|
|
740
759
|
const forId = forProp || forIdProp || '__all__';
|
|
@@ -799,7 +818,7 @@ function DropdownSection({
|
|
|
799
818
|
children: [title && hasNav && !(displayMode === 'tab' && activeNavId === '__all__') && /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
800
819
|
className: `hangoverDropdown-section-title${hasGroups ? ' isClickable' : ''}`,
|
|
801
820
|
onClick: hasGroups ? handleToggleAll : undefined,
|
|
802
|
-
"aria-label": hasGroups ? allExpanded ? 'Collapse all groups' : 'Expand all groups' : undefined,
|
|
821
|
+
"aria-label": hasGroups ? allExpanded ? t('Collapse all groups') : t('Expand all groups') : undefined,
|
|
803
822
|
role: hasGroups ? 'button' : undefined,
|
|
804
823
|
tabIndex: hasGroups ? 0 : undefined,
|
|
805
824
|
onKeyDown: hasGroups ? e => {
|
|
@@ -809,7 +828,7 @@ function DropdownSection({
|
|
|
809
828
|
}
|
|
810
829
|
} : undefined,
|
|
811
830
|
children: /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
812
|
-
children: title
|
|
831
|
+
children: t(title)
|
|
813
832
|
})
|
|
814
833
|
}), children]
|
|
815
834
|
})
|
|
@@ -3025,7 +3044,8 @@ function DropdownItem({
|
|
|
3025
3044
|
selectedItem,
|
|
3026
3045
|
checkedItems,
|
|
3027
3046
|
searchQuery,
|
|
3028
|
-
fireEvent
|
|
3047
|
+
fireEvent,
|
|
3048
|
+
t
|
|
3029
3049
|
} = useDropdownContext();
|
|
3030
3050
|
const groupCtx = react.useContext(GroupContext);
|
|
3031
3051
|
const groupLabel = groupCtx?.groupLabel ?? '';
|
|
@@ -3063,6 +3083,7 @@ function DropdownItem({
|
|
|
3063
3083
|
fireEvent('select', {
|
|
3064
3084
|
id,
|
|
3065
3085
|
label,
|
|
3086
|
+
groupId,
|
|
3066
3087
|
groupLabel
|
|
3067
3088
|
});
|
|
3068
3089
|
}
|
|
@@ -3113,7 +3134,7 @@ function DropdownItem({
|
|
|
3113
3134
|
"aria-checked": type === 'checkbox' ? isChecked : undefined,
|
|
3114
3135
|
tabIndex: 0,
|
|
3115
3136
|
className: classNames,
|
|
3116
|
-
title: label
|
|
3137
|
+
title: label ? t(label) : undefined,
|
|
3117
3138
|
onClick: () => {
|
|
3118
3139
|
handleClick();
|
|
3119
3140
|
userOnClick?.();
|
|
@@ -3130,7 +3151,7 @@ function DropdownItem({
|
|
|
3130
3151
|
children: renderIcon(icon)
|
|
3131
3152
|
}), /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3132
3153
|
className: "hangoverDropdown-item-label",
|
|
3133
|
-
children: children
|
|
3154
|
+
children: typeof children === 'string' ? t(children) : children
|
|
3134
3155
|
}), actionsNode && /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3135
3156
|
className: "hangoverDropdown-item-actions",
|
|
3136
3157
|
onClick: e => e.stopPropagation(),
|
|
@@ -3229,7 +3250,8 @@ function DropdownGroup({
|
|
|
3229
3250
|
displayMode,
|
|
3230
3251
|
activeNavId,
|
|
3231
3252
|
registerGroupItems,
|
|
3232
|
-
searchQuery
|
|
3253
|
+
searchQuery,
|
|
3254
|
+
t
|
|
3233
3255
|
} = useDropdownContext();
|
|
3234
3256
|
|
|
3235
3257
|
// Determine initial expanded state
|
|
@@ -3318,7 +3340,7 @@ function DropdownGroup({
|
|
|
3318
3340
|
role: "checkbox",
|
|
3319
3341
|
"aria-checked": selectAllChecked,
|
|
3320
3342
|
tabIndex: 0,
|
|
3321
|
-
title:
|
|
3343
|
+
title: t('Select all'),
|
|
3322
3344
|
className: `hangoverDropdown-item isCheckboxType${selectAllChecked ? ' isChecked' : ''}`,
|
|
3323
3345
|
onClick: handleSelectAll,
|
|
3324
3346
|
onKeyDown: e => {
|
|
@@ -3329,7 +3351,7 @@ function DropdownGroup({
|
|
|
3329
3351
|
},
|
|
3330
3352
|
children: [/*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3331
3353
|
className: "hangoverDropdown-item-label",
|
|
3332
|
-
children:
|
|
3354
|
+
children: t('Select all')
|
|
3333
3355
|
}), /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3334
3356
|
className: `hangoverDropdown-item-check-icon${selectAllChecked ? ' isVisible' : ''}`,
|
|
3335
3357
|
children: selectAllChecked && /*#__PURE__*/jsxRuntime.jsx("svg", {
|
|
@@ -3350,10 +3372,10 @@ function DropdownGroup({
|
|
|
3350
3372
|
const visibleItemIds = react.useMemo(() => {
|
|
3351
3373
|
const searchableItems = react.Children.toArray(children).map(child => ({
|
|
3352
3374
|
id: child?.props?.id,
|
|
3353
|
-
label: typeof child?.props?.children === 'string' ? child.props.children : ''
|
|
3375
|
+
label: typeof child?.props?.children === 'string' ? t(child.props.children) : ''
|
|
3354
3376
|
}));
|
|
3355
3377
|
return getMatchingItemIds(searchableItems, searchQuery);
|
|
3356
|
-
}, [children, searchQuery]);
|
|
3378
|
+
}, [children, searchQuery, t]);
|
|
3357
3379
|
const groupContextValue = {
|
|
3358
3380
|
groupLabel: label,
|
|
3359
3381
|
groupId,
|
|
@@ -3371,8 +3393,11 @@ function DropdownGroup({
|
|
|
3371
3393
|
}
|
|
3372
3394
|
},
|
|
3373
3395
|
"aria-expanded": isExpanded,
|
|
3374
|
-
"aria-label":
|
|
3375
|
-
|
|
3396
|
+
"aria-label": t('{label} — {action}', {
|
|
3397
|
+
label,
|
|
3398
|
+
action: t(isExpanded ? 'collapse' : 'expand')
|
|
3399
|
+
}),
|
|
3400
|
+
title: t(label),
|
|
3376
3401
|
children: [/*#__PURE__*/jsxRuntime.jsx("div", {
|
|
3377
3402
|
className: "hangoverDropdown-group-header-accent"
|
|
3378
3403
|
}), /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
@@ -3384,7 +3409,7 @@ function DropdownGroup({
|
|
|
3384
3409
|
children: renderIcon(icon)
|
|
3385
3410
|
}), /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3386
3411
|
className: "hangoverDropdown-group-header-label",
|
|
3387
|
-
children: label
|
|
3412
|
+
children: t(label)
|
|
3388
3413
|
}), /*#__PURE__*/jsxRuntime.jsx("span", {
|
|
3389
3414
|
className: "hangoverDropdown-group-header-chevron",
|
|
3390
3415
|
children: /*#__PURE__*/jsxRuntime.jsx(Chevron, {})
|
|
@@ -3398,14 +3423,14 @@ function DropdownGroup({
|
|
|
3398
3423
|
className: `hangoverDropdown-group-items-wrap${isExpanded ? ' isExpanded' : ''}`,
|
|
3399
3424
|
children: /*#__PURE__*/jsxRuntime.jsxs("div", {
|
|
3400
3425
|
role: "group",
|
|
3401
|
-
"aria-label": label,
|
|
3426
|
+
"aria-label": t(label),
|
|
3402
3427
|
className: "hangoverDropdown-group-items",
|
|
3403
3428
|
children: [showSelectAll && selectAllPosition === 'top' && selectAllItem, hasChildren ? hasVisibleItems ? children : /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
3404
3429
|
className: "hangoverDropdown-group-empty",
|
|
3405
|
-
children: noResultsText
|
|
3430
|
+
children: t(noResultsText)
|
|
3406
3431
|
}) : /*#__PURE__*/jsxRuntime.jsx("div", {
|
|
3407
3432
|
className: "hangoverDropdown-group-empty",
|
|
3408
|
-
children: emptyText
|
|
3433
|
+
children: t(emptyText)
|
|
3409
3434
|
}), showSelectAll && selectAllPosition === 'bottom' && selectAllItem]
|
|
3410
3435
|
})
|
|
3411
3436
|
});
|
|
@@ -3717,9 +3742,10 @@ const Dropdown$1 = /*#__PURE__*/react.forwardRef(function Dropdown({
|
|
|
3717
3742
|
hideOnSelection: hideOnSelectionProp = true,
|
|
3718
3743
|
onEvent: onEventProp,
|
|
3719
3744
|
fromConfig,
|
|
3720
|
-
darkMode = false,
|
|
3745
|
+
darkMode: darkModeProp = false,
|
|
3721
3746
|
searchQuery: searchQueryProp,
|
|
3722
|
-
defaultSearchQuery = '',
|
|
3747
|
+
defaultSearchQuery: defaultSearchQueryProp = '',
|
|
3748
|
+
useTranslationFunction: useTranslationFunctionProp,
|
|
3723
3749
|
children,
|
|
3724
3750
|
...rest
|
|
3725
3751
|
}, ref) {
|
|
@@ -3728,6 +3754,23 @@ const Dropdown$1 = /*#__PURE__*/react.forwardRef(function Dropdown({
|
|
|
3728
3754
|
const defaultGroupExpanded = fromConfig?.defaultGroupExpanded ?? defaultGroupExpandedProp;
|
|
3729
3755
|
const hideOnSelection = fromConfig?.hideOnSelection ?? hideOnSelectionProp;
|
|
3730
3756
|
const onEvent = fromConfig?.onEvent ?? onEventProp;
|
|
3757
|
+
const darkMode = fromConfig?.darkMode ?? darkModeProp;
|
|
3758
|
+
const defaultSearchQuery = fromConfig?.defaultSearchQuery ?? defaultSearchQueryProp;
|
|
3759
|
+
const controlledSearchQuery = fromConfig?.searchQuery ?? searchQueryProp;
|
|
3760
|
+
const translationFn = fromConfig?.useTranslationFunction ?? useTranslationFunctionProp;
|
|
3761
|
+
|
|
3762
|
+
// Translation helper. Every user-facing string is routed through this.
|
|
3763
|
+
// - With a translation function: returns translationFn(str, payload).
|
|
3764
|
+
// - Without one: returns the string, interpolating any {placeholder}
|
|
3765
|
+
// tokens from the optional payload object.
|
|
3766
|
+
const t = react.useCallback((str, payload) => {
|
|
3767
|
+
if (typeof str !== 'string') return str;
|
|
3768
|
+
if (typeof translationFn === 'function') return translationFn(str, payload);
|
|
3769
|
+
if (payload) {
|
|
3770
|
+
return str.replace(/\{(\w+)\}/g, (match, key) => key in payload ? payload[key] : match);
|
|
3771
|
+
}
|
|
3772
|
+
return str;
|
|
3773
|
+
}, [translationFn]);
|
|
3731
3774
|
const [isOpen, setIsOpen] = react.useState(defaultOpen);
|
|
3732
3775
|
const [selectedItem, setSelectedItem] = react.useState(null);
|
|
3733
3776
|
const [checkedItems, setCheckedItems] = react.useState(() => new Map());
|
|
@@ -3737,10 +3780,10 @@ const Dropdown$1 = /*#__PURE__*/react.forwardRef(function Dropdown({
|
|
|
3737
3780
|
const [hasNav, setHasNav] = react.useState(false);
|
|
3738
3781
|
|
|
3739
3782
|
// Controlled searchQuery — sync internal state whenever the prop changes
|
|
3740
|
-
const isControlledSearch =
|
|
3783
|
+
const isControlledSearch = controlledSearchQuery !== undefined;
|
|
3741
3784
|
react.useEffect(() => {
|
|
3742
|
-
if (isControlledSearch) setSearchQuery(
|
|
3743
|
-
}, [isControlledSearch,
|
|
3785
|
+
if (isControlledSearch) setSearchQuery(controlledSearchQuery);
|
|
3786
|
+
}, [isControlledSearch, controlledSearchQuery]);
|
|
3744
3787
|
const triggerRef = react.useRef(null);
|
|
3745
3788
|
const contentRef = react.useRef(null); // scroll container inside DropdownContent
|
|
3746
3789
|
const firstGroupClaimedRef = react.useRef(false);
|
|
@@ -4005,6 +4048,8 @@ const Dropdown$1 = /*#__PURE__*/react.forwardRef(function Dropdown({
|
|
|
4005
4048
|
hasNav,
|
|
4006
4049
|
darkMode,
|
|
4007
4050
|
setHasNav,
|
|
4051
|
+
// i18n
|
|
4052
|
+
t,
|
|
4008
4053
|
// Refs
|
|
4009
4054
|
triggerRef,
|
|
4010
4055
|
contentRef,
|
|
@@ -4024,7 +4069,7 @@ const Dropdown$1 = /*#__PURE__*/react.forwardRef(function Dropdown({
|
|
|
4024
4069
|
registerSectionRef
|
|
4025
4070
|
}), [isOpen, selectedItem, checkedItems, activeNavId, activeNavLabel, searchQuery, hasNav, displayMode, defaultGroupExpanded, darkMode,
|
|
4026
4071
|
// all others are stable references
|
|
4027
|
-
fireEvent, registerGroupItems, setScrollSpyActive, registerNavLabel, registerSectionRef]);
|
|
4072
|
+
fireEvent, registerGroupItems, setScrollSpyActive, registerNavLabel, registerSectionRef, t]);
|
|
4028
4073
|
const resolvedChildren = (() => {
|
|
4029
4074
|
if (fromConfig && children) {
|
|
4030
4075
|
console.warn('[Dropdown] `fromConfig` and `children` cannot be used together. ' + '`fromConfig` takes precedence — `children` will be ignored.');
|
package/dist/index.esm.js
CHANGED
|
@@ -30,7 +30,8 @@ function DropdownTrigger({
|
|
|
30
30
|
const {
|
|
31
31
|
triggerRef,
|
|
32
32
|
isOpen,
|
|
33
|
-
fireEvent
|
|
33
|
+
fireEvent,
|
|
34
|
+
t
|
|
34
35
|
} = useDropdownContext();
|
|
35
36
|
const child = Children.only(children);
|
|
36
37
|
function handleClick(e) {
|
|
@@ -45,12 +46,14 @@ function DropdownTrigger({
|
|
|
45
46
|
}
|
|
46
47
|
child.props.onClick?.(e);
|
|
47
48
|
}
|
|
49
|
+
const childChildren = child.props.children;
|
|
50
|
+
const translatedChildren = typeof childChildren === 'string' ? t(childChildren) : childChildren;
|
|
48
51
|
return /*#__PURE__*/cloneElement(child, {
|
|
49
52
|
ref: triggerRef,
|
|
50
53
|
onClick: handleClick,
|
|
51
54
|
'aria-expanded': isOpen,
|
|
52
55
|
'aria-haspopup': 'dialog'
|
|
53
|
-
});
|
|
56
|
+
}, translatedChildren);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -338,6 +341,7 @@ function useOutsideClick(refs, callback) {
|
|
|
338
341
|
function DropdownPanel({
|
|
339
342
|
placement = 'bottom-start',
|
|
340
343
|
offset = 8,
|
|
344
|
+
title,
|
|
341
345
|
anchor,
|
|
342
346
|
component: Comp,
|
|
343
347
|
children,
|
|
@@ -349,7 +353,8 @@ function DropdownPanel({
|
|
|
349
353
|
triggerRef,
|
|
350
354
|
fireEvent,
|
|
351
355
|
hasNav,
|
|
352
|
-
darkMode
|
|
356
|
+
darkMode,
|
|
357
|
+
t
|
|
353
358
|
} = useDropdownContext();
|
|
354
359
|
const panelRef = useRef(null);
|
|
355
360
|
const anchorRef = anchor ?? triggerRef;
|
|
@@ -387,20 +392,24 @@ function DropdownPanel({
|
|
|
387
392
|
placement: actualPlacement,
|
|
388
393
|
style: style,
|
|
389
394
|
className: classNames,
|
|
395
|
+
title: t(title),
|
|
390
396
|
...rest,
|
|
391
397
|
children: children
|
|
392
|
-
}) : /*#__PURE__*/
|
|
398
|
+
}) : /*#__PURE__*/jsxs("div", {
|
|
393
399
|
ref: panelRef,
|
|
394
400
|
className: classNames,
|
|
395
401
|
style: style,
|
|
396
402
|
role: "dialog",
|
|
397
403
|
"aria-modal": "true",
|
|
398
|
-
"aria-label":
|
|
404
|
+
"aria-label": t('Dropdown'),
|
|
399
405
|
...rest,
|
|
400
|
-
children: /*#__PURE__*/jsx("div", {
|
|
406
|
+
children: [title && /*#__PURE__*/jsx("div", {
|
|
407
|
+
className: "hangoverDropdown-panel-title",
|
|
408
|
+
children: t(title)
|
|
409
|
+
}), /*#__PURE__*/jsx("div", {
|
|
401
410
|
className: "hangoverDropdown-panel-inner",
|
|
402
411
|
children: children
|
|
403
|
-
})
|
|
412
|
+
})]
|
|
404
413
|
});
|
|
405
414
|
return /*#__PURE__*/createPortal(content, document.body);
|
|
406
415
|
}
|
|
@@ -433,7 +442,8 @@ function DropdownNavItem({
|
|
|
433
442
|
displayMode,
|
|
434
443
|
contentRef,
|
|
435
444
|
sectionRefs,
|
|
436
|
-
registerNavLabel
|
|
445
|
+
registerNavLabel,
|
|
446
|
+
t
|
|
437
447
|
} = useDropdownContext();
|
|
438
448
|
const isActive = activeNavId === id;
|
|
439
449
|
useEffect(() => {
|
|
@@ -489,7 +499,7 @@ function DropdownNavItem({
|
|
|
489
499
|
handleClick();
|
|
490
500
|
userOnClick?.();
|
|
491
501
|
},
|
|
492
|
-
title: typeof children === 'string' ? children : undefined,
|
|
502
|
+
title: typeof children === 'string' ? t(children) : undefined,
|
|
493
503
|
"data-ho-active": isActive,
|
|
494
504
|
...navItemRest,
|
|
495
505
|
children: [icon && /*#__PURE__*/jsx("span", {
|
|
@@ -498,7 +508,7 @@ function DropdownNavItem({
|
|
|
498
508
|
children: renderIcon(icon)
|
|
499
509
|
}), /*#__PURE__*/jsx("span", {
|
|
500
510
|
className: "hangoverDropdown-nav-item-label",
|
|
501
|
-
children: children
|
|
511
|
+
children: typeof children === 'string' ? t(children) : children
|
|
502
512
|
})]
|
|
503
513
|
});
|
|
504
514
|
}
|
|
@@ -611,12 +621,15 @@ function DefaultSearchIcon() {
|
|
|
611
621
|
*
|
|
612
622
|
* Props:
|
|
613
623
|
* searchPlaceholder string (default "Search")
|
|
624
|
+
* emptyText string (default "Nothing to show here") — shown when
|
|
625
|
+
* Content has no children; the search bar is hidden too
|
|
614
626
|
* title string — overrides active nav label as section title
|
|
615
627
|
* component custom wrapper component
|
|
616
628
|
* children DropdownSection / DropdownGroup / DropdownItem elements
|
|
617
629
|
*/
|
|
618
630
|
function DropdownContent({
|
|
619
631
|
searchPlaceholder = 'Search',
|
|
632
|
+
emptyText = 'Nothing to show here',
|
|
620
633
|
component: Comp,
|
|
621
634
|
children,
|
|
622
635
|
...rest
|
|
@@ -627,7 +640,8 @@ function DropdownContent({
|
|
|
627
640
|
contentRef,
|
|
628
641
|
displayMode,
|
|
629
642
|
activeNavId,
|
|
630
|
-
setScrollSpyActive
|
|
643
|
+
setScrollSpyActive,
|
|
644
|
+
t
|
|
631
645
|
} = useDropdownContext();
|
|
632
646
|
|
|
633
647
|
// Scroll spy: update active nav based on scroll position
|
|
@@ -682,8 +696,9 @@ function DropdownContent({
|
|
|
682
696
|
query: e.target.value
|
|
683
697
|
});
|
|
684
698
|
}
|
|
699
|
+
const isEmpty = Children.count(children) === 0;
|
|
685
700
|
const inner = /*#__PURE__*/jsxs(Fragment, {
|
|
686
|
-
children: [/*#__PURE__*/jsxs("label", {
|
|
701
|
+
children: [!isEmpty && /*#__PURE__*/jsxs("label", {
|
|
687
702
|
className: "hangoverDropdown-search",
|
|
688
703
|
children: [/*#__PURE__*/jsx("span", {
|
|
689
704
|
className: "hangoverDropdown-search-icon",
|
|
@@ -691,8 +706,8 @@ function DropdownContent({
|
|
|
691
706
|
}), /*#__PURE__*/jsx("input", {
|
|
692
707
|
type: "text",
|
|
693
708
|
className: "hangoverDropdown-search-input",
|
|
694
|
-
placeholder: searchPlaceholder,
|
|
695
|
-
"aria-label": searchPlaceholder,
|
|
709
|
+
placeholder: t(searchPlaceholder),
|
|
710
|
+
"aria-label": t(searchPlaceholder),
|
|
696
711
|
value: searchQuery,
|
|
697
712
|
onChange: handleSearch
|
|
698
713
|
})]
|
|
@@ -700,7 +715,10 @@ function DropdownContent({
|
|
|
700
715
|
role: "listbox",
|
|
701
716
|
className: `hangoverDropdown-list${displayMode === 'tab' ? ' isTabMode' : ''}${displayMode === 'tab' && activeNavId === '__all__' ? ' isAllActive' : ''}`,
|
|
702
717
|
ref: contentRef,
|
|
703
|
-
children:
|
|
718
|
+
children: isEmpty ? /*#__PURE__*/jsx("div", {
|
|
719
|
+
className: "hangoverDropdown-content-empty",
|
|
720
|
+
children: t(emptyText)
|
|
721
|
+
}) : children
|
|
704
722
|
})]
|
|
705
723
|
});
|
|
706
724
|
if (Comp) {
|
|
@@ -730,7 +748,8 @@ function DropdownSection({
|
|
|
730
748
|
activeNavId,
|
|
731
749
|
displayMode,
|
|
732
750
|
registerSectionRef,
|
|
733
|
-
hasNav
|
|
751
|
+
hasNav,
|
|
752
|
+
t
|
|
734
753
|
} = useDropdownContext();
|
|
735
754
|
const sectionRef = useRef(null);
|
|
736
755
|
const forId = forProp || forIdProp || '__all__';
|
|
@@ -795,7 +814,7 @@ function DropdownSection({
|
|
|
795
814
|
children: [title && hasNav && !(displayMode === 'tab' && activeNavId === '__all__') && /*#__PURE__*/jsx("div", {
|
|
796
815
|
className: `hangoverDropdown-section-title${hasGroups ? ' isClickable' : ''}`,
|
|
797
816
|
onClick: hasGroups ? handleToggleAll : undefined,
|
|
798
|
-
"aria-label": hasGroups ? allExpanded ? 'Collapse all groups' : 'Expand all groups' : undefined,
|
|
817
|
+
"aria-label": hasGroups ? allExpanded ? t('Collapse all groups') : t('Expand all groups') : undefined,
|
|
799
818
|
role: hasGroups ? 'button' : undefined,
|
|
800
819
|
tabIndex: hasGroups ? 0 : undefined,
|
|
801
820
|
onKeyDown: hasGroups ? e => {
|
|
@@ -805,7 +824,7 @@ function DropdownSection({
|
|
|
805
824
|
}
|
|
806
825
|
} : undefined,
|
|
807
826
|
children: /*#__PURE__*/jsx("span", {
|
|
808
|
-
children: title
|
|
827
|
+
children: t(title)
|
|
809
828
|
})
|
|
810
829
|
}), children]
|
|
811
830
|
})
|
|
@@ -3021,7 +3040,8 @@ function DropdownItem({
|
|
|
3021
3040
|
selectedItem,
|
|
3022
3041
|
checkedItems,
|
|
3023
3042
|
searchQuery,
|
|
3024
|
-
fireEvent
|
|
3043
|
+
fireEvent,
|
|
3044
|
+
t
|
|
3025
3045
|
} = useDropdownContext();
|
|
3026
3046
|
const groupCtx = useContext(GroupContext);
|
|
3027
3047
|
const groupLabel = groupCtx?.groupLabel ?? '';
|
|
@@ -3059,6 +3079,7 @@ function DropdownItem({
|
|
|
3059
3079
|
fireEvent('select', {
|
|
3060
3080
|
id,
|
|
3061
3081
|
label,
|
|
3082
|
+
groupId,
|
|
3062
3083
|
groupLabel
|
|
3063
3084
|
});
|
|
3064
3085
|
}
|
|
@@ -3109,7 +3130,7 @@ function DropdownItem({
|
|
|
3109
3130
|
"aria-checked": type === 'checkbox' ? isChecked : undefined,
|
|
3110
3131
|
tabIndex: 0,
|
|
3111
3132
|
className: classNames,
|
|
3112
|
-
title: label
|
|
3133
|
+
title: label ? t(label) : undefined,
|
|
3113
3134
|
onClick: () => {
|
|
3114
3135
|
handleClick();
|
|
3115
3136
|
userOnClick?.();
|
|
@@ -3126,7 +3147,7 @@ function DropdownItem({
|
|
|
3126
3147
|
children: renderIcon(icon)
|
|
3127
3148
|
}), /*#__PURE__*/jsx("span", {
|
|
3128
3149
|
className: "hangoverDropdown-item-label",
|
|
3129
|
-
children: children
|
|
3150
|
+
children: typeof children === 'string' ? t(children) : children
|
|
3130
3151
|
}), actionsNode && /*#__PURE__*/jsx("span", {
|
|
3131
3152
|
className: "hangoverDropdown-item-actions",
|
|
3132
3153
|
onClick: e => e.stopPropagation(),
|
|
@@ -3225,7 +3246,8 @@ function DropdownGroup({
|
|
|
3225
3246
|
displayMode,
|
|
3226
3247
|
activeNavId,
|
|
3227
3248
|
registerGroupItems,
|
|
3228
|
-
searchQuery
|
|
3249
|
+
searchQuery,
|
|
3250
|
+
t
|
|
3229
3251
|
} = useDropdownContext();
|
|
3230
3252
|
|
|
3231
3253
|
// Determine initial expanded state
|
|
@@ -3314,7 +3336,7 @@ function DropdownGroup({
|
|
|
3314
3336
|
role: "checkbox",
|
|
3315
3337
|
"aria-checked": selectAllChecked,
|
|
3316
3338
|
tabIndex: 0,
|
|
3317
|
-
title:
|
|
3339
|
+
title: t('Select all'),
|
|
3318
3340
|
className: `hangoverDropdown-item isCheckboxType${selectAllChecked ? ' isChecked' : ''}`,
|
|
3319
3341
|
onClick: handleSelectAll,
|
|
3320
3342
|
onKeyDown: e => {
|
|
@@ -3325,7 +3347,7 @@ function DropdownGroup({
|
|
|
3325
3347
|
},
|
|
3326
3348
|
children: [/*#__PURE__*/jsx("span", {
|
|
3327
3349
|
className: "hangoverDropdown-item-label",
|
|
3328
|
-
children:
|
|
3350
|
+
children: t('Select all')
|
|
3329
3351
|
}), /*#__PURE__*/jsx("span", {
|
|
3330
3352
|
className: `hangoverDropdown-item-check-icon${selectAllChecked ? ' isVisible' : ''}`,
|
|
3331
3353
|
children: selectAllChecked && /*#__PURE__*/jsx("svg", {
|
|
@@ -3346,10 +3368,10 @@ function DropdownGroup({
|
|
|
3346
3368
|
const visibleItemIds = useMemo(() => {
|
|
3347
3369
|
const searchableItems = Children.toArray(children).map(child => ({
|
|
3348
3370
|
id: child?.props?.id,
|
|
3349
|
-
label: typeof child?.props?.children === 'string' ? child.props.children : ''
|
|
3371
|
+
label: typeof child?.props?.children === 'string' ? t(child.props.children) : ''
|
|
3350
3372
|
}));
|
|
3351
3373
|
return getMatchingItemIds(searchableItems, searchQuery);
|
|
3352
|
-
}, [children, searchQuery]);
|
|
3374
|
+
}, [children, searchQuery, t]);
|
|
3353
3375
|
const groupContextValue = {
|
|
3354
3376
|
groupLabel: label,
|
|
3355
3377
|
groupId,
|
|
@@ -3367,8 +3389,11 @@ function DropdownGroup({
|
|
|
3367
3389
|
}
|
|
3368
3390
|
},
|
|
3369
3391
|
"aria-expanded": isExpanded,
|
|
3370
|
-
"aria-label":
|
|
3371
|
-
|
|
3392
|
+
"aria-label": t('{label} — {action}', {
|
|
3393
|
+
label,
|
|
3394
|
+
action: t(isExpanded ? 'collapse' : 'expand')
|
|
3395
|
+
}),
|
|
3396
|
+
title: t(label),
|
|
3372
3397
|
children: [/*#__PURE__*/jsx("div", {
|
|
3373
3398
|
className: "hangoverDropdown-group-header-accent"
|
|
3374
3399
|
}), /*#__PURE__*/jsx("div", {
|
|
@@ -3380,7 +3405,7 @@ function DropdownGroup({
|
|
|
3380
3405
|
children: renderIcon(icon)
|
|
3381
3406
|
}), /*#__PURE__*/jsx("span", {
|
|
3382
3407
|
className: "hangoverDropdown-group-header-label",
|
|
3383
|
-
children: label
|
|
3408
|
+
children: t(label)
|
|
3384
3409
|
}), /*#__PURE__*/jsx("span", {
|
|
3385
3410
|
className: "hangoverDropdown-group-header-chevron",
|
|
3386
3411
|
children: /*#__PURE__*/jsx(Chevron, {})
|
|
@@ -3394,14 +3419,14 @@ function DropdownGroup({
|
|
|
3394
3419
|
className: `hangoverDropdown-group-items-wrap${isExpanded ? ' isExpanded' : ''}`,
|
|
3395
3420
|
children: /*#__PURE__*/jsxs("div", {
|
|
3396
3421
|
role: "group",
|
|
3397
|
-
"aria-label": label,
|
|
3422
|
+
"aria-label": t(label),
|
|
3398
3423
|
className: "hangoverDropdown-group-items",
|
|
3399
3424
|
children: [showSelectAll && selectAllPosition === 'top' && selectAllItem, hasChildren ? hasVisibleItems ? children : /*#__PURE__*/jsx("div", {
|
|
3400
3425
|
className: "hangoverDropdown-group-empty",
|
|
3401
|
-
children: noResultsText
|
|
3426
|
+
children: t(noResultsText)
|
|
3402
3427
|
}) : /*#__PURE__*/jsx("div", {
|
|
3403
3428
|
className: "hangoverDropdown-group-empty",
|
|
3404
|
-
children: emptyText
|
|
3429
|
+
children: t(emptyText)
|
|
3405
3430
|
}), showSelectAll && selectAllPosition === 'bottom' && selectAllItem]
|
|
3406
3431
|
})
|
|
3407
3432
|
});
|
|
@@ -3713,9 +3738,10 @@ const Dropdown$1 = /*#__PURE__*/forwardRef(function Dropdown({
|
|
|
3713
3738
|
hideOnSelection: hideOnSelectionProp = true,
|
|
3714
3739
|
onEvent: onEventProp,
|
|
3715
3740
|
fromConfig,
|
|
3716
|
-
darkMode = false,
|
|
3741
|
+
darkMode: darkModeProp = false,
|
|
3717
3742
|
searchQuery: searchQueryProp,
|
|
3718
|
-
defaultSearchQuery = '',
|
|
3743
|
+
defaultSearchQuery: defaultSearchQueryProp = '',
|
|
3744
|
+
useTranslationFunction: useTranslationFunctionProp,
|
|
3719
3745
|
children,
|
|
3720
3746
|
...rest
|
|
3721
3747
|
}, ref) {
|
|
@@ -3724,6 +3750,23 @@ const Dropdown$1 = /*#__PURE__*/forwardRef(function Dropdown({
|
|
|
3724
3750
|
const defaultGroupExpanded = fromConfig?.defaultGroupExpanded ?? defaultGroupExpandedProp;
|
|
3725
3751
|
const hideOnSelection = fromConfig?.hideOnSelection ?? hideOnSelectionProp;
|
|
3726
3752
|
const onEvent = fromConfig?.onEvent ?? onEventProp;
|
|
3753
|
+
const darkMode = fromConfig?.darkMode ?? darkModeProp;
|
|
3754
|
+
const defaultSearchQuery = fromConfig?.defaultSearchQuery ?? defaultSearchQueryProp;
|
|
3755
|
+
const controlledSearchQuery = fromConfig?.searchQuery ?? searchQueryProp;
|
|
3756
|
+
const translationFn = fromConfig?.useTranslationFunction ?? useTranslationFunctionProp;
|
|
3757
|
+
|
|
3758
|
+
// Translation helper. Every user-facing string is routed through this.
|
|
3759
|
+
// - With a translation function: returns translationFn(str, payload).
|
|
3760
|
+
// - Without one: returns the string, interpolating any {placeholder}
|
|
3761
|
+
// tokens from the optional payload object.
|
|
3762
|
+
const t = useCallback((str, payload) => {
|
|
3763
|
+
if (typeof str !== 'string') return str;
|
|
3764
|
+
if (typeof translationFn === 'function') return translationFn(str, payload);
|
|
3765
|
+
if (payload) {
|
|
3766
|
+
return str.replace(/\{(\w+)\}/g, (match, key) => key in payload ? payload[key] : match);
|
|
3767
|
+
}
|
|
3768
|
+
return str;
|
|
3769
|
+
}, [translationFn]);
|
|
3727
3770
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
3728
3771
|
const [selectedItem, setSelectedItem] = useState(null);
|
|
3729
3772
|
const [checkedItems, setCheckedItems] = useState(() => new Map());
|
|
@@ -3733,10 +3776,10 @@ const Dropdown$1 = /*#__PURE__*/forwardRef(function Dropdown({
|
|
|
3733
3776
|
const [hasNav, setHasNav] = useState(false);
|
|
3734
3777
|
|
|
3735
3778
|
// Controlled searchQuery — sync internal state whenever the prop changes
|
|
3736
|
-
const isControlledSearch =
|
|
3779
|
+
const isControlledSearch = controlledSearchQuery !== undefined;
|
|
3737
3780
|
useEffect(() => {
|
|
3738
|
-
if (isControlledSearch) setSearchQuery(
|
|
3739
|
-
}, [isControlledSearch,
|
|
3781
|
+
if (isControlledSearch) setSearchQuery(controlledSearchQuery);
|
|
3782
|
+
}, [isControlledSearch, controlledSearchQuery]);
|
|
3740
3783
|
const triggerRef = useRef(null);
|
|
3741
3784
|
const contentRef = useRef(null); // scroll container inside DropdownContent
|
|
3742
3785
|
const firstGroupClaimedRef = useRef(false);
|
|
@@ -4001,6 +4044,8 @@ const Dropdown$1 = /*#__PURE__*/forwardRef(function Dropdown({
|
|
|
4001
4044
|
hasNav,
|
|
4002
4045
|
darkMode,
|
|
4003
4046
|
setHasNav,
|
|
4047
|
+
// i18n
|
|
4048
|
+
t,
|
|
4004
4049
|
// Refs
|
|
4005
4050
|
triggerRef,
|
|
4006
4051
|
contentRef,
|
|
@@ -4020,7 +4065,7 @@ const Dropdown$1 = /*#__PURE__*/forwardRef(function Dropdown({
|
|
|
4020
4065
|
registerSectionRef
|
|
4021
4066
|
}), [isOpen, selectedItem, checkedItems, activeNavId, activeNavLabel, searchQuery, hasNav, displayMode, defaultGroupExpanded, darkMode,
|
|
4022
4067
|
// all others are stable references
|
|
4023
|
-
fireEvent, registerGroupItems, setScrollSpyActive, registerNavLabel, registerSectionRef]);
|
|
4068
|
+
fireEvent, registerGroupItems, setScrollSpyActive, registerNavLabel, registerSectionRef, t]);
|
|
4024
4069
|
const resolvedChildren = (() => {
|
|
4025
4070
|
if (fromConfig && children) {
|
|
4026
4071
|
console.warn('[Dropdown] `fromConfig` and `children` cannot be used together. ' + '`fromConfig` takes precedence — `children` will be ignored.');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diabolic/hangover",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "A headless-style, compound React dropdown/field-picker component library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "bugrakaan",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"import": "./dist/index.esm.js",
|
|
28
28
|
"require": "./dist/index.cjs.js"
|
|
29
29
|
},
|
|
30
|
-
"prepublishOnly": "npm run build",
|
|
31
30
|
"./styles": "./dist/hangover.css"
|
|
32
31
|
},
|
|
33
32
|
"files": [
|
|
@@ -49,6 +48,8 @@
|
|
|
49
48
|
"dev": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && rollup -c -w",
|
|
50
49
|
"lint": "eslint \"src/**/*.{js,jsx}\"",
|
|
51
50
|
"lint:fix": "eslint --fix \"src/**/*.{js,jsx}\"",
|
|
51
|
+
"export": "node scripts/export-component.mjs",
|
|
52
|
+
"prepublishOnly": "npm run build",
|
|
52
53
|
"storybook": "storybook dev -p 6006",
|
|
53
54
|
"build-storybook": "storybook build"
|
|
54
55
|
},
|
|
@@ -60,8 +61,8 @@
|
|
|
60
61
|
"@rollup/plugin-babel": "^6.0.4",
|
|
61
62
|
"@rollup/plugin-commonjs": "^25.0.7",
|
|
62
63
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
63
|
-
"@storybook/addon-docs": "^10.
|
|
64
|
-
"@storybook/react-vite": "^10.
|
|
64
|
+
"@storybook/addon-docs": "^10.4.6",
|
|
65
|
+
"@storybook/react-vite": "^10.4.6",
|
|
65
66
|
"eslint": "^9.39.4",
|
|
66
67
|
"eslint-plugin-react": "^7.37.5",
|
|
67
68
|
"react": "^18.2.0",
|
|
@@ -69,7 +70,8 @@
|
|
|
69
70
|
"rollup": "^4.13.0",
|
|
70
71
|
"rollup-plugin-scss": "^4.0.0",
|
|
71
72
|
"sass": "^1.99.0",
|
|
72
|
-
"storybook": "^10.
|
|
73
|
+
"storybook": "^10.4.6",
|
|
74
|
+
"eslint-plugin-storybook": "10.4.6"
|
|
73
75
|
},
|
|
74
76
|
"dependencies": {
|
|
75
77
|
"fuse.js": "^7.3.0"
|