@reykjavik/hanna-react 0.10.112 → 0.10.113

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.
Files changed (72) hide show
  1. package/Alert.js +1 -1
  2. package/BasicTable.js +4 -4
  3. package/CHANGELOG.md +28 -1
  4. package/ContactBubble.js +8 -3
  5. package/Datepicker.d.ts +1 -0
  6. package/Datepicker.js +51 -13
  7. package/{_abstract/_FocusTrap.d.ts → FocusTrap.d.ts} +5 -1
  8. package/{_abstract/_FocusTrap.js → FocusTrap.js} +5 -1
  9. package/Layout.d.ts +7 -3
  10. package/Layout.js +7 -26
  11. package/MainMenu.d.ts +52 -20
  12. package/MainMenu.js +27 -6
  13. package/MainMenu2.d.ts +114 -0
  14. package/MainMenu2.js +235 -0
  15. package/MobileMenuToggler/_useMobileMenuToggling.d.ts +21 -0
  16. package/{utils/useMenuToggling.js → MobileMenuToggler/_useMobileMenuToggling.js} +34 -14
  17. package/MobileMenuToggler.d.ts +21 -0
  18. package/MobileMenuToggler.js +43 -0
  19. package/Multiselect.js +13 -6
  20. package/TagPill.d.ts +2 -0
  21. package/_abstract/_AbstractModal.js +3 -3
  22. package/_abstract/_Button.d.ts +3 -3
  23. package/_abstract/_Button.js +3 -3
  24. package/_abstract/_Table.d.ts +3 -3
  25. package/_abstract/_Table.js +1 -1
  26. package/esm/Alert.js +1 -1
  27. package/esm/BasicTable.js +4 -4
  28. package/esm/ContactBubble.js +8 -3
  29. package/esm/Datepicker.d.ts +1 -0
  30. package/esm/Datepicker.js +51 -13
  31. package/esm/{_abstract/_FocusTrap.d.ts → FocusTrap.d.ts} +5 -1
  32. package/esm/{_abstract/_FocusTrap.js → FocusTrap.js} +5 -1
  33. package/esm/Layout.d.ts +7 -3
  34. package/esm/Layout.js +8 -27
  35. package/esm/MainMenu.d.ts +52 -20
  36. package/esm/MainMenu.js +27 -7
  37. package/esm/MainMenu2.d.ts +114 -0
  38. package/esm/MainMenu2.js +230 -0
  39. package/esm/MobileMenuToggler/_useMobileMenuToggling.d.ts +21 -0
  40. package/esm/{utils/useMenuToggling.js → MobileMenuToggler/_useMobileMenuToggling.js} +32 -13
  41. package/esm/MobileMenuToggler.d.ts +21 -0
  42. package/esm/MobileMenuToggler.js +37 -0
  43. package/esm/Multiselect.js +12 -5
  44. package/esm/TagPill.d.ts +2 -0
  45. package/esm/_abstract/_AbstractModal.js +1 -1
  46. package/esm/_abstract/_Button.d.ts +3 -3
  47. package/esm/_abstract/_Button.js +3 -3
  48. package/esm/_abstract/_Table.d.ts +3 -3
  49. package/esm/_abstract/_Table.js +1 -1
  50. package/esm/index.d.ts +3 -0
  51. package/esm/utils/a11yHelpers.d.ts +2 -0
  52. package/esm/utils/a11yHelpers.js +11 -0
  53. package/esm/utils/types.d.ts +4 -0
  54. package/esm/utils/useFormatMonitor.d.ts +4 -11
  55. package/esm/utils/useFormatMonitor.js +0 -10
  56. package/esm/utils.d.ts +7 -2
  57. package/esm/utils.js +8 -2
  58. package/index.d.ts +3 -0
  59. package/package.json +13 -1
  60. package/utils/a11yHelpers.d.ts +2 -0
  61. package/utils/a11yHelpers.js +15 -0
  62. package/utils/types.d.ts +4 -0
  63. package/utils/useFormatMonitor.d.ts +4 -11
  64. package/utils/useFormatMonitor.js +0 -10
  65. package/utils.d.ts +7 -2
  66. package/utils.js +9 -5
  67. package/esm/utils/HannaUIState.d.ts +0 -7
  68. package/esm/utils/HannaUIState.js +0 -7
  69. package/esm/utils/useMenuToggling.d.ts +0 -10
  70. package/utils/HannaUIState.d.ts +0 -7
  71. package/utils/HannaUIState.js +0 -11
  72. package/utils/useMenuToggling.d.ts +0 -10
