@reykjavik/hanna-react 0.10.160 → 0.10.162
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/ButtonTertiary.d.ts +1 -1
- package/ButtonTertiary.js +1 -2
- package/CHANGELOG.md +23 -0
- package/ContextMenu.d.ts +86 -0
- package/ContextMenu.js +103 -0
- package/DropdownButton.d.ts +82 -16
- package/DropdownButton.js +44 -14
- package/Icon.d.ts +7 -0
- package/Icon.js +11 -0
- package/MainMenu2.d.ts +16 -11
- package/MainMenu2.js +29 -22
- package/ReykjavikWaves.d.ts +8 -0
- package/ReykjavikWaves.js +8 -0
- package/SearchInput.js +1 -1
- package/_abstract/_Button.d.ts +8 -6
- package/_abstract/_Button.js +8 -11
- package/esm/ButtonTertiary.d.ts +1 -1
- package/esm/ButtonTertiary.js +1 -2
- package/esm/ContextMenu.d.ts +86 -0
- package/esm/ContextMenu.js +98 -0
- package/esm/DropdownButton.d.ts +82 -16
- package/esm/DropdownButton.js +45 -15
- package/esm/Icon.d.ts +7 -0
- package/esm/Icon.js +7 -0
- package/esm/MainMenu2.d.ts +16 -11
- package/esm/MainMenu2.js +29 -22
- package/esm/ReykjavikWaves.d.ts +8 -0
- package/esm/ReykjavikWaves.js +3 -0
- package/esm/SearchInput.js +1 -1
- package/esm/_abstract/_Button.d.ts +8 -6
- package/esm/_abstract/_Button.js +8 -11
- package/esm/index.d.ts +3 -0
- package/index.d.ts +3 -0
- package/package.json +14 -2
package/ButtonTertiary.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { ButtonProps, ButtonVariantProps } from './_abstract/_Button.js';
|
|
3
3
|
type TertiarySize = Extract<ButtonVariantProps['size'], 'normal' | 'small'>;
|
|
4
|
-
type TertiaryIcon = Extract<ButtonVariantProps['icon'], '
|
|
4
|
+
type TertiaryIcon = Extract<ButtonVariantProps['icon'], 'go-back'>;
|
|
5
5
|
export type ButtonTertiaryProps = ButtonProps & Omit<ButtonVariantProps, 'icon' | 'size'> & {
|
|
6
6
|
size?: TertiarySize;
|
|
7
7
|
icon?: TertiaryIcon;
|
package/ButtonTertiary.js
CHANGED
|
@@ -9,13 +9,12 @@ const sizes = {
|
|
|
9
9
|
small: 'small',
|
|
10
10
|
};
|
|
11
11
|
const icons = {
|
|
12
|
-
none: 'none',
|
|
13
12
|
'go-back': 'go-back',
|
|
14
13
|
};
|
|
15
14
|
// NOTE: As a `_abstract/_Button.tsx`-derived component, all `<button/>` and
|
|
16
15
|
// `<a/>` props are allowed directly, so adding `wrapperProps` makes no sense.
|
|
17
16
|
const ButtonTertiary = (props) => {
|
|
18
|
-
const { size = 'normal', icon = '
|
|
17
|
+
const { size = 'normal', icon = '' } = props;
|
|
19
18
|
return react_1.default.createElement(_Button_js_1.Button, Object.assign({ bem: "ButtonTertiary" }, props, { size: sizes[size], icon: icons[icon] }));
|
|
20
19
|
};
|
|
21
20
|
exports.ButtonTertiary = ButtonTertiary;
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
- ... <!-- Add new lines here. -->
|
|
6
6
|
|
|
7
|
+
## 0.10.162
|
|
8
|
+
|
|
9
|
+
_2026-01-16_
|
|
10
|
+
|
|
11
|
+
- feat: Add component `ContextMenu` (previously `DropdownButton`)
|
|
12
|
+
- feat: Deprecate component `DropdownButton` (in favor of `ContextMenu`)
|
|
13
|
+
|
|
14
|
+
## 0.10.161
|
|
15
|
+
|
|
16
|
+
_2026-01-15_
|
|
17
|
+
|
|
18
|
+
- feat: Add component `Icon`
|
|
19
|
+
- feat: Add component `ReykjavikWaves`
|
|
20
|
+
- `DropdownButton`:
|
|
21
|
+
- feat: Add prop `Toggler` for custom toggler content
|
|
22
|
+
- feat: Add "customitem" object with `Content`, `modifier` and `current`
|
|
23
|
+
- `MainMenu2`:
|
|
24
|
+
- feat: Add prop `redhot` to `MainMenu2Props.items.hot` items to always show
|
|
25
|
+
them on mobile
|
|
26
|
+
- feat: Add "customitem" object with `Content`, `modifier` and `current`
|
|
27
|
+
- docs: Minor JSDoc corrections for on-click handlers
|
|
28
|
+
- fix: Clicking `.SearchInput__button` always passed empty string as value
|
|
29
|
+
|
|
7
30
|
## 0.10.160
|
|
8
31
|
|
|
9
32
|
_2025-10-13_
|
package/ContextMenu.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { HTMLAttributeAnchorTarget, ReactElement } from 'react';
|
|
2
|
+
import { IconToken } from '@reykjavik/hanna-css';
|
|
3
|
+
import { ClassNameModifiers, EitherObj } from '@reykjavik/hanna-utils';
|
|
4
|
+
import { IconName_old } from '../../hanna-css/src/lib/icons.js';
|
|
5
|
+
import { ButtonVariantProps } from './_abstract/_Button.js';
|
|
6
|
+
import { SSRSupportProps, WrapperElmProps } from './utils.js';
|
|
7
|
+
type Prefix<record extends Record<string, unknown>, prefix extends string> = {
|
|
8
|
+
[K in keyof record as `${prefix}${Capitalize<string & K>}`]: record[K];
|
|
9
|
+
};
|
|
10
|
+
export type ContextMenuItem = {
|
|
11
|
+
/** Visible label text */
|
|
12
|
+
label: string | ReactElement;
|
|
13
|
+
/** Un-abbreviated label set as `aria-label=""` */
|
|
14
|
+
labelLong?: string;
|
|
15
|
+
/** Language of the link label */
|
|
16
|
+
lang?: string;
|
|
17
|
+
/** Languge of the linked resource */
|
|
18
|
+
hrefLang?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Puts a modifier className for the menu __item <li/> element.
|
|
21
|
+
* */
|
|
22
|
+
modifier?: ClassNameModifiers;
|
|
23
|
+
/** Signifies if the menu item is part of the page's breadcrumb trail */
|
|
24
|
+
current?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The URL the link points to.
|
|
27
|
+
*
|
|
28
|
+
* If neither `href` nor `onClick` is passed, then the item is not rendered
|
|
29
|
+
* at all.
|
|
30
|
+
*/
|
|
31
|
+
href?: string;
|
|
32
|
+
/** Sets `target=""` on anchor tags with a `href` attribute. */
|
|
33
|
+
target?: HTMLAttributeAnchorTarget;
|
|
34
|
+
/**
|
|
35
|
+
* Adding `onClick` automatically results in a <button/> element being
|
|
36
|
+
* rendered. If `href` is also passed, then a <a href/> element is rendered
|
|
37
|
+
* during initial (server-side) render, which then gets replaced by a
|
|
38
|
+
* <button/> element during the first client-side
|
|
39
|
+
*
|
|
40
|
+
* NOTE: Clicking a menu item will automatically close tghe menu
|
|
41
|
+
* … unless the `onClick` function explicitly returns `false`.
|
|
42
|
+
*/
|
|
43
|
+
onClick?: (item: ContextMenuItem) => void | boolean;
|
|
44
|
+
/** Sets `aria-controls=""` on `<button/>`s with `onClick` */
|
|
45
|
+
controlsId?: string;
|
|
46
|
+
Content?: never;
|
|
47
|
+
/** Seldom used flag for buttons that do destruction */
|
|
48
|
+
destructive?: boolean;
|
|
49
|
+
icon?: IconToken | IconName_old;
|
|
50
|
+
};
|
|
51
|
+
type ContextMenuCustomItemFn = (props: {
|
|
52
|
+
closeMenu: () => void;
|
|
53
|
+
}) => ReactElement;
|
|
54
|
+
export type ContextMenuCustomItem = Pick<ContextMenuItem, 'modifier' | 'current'> & {
|
|
55
|
+
Content: ContextMenuCustomItemFn;
|
|
56
|
+
};
|
|
57
|
+
/** Renders a divider line between `ContextMenu*Item`s with an optional legend */
|
|
58
|
+
export type ContextMenuItemDivider = {
|
|
59
|
+
divider: true;
|
|
60
|
+
label?: string;
|
|
61
|
+
};
|
|
62
|
+
export type ContextMenuProps = {
|
|
63
|
+
/** The items to display inside the dropdown menu */
|
|
64
|
+
items: Array<ContextMenuItem | ContextMenuCustomItem | ContextMenuItemDivider>;
|
|
65
|
+
/**
|
|
66
|
+
* NOTE: Clicking a ContextMenu item will automatically close the drropdown
|
|
67
|
+
* … unless the `onItemClick` function explicitly returns `false`.
|
|
68
|
+
*
|
|
69
|
+
* **NOTE:** Customm items will need to call `closeMenu()` themselves.
|
|
70
|
+
*/
|
|
71
|
+
onItemClick?: (item: ContextMenuItem) => void | boolean;
|
|
72
|
+
} & EitherObj<{
|
|
73
|
+
/** Label for the toggler */
|
|
74
|
+
label: string | ReactElement;
|
|
75
|
+
/** Longer accessible toggler label text */
|
|
76
|
+
labelLong?: string;
|
|
77
|
+
/** Default: `"secondary"` */
|
|
78
|
+
togglerType?: 'primary' | 'secondary';
|
|
79
|
+
} & Prefix<Omit<ButtonVariantProps, 'small'>, 'toggler'>, {
|
|
80
|
+
/** Custom toggler rendering function component */
|
|
81
|
+
Toggler: (props: {
|
|
82
|
+
isOpen: boolean;
|
|
83
|
+
}) => ReactElement;
|
|
84
|
+
}> & WrapperElmProps<'details', 'open' | 'name'> & SSRSupportProps;
|
|
85
|
+
export declare const ContextMenu: (props: ContextMenuProps) => JSX.Element;
|
|
86
|
+
export {};
|
package/ContextMenu.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContextMenu = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
6
|
+
const react_2 = require("@floating-ui/react");
|
|
7
|
+
const hanna_utils_1 = require("@reykjavik/hanna-utils");
|
|
8
|
+
const _Button_js_1 = require("./_abstract/_Button.js");
|
|
9
|
+
const useCallbackOnEsc_js_1 = require("./utils/useCallbackOnEsc.js");
|
|
10
|
+
const useLaggedState_js_1 = require("./utils/useLaggedState.js");
|
|
11
|
+
const useOnClickOutside_js_1 = require("./utils/useOnClickOutside.js");
|
|
12
|
+
const FocusTrap_js_1 = require("./FocusTrap.js");
|
|
13
|
+
const utils_js_1 = require("./utils.js");
|
|
14
|
+
const ContextMenu = (props) => {
|
|
15
|
+
const [isOpen, setIsOpen] = (0, useLaggedState_js_1.useLaggedState)(false, 10);
|
|
16
|
+
const isBrowser = (0, utils_js_1.useIsBrowserSide)(props.ssr);
|
|
17
|
+
const [isHovering, setIsHovering] = (0, react_1.useState)(false);
|
|
18
|
+
const wrapperRef = (0, react_1.useRef)(null);
|
|
19
|
+
const closeMenuStat = () => setIsOpen(false, 0);
|
|
20
|
+
(0, useOnClickOutside_js_1.useOnClickOutside)(wrapperRef, isOpen && closeMenuStat);
|
|
21
|
+
(0, useCallbackOnEsc_js_1.useCallbackOnEsc)(isOpen && closeMenuStat);
|
|
22
|
+
const { x, y, refs } = (0, react_2.useFloating)({
|
|
23
|
+
placement: 'bottom-start',
|
|
24
|
+
middleware: [(0, react_2.flip)(), (0, react_2.shift)()],
|
|
25
|
+
whileElementsMounted: react_2.autoUpdate,
|
|
26
|
+
});
|
|
27
|
+
const { onItemClick, wrapperProps = {} } = props;
|
|
28
|
+
const toggle = (e) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
setIsOpen(!isOpen, 0);
|
|
31
|
+
};
|
|
32
|
+
return (react_1.default.createElement("details", Object.assign({}, wrapperProps, { className: (0, hanna_utils_1.modifiedClass)('ContextMenu', isOpen && 'open', wrapperProps.className), open: isOpen, onBlur: (e) => {
|
|
33
|
+
var _a;
|
|
34
|
+
if (!isHovering) {
|
|
35
|
+
setIsOpen(false, 300);
|
|
36
|
+
}
|
|
37
|
+
(_a = wrapperProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(wrapperProps, e);
|
|
38
|
+
}, ref: (elm) => {
|
|
39
|
+
if (!elm) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
wrapperRef.current = elm;
|
|
43
|
+
refs.setReference(elm.querySelector('.ContextMenu__toggler'));
|
|
44
|
+
refs.setFloating(elm.querySelector('.ContextMenu__menu'));
|
|
45
|
+
} }),
|
|
46
|
+
props.Toggler ? (react_1.default.createElement("summary", { className: "ContextMenu__toggler", onClick: toggle },
|
|
47
|
+
react_1.default.createElement(props.Toggler, { isOpen: isOpen }))) : (react_1.default.createElement(_Button_js_1.Button, { as: "summary", className: "ContextMenu__toggler", bem: props.togglerType === 'primary' ? 'ButtonPrimary' : 'ButtonSecondary', icon: props.togglerIcon, size: props.togglerSize, variant: props.togglerVariant, "aria-label": props.labelLong, onClick: toggle }, props.label)),
|
|
48
|
+
react_1.default.createElement("ul", { className: "ContextMenu__menu", onMouseEnter: () => {
|
|
49
|
+
setIsHovering(true);
|
|
50
|
+
}, onMouseLeave: () => {
|
|
51
|
+
setIsHovering(false);
|
|
52
|
+
}, onFocus: () => {
|
|
53
|
+
setIsOpen(true, 0);
|
|
54
|
+
}, style: x != null
|
|
55
|
+
? {
|
|
56
|
+
'--ContextMenu-pos-y': `${y}px`,
|
|
57
|
+
'--ContextMenu-pos-x': `${x}px`,
|
|
58
|
+
}
|
|
59
|
+
: undefined },
|
|
60
|
+
props.items.map(
|
|
61
|
+
// eslint-disable-next-line complexity
|
|
62
|
+
(item, i) => {
|
|
63
|
+
if ('divider' in item) {
|
|
64
|
+
if ((i === 0 && !item.label) || i === props.items.length - 1) {
|
|
65
|
+
// Gracefully omit pointless dividers
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return (react_1.default.createElement("li", { key: i, className: (0, hanna_utils_1.modifiedClass)('ContextMenu__itemDivider', item.label && 'labelled') }, item.label || false));
|
|
69
|
+
}
|
|
70
|
+
if (typeof item === 'function') {
|
|
71
|
+
item = { Content: item };
|
|
72
|
+
}
|
|
73
|
+
const itemProps = {
|
|
74
|
+
className: (0, hanna_utils_1.modifiedClass)('ContextMenu__item', item.modifier),
|
|
75
|
+
'aria-current': item.current || undefined,
|
|
76
|
+
};
|
|
77
|
+
if ('Content' in item && item.Content) {
|
|
78
|
+
return (react_1.default.createElement("li", Object.assign({ key: i, "data-customitem": "" }, itemProps),
|
|
79
|
+
react_1.default.createElement(item.Content, { closeMenu: closeMenuStat })));
|
|
80
|
+
}
|
|
81
|
+
const { label, onClick, href, destructive } = item;
|
|
82
|
+
const commonProps = {
|
|
83
|
+
className: (0, hanna_utils_1.modifiedClass)('ContextMenu__itembutton', destructive && 'destructive'),
|
|
84
|
+
lang: item.lang,
|
|
85
|
+
'data-icon': item.icon,
|
|
86
|
+
'arial-label': item.labelLong,
|
|
87
|
+
};
|
|
88
|
+
const doRenderButton = isBrowser && (onClick || (onItemClick && href == null));
|
|
89
|
+
// TypeScript type-narrowing helper for the onClick callbacks below — because
|
|
90
|
+
// `item` is a variable and could hypothetically change before the click occurs
|
|
91
|
+
const _item = item;
|
|
92
|
+
return (react_1.default.createElement("li", { key: i, className: (0, hanna_utils_1.modifiedClass)('ContextMenu__item', item.modifier), "aria-current": item.current || undefined }, doRenderButton ? (react_1.default.createElement("button", Object.assign({}, commonProps, { type: "button", "aria-controls": item.controlsId, onClick: () => {
|
|
93
|
+
const keepOpen1 = onClick && onClick(_item) === false;
|
|
94
|
+
const keepOpen2 = onItemClick && onItemClick(_item) === false;
|
|
95
|
+
!(keepOpen1 || keepOpen2) && closeMenuStat();
|
|
96
|
+
} }), label)) : href != null ? (react_1.default.createElement("a", Object.assign({}, commonProps, { href: href, hrefLang: item.hrefLang, target: item.target, onClick: () => {
|
|
97
|
+
const keepOpen = onItemClick && onItemClick(_item) === false;
|
|
98
|
+
!keepOpen && closeMenuStat();
|
|
99
|
+
} }), label)) : null));
|
|
100
|
+
}),
|
|
101
|
+
react_1.default.createElement(FocusTrap_js_1.FocusTrap, { Tag: "li", depth: 2 }))));
|
|
102
|
+
};
|
|
103
|
+
exports.ContextMenu = ContextMenu;
|
package/DropdownButton.d.ts
CHANGED
|
@@ -1,29 +1,95 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { HTMLAttributeAnchorTarget, ReactElement } from 'react';
|
|
2
|
+
import { IconToken } from '@reykjavik/hanna-css';
|
|
3
|
+
import { ClassNameModifiers, EitherObj } from '@reykjavik/hanna-utils';
|
|
4
|
+
import { IconName_old } from '../../hanna-css/src/lib/icons.js';
|
|
3
5
|
import { ButtonVariantProps } from './_abstract/_Button.js';
|
|
4
|
-
import {
|
|
6
|
+
import { ContextMenuItem } from './ContextMenu.js';
|
|
5
7
|
import { SSRSupportProps, WrapperElmProps } from './utils.js';
|
|
6
8
|
type Prefix<record extends Record<string, unknown>, prefix extends string> = {
|
|
7
9
|
[K in keyof record as `${prefix}${Capitalize<string & K>}`]: record[K];
|
|
8
10
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
type _DropdownButtonItem = {
|
|
12
|
+
/** Visible label text */
|
|
13
|
+
label: string | ReactElement;
|
|
14
|
+
/** Un-abbreviated label set as `aria-label=""` */
|
|
15
|
+
labelLong?: string;
|
|
16
|
+
/** Language of the link label */
|
|
17
|
+
lang?: string;
|
|
18
|
+
/** Languge of the linked resource */
|
|
19
|
+
hrefLang?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Puts a modifier className for the menu __item <li/> element.
|
|
22
|
+
* */
|
|
23
|
+
modifier?: ClassNameModifiers;
|
|
24
|
+
/** Signifies if the menu item is part of the page's breadcrumb trail */
|
|
25
|
+
current?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The URL the link points to.
|
|
28
|
+
*
|
|
29
|
+
* If neither `href` nor `onClick` is passed, then the item is not rendered
|
|
30
|
+
* at all.
|
|
31
|
+
*/
|
|
32
|
+
href?: string;
|
|
33
|
+
/** Sets `target=""` on anchor tags with a `href` attribute. */
|
|
34
|
+
target?: HTMLAttributeAnchorTarget;
|
|
35
|
+
/**
|
|
36
|
+
* Adding `onClick` automatically results in a <button/> element being
|
|
37
|
+
* rendered. If `href` is also passed, then a <a href/> element is rendered
|
|
38
|
+
* during initial (server-side) render, which then gets replaced by a
|
|
39
|
+
* <button/> element during the first client-side
|
|
40
|
+
*
|
|
41
|
+
* NOTE: Clicking a menu item will automatically close tghe menu
|
|
42
|
+
* … unless the `onClick` function explicitly returns `false`.
|
|
43
|
+
*/
|
|
44
|
+
onClick?: (item: _DropdownButtonItem) => void | boolean;
|
|
45
|
+
/** Sets `aria-controls=""` on `<button/>`s with `onClick` */
|
|
46
|
+
controlsId?: string;
|
|
47
|
+
Content?: never;
|
|
48
|
+
/** Seldom used flag for buttons that do destruction */
|
|
49
|
+
destructive?: boolean;
|
|
50
|
+
icon?: IconToken | IconName_old;
|
|
11
51
|
};
|
|
12
|
-
|
|
52
|
+
type DropdownbButtonCustomItemFn = (props: {
|
|
13
53
|
closeMenu: () => void;
|
|
14
|
-
}) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
54
|
+
}) => ReactElement;
|
|
55
|
+
type _DropdownButtonCustomItem = Pick<ContextMenuItem, 'modifier' | 'current'> & {
|
|
56
|
+
Content: DropdownbButtonCustomItemFn;
|
|
57
|
+
};
|
|
58
|
+
type _DropdownButtonItemDivider = {
|
|
59
|
+
divider: true;
|
|
60
|
+
label?: string;
|
|
61
|
+
};
|
|
62
|
+
type _DropdownButtonProps = {
|
|
63
|
+
/** The items to display inside the dropdown menu */
|
|
64
|
+
items: Array<_DropdownButtonItem | _DropdownButtonCustomItem | _DropdownButtonItemDivider>;
|
|
19
65
|
/**
|
|
20
|
-
* NOTE: Clicking a
|
|
21
|
-
* "Hamburger menu" (a.k.a. "Mobile menu")
|
|
66
|
+
* NOTE: Clicking a ContextMenu item will automatically close the drropdown
|
|
22
67
|
* … unless the `onItemClick` function explicitly returns `false`.
|
|
68
|
+
*
|
|
69
|
+
* **NOTE:** Customm items will need to call `closeMenu()` themselves.
|
|
23
70
|
*/
|
|
24
|
-
onItemClick?: (item:
|
|
25
|
-
|
|
71
|
+
onItemClick?: (item: ContextMenuItem) => void | boolean;
|
|
72
|
+
} & EitherObj<{
|
|
73
|
+
/** Label for the toggler */
|
|
74
|
+
label: string | ReactElement;
|
|
75
|
+
/** Longer accessible toggler label text */
|
|
76
|
+
labelLong?: string;
|
|
77
|
+
/** Default: `"secondary"` */
|
|
26
78
|
buttonType?: 'primary' | 'secondary';
|
|
27
|
-
} & Prefix<Omit<ButtonVariantProps, 'small'>, 'button'
|
|
79
|
+
} & Prefix<Omit<ButtonVariantProps, 'small'>, 'button'>, {
|
|
80
|
+
/** Custom toggler rendering function component */
|
|
81
|
+
Toggler: (props: {
|
|
82
|
+
isOpen: boolean;
|
|
83
|
+
}) => ReactElement;
|
|
84
|
+
}> & WrapperElmProps<'details', 'open' | 'name'> & SSRSupportProps;
|
|
85
|
+
/** @deprecated Use `ContextMenu` instead. (Will be removed in v0.11) */
|
|
28
86
|
export declare const DropdownButton: (props: DropdownButtonProps) => JSX.Element;
|
|
87
|
+
/** @deprecated Use `ContextMenuItem` instead. (Will be removed in v0.11) */
|
|
88
|
+
export type DropdownButtonItem = _DropdownButtonItem;
|
|
89
|
+
/** @deprecated Use `ContextMenuCustomItem` instead. (Will be removed in v0.11) */
|
|
90
|
+
export type DropdownButtonCustomItem = _DropdownButtonCustomItem;
|
|
91
|
+
/** @deprecated Use `ContextMenuItemDivider` instead. (Will be removed in v0.11) */
|
|
92
|
+
export type DropdownButtonItemDivider = _DropdownButtonItemDivider;
|
|
93
|
+
/** @deprecated Use `ContextMenuProps` instead. (Will be removed in v0.11) */
|
|
94
|
+
export type DropdownButtonProps = _DropdownButtonProps;
|
|
29
95
|
export {};
|
package/DropdownButton.js
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DropdownButton = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
// ===========================================================================
|
|
6
|
+
/*
|
|
7
|
+
NOTE:
|
|
8
|
+
THIS COMPONENT IS DEPRECATED AND WILL BE REMOVED IN A FUTURE VERSION.
|
|
9
|
+
DO NOT UPDATE IT OR ADD NEW FEATURES.
|
|
10
|
+
|
|
11
|
+
*/
|
|
12
|
+
// ===========================================================================
|
|
5
13
|
const react_1 = tslib_1.__importStar(require("react"));
|
|
6
14
|
const react_2 = require("@floating-ui/react");
|
|
7
15
|
const hanna_utils_1 = require("@reykjavik/hanna-utils");
|
|
@@ -11,11 +19,13 @@ const useLaggedState_js_1 = require("./utils/useLaggedState.js");
|
|
|
11
19
|
const useOnClickOutside_js_1 = require("./utils/useOnClickOutside.js");
|
|
12
20
|
const FocusTrap_js_1 = require("./FocusTrap.js");
|
|
13
21
|
const utils_js_1 = require("./utils.js");
|
|
22
|
+
/** @deprecated Use `ContextMenu` instead. (Will be removed in v0.11) */
|
|
23
|
+
// eslint-disable-next-line deprecation/deprecation
|
|
14
24
|
const DropdownButton = (props) => {
|
|
15
25
|
const [isOpen, setIsOpen] = (0, useLaggedState_js_1.useLaggedState)(false, 10);
|
|
16
26
|
const isBrowser = (0, utils_js_1.useIsBrowserSide)(props.ssr);
|
|
17
27
|
const [isHovering, setIsHovering] = (0, react_1.useState)(false);
|
|
18
|
-
const wrapperRef = react_1.
|
|
28
|
+
const wrapperRef = (0, react_1.useRef)(null);
|
|
19
29
|
const closeMenuStat = () => setIsOpen(false, 0);
|
|
20
30
|
(0, useOnClickOutside_js_1.useOnClickOutside)(wrapperRef, isOpen && closeMenuStat);
|
|
21
31
|
(0, useCallbackOnEsc_js_1.useCallbackOnEsc)(isOpen && closeMenuStat);
|
|
@@ -25,6 +35,10 @@ const DropdownButton = (props) => {
|
|
|
25
35
|
whileElementsMounted: react_2.autoUpdate,
|
|
26
36
|
});
|
|
27
37
|
const { onItemClick, wrapperProps = {} } = props;
|
|
38
|
+
const toggle = (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
setIsOpen(!isOpen, 0);
|
|
41
|
+
};
|
|
28
42
|
return (react_1.default.createElement("details", Object.assign({}, wrapperProps, { className: (0, hanna_utils_1.modifiedClass)('DropdownButton', isOpen && 'open', wrapperProps.className), open: isOpen, onBlur: (e) => {
|
|
29
43
|
var _a;
|
|
30
44
|
if (!isHovering) {
|
|
@@ -39,10 +53,8 @@ const DropdownButton = (props) => {
|
|
|
39
53
|
refs.setReference(elm.querySelector('.DropdownButton__toggler'));
|
|
40
54
|
refs.setFloating(elm.querySelector('.DropdownButton__menu'));
|
|
41
55
|
} }),
|
|
42
|
-
react_1.default.createElement(
|
|
43
|
-
|
|
44
|
-
setIsOpen(!isOpen, 0);
|
|
45
|
-
} }, props.label),
|
|
56
|
+
props.Toggler ? (react_1.default.createElement("summary", { className: "DropdownButton__toggler", onClick: toggle },
|
|
57
|
+
react_1.default.createElement(props.Toggler, { isOpen: isOpen }))) : (react_1.default.createElement(_Button_js_1.Button, { as: "summary", className: "DropdownButton__toggler", bem: props.buttonType === 'primary' ? 'ButtonPrimary' : 'ButtonSecondary', icon: props.buttonIcon, size: props.buttonSize, variant: props.buttonVariant, "aria-label": props.labelLong, onClick: toggle }, props.label)),
|
|
46
58
|
react_1.default.createElement("ul", { className: "DropdownButton__menu", onMouseEnter: () => {
|
|
47
59
|
setIsHovering(true);
|
|
48
60
|
}, onMouseLeave: () => {
|
|
@@ -55,26 +67,44 @@ const DropdownButton = (props) => {
|
|
|
55
67
|
'--DropdownButton-pos-x': `${x}px`,
|
|
56
68
|
}
|
|
57
69
|
: undefined },
|
|
58
|
-
props.items.map(
|
|
70
|
+
props.items.map(
|
|
71
|
+
// eslint-disable-next-line complexity
|
|
72
|
+
(item, i) => {
|
|
73
|
+
if ('divider' in item) {
|
|
74
|
+
if ((i === 0 && !item.label) || i === props.items.length - 1) {
|
|
75
|
+
// Gracefully omit pointless dividers
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return (react_1.default.createElement("li", { key: i, className: (0, hanna_utils_1.modifiedClass)('DropdownButton__itemDivider', item.label && 'labelled') }, item.label || false));
|
|
79
|
+
}
|
|
59
80
|
if (typeof item === 'function') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
item = { Content: item };
|
|
82
|
+
}
|
|
83
|
+
const itemProps = {
|
|
84
|
+
className: (0, hanna_utils_1.modifiedClass)('DropdownButton__item', item.modifier),
|
|
85
|
+
'aria-current': item.current || undefined,
|
|
86
|
+
};
|
|
87
|
+
if ('Content' in item && item.Content) {
|
|
88
|
+
return (react_1.default.createElement("li", Object.assign({ key: i, "data-customitem": "" }, itemProps),
|
|
89
|
+
react_1.default.createElement(item.Content, { closeMenu: closeMenuStat })));
|
|
63
90
|
}
|
|
64
|
-
const { label, onClick, href } = item;
|
|
91
|
+
const { label, onClick, href, destructive } = item;
|
|
65
92
|
const commonProps = {
|
|
66
|
-
className: 'DropdownButton__itembutton',
|
|
93
|
+
className: (0, hanna_utils_1.modifiedClass)('DropdownButton__itembutton', destructive && 'destructive'),
|
|
67
94
|
lang: item.lang,
|
|
68
95
|
'data-icon': item.icon,
|
|
69
96
|
'arial-label': item.labelLong,
|
|
70
97
|
};
|
|
71
98
|
const doRenderButton = isBrowser && (onClick || (onItemClick && href == null));
|
|
99
|
+
// TypeScript type-narrowing helper for the onClick callbacks below — because
|
|
100
|
+
// `item` is a variable and could hypothetically change before the click occurs
|
|
101
|
+
const _item = item;
|
|
72
102
|
return (react_1.default.createElement("li", { key: i, className: (0, hanna_utils_1.modifiedClass)('DropdownButton__item', item.modifier), "aria-current": item.current || undefined }, doRenderButton ? (react_1.default.createElement("button", Object.assign({}, commonProps, { type: "button", "aria-controls": item.controlsId, onClick: () => {
|
|
73
|
-
const keepOpen1 = onClick && onClick(
|
|
74
|
-
const keepOpen2 = onItemClick && onItemClick(
|
|
103
|
+
const keepOpen1 = onClick && onClick(_item) === false;
|
|
104
|
+
const keepOpen2 = onItemClick && onItemClick(_item) === false;
|
|
75
105
|
!(keepOpen1 || keepOpen2) && closeMenuStat();
|
|
76
106
|
} }), label)) : href != null ? (react_1.default.createElement("a", Object.assign({}, commonProps, { href: href, hrefLang: item.hrefLang, target: item.target, onClick: () => {
|
|
77
|
-
const keepOpen = onItemClick && onItemClick(
|
|
107
|
+
const keepOpen = onItemClick && onItemClick(_item) === false;
|
|
78
108
|
!keepOpen && closeMenuStat();
|
|
79
109
|
} }), label)) : null));
|
|
80
110
|
}),
|
package/Icon.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IconToken } from '../../hanna-css/src/iconfontTokens.js';
|
|
2
|
+
import { HTMLProps } from './utils.js';
|
|
3
|
+
export type IconProps = {
|
|
4
|
+
type: IconToken;
|
|
5
|
+
size?: 'small' | 'medium' | 'large';
|
|
6
|
+
} & HTMLProps<'span'>;
|
|
7
|
+
export declare const Icon: (props: IconProps) => JSX.Element;
|
package/Icon.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Icon = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const react_1 = tslib_1.__importDefault(require("react"));
|
|
6
|
+
const hanna_utils_1 = require("@reykjavik/hanna-utils");
|
|
7
|
+
const Icon = (props) => {
|
|
8
|
+
const { className, type, size = 'medium' } = props, rest = tslib_1.__rest(props, ["className", "type", "size"]);
|
|
9
|
+
return (react_1.default.createElement("span", Object.assign({}, rest, { className: (0, hanna_utils_1.classes)('Icon', props.className), "data-icon": type, "data-icon-size": size !== 'medium' ? size : undefined })));
|
|
10
|
+
};
|
|
11
|
+
exports.Icon = Icon;
|
package/MainMenu2.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactElement } from 'react';
|
|
2
|
+
import { IconName, IconToken } from '@reykjavik/hanna-css';
|
|
2
3
|
import { ClassNameModifiers, Cleanup, EitherObj } from '@reykjavik/hanna-utils';
|
|
3
4
|
import { Illustration } from '@reykjavik/hanna-utils/assets';
|
|
4
5
|
import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
|
|
@@ -44,32 +45,35 @@ export type MainMenu2Item = {
|
|
|
44
45
|
* during initial (server-side) render, which then gets replaced by a
|
|
45
46
|
* <button/> element during the first client-side
|
|
46
47
|
*
|
|
47
|
-
* NOTE: Clicking a menu item will automatically close
|
|
48
|
-
* "Hamburger menu" (a.k.a. "Mobile menu")
|
|
48
|
+
* NOTE: Clicking a menu item will automatically close tghe menu
|
|
49
49
|
* … unless the `onClick` function explicitly returns `false`.
|
|
50
50
|
*/
|
|
51
51
|
onClick?: (item: MainMenu2Item) => void | boolean;
|
|
52
52
|
/** Sets `aria-controls=""` on `<button/>`s with `onClick` */
|
|
53
53
|
controlsId?: string;
|
|
54
|
+
Content?: never;
|
|
54
55
|
};
|
|
55
56
|
export type MainMenu2ButtonItem = MainMenu2Item & {
|
|
56
|
-
icon?:
|
|
57
|
+
icon?: IconToken | IconName;
|
|
57
58
|
};
|
|
58
|
-
|
|
59
|
+
type MainMenu2CustomItemFn = (props: {
|
|
59
60
|
closeMenu: () => void;
|
|
60
61
|
openMenu: () => void;
|
|
61
62
|
}) => ReactElement;
|
|
63
|
+
export type MainMenu2CustomItem = Pick<MainMenu2Item, 'modifier' | 'current'> & {
|
|
64
|
+
Content: MainMenu2CustomItemFn;
|
|
65
|
+
};
|
|
62
66
|
export type MainMenu2SubMenuItem = MainMenu2Item & {
|
|
63
67
|
descr?: string;
|
|
64
68
|
};
|
|
65
69
|
export type MainMenu2SubMenu = {
|
|
66
70
|
title: string;
|
|
67
71
|
current?: boolean;
|
|
68
|
-
subItems: Array<MainMenu2SubMenuItem | MainMenu2CustomItem | Falseish>;
|
|
72
|
+
subItems: Array<MainMenu2SubMenuItem | MainMenu2CustomItem | MainMenu2CustomItemFn | Falseish>;
|
|
69
73
|
};
|
|
70
|
-
export type MainMenu2ItemList = Array<MainMenu2Item | MainMenu2CustomItem | Falseish>;
|
|
71
|
-
export type MainMenu2ButtonItemList = Array<MainMenu2ButtonItem | MainMenu2CustomItem | Falseish>;
|
|
72
|
-
export type MainMenu2SubMenuItemList = Array<MainMenu2SubMenuItem | MainMenu2CustomItem | Falseish>;
|
|
74
|
+
export type MainMenu2ItemList = Array<MainMenu2Item | MainMenu2CustomItem | MainMenu2CustomItemFn | Falseish>;
|
|
75
|
+
export type MainMenu2ButtonItemList<Extra = unknown> = Array<(MainMenu2ButtonItem & Extra) | (MainMenu2CustomItem & Extra) | MainMenu2CustomItemFn | Falseish>;
|
|
76
|
+
export type MainMenu2SubMenuItemList = Array<MainMenu2SubMenuItem | MainMenu2CustomItem | MainMenu2CustomItemFn | Falseish>;
|
|
73
77
|
export type MainMenu2Props = {
|
|
74
78
|
/**
|
|
75
79
|
* URL for the mandatory (usually screen-reader-only) homepage Link.
|
|
@@ -96,7 +100,9 @@ export type MainMenu2Props = {
|
|
|
96
100
|
* "Open Menu" button. Make sure to only use 2–3 items, and remember that
|
|
97
101
|
* they may be hidden on smaller screens.
|
|
98
102
|
*/
|
|
99
|
-
hot?: MainMenu2ButtonItemList
|
|
103
|
+
hot?: MainMenu2ButtonItemList<{
|
|
104
|
+
redhot?: true;
|
|
105
|
+
}>;
|
|
100
106
|
extra?: MainMenu2ButtonItemList;
|
|
101
107
|
relatedTitle?: string;
|
|
102
108
|
related?: MainMenu2ButtonItemList;
|
|
@@ -104,8 +110,7 @@ export type MainMenu2Props = {
|
|
|
104
110
|
/** Visual type */
|
|
105
111
|
variant?: 'default' | 'light';
|
|
106
112
|
/**
|
|
107
|
-
* NOTE: Clicking a MainMenu2 item will automatically close
|
|
108
|
-
* "Hamburger menu" (a.k.a. "Mobile menu")
|
|
113
|
+
* NOTE: Clicking a `MainMenu2` item will automatically close the menu
|
|
109
114
|
* … unless the `onItemClick` function explicitly returns `false`.
|
|
110
115
|
*/
|
|
111
116
|
onItemClick?: (item: MainMenu2Item) => void | boolean;
|