@reykjavik/hanna-react 0.10.111 → 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 +34 -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 +9 -4
  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 +7 -2
  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/Alert.js CHANGED
@@ -52,8 +52,8 @@ const Alert = (props) => {
52
52
  closeUrl, closable = !!(onClose || closeUrl != null), ssr, onClosed, wrapperProps, } = props;
53
53
  const autoClose = Math.max(props.autoClose || 0, 0);
54
54
  const closing = (0, react_1.useRef)();
55
- const [open, setOpen] = (0, react_1.useState)(!!ssr);
56
55
  const isBrowser = (0, utils_js_1.useIsBrowserSide)(ssr);
56
+ const [open, setOpen] = (0, react_1.useState)(!isBrowser);
57
57
  const showCloseButton = closable && (isBrowser || closeUrl != null);
58
58
  const { closeLabel, closeLabelLong } = (0, i18n_1.getTexts)(props, exports.defaultAlertTexts);
59
59
  (0, react_1.useEffect)(() => {
package/BasicTable.js CHANGED
@@ -12,7 +12,7 @@ const tableTypes = {
12
12
  };
13
13
  const BasicTable = (props) => {
14
14
  // eslint-disable-next-line deprecation/deprecation
15
- const { align, fullWidth, type, tbody, tbodies, modifier } = props;
15
+ const { align, fullWidth, type, tbody, tbodies, modifier, thead, tfoot, tableProps, caption, rowProps, compact, cols, wrapperProps, } = props;
16
16
  return (react_1.default.createElement(_ScrollWrapper_js_1.ScrollWrapper, { bem: "TableWrapper", modifier: [
17
17
  'BasicTable',
18
18
  modifier && 'BasicTable--' + modifier,
@@ -21,12 +21,12 @@ const BasicTable = (props) => {
21
21
  : align === 'right'
22
22
  ? 'BasicTable--align--' + align
23
23
  : undefined,
24
- ], wrapperProps: props.wrapperProps },
24
+ ], wrapperProps: wrapperProps },
25
25
  react_1.default.createElement(_Table_js_1.Table, Object.assign({ className: (0, classUtils_1.modifiedClass)('BasicTable', [
26
- props.compact && 'compact',
26
+ compact && 'compact',
27
27
  type && tableTypes[type],
28
28
  modifier,
29
- ]), cols: props.cols, caption: props.caption, thead: props.thead, tfoot: props.tfoot }, (tbody ? { tbody } : { tbodies }), { wrapperProps: props.tableProps }))));
29
+ ]), cols: cols, caption: caption, thead: thead, tfoot: tfoot }, (tbody ? { tbody } : { tbodies }), { rowProps: rowProps, wrapperProps: tableProps }))));
30
30
  };
31
31
  exports.BasicTable = BasicTable;
32
32
  exports.default = exports.BasicTable;
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.113
8
+
9
+ _2023-12-08_
10
+
11
+ - feat: Add component `MainMenu2`
12
+ - feat: Make `Datepicker` more flexible when parsing manual input strings –
13
+ adds several, localized default formats.
14
+ - feat: Add accessibility helper component `FocusTrap`
15
+ - Decouple all mobile-menu toggling logic from the `Layout` component
16
+ - feat: Deprecate `useFormatMonitor` media flags `*Hamburger`, `*Topmenu`
17
+ - feat: Deprecate `useMenuToggling` hook
18
+ - feat: Add `.Layout__header__navlink`
19
+ - feat: Make `MainMenu` define its own toggler button and "Hamburger" mode
20
+ - feat: Add standalone `MobileMenuToggler` component
21
+ - `Multiselect`:
22
+ - fix: Suppress required asterisks on individual options
23
+ - fix: Deduplicate the currentvalues list
24
+ - fix: Manage focus after clicking remove buttons on currentvalues
25
+ - `BasicTable`:
26
+ - feat: Pass `rowData` as 3rd param to `rowProps` callbacks
27
+ - fix: Actually apply `rowProps` to the `<tr/>`s. (Awk)
28
+ - `ButtonPrimary`, `ButtonSecondary`, `ButtonTertiary`, `ButtonBack`,
29
+ `TextButton`:
30
+ - feat: Allow props `className` and `style`
31
+ - feat: Tweak `ContactBubble`'s show/hide scroll-distance thresholds
32
+ - fix: Suppress `MainMenu` server-rendering no-op `<button/>` elements
33
+
34
+ ## 0.10.112
35
+
36
+ _2023-11-17_
37
+
38
+ - fix: `Modal` not reoppening when `open` prop is toggled
39
+
7
40
  ## 0.10.110 – 0.10.111
8
41
 
9
42
  _2023-11-10_
@@ -150,7 +183,7 @@ _2023-07-26_
150
183
  _2023-07-25_
151
184
 
152
185
  - feat: Add optional `altText` parameter to `useGetSVGtext()`