package/MainMenu2.js ADDED
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MainMenu2 = exports.defaultMainMenu2Texts = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
+ const classUtils_1 = require("@hugsmidjan/qj/classUtils");
7
+ const assets_1 = require("@reykjavik/hanna-utils/assets");
8
+ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
9
+ const _Link_js_1 = require("./_abstract/_Link.js");
10
+ const a11yHelpers_js_1 = require("./utils/a11yHelpers.js");
11
+ const ButtonPrimary_js_1 = tslib_1.__importDefault(require("./ButtonPrimary.js"));
12
+ const ButtonSecondary_js_1 = tslib_1.__importDefault(require("./ButtonSecondary.js"));
13
+ const FocusTrap_js_1 = require("./FocusTrap.js");
14
+ const utils_js_1 = require("./utils.js");
15
+ const htmlCl =
16
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
17
+ (typeof document !== 'undefined') && document.documentElement.classList;
18
+ const globalClasses = {
19
+ // menuIsActive: '.menu-is-active',
20
+ menuIsOpen: 'menu-is-open',
21
+ menuIsClosed: 'menu-is-closed',
22
+ };
23
+ exports.defaultMainMenu2Texts = {
24
+ is: {
25
+ title: 'Aðalvalmynd',
26
+ homeLink: 'Forsíða',
27
+ openMenu: 'Valmynd',
28
+ openMenuLong: 'Opna Aðalvalmynd',
29
+ closeMenu: 'Loka',
30
+ closeMenuLong: 'Loka Aðalvalmynd',
31
+ },
32
+ en: {
33
+ title: 'Main Menu',
34
+ homeLink: 'Home page',
35
+ openMenu: 'Menu',
36
+ openMenuLong: 'Open main menu',
37
+ closeMenu: 'Close',
38
+ closeMenuLong: 'Close main menu',
39
+ },
40
+ pl: {
41
+ title: 'Menu główne',
42
+ homeLink: 'Strona główna',
43
+ openMenu: 'Menu',
44
+ openMenuLong: 'Otwórz menu główne',
45
+ closeMenu: 'Zamknij',
46
+ closeMenuLong: 'Zamknij menu główne',
47
+ },
48
+ se: {
49
+ title: 'Huvudmeny',
50
+ homeLink: 'Förstasida',
51
+ openMenu: 'Meny',
52
+ openMenuLong: 'Öppna huvudmenyn',
53
+ closeMenu: 'Stäng',
54
+ closeMenuLong: 'Stäng huvudmenyn',
55
+ },
56
+ };
57
+ // ---------------------------------------------------------------------------
58
+ const iconMap = {
59
+ alert: 'info',
60
+ globe: undefined,
61
+ search: 'search',
62
+ user: 'user',
63
+ // NOTE: We're temporarily coerceing `IconName` to `ButtonIcon`
64
+ // TODO: Remove this once Hanna icons (and `ButtonIcons` sperifically)
65
+ // have been expanded better standardised.
66
+ };
67
+ /**
68
+ * Function that turns menu item props/objects into HTML
69
+ * rendering <a/> or <button/> elements depending on the context,
70
+ * Whether we're rendering the menu on the server or in the browser, etc.
71
+ */
72
+ const getRenderers = (props) => {
73
+ const { onItemClick, closeMenu, isBrowser } = props;
74
+ // eslint-disable-next-line complexity
75
+ const renderItem = (classPrefix, item, opts = {}) => {
76
+ const { key, Tag = 'li', button } = opts;
77
+ if (typeof item === 'function') {
78
+ const Item = item;
79
+ return (react_1.default.createElement("li", { key: key, className: `${classPrefix}item` },
80
+ react_1.default.createElement(Item, { closeMenu: closeMenu })));
81
+ }
82
+ const linkClassName = `${classPrefix}link`;
83
+ const { label, labelLong, href, target, lang, controlsId, onClick, descr, icon } = item;
84
+ const itemDescr = descr && (react_1.default.createElement(react_1.default.Fragment, null,
85
+ ' ',
86
+ react_1.default.createElement("small", { className: `${linkClassName}__descr` }, descr)));
87
+ const ButtonTag = button ? ButtonSecondary_js_1.default : 'button';
88
+ const LinkTag = button ? ButtonSecondary_js_1.default : _Link_js_1.Link;
89
+ const buttonCompProps = button
90
+ ? {
91
+ size: 'small',
92
+ 'data-icon': icon && iconMap[icon],
93
+ }
94
+ : undefined;
95
+ return (react_1.default.createElement(Tag, { key: key, className: (0, classUtils_1.modifiedClass)(`${classPrefix}item`, item.modifier), "aria-current": item.current || undefined }, isBrowser && (onClick || !href) ? (react_1.default.createElement(ButtonTag, Object.assign({ className: linkClassName, type: "button", onClick: () => {
96
+ const keepOpen1 = onClick && onClick(item) === false;
97
+ const keepOpen2 = onItemClick && onItemClick(item) === false;
98
+ !(keepOpen1 || keepOpen2) && closeMenu();
99
+ }, "aria-controls": controlsId, "aria-label": labelLong, title: labelLong, lang: lang }, buttonCompProps),
100
+ label,
101
+ " ",
102
+ itemDescr)) : href ? (react_1.default.createElement(LinkTag, Object.assign({ className: linkClassName, href: href, target: target, "aria-label": labelLong, title: labelLong, onClick: () => {
103
+ const keepOpen = onItemClick && onItemClick(item) === false;
104
+ !keepOpen && closeMenu();
105
+ }, lang: lang, hrefLang: item.hrefLang }, buttonCompProps),
106
+ label,
107
+ " ",
108
+ itemDescr)) : null));
109
+ };
110
+ const renderList = (classSuffix, items, opts = {}) => {
111
+ if (!items || !items.length) {
112
+ return null;
113
+ }
114
+ return (react_1.default.createElement("ul", Object.assign({ className: `${classSuffix}items` }, opts.listProps), items.map((listItem, i) => renderItem(classSuffix, listItem, { key: i, button: opts.buttons }))));
115
+ };
116
+ return { renderList, renderItem };
117
+ };
118
+ // eslint-disable-next-line complexity
119
+ const MainMenu2 = (props) => {
120
+ const { homeLink = '/', items, onItemClick, illustration, imageUrl, wrapperProps = {}, } = props;
121
+ const domid = (0, utils_js_1.useDomid)(wrapperProps.id);
122
+ const isBrowser = (0, utils_js_1.useIsBrowserSide)(props.ssr);
123
+ const [isMenuOpen, setIsMenuOpen] = (0, react_1.useState)(false);
124
+ const _wrapperRef = (0, react_1.useRef)(null);
125
+ const wrapperRef = wrapperProps.ref || _wrapperRef;
126
+ const escHandler = (0, react_1.useCallback)(
127
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
128
+ (e) => e.key === 'Escape' && closeMenu(),
129
+ // eslint-disable-next-line react-hooks/exhaustive-deps
130
+ []);
131
+ const openMenu = () => {
132
+ htmlCl.add(globalClasses.menuIsOpen);
133
+ htmlCl.remove(globalClasses.menuIsClosed);
134
+ setIsMenuOpen(true);
135
+ document.addEventListener('keydown', escHandler);
136
+ };
137
+ const closeMenu = () => {
138
+ htmlCl.remove(globalClasses.menuIsOpen);
139
+ htmlCl.add(globalClasses.menuIsClosed);
140
+ setIsMenuOpen(false);
141
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
142
+ setActiveSubmenu(defaultActive);
143
+ wrapperRef.current.scrollTo(0, 0);
144
+ document.removeEventListener('keydown', escHandler);
145
+ };
146
+ (0, react_1.useEffect)(() => {
147
+ if (!isBrowser) {
148
+ return;
149
+ }
150
+ htmlCl.add(globalClasses.menuIsClosed);
151
+ return () => {
152
+ closeMenu();
153
+ htmlCl.remove(globalClasses.menuIsClosed);
154
+ };
155
+ // eslint-disable-next-line react-hooks/exhaustive-deps
156
+ }, [isBrowser]);
157
+ const { mainItems, defaultActive } = (0, react_1.useMemo)(() => {
158
+ if (!items.main || !items.main.length) {
159
+ return { mainItems: undefined, defaultActive: -1 };
160
+ }
161
+ const mainItems = items.main.map((item) => {
162
+ var _a;
163
+ if (!('title' in item) || 'current' in item) {
164
+ return item;
165
+ }
166
+ const current = (_a = item.subItems.find((subItem) => 'current' in subItem && !!subItem.current)) === null || _a === void 0 ? void 0 : _a.current;
167
+ return Object.assign(Object.assign({}, item), { current });
168
+ });
169
+ const defaultActive = mainItems.findIndex((item) => 'current' in item && item.current);
170
+ return { mainItems, defaultActive };
171
+ }, [items.main]);
172
+ const [activeSubmenu, setActiveSubmenu] = (0, react_1.useState)(defaultActive);
173
+ // Insta-reset activeSubmenu when defaultActive changes (i.e. when the
174
+ // menu-items updated) because we otherwise retain the menu's
175
+ // activeSubmenu state between open/close cycles.
176
+ const lastDefaultActive = (0, react_1.useRef)(defaultActive);
177
+ if (defaultActive !== lastDefaultActive.current) {
178
+ lastDefaultActive.current = defaultActive;
179
+ setActiveSubmenu(defaultActive);
180
+ }
181
+ const txt = (0, i18n_1.getTexts)(props, exports.defaultMainMenu2Texts);
182
+ const { renderItem, renderList } = getRenderers({
183
+ onItemClick,
184
+ closeMenu,
185
+ isBrowser,
186
+ });
187
+ const homeLinkItem = Object.assign(Object.assign({}, (typeof homeLink === 'string'
188
+ ? { href: homeLink, label: txt.homeLink }
189
+ : homeLink)), { modifier: 'home' });
190
+ const menuImageUrl = imageUrl || (illustration && (0, assets_1.getIllustrationUrl)(illustration));
191
+ const menuId = `${domid}-menu`;
192
+ return (react_1.default.createElement("nav", Object.assign({}, props.wrapperProps, { className: (0, classUtils_1.modifiedClass)('MainMenu2', isBrowser && (isMenuOpen ? 'open' : 'closed'), wrapperProps.className), style: menuImageUrl
193
+ ? Object.assign(Object.assign({}, wrapperProps.style), { '--menu-image': `url(${menuImageUrl})` })
194
+ : wrapperProps.style, ref: wrapperRef, "aria-label": txt.title, "data-sprinkled": isBrowser, id: menuId }),
195
+ isMenuOpen && react_1.default.createElement(FocusTrap_js_1.FocusTrap, { atTop: true }),
196
+ react_1.default.createElement("div", { className: "MainMenu2__content" },
197
+ react_1.default.createElement("h2", { className: "MainMenu2__title" }, txt.title),
198
+ isBrowser ? (react_1.default.createElement(ButtonPrimary_js_1.default, Object.assign({ className: "MainMenu2__toggler", size: "small", type: "button", "aria-pressed": isMenuOpen, "aria-controls": menuId, "data-icon": "text" }, (isMenuOpen
199
+ ? {
200
+ onClick: closeMenu,
201
+ 'aria-label': txt.closeMenuLong,
202
+ title: txt.closeMenuLong,
203
+ children: txt.closeMenu,
204
+ }
205
+ : {
206
+ onClick: openMenu,
207
+ 'aria-label': txt.openMenuLong,
208
+ title: txt.openMenuLong,
209
+ children: txt.openMenu,
210
+ })))) : (react_1.default.createElement(ButtonPrimary_js_1.default, { className: "MainMenu2__toggler", size: "small", href: `#${menuId}`, onClick: a11yHelpers_js_1.handleAnchorLinkClick, "aria-hidden": "true", "data-icon": "text" }, txt.title)),
211
+ mainItems && (react_1.default.createElement("div", { className: (0, classUtils_1.modifiedClass)('MainMenu2__main', activeSubmenu < 0 && 'noneActive') },
212
+ renderItem('MainMenu2__main__', homeLinkItem, { Tag: 'div' }),
213
+ mainItems.map((mainItem, i) => {
214
+ if ('title' in mainItem) {
215
+ const submenuId = `${domid}-submenu-${i}`;
216
+ const isActive = i === activeSubmenu;
217
+ return (react_1.default.createElement(react_1.Fragment, { key: i },
218
+ react_1.default.createElement("div", { className: "MainMenu2__main__item", "aria-current": mainItem.current || undefined }, isBrowser ? (react_1.default.createElement("button", { className: "MainMenu2__main__link", type: "button", onClick: () => setActiveSubmenu(i), "aria-controls": submenuId, "aria-pressed": isActive }, mainItem.title)) : (react_1.default.createElement("strong", { className: "MainMenu2__main__link" }, mainItem.title))),
219
+ renderList('MainMenu2__main__sub__', mainItem.subItems, isBrowser && {
220
+ listProps: {
221
+ id: submenuId,
222
+ hidden: !isActive,
223
+ },
224
+ })));
225
+ }
226
+ return renderItem('MainMenu2__main__', mainItem, { key: i, Tag: 'div' });
227
+ }))),
228
+ renderList('MainMenu2__hot__', items.hot, { buttons: true }),
229
+ renderList('MainMenu2__extra__', items.extra, { buttons: true }),
230
+ items.related && items.related.length > 0 && (react_1.default.createElement("div", { className: "MainMenu2__related" },
231
+ items.relatedTitle && (react_1.default.createElement("h3", { className: "MainMenu2__related__title" }, items.relatedTitle)),
232
+ renderList('MainMenu2__related__', items.related)))),
233
+ isMenuOpen && react_1.default.createElement(FocusTrap_js_1.FocusTrap, null)));
234
+ };
235
+ exports.MainMenu2 = MainMenu2;
@@ -0,0 +1,21 @@
1
+ /// <reference types="react" />
2
+ type MenuTogglingState = {
3
+ isMenuActive: true | undefined;
4
+ isMenuOpen: boolean;
5
+ toggleMenu: () => void;
6
+ closeMenu: () => void;
7
+ uiState: MobileMenuTogglerState;
8
+ };
9
+ type Opts = {
10
+ doInitialize?: boolean;
11
+ togglerElm?: string | HTMLElement;
12
+ };
13
+ export declare const useMobileMenuToggling: (opts?: boolean | Opts) => MenuTogglingState;
14
+ export type MobileMenuTogglerState = {
15
+ closeHamburgerMenu: () => void;
16
+ isHamburgerMenuOpen: boolean | undefined;
17
+ isHamburgerMenuActive: boolean | undefined;
18
+ };
19
+ export declare const MobileMenuStateProvider: import("react").Provider<MobileMenuTogglerState>;
20
+ export declare const useMobileMenuTogglerState: () => MobileMenuTogglerState;
21
+ export {};
@@ -1,15 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useMenuToggling = void 0;
3
+ exports.useMobileMenuTogglerState = exports.MobileMenuStateProvider = exports.useMobileMenuToggling = void 0;
4
4
  const react_1 = require("react");
5
5
  const hanna_utils_1 = require("@reykjavik/hanna-utils");
6
- const useFormatMonitor_js_1 = require("./useFormatMonitor.js");
6
+ const useFormatMonitor_js_1 = require("../utils/useFormatMonitor.js");
7
7
  const htmlClass = (className, add) => {
8
8
  document.documentElement.classList[add ? 'add' : 'remove'](className);
9
9
  };
10
10
  const noop = () => undefined;
11
- // ---------------------------------------------------------------------------
12
- const useMenuToggling = (doInitialize = true) => {
11
+ const HamburgerMedias = { phone: 1, phablet: 1, tablet: 1 };
12
+ const useMobileMenuToggling = (opts) => {
13
+ const { doInitialize, togglerElm = '.MainMenuToggler' } = typeof opts === 'boolean'
14
+ ? ({ doInitialize: opts })
15
+ : !opts
16
+ ? ({ doInitialize: true })
17
+ : opts;
13
18
  const stateRef = (0, react_1.useRef)({
14
19
  isMenuOpen: false,
15
20
  isMenuActive: undefined,
@@ -22,16 +27,17 @@ const useMenuToggling = (doInitialize = true) => {
22
27
  }
23
28
  : noop,
24
29
  // eslint-disable-next-line @typescript-eslint/no-use-before-define
25
- closeMenu: () => doInitialize && _closeMenu(),
30
+ closeMenu: doInitialize ? () => _closeMenu() : noop,
26
31
  });
27
- const [{ isMenuOpen, isMenuActive, uiState }, setMenuState] = (0, react_1.useState)({
32
+ const [{ isMenuOpen, isMenuActive, uiState }, setMenuState] = (0, react_1.useState)(() => ({
28
33
  isMenuOpen: false,
29
34
  isMenuActive: undefined,
30
35
  uiState: {
31
36
  closeHamburgerMenu: () => stateRef.current.closeMenu(),
32
37
  isHamburgerMenuOpen: false,
38
+ isHamburgerMenuActive: false,
33
39
  },
34
- });
40
+ }));
35
41
  stateRef.current.isMenuOpen = isMenuOpen;
36
42
  stateRef.current.isMenuActive = isMenuActive;
37
43
  const _openMenu = () => {
@@ -39,7 +45,11 @@ const useMenuToggling = (doInitialize = true) => {
39
45
  setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuOpen: true, uiState: Object.assign(Object.assign({}, state.uiState), { isHamburgerMenuOpen: true }) })));