153
- - feat: Export `useMenuToggling` from 'utils' module
186
+ - feat: Export `useMenuToggling` from `utils` module
154
187
  - feat: Export `SSRSupportProps` type
155
188
  - perf: Reduce render thrashing of `Layout`'s navChildren
156
189
  - fix: `Layout` components set `alt="Reykjavík"` text on their header logo
package/ContactBubble.js CHANGED
@@ -59,7 +59,9 @@ const ContactBubble = (props) => {
59
59
  onToggle && onToggle(false);
60
60
  setFocus !== false && (0, focusElm_1.focusElm)(wrapperRef.current);
61
61
  },
62
- }), [useLocalState, onToggle]);
62
+ }),
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ [useLocalState, onToggle]);
63
65
  (0, react_1.useEffect)(() => {
64
66
  const wrapperElm = wrapperRef.current;
65
67
  if (!wrapperElm) {
@@ -77,7 +79,8 @@ const ContactBubble = (props) => {
77
79
  const scrollLength = scrollHeight - clientHeight;
78
80
  // const f = scrollLength > 600 ? 1 : (scrollLength - 200) / 600;
79
81
  const f = 1;
80
- const show = scrollTop > f * 150 && scrollLength - scrollTop > f * 250;
82
+ const show = scrollTop > f * 130 && // minimum distance from the top
83
+ scrollLength - scrollTop > f * 200; // ...and bottom
81
84
  wrapperElm.dataset.show = String(show);
82
85
  !show && closeBubble(false);
83
86
  pending = 0;
@@ -98,7 +101,9 @@ const ContactBubble = (props) => {
98
101
  document.removeEventListener('scroll', checkScroll);
99
102
  document.documentElement.removeEventListener('scroll', checkScroll);
100
103
  };
101
- }, [isBrowser, alwaysShow, closeBubble]);
104
+ },
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps
106
+ [isBrowser, alwaysShow, closeBubble]);
102
107
  (0, react_1.useEffect)(() => {
103
108
  const escHandler = (e) => e.key === 'Escape' && closeBubble();
104
109
  const outsideClickHandler = (e) => {
package/Datepicker.d.ts CHANGED
@@ -63,6 +63,7 @@ export type DatepickerLocaleProps = {
63
63
  weekLabel: string;
64
64
  chooseDayAriaLabelPrefix: string;
65
65
  disabledDayAriaLabelPrefix: string;
66
+ dateFormats: Array<string>;
66
67
  };
67
68
  /**
68
69
  * A compo
package/Datepicker.js CHANGED
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Datepicker = exports.getDateDiff = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const react_1 = tslib_1.__importDefault(require("react"));
5
+ const react_1 = tslib_1.__importStar(require("react"));
6
6
  const i18n_1 = require("@reykjavik/hanna-utils/i18n");
7
7
  // For more info on localization see: https://stackoverflow.com/questions/54399084/change-locale-in-react-datepicker/58306958#58306958
8
8
  const index_js_1 = tslib_1.__importDefault(require("date-fns/locale/is/index.js"));
@@ -40,6 +40,16 @@ const defaultDatepickerTexts = {
40
40
  monthAriaLabelPrefix: 'Mánuður:',
41
41
  chooseDayAriaLabelPrefix: 'Veldu:',
42
42
  disabledDayAriaLabelPrefix: 'Ekki í boði:',
43
+ dateFormats: [
44
+ 'dd.MM.yyyy',
45
+ 'dd.MM.yy',
46
+ 'dd/MM/yyyy',
47
+ 'dd/MM/yy',
48
+ 'dd. MM. yyyy',
49
+ 'dd. MM. yy',
50
+ 'dd MM yyyy',
51
+ 'dd MM yy',
52
+ ],
43
53
  },
44
54
  // React-datepicker has its own (default) English translation built in.
45
55
  // No need to repeat all of it here.
@@ -59,6 +69,16 @@ const defaultDatepickerTexts = {
59
69
  monthAriaLabelPrefix: 'Month:',
60
70
  chooseDayAriaLabelPrefix: 'Choose:',
61
71
  disabledDayAriaLabelPrefix: 'Not available:',
72
+ dateFormats: [
73
+ 'MM.dd.yyyy',
74
+ 'MM.dd.yy',
75
+ 'MM/dd/yyyy',
76
+ 'MM/dd/yy',
77
+ 'MM. dd. yyyy',
78
+ 'MM. dd. yy',
79
+ 'MM dd yyyy',
80
+ 'MM dd yy',
81
+ ],
62
82
  },
63
83
  pl: {
64
84
  ariaLabelClose: 'Zamknij',
@@ -76,6 +96,16 @@ const defaultDatepickerTexts = {
76
96
  monthAriaLabelPrefix: 'Miesiąc:',
77
97
  chooseDayAriaLabelPrefix: 'Wybierać:',
78
98
  disabledDayAriaLabelPrefix: 'Niedostępna:',
99
+ dateFormats: [
100
+ 'dd.MM.yyyy',
101
+ 'dd.MM.yy',
102
+ 'dd/MM/yyyy',
103
+ 'dd/MM/yy',
104
+ 'dd. MM. yyyy',
105
+ 'dd. MM. yy',
106
+ 'dd MM yyyy',
107
+ 'dd MM yy',
108
+ ],
79
109
  },
80
110
  };
81
111
  /**
@@ -84,7 +114,7 @@ const defaultDatepickerTexts = {
84
114
  * Internally, this component uses the [`react-datepicker`](https://reactdatepicker.com/) component.
85
115
  */
86
116
  const Datepicker = (props) => {
87
- const { placeholder, dateFormat = 'd.M.yyyy', name, startDate, endDate, minDate, maxDate, isStartDate = false, isEndDate = false, onChange, datepickerExtraProps, inputRef, isoMode, texts, lang = props.localeCode, fieldWrapperProps, } = (0, FormField_js_1.groupFormFieldWrapperProps)(props);
117
+ const { placeholder, dateFormat, name, startDate, endDate, minDate, maxDate, isStartDate = false, isEndDate = false, onChange, datepickerExtraProps, inputRef, isoMode, texts, lang = props.localeCode, fieldWrapperProps, } = (0, FormField_js_1.groupFormFieldWrapperProps)(props);
88
118
  const [value, setValue] = utils_js_1.useMixedControlState.raw(props.value || props.initialDate, // eslint-disable-line deprecation/deprecation
89
119
  props.defaultValue, 'value');
90
120
  /*
@@ -96,6 +126,24 @@ const Datepicker = (props) => {
96
126
  const txts = (0, i18n_1.getTexts)({ texts, lang }, defaultDatepickerTexts);
97
127
  const filled = !!value;
98
128
  const empty = !filled && !placeholder;
129
+ const normalizedDateFormats = (0, react_1.useMemo)(() => {
130
+ const dateFormatProp = !dateFormat
131
+ ? ['d.M.yyyy']
132
+ : typeof dateFormat === 'string'
133
+ ? [dateFormat]
134
+ : dateFormat.slice(0);
135
+ // NOTE: Force all dateFormat values into Array<string> to temporarily work around
136
+ // a bug in the current version of react-datepicker where invalid `string` values
137
+ // are re-parsed with `new Date()`, causing surprising (weird) false positives
138
+ // AND where Arrayed formats get parsed in order of "increasing priority".
139
+ //
140
+ // TODO: Revert back to the plain `dateFormat={dateFormat}` pass-through once
141
+ // https://github.com/Hacker0x01/react-datepicker/pull/3988 has been accepted and released.
142
+ return dateFormatProp
143
+ .concat(['yyyy-MM-dd'])
144
+ .concat(txts.dateFormats)
145
+ .concat(['P', 'PP', 'PPP']);
146
+ }, [dateFormat, txts]);
99
147
  return (react_1.default.createElement(FormField_js_1.FormField, Object.assign({ extraClassName: "Datepicker", filled: filled, empty: empty }, fieldWrapperProps, { renderInput: (className, inputProps, addFocusProps) => {
100
148
  return (react_1.default.createElement("div", Object.assign({ className: className.input, onClick: ({ target, currentTarget }) => { var _a; return target === currentTarget && ((_a = currentTarget.querySelector('input')) === null || _a === void 0 ? void 0 : _a.focus()); }, ref: inputRef &&
101
149
  ((elm) => {
@@ -104,17 +152,7 @@ const Datepicker = (props) => {
104
152
  return elm;
105
153
  }) }, addFocusProps()),
106
154
  isoMode && (react_1.default.createElement("input", { type: "hidden", name: name, value: value === null || value === void 0 ? void 0 : value.toISOString().slice(0, 10) })),
107
- react_1.default.createElement(ReactDatepicker_js_1.ReactDatePicker, Object.assign({ id: domid, required: inputProps.required, disabled: inputProps.disabled, readOnly: inputProps.readOnly, selected: value, name: isoMode ? undefined : name, locale: lang, dateFormat:
108
- // NOTE: Force all dateFormat values into Array<string> to temporarily work around
109
- // a bug in the current version of react-datepicker where invalid `string` values
110
- // are re-parsed with `new Date()`, causing surprising (weird) false positives
111
- // AND where Arrayed formats get parsed in order of "increasing priority".
112
- //
113
- // TODO: Revert back to the plain `dateFormat={dateFormat}` pass-through once
114
- // https://github.com/Hacker0x01/react-datepicker/pull/3988 has been accepted and released.
115
- typeof dateFormat === 'string'
116
- ? [dateFormat]
117
- : dateFormat.slice(0).reverse(), onChange: (date) => {
155
+ react_1.default.createElement(ReactDatepicker_js_1.ReactDatePicker, Object.assign({ id: domid, required: inputProps.required, disabled: inputProps.disabled, readOnly: inputProps.readOnly, selected: value, name: isoMode ? undefined : name, locale: lang, dateFormat: normalizedDateFormats, onChange: (date) => {
118
156
  date = date || undefined;
119
157
  setValue(date);
120
158
  onChange && onChange(date);
@@ -10,5 +10,9 @@ export type FocusTrapProps = {
10
10
  */
11
11
  depth?: number;
12
12
  };
13
- /** A focus trap element that can be used to keep keyboard focus within a container block. */
13
+ /**
14
+ * A focus trap element that can be used to keep keyboard focus within a container block.
15
+ *
16
+ * Make sure you only trap focus when a modal or
17
+ */
14
18
  export declare const FocusTrap: (props: FocusTrapProps) => JSX.Element;
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FocusTrap = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
- /** A focus trap element that can be used to keep keyboard focus within a container block. */
6
+ /**
7
+ * A focus trap element that can be used to keep keyboard focus within a container block.
8
+ *
9
+ * Make sure you only trap focus when a modal or
10
+ */
7
11
  const FocusTrap = (props) => {
8
12
  const Tag = props.Tag || 'span';
9
13
  return (react_1.default.createElement(Tag, { tabIndex: 0, onFocus: (e) => {
package/Layout.d.ts CHANGED
@@ -3,10 +3,12 @@ import type { HannaColorTheme } from '@reykjavik/hanna-css';
3
3
  import { EitherObj } from '@reykjavik/hanna-utils';
4
4
  import { DefaultTexts, HannaLang } from '@reykjavik/hanna-utils/i18n';
5
5
  import { BemModifierProps } from './utils/types.js';
6
- import { SSRSupportProps, WrapperElmProps } from './utils.js';
6
+ import { SSRSupport, WrapperElmProps } from './utils.js';
7
7
  export type LayoutI18n = {
8
8
  skipLinkLabel: string;
9
- closeMenuLabel: string;
9
+ /** @deprecated Not used (Will be removed in v0.11) */
10
+ closeMenuLabel?: string;
11
+ /** @deprecated Not used (Will be removed in v0.11) */
10
12
  closeMenuLabelLong?: string;
11
13
  /** @deprecated Not used (Will be removed in v0.11) */
12
14
  lang?: string;
@@ -21,7 +23,9 @@ type LayoutProps = {
21
23
  siteName?: string;
22
24
  texts?: LayoutI18n;
23
25
  lang?: HannaLang;
24
- } & SSRSupportProps & WrapperElmProps & BemModifierProps & EitherObj<{
26
+ /** @deprecated Not used (Will be removed in v0.11) */
27
+ ssr?: SSRSupport;
28
+ } & WrapperElmProps & BemModifierProps & EitherObj<{
25
29
  mainChildren: ReactNode;
26
30
  }, {
27
31
  children: ReactNode;
package/Layout.js CHANGED
@@ -6,30 +6,17 @@ const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const classUtils_1 = require("@hugsmidjan/qj/classUtils");
7
7
  const i18n_1 = require("@reykjavik/hanna-utils/i18n");
8
8
  const _Layouts_js_1 = require("./_abstract/_Layouts.js");
9
- const _Link_js_1 = require("./_abstract/_Link.js");
9
+ const a11yHelpers_js_1 = require("./utils/a11yHelpers.js");
10
10
  const utils_js_1 = require("./utils.js");
11
11
  exports.defaultLayoutTexts = {
12
- is: {
13
- skipLinkLabel: 'Valmynd',
14
- closeMenuLabel: 'Loka',
15
- closeMenuLabelLong: 'Loka valmynd',
16
- },
17
- en: {
18
- skipLinkLabel: 'Skip to navigation',
19
- closeMenuLabel: 'Close',
20
- closeMenuLabelLong: 'Close menu',
21
- },
22
- pl: {
23
- skipLinkLabel: 'Przejdź do nawigacji',
24
- closeMenuLabel: 'Zamknij',
25
- closeMenuLabelLong: 'Zamknij menu',
26
- },
12
+ is: { skipLinkLabel: 'Fara í leiðakerfi' },
13
+ en: { skipLinkLabel: 'Skip to navigation' },
14
+ pl: { skipLinkLabel: 'Przejdź do nawigacji' },
27
15
  };
28
16
  const Layout = (props) => {
29
17
  (0, utils_js_1.useScrollbarWidthCSSVar)();
30
- const { ssr, globalAlerts, mainChildren, navChildren, footerChildren, colorTheme, children, siteName, logoLink = '/', wrapperProps, } = props;
18
+ const { globalAlerts, mainChildren, navChildren, footerChildren, colorTheme, children, siteName, logoLink = '/', wrapperProps, } = props;
31
19
  (0, _Layouts_js_1.issueSiteNameWarningInDev)(props);
32
- const { isMenuActive, uiState, closeMenu, toggleMenu } = (0, utils_js_1.useMenuToggling)(ssr !== 'ssr-only');
33
20
  const txt = (0, i18n_1.getTexts)(props, exports.defaultLayoutTexts);
34
21
  return (react_1.default.createElement("div", Object.assign({}, wrapperProps, { className: (0, classUtils_1.modifiedClass)('Layout', props.modifier, (wrapperProps || {}).className), "data-color-theme": colorTheme }),
35
22
  globalAlerts && (react_1.default.createElement("div", { className: "Layout__alerts", role: "alert" }, globalAlerts)),
@@ -37,15 +24,9 @@ const Layout = (props) => {
37
24
  react_1.default.createElement("div", { className: "Layout__header", role: "banner" },
38
25
  (0, _Layouts_js_1.renderLayoutHomeLink)('Layout', logoLink, siteName),
39
26
  ' ',
40
- navChildren && (react_1.default.createElement(_Link_js_1.Link, { className: "Layout__header__skiplink", href: "#pagenav", onClick: isMenuActive &&
41
- ((e) => {
42
- e.preventDefault();
43
- toggleMenu();
44
- }), "aria-label": txt.skipLinkLabel }, txt.skipLinkLabel))),
27
+ navChildren && (react_1.default.createElement("a", { className: "Layout__header__navlink", href: "#pagenav", onClick: a11yHelpers_js_1.handleAnchorLinkClick, "aria-label": txt.skipLinkLabel }, txt.skipLinkLabel))),
45
28
  react_1.default.createElement("div", { className: "Layout__main", role: "main" }, mainChildren || children),
46
- navChildren && (react_1.default.createElement("div", { className: "Layout__nav", id: "pagenav", role: "navigation" },
47
- react_1.default.createElement(utils_js_1.HannaUIState, { value: uiState }, navChildren),
48
- isMenuActive && (react_1.default.createElement("button", { className: "Layout__nav__closebutton", onClick: closeMenu, "aria-label": txt.closeMenuLabelLong, type: "button" }, txt.closeMenuLabel)))),
29
+ navChildren && (react_1.default.createElement("div", { className: "Layout__nav", id: "pagenav", tabIndex: -1, role: "navigation" }, navChildren)),
49
30
  react_1.default.createElement("div", { className: "Layout__footer", role: "complementary" }, footerChildren))));
50
31
  };
51
32
  exports.Layout = Layout;
package/MainMenu.d.ts CHANGED
@@ -1,48 +1,81 @@
1
- import React from 'react';
1
+ import React, { ReactElement } from 'react';
2
2
  import { Modifiers } from '@hugsmidjan/qj/classUtils';
3
- import { Cleanup } from '@reykjavik/hanna-utils';
4
- import { DefaultTexts, HannaLang } from '@reykjavik/hanna-utils/i18n';
3
+ import { Cleanup, EitherObj } from '@reykjavik/hanna-utils';
5
4
  import { AuxilaryPanelIllustration, AuxiliaryPanelProps } from './MainMenu/_Auxiliary.js';
6
5
  import { MegaMenuItem, MegaMenuItemList, MegaMenuPanel, PrimaryPanelI18n } from './MainMenu/_PrimaryPanel.js';
6
+ import { I18NProps } from './utils/types.js';
7
+ import { MobileMenuTogglerI18n } from './MobileMenuToggler.js';
7
8
  import { SSRSupportProps, WrapperElmProps } from './utils.js';
8
9
  export type MainMenuI18n = Cleanup<{
9
10
  homeLabel?: string;
10
11
  title: string;
11
12
  /** @deprecated Not used (Will be removed in v0.11) */
12
13
  lang?: string;
13
- } & PrimaryPanelI18n>;
14
- export declare const defaultMainMenuTexts: DefaultTexts<Required<Omit<MainMenuI18n, 'lang'>>>;
14
+ } & PrimaryPanelI18n & EitherObj<MobileMenuTogglerI18n, {}>>;
15
+ export declare const defaultMainMenuTexts: {
16
+ is: {
17
+ title: string;
18
+ homeLabel: string;
19
+ backToMenu: string;
20
+ backToMenuLong: string;
21
+ };
22
+ en: {
23
+ title: string;
24
+ homeLabel: string;
25
+ backToMenu: string;
26
+ backToMenuLong: string;
27
+ };
28
+ pl: {
29
+ title: string;
30
+ homeLabel: string;
31
+ backToMenu: string;
32
+ backToMenuLong: string;
33
+ };
34
+ };
15
35
  export type { AuxilaryPanelIllustration, AuxiliaryPanelProps, MegaMenuItem, MegaMenuItemList, MegaMenuPanel, };
16
36
  export type MainMenuItem = {
37
+ /** Visible label text */
17
38
  label: string;
39
+ /** Un-abbreviated label set as `title=""` and `aria-label=""` */
18
40
  labelLong?: string;
41
+ /** Language of the link label */
19
42
  lang?: string;
43
+ /** Languge of the linked resource */
20
44
  hrefLang?: string;
21
45
  /**
22
- * Puts a modifier className on the menu item element.
23
- *
24
- * Example:
25
- *
26
- * ```html
27
- * <li class="MainMenu__item MainMenu__item--${modifier}">
28
- * ```
46
+ * Puts a modifier className for the menu __item <li/> element.
29
47
  * */
30
48
  modifier?: Modifiers;
49
+ /** Signifies if the menu item is part of the page's breadcrumb trail */
31
50
  current?: boolean;
51
+ /**
52
+ * The URL the link points to.
53
+ *
54
+ * If neither `href` nor `onClick` is passed, then the item is not rendered
55
+ * at all.
56
+ */
32
57
  href?: string;
58
+ /** Sets `target=""` on anchor tags with a `href` attribute. */
59
+ target?: React.HTMLAttributeAnchorTarget;
33
60
  /**
34
- * Adding `onClick` automatically results in a <button/> element being rendered.
61
+ * Adding `onClick` automatically results in a <button/> element being
62
+ * rendered. If `href` is also passed, then a <a href/> element is rendered
63
+ * during initial (server-side) render, which then gets replaced by a
64
+ * <button/> element during the first client-side render.
35
65
  *
36
- * NOTE: Clicking a MainMenu item will automatically close HannaUIState's
66
+ * NOTE: Clicking a menu item will automatically close HannaUIState's
37
67
  * "Hamburger menu" (a.k.a. "Mobile menu")
38
68
  * … unless the `onClick` function explicitly returns `false`.
39
69
  */
40
70
  onClick?: (index: number, item: MainMenuItem) => void | boolean;
71
+ /** Sets `aria-controls=""` on `<button/>`s with `onClick` */
41
72
  controlsId?: string;
42
- target?: React.HTMLAttributeAnchorTarget;
43
73
  };
74
+ /** String token that hints that a flexible space should be inserted at this
75
+ * point in the menu item list.
76
+ */
44
77
  export type MainMenuSeparator = '---';
45
- export type MainMenuItemList = Array<MainMenuItem | MainMenuSeparator | (() => JSX.Element)>;
78
+ export type MainMenuItemList = Array<MainMenuItem | MainMenuSeparator | (() => ReactElement)>;
46
79
  export type MainMenuProps = {
47
80
  /**
48
81
  * Top-level screen-reader headline/label for the whole menu.
@@ -64,8 +97,7 @@ export type MainMenuProps = {
64
97
  */
65
98
  onItemClick?: (index: number, item: MainMenuItem) => void | boolean;
66
99
  activePanelId?: string;
67
- texts?: MainMenuI18n;
68
- lang?: HannaLang;
69
- } & SSRSupportProps & WrapperElmProps<null, 'aria-label'>;
70
- export declare const MainMenu: (props: MainMenuProps) => JSX.Element | null;
100
+ } & SSRSupportProps & I18NProps<MainMenuI18n> & WrapperElmProps<null, 'aria-label'>;
101
+ export declare const _MainMenu: (props: MainMenuProps) => JSX.Element | null;
102
+ export declare const MainMenu: (props: MainMenuProps) => JSX.Element;
71
103
  export default MainMenu;
package/MainMenu.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MainMenu = exports.defaultMainMenuTexts = void 0;
3
+ exports.MainMenu = exports._MainMenu = exports.defaultMainMenuTexts = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
6
  const classUtils_1 = require("@hugsmidjan/qj/classUtils");
@@ -9,11 +9,13 @@ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
9
9
  const _Link_js_1 = require("./_abstract/_Link.js");
10
10
  const _Auxiliary_js_1 = require("./MainMenu/_Auxiliary.js");
11
11
  const _PrimaryPanel_js_1 = require("./MainMenu/_PrimaryPanel.js");
12
- const HannaUIState_js_1 = require("./utils/HannaUIState.js");
13
12
  const useFormatMonitor_js_1 = require("./utils/useFormatMonitor.js");
14
13
  const useShortState_js_1 = require("./utils/useShortState.js");
14
+ const MobileMenuToggler_js_1 = require("./MobileMenuToggler.js");
15
15
  const utils_js_1 = require("./utils.js");
16
16
  const findActivePanel = (megaPanels, activeId) => activeId ? megaPanels.find((panel) => activeId === panel.id) : undefined;
17
+ // const HamburgerMedias: Record<string, 1> = { phone: 1, phablet: 1, tablet: 1 };
18
+ const TopmenuMedias = { netbook: 1, wide: 1 };
17
19
  exports.defaultMainMenuTexts = {
18
20
  is: {
19
21
  title: 'Aðalvalmynd',
@@ -80,11 +82,19 @@ const normalizeMenuItems = (itemsProp, megaPanels, homeLink, texts) => {
80
82
  };
81
83
  // ---------------------------------------------------------------------------
82
84
  const emptyPanelList = [];
83
- const MainMenu = (props) => {
85
+ /*
86
+ NOTE:
87
+ This is made into sub-component to allow using the `useMobileMenuTogglerState`
88
+ hook. This MainMenu component will eventually be deprecated, and we're
89
+ maintaining a bunch of already deprecated legacy hook/utils exports,
90
+ so it doesn't seem worth the effort to refactor this into something slightly
91
+ "cleaner", cecause that would increase more complexity for the legacy exports.
92
+ */
93
+ const _MainMenu = (props) => {
84
94
  const { megaPanels = emptyPanelList, onItemClick, ssr, auxiliaryPanel, wrapperProps = {}, } = props;
85
95
  const texts = (0, i18n_1.getTexts)(props, exports.defaultMainMenuTexts);
86
96
  const title = props.title || texts.title;
87
- const { closeHamburgerMenu } = (0, HannaUIState_js_1.useHannaUIState)();
97
+ const { closeHamburgerMenu } = (0, MobileMenuToggler_js_1.useMobileMenuTogglerState)();
88
98
  const isBrowser = (0, utils_js_1.useIsBrowserSide)(ssr);
89
99
  const _menuElmRef = (0, react_1.useRef)(null);
90
100
  const menuElmRef = wrapperProps.ref || _menuElmRef;
@@ -132,7 +142,8 @@ const MainMenu = (props) => {
132
142
  }
133
143
  : () => undefined, [setLaggyActivePanel, isBrowser]);
134
144
  (0, useFormatMonitor_js_1.useFormatMonitor)((media) => {
135
- if (media.leftTopmenu) {
145
+ const leftTopmenu = !TopmenuMedias[media.is] && TopmenuMedias[media.was || ''];
146
+ if (leftTopmenu) {
136
147
  setActivePanel(undefined);
137
148
  }
138
149
  });
@@ -190,7 +201,7 @@ const MainMenu = (props) => {
190
201
  }
191
202
  const { label, labelLong, lang, controlsId, onClick } = item;
192
203
  const pressed = (activePanel && controlsId === activePanel.id) || undefined;
193
- return (react_1.default.createElement("li", { key: i, className: (0, classUtils_1.modifiedClass)('MainMenu__item', item.modifier), "aria-current": item.current || undefined }, onClick || (!!item.megaPanel && (isBrowser || !item.href)) ? (
204
+ return (react_1.default.createElement("li", { key: i, className: (0, classUtils_1.modifiedClass)('MainMenu__item', item.modifier), "aria-current": item.current || undefined }, isBrowser && !!(item.megaPanel || onClick) ? (
194
205
  // only print script-driven buttons in the browser
195
206
  react_1.default.createElement("button", { className: "MainMenu__link", onClick: () => {
196
207
  const keepOpen1 = onClick && onClick(i, item) === false;
@@ -223,5 +234,15 @@ const MainMenu = (props) => {
223
234
  }),
224
235
  auxiliaryPanel && react_1.default.createElement(_Auxiliary_js_1.AuxiliaryPanel, Object.assign({}, auxiliaryPanel)))))));
225
236
  };
237
+ exports._MainMenu = _MainMenu;
238
+ // ---------------------------------------------------------------------------
239
+ const MainMenu = (props) => {
240
+ const _wrapperProps = props.wrapperProps;
241
+ const id = (0, utils_js_1.useDomid)(_wrapperProps && _wrapperProps.id);
242
+ const wrapperProps = _wrapperProps ? Object.assign(Object.assign({}, _wrapperProps), { id }) : { id };
243
+ const texts = (0, i18n_1.getTexts)(props, exports.defaultMainMenuTexts);
244
+ return (react_1.default.createElement(MobileMenuToggler_js_1.MobileMenuToggler, { ssr: props.ssr, lang: props.lang, texts: texts.togglerLabel ? texts : undefined, controlsId: id },
245
+ react_1.default.createElement(exports._MainMenu, Object.assign({}, props, { wrapperProps: wrapperProps }))));
246
+ };
226
247
  exports.MainMenu = MainMenu;
227
248
  exports.default = exports.MainMenu;
package/MainMenu2.d.ts ADDED
@@ -0,0 +1,114 @@
1
+ import React, { ReactElement } from 'react';
2
+ import { Modifiers } from '@hugsmidjan/qj/classUtils';
3
+ import { Cleanup, EitherObj } from '@reykjavik/hanna-utils';
4
+ import { Illustration } from '@reykjavik/hanna-utils/assets';
5
+ import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
6
+ import { I18NProps } from './utils/types.js';
7
+ import { SSRSupportProps, WrapperElmProps } from './utils.js';
8
+ export type MainMenu2I18n = {
9
+ title: string;
10
+ homeLink: string;
11
+ openMenu: string;
12
+ openMenuLong: string;
13
+ closeMenu: string;
14
+ closeMenuLong: string;
15
+ };
16
+ export declare const defaultMainMenu2Texts: DefaultTexts<MainMenu2I18n>;
17
+ export type MainMenu2Item = {
18
+ /** Visible label text */
19
+ label: string;
20
+ /** Un-abbreviated label set as `title=""` and `aria-label=""` */
21
+ labelLong?: string;
22
+ /** Language of the link label */
23
+ lang?: string;
24
+ /** Languge of the linked resource */
25
+ hrefLang?: string;
26
+ /**
27
+ * Puts a modifier className for the menu __item <li/> element.
28
+ * */
29
+ modifier?: Modifiers;
30
+ /** Signifies if the menu item is part of the page's breadcrumb trail */
31
+ current?: boolean;
32
+ /**
33
+ * The URL the link points to.
34
+ *
35
+ * If neither `href` nor `onClick` is passed, then the item is not rendered
36
+ * at all.
37
+ */
38
+ href?: string;
39
+ /** Sets `target=""` on anchor tags with a `href` attribute. */
40
+ target?: React.HTMLAttributeAnchorTarget;
41
+ /**
42
+ * Adding `onClick` automatically results in a <button/> element being
43
+ * rendered. If `href` is also passed, then a <a href/> element is rendered
44
+ * during initial (server-side) render, which then gets replaced by a
45
+ * <button/> element during the first client-side
46
+ *
47
+ * NOTE: Clicking a menu item will automatically close HannaUIState's
48
+ * "Hamburger menu" (a.k.a. "Mobile menu")
49
+ * … unless the `onClick` function explicitly returns `false`.
50
+ */
51
+ onClick?: (item: MainMenu2Item) => void | boolean;
52
+ /** Sets `aria-controls=""` on `<button/>`s with `onClick` */
53
+ controlsId?: string;
54
+ };
55
+ export type MainMenu2ButtonItem = MainMenu2Item & {
56
+ icon?: 'search' | 'user' | 'alert' | 'globe';
57
+ };
58
+ export type MainMenu2CustomItem = (props: {
59
+ closeMenu: () => void;
60
+ }) => ReactElement;
61
+ export type MainMenu2SubMenuItem = MainMenu2Item & {
62
+ descr?: string;
63
+ };
64
+ export type MainMenu2SubMenu = {
65
+ title: string;
66
+ current?: boolean;
67
+ subItems: Array<MainMenu2SubMenuItem | MainMenu2CustomItem>;
68
+ };
69
+ export type MainMenu2ItemList = Array<MainMenu2Item | MainMenu2CustomItem>;
70
+ export type MainMenu2ButtonItemList = Array<MainMenu2ButtonItem | MainMenu2CustomItem>;
71
+ export type MainMenu2SubMenuItemList = Array<MainMenu2SubMenuItem | MainMenu2CustomItem>;
72
+ export type MainMenu2Props = {
73
+ /**
74
+ * URL for the mandatory (usually screen-reader-only) homepage Link.
75
+ *
76
+ * Default: `"/"`
77
+ *
78
+ * NOTE: The link's label is by default "Forsíða"/"Home Page"/"Strona główna"
79
+ * (depending on your page language) but it can be custom-translated via the
80
+ * `props.texts` translation prop.
81
+ */
82
+ homeLink?: string | Cleanup<Omit<MainMenu2Item, 'modifier' | 'controlsId'> & {
83
+ href: string;
84
+ }>;
85
+ items: {
86
+ /**
87
+ * The "Main" menu items that appear once the menu is open.
88
+ *
89
+ * Each of these items normally contains a list of `subItems` and a title,
90
+ * but it can also be a direct link/button (or even a custom component).
91
+ */
92
+ main?: Array<MainMenu2SubMenu | MainMenu2Item | MainMenu2CustomItem>;
93
+ /**
94
+ * The always-visible items that appear at the top of the page next the
95
+ * "Open Menu" button. Make sure to only use 2–3 items, and remember that
96
+ * they may be hidden on smaller screens.
97
+ */
98
+ hot?: MainMenu2ButtonItemList;
99
+ extra?: MainMenu2ButtonItemList;
100
+ relatedTitle?: string;
101
+ related?: MainMenu2ItemList;
102
+ };
103
+ /**
104
+ * NOTE: Clicking a MainMenu2 item will automatically close HannaUIState's
105
+ * "Hamburger menu" (a.k.a. "Mobile menu")
106
+ * … unless the `onItemClick` function explicitly returns `false`.
107
+ */
108
+ onItemClick?: (item: MainMenu2Item) => void | boolean;
109
+ } & EitherObj<{
110
+ illustration?: Illustration;
111
+ }, {
112
+ imageUrl?: string;
113
+ }> & WrapperElmProps & I18NProps<MainMenu2I18n> & SSRSupportProps;
114
+ export declare const MainMenu2: (props: MainMenu2Props) => JSX.Element;