40
46
  htmlClass('menu-is-open', true);
41
47
  htmlClass('menu-is-closed', false);
42
- (0, hanna_utils_1.focusElement)('#pagenav');
48
+ const toggler = typeof togglerElm === 'string' ? document.querySelector(togglerElm) : togglerElm;
49
+ const menuElmId = toggler === null || toggler === void 0 ? void 0 : toggler.getAttribute('aria-controls');
50
+ if (menuElmId) {
51
+ (0, hanna_utils_1.focusElement)('#' + menuElmId);
52
+ }
43
53
  }
44
54
  };
45
55
  const _closeMenu = () => {
@@ -47,19 +57,21 @@ const useMenuToggling = (doInitialize = true) => {
47
57
  setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuOpen: false, uiState: Object.assign(Object.assign({}, state.uiState), { isHamburgerMenuOpen: false }) })));
48
58
  htmlClass('menu-is-closed', true);
49
59
  htmlClass('menu-is-open', false);
50
- (0, hanna_utils_1.focusElement)('.Layout__header__skiplink');
60
+ (0, hanna_utils_1.focusElement)(togglerElm);
51
61
  }
52
62
  };
53
63
  (0, useFormatMonitor_js_1.useFormatMonitor)(doInitialize
54
64
  ? (media) => {
55
- if (media.becameHamburger) {
56
- setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuActive: true })));
65
+ const becameHamburger = HamburgerMedias[media.is] && !HamburgerMedias[media.was || ''];
66
+ const leftHamburger = !HamburgerMedias[media.is] && HamburgerMedias[media.was || ''];
67
+ if (becameHamburger) {
68
+ setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuActive: true, uiState: Object.assign(Object.assign({}, state.uiState), { isHamburgerMenuActive: true }) })));
57
69
  htmlClass('menu-is-active', true);
58
70
  htmlClass('menu-is-closed', true);
59
71
  }
60
- if (media.leftHamburger) {
72
+ else if (leftHamburger) {
61
73
  _closeMenu();
62
- setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuActive: undefined })));
74
+ setMenuState((state) => (Object.assign(Object.assign({}, state), { isMenuActive: undefined, uiState: Object.assign(Object.assign({}, state.uiState), { isHamburgerMenuActive: false }) })));
63
75
  htmlClass('menu-is-active', false);
64
76
  htmlClass('menu-is-closed', false);
65
77
  }
@@ -73,4 +85,12 @@ const useMenuToggling = (doInitialize = true) => {
73
85
  uiState,
74
86
  };
75
87
  };
76
- exports.useMenuToggling = useMenuToggling;
88
+ exports.useMobileMenuToggling = useMobileMenuToggling;
89
+ const _MobileMenuTogglerContext = (0, react_1.createContext)({
90
+ closeHamburgerMenu: () => undefined,
91
+ isHamburgerMenuOpen: undefined,
92
+ isHamburgerMenuActive: undefined,
93
+ });
94
+ exports.MobileMenuStateProvider = _MobileMenuTogglerContext.Provider;
95
+ const useMobileMenuTogglerState = () => (0, react_1.useContext)(_MobileMenuTogglerContext);
96
+ exports.useMobileMenuTogglerState = useMobileMenuTogglerState;
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from 'react';
2
+ import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
3
+ import { I18NProps } from './utils/types.js';
4
+ import { SSRSupportProps } from './utils.js';
5
+ export type MobileMenuTogglerI18n = {
6
+ togglerLabel: string;
7
+ closeMenuLabel: string;
8
+ closeMenuLabelLong?: string;
9
+ };
10
+ export declare const defaultMobileMenuTogglerTexts: DefaultTexts<MobileMenuTogglerI18n>;
11
+ export type MobileMenuTogglerProps = {
12
+ /** The DOM id of the menu that is being toggled */
13
+ controlsId: string;
14
+ children: NonNullable<ReactNode>;
15
+ } & I18NProps<MobileMenuTogglerI18n> & SSRSupportProps;
16
+ /**
17
+ * A wrapper component that handles conditional hiding/toggling
18
+ * behavior, similar to the one `MainMenu` uses.
19
+ */
20
+ export declare const MobileMenuToggler: (props: MobileMenuTogglerProps) => JSX.Element;
21
+ export { type MobileMenuTogglerState, useMobileMenuTogglerState, } from './MobileMenuToggler/_useMobileMenuToggling.js';
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useMobileMenuTogglerState = exports.MobileMenuToggler = exports.defaultMobileMenuTogglerTexts = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
7
+ const _useMobileMenuToggling_js_1 = require("./MobileMenuToggler/_useMobileMenuToggling.js");
8
+ exports.defaultMobileMenuTogglerTexts = {
9
+ is: {
10
+ togglerLabel: 'Opna/loka Aðalvalmynd',
11
+ closeMenuLabel: 'Loka',
12
+ closeMenuLabelLong: 'Loka aðalvalmynd',
13
+ },
14
+ en: {
15
+ togglerLabel: 'Toggle Main Menu',
16
+ closeMenuLabel: 'Close',
17
+ closeMenuLabelLong: 'Close main menu',
18
+ },
19
+ pl: {
20
+ togglerLabel: 'Otwórz/zamknij menu główne',
21
+ closeMenuLabel: 'Zamknij',
22
+ closeMenuLabelLong: 'Zamknij menu główne',
23
+ },
24
+ };
25
+ /**
26
+ * A wrapper component that handles conditional hiding/toggling
27
+ * behavior, similar to the one `MainMenu` uses.
28
+ */
29
+ const MobileMenuToggler = (props) => {
30
+ const { isMenuActive, isMenuOpen, uiState, closeMenu, toggleMenu } = (0, _useMobileMenuToggling_js_1.useMobileMenuToggling)({
31
+ doInitialize: props.ssr !== 'ssr-only',
32
+ });
33
+ const txt = (0, i18n_1.getTexts)(props, exports.defaultMobileMenuTogglerTexts);
34
+ // const isBrowser = useIsBrowserSide()
35
+ return (react_1.default.createElement(react_1.default.Fragment, null,
36
+ isMenuActive && (react_1.default.createElement("button", { className: "MobileMenuToggler", onClick: toggleMenu, "aria-controls": props.controlsId, "aria-pressed": isMenuOpen }, txt.togglerLabel)),
37
+ react_1.default.createElement(_useMobileMenuToggling_js_1.MobileMenuStateProvider, { value: uiState }, props.children),
38
+ isMenuActive && (react_1.default.createElement("button", { className: "MobileMenuToggler__closebutton", onClick: closeMenu, "aria-label": txt.closeMenuLabelLong, type: "button" }, txt.closeMenuLabel))));
39
+ };
40
+ exports.MobileMenuToggler = MobileMenuToggler;
41
+ // ---------------------------------------------------------------------------
42
+ var _useMobileMenuToggling_js_2 = require("./MobileMenuToggler/_useMobileMenuToggling.js");
43
+ Object.defineProperty(exports, "useMobileMenuTogglerState", { enumerable: true, get: function () { return _useMobileMenuToggling_js_2.useMobileMenuTogglerState; } });
package/Multiselect.js CHANGED
@@ -7,11 +7,11 @@ const classUtils_1 = require("@hugsmidjan/qj/classUtils");
7
7
  const domid_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/domid"));
8
8
  const hanna_utils_1 = require("@reykjavik/hanna-utils");
9
9
  const i18n_1 = require("@reykjavik/hanna-utils/i18n");
10
- const _FocusTrap_js_1 = require("./_abstract/_FocusTrap.js");
11
10
  const _Multiselect_search_js_1 = require("./Multiselect/_Multiselect.search.js");
12
11
  const useDomid_js_1 = require("./utils/useDomid.js");
13
12
  const useOnClickOutside_js_1 = require("./utils/useOnClickOutside.js");
14
13
  const Checkbox_js_1 = tslib_1.__importDefault(require("./Checkbox.js"));
14
+ const FocusTrap_js_1 = require("./FocusTrap.js");
15
15
  const FormField_js_1 = tslib_1.__importStar(require("./FormField.js"));
16
16
  const TagPill_js_1 = tslib_1.__importDefault(require("./TagPill.js"));
17
17
  const utils_js_1 = require("./utils.js");
@@ -105,9 +105,12 @@ const Multiselect = (props) => {
105
105
  const _newValues = isAdding
106
106
  ? [...values, selValue]
107
107
  : values.filter((value) => value !== selValue);
108
- const selectedValues = options
109
- .filter((item) => _newValues.includes(item.value))
110
- .map((item) => item.value);
108
+ const selectedValues = [
109
+ // deduplicate selectedValues
110
+ ...new Set(options
111
+ .filter((item) => _newValues.includes(item.value))
112
+ .map((item) => item.value)),
113
+ ];
111
114
  setValues(selectedValues);
112
115
  if (onSelected) {
113
116
  onSelected({
@@ -221,6 +224,10 @@ const Multiselect = (props) => {
221
224
  removable: true,
222
225
  onRemove: () => {
223
226
  handleCheckboxSelection(item);
227
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
228
+ inputWrapperRef
229
+ .current.querySelector('.Multiselect__choices')
230
+ .focus();
224
231
  },
225
232
  }
226
233
  : { removable: false }))))))),
@@ -233,14 +240,14 @@ const Multiselect = (props) => {
233
240
  const insertGroupSeparator = !isFiltered &&
234
241
  item.group !== (filteredOptions[idx - 1] || {}).group &&
235
242
  (idx > 0 || !!item.group);
236
- const checkbox = (react_1.default.createElement(Checkbox_js_1.default, Object.assign({ key: idx, className: (0, classUtils_1.modifiedClass)('Multiselect__option', activeItemIndex === idx && 'focused'), disabled: isDisabled, readOnly: readOnly, required: props.required, Wrapper: "li", name: name }, item, { checked: isChecked, "aria-invalid": props.invalid, label: item.label || item.value, onChange: () => handleCheckboxSelection(item), onFocus: () => setActiveItemIndex(idx), wrapperProps: {
243
+ const checkbox = (react_1.default.createElement(Checkbox_js_1.default, Object.assign({ key: idx, className: (0, classUtils_1.modifiedClass)('Multiselect__option', activeItemIndex === idx && 'focused'), reqText: false, disabled: isDisabled, readOnly: readOnly, required: props.required, Wrapper: "li", name: name }, item, { checked: isChecked, "aria-invalid": props.invalid, label: item.label || item.value, onChange: () => handleCheckboxSelection(item), onFocus: () => setActiveItemIndex(idx), wrapperProps: {
237
244
  onMouseEnter: () => setActiveItemIndex(idx),
238
245
  } })));
239
246
  return insertGroupSeparator ? (react_1.default.createElement(react_1.Fragment, { key: idx },
240
247
  react_1.default.createElement("li", { className: (0, classUtils_1.modifiedClass)('Multiselect__optionSeparator', !item.group && 'empty'), "aria-label": item.group ? undefined : '—' }, item.group || false),
241
248
  checkbox)) : (checkbox);
242
249
  })) : searchQuery ? (react_1.default.createElement("li", { className: "Multiselect__noresults" }, texts.noneFoundMsg)) : undefined,
243
- isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
250
+ isBrowser && react_1.default.createElement(FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
244
251
  } })));
245
252
  };
246
253
  exports.Multiselect = Multiselect;
package/TagPill.d.ts CHANGED
@@ -10,6 +10,8 @@ declare const colors: {
10
10
  };
11
11
  export type TagPillColor = keyof typeof colors;
12
12
  export type TagPillProps = ButtonProps & {
13
+ /** Not allowed */
14
+ className?: never;
13
15
  children?: ReactNode;
14
16
  large?: boolean;
15
17
  color?: TagPillColor;
@@ -6,9 +6,9 @@ const react_1 = tslib_1.__importStar(require("react"));
6
6
  const classUtils_1 = require("@hugsmidjan/qj/classUtils");
7
7
  const focusElm_1 = require("@hugsmidjan/qj/focusElm");
8
8
  const i18n_1 = require("@reykjavik/hanna-utils/i18n");
9
+ const FocusTrap_js_1 = require("../FocusTrap.js");
9
10
  const utils_js_1 = require("../utils.js");
10
11
  const useCallbackOnEsc_js_1 = require("../utils/useCallbackOnEsc.js");
11
- const _FocusTrap_js_1 = require("./_FocusTrap.js");
12
12
  const _Portal_js_1 = require("./_Portal.js");
13
13
  const MODAL_OPEN_CLASS = 'modal-open';
14
14
  // ---------------------------------------------------------------------------
@@ -121,10 +121,10 @@ const AbstractModal = (props) => {
121
121
  onClick(e);
122
122
  }
123
123
  : closeOnCurtainClick || onClick, id: domid }),
124
- isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { atTop: true }),
124
+ isBrowser && react_1.default.createElement(FocusTrap_js_1.FocusTrap, { atTop: true }),
125
125
  react_1.default.createElement("div", { className: (0, classUtils_1.modifiedClass)(bem, modifier), ref: modalElmRef },
126
126
  children,
127
127
  isBrowser && !props.noCloseButton && (react_1.default.createElement("button", { className: `${bem}__closebutton`, type: "button", onClick: closeModal, "aria-label": closeButtonLabel, "aria-controls": domid, title: closeButtonLabel }, txt.closeButton))),
128
- isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { atTop: true }))));
128
+ isBrowser && react_1.default.createElement(FocusTrap_js_1.FocusTrap, { atTop: true }))));
129
129
  };
130
130
  exports.AbstractModal = AbstractModal;
@@ -2,13 +2,13 @@ import { ReactNode } from 'react';
2
2
  import { BemModifierProps, BemProps } from '../utils/types.js';
3
3
  type ButtonElmProps = {
4
4
  href?: never;
5
- } & BemModifierProps & Omit<JSX.IntrinsicElements['button'], 'className' | 'style'>;
5
+ } & BemModifierProps & JSX.IntrinsicElements['button'];
6
6
  type AnchorElmProps = {
7
7
  href: string;
8
8
  type?: never;
9
9
  name?: never;
10
10
  value?: never;
11
- } & BemModifierProps & Omit<JSX.IntrinsicElements['a'], 'className' | 'style'>;
11
+ } & BemModifierProps & JSX.IntrinsicElements['a'];
12
12
  export type ButtonProps = {
13
13
  /** Label takes preference over `children` */
14
14
  label?: string | JSX.Element;
@@ -25,7 +25,7 @@ declare const variants: {
25
25
  };
26
26
  type ButtonVariant = keyof typeof variants;
27
27
  type NavigationFlag = 'none' | 'go-back' | 'go-forward';
28
- type ButtonIcon = 'edit';
28
+ export type ButtonIcon = 'edit';
29
29
  export type ButtonVariantProps = {
30
30
  size?: ButtonSize;
31
31
  variant?: ButtonVariant;
@@ -31,13 +31,13 @@ const Button = (props) => {
31
31
  const { bem, small, // eslint-disable-line deprecation/deprecation
32
32
  size = small ? 'small' : 'normal', modifier, children, variant = 'normal', icon = 'none', label = children } = props, buttonProps = tslib_1.__rest(props, ["bem", "small", "size", "modifier", "children", "variant", "icon", "label"]);
33
33
  const className = bem &&
34
- (0, classUtils_1.modifiedClass)(bem, [modifier, variants[variant], sizes[size], navigationFlags[icon]]);
34
+ (0, classUtils_1.modifiedClass)(bem, [modifier, variants[variant], sizes[size], navigationFlags[icon]], props.className);
35
35
  const iconProp = icons[icon] && { 'data-icon': icons[icon] };
36
36
  if (buttonProps.href != null) {
37
- return (react_1.default.createElement(_Link_js_1.Link, Object.assign({ className: className }, buttonProps, { style: undefined }, iconProp), label));
37
+ return (react_1.default.createElement(_Link_js_1.Link, Object.assign({}, buttonProps, { className: className }, iconProp), label));
38
38
  }
39
39
  else {
40
- return (react_1.default.createElement("button", Object.assign({ className: className, type: "button" }, buttonProps, { style: undefined }, iconProp), label));
40
+ return (react_1.default.createElement("button", Object.assign({ type: "button" }, buttonProps, { className: className }, iconProp), label));
41
41
  }
42
42
  };
43
43
  exports.Button = Button;
@@ -1,7 +1,7 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { HTMLProps, WrapperElmProps } from '../utils.js';
3
3
  type SectionTag = 'thead' | 'tfoot' | 'tbody';
4
- type RowPropsFunction = (rowIdx: number, section: SectionTag) => HTMLProps<'tr'> | undefined;
4
+ type RowPropsFunction = (rowIdx: number, section: SectionTag, rowData: Array<TableCellData>) => HTMLProps<'tr'> | undefined;
5
5
  export type TableCellMeta = {
6
6
  className?: string;
7
7
  number?: false;
@@ -28,7 +28,7 @@ export type TableCellData = {
28
28
  colSpan?: number;
29
29
  key?: string | number;
30
30
  } & TableCellMeta;
31
- type RowData = {
31
+ type _RowData = {
32
32
  cells: Array<TableCellData>;
33
33
  key: string | number | undefined;
34
34
  };
@@ -55,7 +55,7 @@ export type TableData = {
55
55
  tbodies: Array<TableBody>;
56
56
  });
57
57
  type TableSectionProps = {
58
- section: Array<RowData>;
58
+ section: Array<_RowData>;
59
59
  cols?: TableCols;
60
60
  Tag: SectionTag;
61
61
  getRowProps: RowPropsFunction;
@@ -24,7 +24,7 @@ const TableCell = (props) => {
24
24
  };
25
25
  const TableSection = ({ section, cols = [], Tag, getRowProps }) => section.length ? (react_1.default.createElement(Tag, null, section.map(({ key, cells }, rowIdx) => {
26
26
  let colIdx = 0;
27
- return (react_1.default.createElement("tr", Object.assign({}, getRowProps(rowIdx, Tag), { key: key != null ? key : rowIdx }), cells.map((cell, i) => {
27
+ return (react_1.default.createElement("tr", Object.assign({}, getRowProps(rowIdx, Tag, cells), { key: key != null ? key : rowIdx }), cells.map((cell, i) => {
28
28
  const rowScope = i === 0;
29
29
  const meta = cols[colIdx];
30
30
  colIdx += cell.colSpan || 1;
package/esm/Alert.js CHANGED
@@ -48,8 +48,8 @@ export const Alert = (props) => {
48
48
  closeUrl, closable = !!(onClose || closeUrl != null), ssr, onClosed, wrapperProps, } = props;
49
49
  const autoClose = Math.max(props.autoClose || 0, 0);
50
50
  const closing = useRef();
51
- const [open, setOpen] = useState(!!ssr);
52
51
  const isBrowser = useIsBrowserSide(ssr);
52
+ const [open, setOpen] = useState(!isBrowser);
53
53
  const showCloseButton = closable && (isBrowser || closeUrl != null);
54
54
  const { closeLabel, closeLabelLong } = getTexts(props, defaultAlertTexts);
55
55
  useEffect(() => {
package/esm/BasicTable.js CHANGED
@@ -8,7 +8,7 @@ const tableTypes = {
8
8
  };
9
9
  export const BasicTable = (props) => {
10
10
  // eslint-disable-next-line deprecation/deprecation
11
- const { align, fullWidth, type, tbody, tbodies, modifier } = props;
11
+ const { align, fullWidth, type, tbody, tbodies, modifier, thead, tfoot, tableProps, caption, rowProps, compact, cols, wrapperProps, } = props;
12
12
  return (React.createElement(ScrollWrapper, { bem: "TableWrapper", modifier: [
13
13
  'BasicTable',
14
14
  modifier && 'BasicTable--' + modifier,
@@ -17,11 +17,11 @@ export const BasicTable = (props) => {
17
17
  : align === 'right'
18
18
  ? 'BasicTable--align--' + align
19
19
  : undefined,
20
- ], wrapperProps: props.wrapperProps },
20
+ ], wrapperProps: wrapperProps },
21
21
  React.createElement(Table, Object.assign({ className: modifiedClass('BasicTable', [
22
- props.compact && 'compact',
22
+ compact && 'compact',
23
23
  type && tableTypes[type],
24
24
  modifier,
25
- ]), cols: props.cols, caption: props.caption, thead: props.thead, tfoot: props.tfoot }, (tbody ? { tbody } : { tbodies }), { wrapperProps: props.tableProps }))));
25
+ ]), cols: cols, caption: caption, thead: thead, tfoot: tfoot }, (tbody ? { tbody } : { tbodies }), { rowProps: rowProps, wrapperProps: tableProps }))));
26
26
  };
27
27
  export default BasicTable;