@reykjavik/hanna-react 0.10.108 → 0.10.110

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/BasicTable.js +1 -2
  2. package/CHANGELOG.md +23 -0
  3. package/CheckboxButtonsGroup.d.ts +2 -0
  4. package/CheckboxButtonsGroup.js +1 -1
  5. package/FormField.d.ts +1 -1
  6. package/Gallery/_GalleryModal.js +11 -12
  7. package/Gallery.js +7 -7
  8. package/Heading.js +1 -0
  9. package/HeroBlock.d.ts +3 -2
  10. package/Modal.d.ts +9 -5
  11. package/Modal.js +12 -10
  12. package/Multiselect/_Multiselect.search.d.ts +1 -1
  13. package/Multiselect/_Multiselect.search.js +15 -3
  14. package/Multiselect.d.ts +3 -1
  15. package/Multiselect.js +9 -2
  16. package/Pagination.js +1 -1
  17. package/RadioButtonsGroup.d.ts +2 -0
  18. package/RadioButtonsGroup.js +1 -1
  19. package/_abstract/_AbstractModal.d.ts +92 -0
  20. package/_abstract/_AbstractModal.js +125 -0
  21. package/_abstract/_CarouselPaging.d.ts +14 -0
  22. package/_abstract/_CarouselPaging.js +14 -0
  23. package/_abstract/_FocusTrap.js +1 -1
  24. package/_abstract/_Layouts.d.ts +1 -0
  25. package/_abstract/_Layouts.js +9 -1
  26. package/_abstract/_Portal.d.ts +7 -0
  27. package/_abstract/_Portal.js +20 -0
  28. package/_abstract/_Table.d.ts +2 -2
  29. package/_abstract/_TogglerGroup.d.ts +6 -6
  30. package/_abstract/_TogglerGroupField.d.ts +2 -2
  31. package/esm/BasicTable.js +1 -2
  32. package/esm/CheckboxButtonsGroup.d.ts +2 -0
  33. package/esm/CheckboxButtonsGroup.js +1 -1
  34. package/esm/FormField.d.ts +1 -1
  35. package/esm/Gallery/_GalleryModal.js +11 -12
  36. package/esm/Gallery.js +7 -7
  37. package/esm/Heading.js +1 -0
  38. package/esm/HeroBlock.d.ts +3 -2
  39. package/esm/Modal.d.ts +9 -5
  40. package/esm/Modal.js +11 -9
  41. package/esm/Multiselect/_Multiselect.search.d.ts +1 -1
  42. package/esm/Multiselect/_Multiselect.search.js +15 -3
  43. package/esm/Multiselect.d.ts +3 -1
  44. package/esm/Multiselect.js +10 -3
  45. package/esm/Pagination.js +1 -1
  46. package/esm/RadioButtonsGroup.d.ts +2 -0
  47. package/esm/RadioButtonsGroup.js +1 -1
  48. package/esm/_abstract/_AbstractModal.d.ts +92 -0
  49. package/esm/_abstract/_AbstractModal.js +120 -0
  50. package/esm/_abstract/_CarouselPaging.d.ts +14 -0
  51. package/esm/_abstract/_CarouselPaging.js +11 -0
  52. package/esm/_abstract/_FocusTrap.js +1 -1
  53. package/esm/_abstract/_Layouts.d.ts +1 -0
  54. package/esm/_abstract/_Layouts.js +7 -0
  55. package/esm/_abstract/_Portal.d.ts +7 -0
  56. package/esm/_abstract/_Portal.js +16 -0
  57. package/esm/_abstract/_Table.d.ts +2 -2
  58. package/esm/_abstract/_TogglerGroup.d.ts +6 -6
  59. package/esm/_abstract/_TogglerGroupField.d.ts +2 -2
  60. package/esm/utils/browserSide.js +2 -1
  61. package/esm/utils/types.d.ts +5 -2
  62. package/esm/utils/useDomid.d.ts +3 -1
  63. package/esm/utils/useDomid.js +3 -1
  64. package/esm/utils/useScrollEdgeDetect.d.ts +2 -2
  65. package/esm/utils/useScrollEdgeDetect.js +30 -24
  66. package/package.json +1 -1
  67. package/utils/browserSide.js +2 -1
  68. package/utils/types.d.ts +5 -2
  69. package/utils/useDomid.d.ts +3 -1
  70. package/utils/useDomid.js +3 -1
  71. package/utils/useScrollEdgeDetect.d.ts +2 -2
  72. package/utils/useScrollEdgeDetect.js +29 -23
package/BasicTable.js CHANGED
@@ -21,8 +21,7 @@ const BasicTable = (props) => {
21
21
  : align === 'right'
22
22
  ? 'BasicTable--align--' + align
23
23
  : undefined,
24
- ], wrapperProps: props.wrapperProps, innerWrap // TODO: Remove this once the CSS style-server has been updated
25
- : true },
24
+ ], wrapperProps: props.wrapperProps },
26
25
  react_1.default.createElement(_Table_js_1.Table, Object.assign({ className: (0, classUtils_1.modifiedClass)('BasicTable', [
27
26
  props.compact && 'compact',
28
27
  type && tableTypes[type],
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.110
8
+
9
+ _2023-11-10_
10
+
11
+ - `Modal`
12
+ - feat: Add prop `wrapperProps`
13
+ - feat: Add prop `stable`, deprecate `fickle` instead
14
+ - feat: Add prop `noCloseButton`
15
+ - refactor: Rewritten and modernized
16
+ - feat: Add prop `stacked` to `.CheckboxButtonsGroup` and `.RadioButtonsGroup`
17
+ - feat: Add property `group` to `MultiselectOption` type, for grouping options
18
+ - fix: Apply `wrapperProps.className` to `Heading`'s wrapper element
19
+ - fix: Non-sensical `Gallery` close button label
20
+
21
+ ## 0.10.109
22
+
23
+ _2023-10-25_
24
+
25
+ - feat: Allow simple JSX in `HeroBlockProps.title`
26
+ - feat: Stop inserting `.TableWrapper__scroller` inner wrapping element
27
+ - fix: Conflicted/ambigious `wrapperProps` element type for `BasicTableProps`
28
+ - fix: Make `Pagination` root-element into a `<nav />`
29
+
7
30
  ## 0.10.108
8
31
 
9
32
  _2023-10-09_
@@ -2,6 +2,8 @@ import { TogglerGroupFieldOptions, TogglerGroupFieldProps } from './_abstract/_T
2
2
  export type CheckboxButtonsGroupProps = TogglerGroupFieldProps & {
3
3
  value?: Array<string>;
4
4
  defaultValue?: Array<string>;
5
+ /** Display the buttons in a single column */
6
+ stacked?: boolean;
5
7
  /** @deprecated (Will be removed in v0.11) */
6
8
  columns?: '2col' | '3col';
7
9
  /** @deprecated (Will be removed in v0.11) */
@@ -12,7 +12,7 @@ const CheckboxButtonsGroup = (props) => {
12
12
  if (props.columns /* eslint-disable-line deprecation/deprecation */) {
13
13
  console.warn('`CheckboxButtonsGroupProps.columns` is deprecated.');
14
14
  }
15
- return (react_1.default.createElement(_TogglerGroupField_js_1.TogglerGroupField, Object.assign({}, props, { bem: "CheckboxButtonsGroup", Toggler: CheckboxButton_js_1.default })));
15
+ return (react_1.default.createElement(_TogglerGroupField_js_1.TogglerGroupField, Object.assign({}, props, { bem: "CheckboxButtonsGroup", modifier: props.stacked && 'stacked', Toggler: CheckboxButton_js_1.default })));
16
16
  };
17
17
  exports.CheckboxButtonsGroup = CheckboxButtonsGroup;
18
18
  exports.default = exports.CheckboxButtonsGroup;
package/FormField.d.ts CHANGED
@@ -86,7 +86,7 @@ export declare const getFormFieldWrapperProps: (props: (FormFieldGroupWrappingPr
86
86
  }) => RequireExplicitUndefined<FormFieldGroupWrappingProps> & {
87
87
  small: boolean | undefined;
88
88
  };
89
- export declare const groupFormFieldWrapperProps: <T extends (FormFieldGroupWrappingProps | Pick<TogglerGroupFieldProps<"default">, "label" | "className" | "id" | "wrapperProps" | "disabled" | "ssr" | "invalid" | "readOnly" | "required" | "errorMessage" | "reqText" | "assistText" | "hideLabel" | "wrapperRef" | "LabelTag">) & {
89
+ export declare const groupFormFieldWrapperProps: <T extends (FormFieldGroupWrappingProps | Pick<TogglerGroupFieldProps<"default", {}>, "label" | "className" | "id" | "wrapperProps" | "disabled" | "ssr" | "invalid" | "readOnly" | "required" | "errorMessage" | "reqText" | "assistText" | "hideLabel" | "wrapperRef" | "LabelTag">) & {
90
90
  small?: boolean | undefined;
91
91
  }>(props: T) => Omit<T, "label" | "small" | "className" | "id" | "wrapperProps" | "disabled" | "ssr" | "invalid" | "readOnly" | "required" | "errorMessage" | "reqText" | "assistText" | "hideLabel" | "wrapperRef" | "LabelTag"> & {
92
92
  fieldWrapperProps: ReturnType<typeof getFormFieldWrapperProps>;
@@ -5,8 +5,8 @@ const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importStar(require("react"));
6
6
  const react_transition_group_1 = require("react-transition-group");
7
7
  const object_1 = require("@hugsmidjan/qj/object");
8
- const CarouselPaging_1 = tslib_1.__importDefault(require("@hugsmidjan/react/CarouselPaging"));
9
- const Modal_1 = tslib_1.__importDefault(require("@hugsmidjan/react/Modal"));
8
+ const _AbstractModal_js_1 = require("../_abstract/_AbstractModal.js");
9
+ const _CarouselPaging_js_1 = tslib_1.__importDefault(require("../_abstract/_CarouselPaging.js"));
10
10
  const _GalleryModalContext_js_1 = require("./_GalleryModalContext.js");
11
11
  const _GalleryModalItem_js_1 = require("./_GalleryModalItem.js");
12
12
  const GalleryModal = (props) => {
@@ -42,17 +42,16 @@ const GalleryModal = (props) => {
42
42
  // FIXME: This if weirdly inefficient. Either memoize,
43
43
  // or do a simpler single-property comparison.
44
44
  (0, object_1.objectIsSame)((0, object_1.objectClean)(image), (0, object_1.objectClean)(item)));
45
- return (react_1.default.createElement(Modal_1.default, { open: true, onClosed: () => {
45
+ return (react_1.default.createElement(_AbstractModal_js_1.AbstractModal, { open: true, onClosed: () => {
46
46
  setCurrentImage(undefined);
47
47
  }, startOpen: false, bem: "GalleryModal", texts: { closeButton: texts.modalCloseLabel } },
48
- react_1.default.createElement(react_1.default.Fragment, null,
49
- react_1.default.createElement(react_transition_group_1.CSSTransition, { in: animated, timeout: 200, onEntered: () => {
50
- setAnimated(!animated);
51
- }, classNames: "GalleryModalItem--" },
52
- react_1.default.createElement(_GalleryModalItem_js_1.GalleryModalItem, Object.assign({}, image))),
53
- react_1.default.createElement(CarouselPaging_1.default, { bem: "GalleryModalPager", itemCount: items.length, current: imageIndex, setCurrent: updateImage, texts: {
54
- next: texts.modalNextLabel,
55
- prev: texts.modalPrevLabel,
56
- } }))));
48
+ react_1.default.createElement(react_transition_group_1.CSSTransition, { in: animated, timeout: 200, onEntered: () => {
49
+ setAnimated(!animated);
50
+ }, classNames: "GalleryModalItem--" },
51
+ react_1.default.createElement(_GalleryModalItem_js_1.GalleryModalItem, Object.assign({}, image))),
52
+ react_1.default.createElement(_CarouselPaging_js_1.default, { bem: "GalleryModalPager", itemCount: items.length, current: imageIndex, setCurrent: updateImage, texts: {
53
+ next: texts.modalNextLabel,
54
+ prev: texts.modalPrevLabel,
55
+ } })));
57
56
  };
58
57
  exports.GalleryModal = GalleryModal;
package/Gallery.js CHANGED
@@ -9,20 +9,20 @@ const _GalleryItem_js_1 = require("./Gallery/_GalleryItem.js");
9
9
  const _GalleryModal_js_1 = require("./Gallery/_GalleryModal.js");
10
10
  const _GalleryModalContext_js_1 = require("./Gallery/_GalleryModalContext.js");
11
11
  const defaultTexts = {
12
- en: {
13
- modalNextLabel: 'Next image',
14
- modalPrevLabel: 'Previous image',
15
- modalCloseLabel: 'Back to article',
16
- },
17
12
  is: {
18
13
  modalNextLabel: 'Næsta mynd',
19
14
  modalPrevLabel: 'Fyrri mynd',
20
- modalCloseLabel: 'Til baka í grein',
15
+ modalCloseLabel: 'Loka mynd',
16
+ },
17
+ en: {
18
+ modalNextLabel: 'Next image',
19
+ modalPrevLabel: 'Previous image',
20
+ modalCloseLabel: 'Close image',
21
21
  },
22
22
  pl: {
23
23
  modalNextLabel: 'Następne zdjęcie',
24
24
  modalPrevLabel: 'Poprzednie zdjęcie',
25
- modalCloseLabel: 'Powrót do artykułu',
25
+ modalCloseLabel: 'Zamknij zdjęcie',
26
26
  },
27
27
  };
28
28
  const Gallery = (props) => {
package/Heading.js CHANGED
@@ -18,6 +18,7 @@ const Heading = (props) => {
18
18
  sizes[size],
19
19
  align === 'right' && 'align--' + align,
20
20
  !align && wide && 'wide',
21
+ (wrapperProps || {}).className,
21
22
  ]) }), children));
22
23
  };
23
24
  exports.Heading = Heading;
package/HeroBlock.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ReactElement } from 'react';
1
2
  import { Illustration } from '@reykjavik/hanna-utils/assets';
2
3
  import { ButtonProps } from './_abstract/_Button.js';
3
4
  import { ImageProps } from './_abstract/_Image.js';
@@ -11,8 +12,8 @@ type HeroBlockImageProps = {
11
12
  illustration?: undefined;
12
13
  };
13
14
  export type HeroBlockProps = {
14
- title: string;
15
- summary: string | JSX.Element;
15
+ title: string | ReactElement;
16
+ summary: string | ReactElement;
16
17
  primaryButton?: ButtonProps;
17
18
  secondaryButton?: ButtonProps;
18
19
  } & HeroBlockImageProps & WrapperElmProps & DeprecatedSeenProp;
package/Modal.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import { ReactElement } from 'react';
2
- import { ModalProps as _ModalProps } from '@hugsmidjan/react/Modal';
3
- import { MissingWrapperElmProps } from './utils.js';
4
- export type ModalProps = Omit<_ModalProps, 'bem' | 'bodyWrap' | 'modifier'> & {
2
+ import { type AbstractModalProps } from './_abstract/_AbstractModal.js';
3
+ export declare const defaultModalTexts: import("@reykjavik/hanna-utils/i18n.js").DefaultTexts<{
4
+ closeButton: string;
5
+ closeButtonLabel?: string | undefined;
6
+ }>;
7
+ export type ModalProps = AbstractModalProps & {
8
+ /** Modal width modifier */
5
9
  modifier?: 'w6' | 'w8' | 'w10';
6
- bling?: ReactElement;
7
- } & MissingWrapperElmProps;
10
+ bling?: ReactElement | (() => ReactElement);
11
+ };
8
12
  export declare const Modal: (props: ModalProps) => JSX.Element;
9
13
  export default Modal;
package/Modal.js CHANGED
@@ -1,18 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Modal = void 0;
3
+ exports.Modal = exports.defaultModalTexts = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
- const Modal_1 = tslib_1.__importDefault(require("@hugsmidjan/react/Modal"));
6
+ const _AbstractModal_js_1 = require("./_abstract/_AbstractModal.js");
7
+ exports.defaultModalTexts = _AbstractModal_js_1.defaultAbstractModalTexts;
7
8
  const Modal = (props) => {
8
- const { closeDelay = 500, texts = {
9
- closeButton: 'Loka ',
10
- }, bling, children, render = () => children, } = props;
11
- return (react_1.default.createElement(Modal_1.default, Object.assign({}, props, { bodyWrap: false, bem: "Modal", closeDelay: closeDelay, texts: texts, render: (args) => bling ? (react_1.default.createElement(react_1.default.Fragment, null,
12
- render(args),
13
- react_1.default.createElement("div", { className: "Modal__blings" },
14
- react_1.default.createElement("div", { className: "Modal__blings__inner" }, bling)))) : (render(args)),
15
- // eslint-disable-next-line react/no-children-prop
9
+ const { bling, render, children } = props;
10
+ return (react_1.default.createElement(_AbstractModal_js_1.AbstractModal, Object.assign({}, props, { bem: "modal", render: (renderProps) => {
11
+ const _children = render ? render(renderProps) : children;
12
+ return bling ? (react_1.default.createElement(react_1.default.Fragment, null,
13
+ _children,
14
+ react_1.default.createElement("div", { className: "Modal__blings" },
15
+ react_1.default.createElement("div", { className: "Modal__blings__inner" }, bling)))) : (_children);
16
+ },
17
+ // Required since props might contain children
16
18
  children: undefined })));
17
19
  };
18
20
  exports.Modal = Modal;
@@ -16,4 +16,4 @@ queryWords: Array<string>,
16
16
  rawQuery: string) => number;
17
17
  export declare const defaultSearchScoring: SearchScoringfn;
18
18
  /** Returns a normalized, filtered list of options */
19
- export declare const filterItems: (options: TogglerGroupOptions<string>, searchQuery: string, searchScoringFn?: SearchScoringfn) => TogglerGroupOptions<string>;
19
+ export declare const filterItems: <Extras = {}>(options: TogglerGroupOptions<string, Extras>, searchQuery: string, searchScoringFn?: SearchScoringfn) => TogglerGroupOptions<string, Extras>;
@@ -62,19 +62,31 @@ const defaultSearchScoring = (item, queryWords) => {
62
62
  };
63
63
  exports.defaultSearchScoring = defaultSearchScoring;
64
64
  // ---------------------------------------------------------------------------
65
+ // banana emoji
66
+ const SEP = '🍌';
65
67
  /** Returns a normalized, filtered list of options */
66
68
  const filterItems = (options, searchQuery, searchScoringFn = exports.defaultSearchScoring) => {
67
69
  if (!searchQuery.trim()) {
68
- return [...options];
70
+ return options;
69
71
  }
72
+ const found = new Set();
70
73
  const queryWords = searchQuery.toLowerCase().trim().split(/\s+/);
71
- return options
74
+ return (options
72
75
  .map((item) => ({
73
76
  item,
74
77
  score: searchScoringFn(item, queryWords, searchQuery),
75
78
  }))
76
79
  .filter(({ score }) => score > 0)
77
80
  .sort((a, b) => (a.score === b.score ? 0 : a.score < b.score ? 1 : -1))
78
- .map(({ item }) => item);
81
+ .map(({ item }) => item)
82
+ // remove duplicates
83
+ .filter((item) => {
84
+ const key = item.value + SEP + item.label;
85
+ if (found.has(key)) {
86
+ return false;
87
+ }
88
+ found.add(key);
89
+ return true;
90
+ }));
79
91
  };
80
92
  exports.filterItems = filterItems;
package/Multiselect.d.ts CHANGED
@@ -10,7 +10,9 @@ export type MultiselectI18n = {
10
10
  export type MultiselectOption = Exclude<MultiselectProps['options'][number], string>;
11
11
  /** @deprecated This type-name has a typo, import `MultiselectOption` instead (Will be removed in v0.11) */
12
12
  export type MultiSelectOption = MultiselectOption;
13
- export type MultiselectProps = TogglerGroupFieldProps<string> & {
13
+ export type MultiselectProps = TogglerGroupFieldProps<string, {
14
+ group?: string;
15
+ }> & {
14
16
  value?: Array<string>;
15
17
  defaultValue?: Array<string>;
16
18
  placeholder?: string;
package/Multiselect.js CHANGED
@@ -98,6 +98,7 @@ const Multiselect = (props) => {
98
98
  const showCurrentValues = values.length > 0 &&
99
99
  (props.forceSummary || !isOpen || options.length >= summaryLimit);
100
100
  const filteredOptions = (0, react_1.useMemo)(() => (0, _Multiselect_search_js_1.filterItems)(options, searchQuery, props.searchScoring), [searchQuery, options, props.searchScoring]);
101
+ const isFiltered = options !== filteredOptions;
101
102
  const handleCheckboxSelection = (0, react_1.useCallback)((selectedItem) => {
102
103
  const selValue = selectedItem.value;
103
104
  const isAdding = values.indexOf(selValue) === -1;
@@ -229,11 +230,17 @@ const Multiselect = (props) => {
229
230
  ? item.disabled
230
231
  : disableds && disableds.includes(idx);
231
232
  const isChecked = values.includes(item.value);
232
- return (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: {
233
+ const insertGroupSeparator = !isFiltered &&
234
+ item.group !== (filteredOptions[idx - 1] || {}).group &&
235
+ (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: {
233
237
  onMouseEnter: () => setActiveItemIndex(idx),
234
238
  } })));
239
+ return insertGroupSeparator ? (react_1.default.createElement(react_1.Fragment, { key: idx },
240
+ react_1.default.createElement("li", { className: (0, classUtils_1.modifiedClass)('Multiselect__optionSeparator', !item.group && 'empty'), "aria-label": item.group ? undefined : '—' }, item.group || false),
241
+ checkbox)) : (checkbox);
235
242
  })) : searchQuery ? (react_1.default.createElement("li", { className: "Multiselect__noresults" }, texts.noneFoundMsg)) : undefined,
236
- react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
243
+ isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
237
244
  } })));
238
245
  };
239
246
  exports.Multiselect = Multiselect;
package/Pagination.js CHANGED
@@ -74,7 +74,7 @@ exports.Pagination = (0, react_1.memo)((props) => {
74
74
  const pageList = (0, Pagination_privates_js_1.generatePageList)(current, pageCount);
75
75
  const texts = (0, i18n_1.getTexts)(props, exports.defaultPaginationTexts);
76
76
  const btn = getBtnRenderer(props);
77
- return (react_1.default.createElement("div", Object.assign({}, wrapperProps, { className: (0, classUtils_1.modifiedClass)('Pagination', null, (wrapperProps || {}).className), "aria-label": props.title || texts.defaultTitle.replace(/\$\{pageCount\}/, `${pageCount}`) }),
77
+ return (react_1.default.createElement("nav", Object.assign({}, wrapperProps, { className: (0, classUtils_1.modifiedClass)('Pagination', null, (wrapperProps || {}).className), "aria-label": props.title || texts.defaultTitle.replace(/\$\{pageCount\}/, `${pageCount}`) }),
78
78
  btn({
79
79
  page: current + 1,
80
80
  modifier: 'next',
@@ -2,6 +2,8 @@ import { TogglerGroupFieldOptions, TogglerGroupFieldProps } from './_abstract/_T
2
2
  export type RadioButtonsGroupProps = TogglerGroupFieldProps & {
3
3
  value?: string;
4
4
  defaultValue?: string;
5
+ /** Display the buttons in a single column */
6
+ stacked?: boolean;
5
7
  /** @deprecated (Will be removed in v0.11) */
6
8
  columns?: '2col' | '3col';
7
9
  /** @deprecated (Will be removed in v0.11) */
@@ -13,7 +13,7 @@ const RadioButtonsGroup = (props) => {
13
13
  if (props.columns /* eslint-disable-line deprecation/deprecation */) {
14
14
  console.warn('`RadioButtonsGroupProps.columns` is deprecated.');
15
15
  }
16
- return (react_1.default.createElement(_TogglerGroupField_js_1.TogglerGroupField, Object.assign({}, props, { bem: "RadioButtonsGroup", Toggler: RadioButton, isRadio: true })));
16
+ return (react_1.default.createElement(_TogglerGroupField_js_1.TogglerGroupField, Object.assign({}, props, { bem: "RadioButtonsGroup", modifier: props.stacked && 'stacked', Toggler: RadioButton, isRadio: true })));
17
17
  };
18
18
  exports.RadioButtonsGroup = RadioButtonsGroup;
19
19
  exports.default = exports.RadioButtonsGroup;
@@ -0,0 +1,92 @@
1
+ import { ReactNode } from 'react';
2
+ import { EitherObj } from '@reykjavik/hanna-utils';
3
+ import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
4
+ import { SSRSupportProps, WrapperElmProps } from '../utils.js';
5
+ import { BemProps } from '../utils/types.js';
6
+ type AbstractModalI18n = {
7
+ closeButton: string;
8
+ closeButtonLabel?: string;
9
+ };
10
+ export declare const defaultAbstractModalTexts: DefaultTexts<AbstractModalI18n>;
11
+ export type AbstractModalProps = {
12
+ /**
13
+ * The transition delay until closing the modal triggers `onClosed()`
14
+ *
15
+ * Default: `500`
16
+ */
17
+ closeDelay?: number;
18
+ /**
19
+ * Indicates if teh Modal should be open or closed. To trigger opening or closing, simply flip this flag.
20
+ *
21
+ * Default: `true`
22
+ */
23
+ open?: boolean;
24
+ /**
25
+ * Set this to `true` for Modals that should render as if they always existed and had already been opened.
26
+ *
27
+ * A Modal that "starts open" will not CSS transition in, and will not trigger its `onOpen` callback on mount.
28
+ *
29
+ * Default: `false`
30
+ */
31
+ startOpen?: boolean;
32
+ /**
33
+ * By default all modals close on ESC and outside clicks.
34
+ * Set thtis to `true` to disable this behaviour.
35
+ */
36
+ stable?: boolean;
37
+ /** Hides the (x) close button */
38
+ noCloseButton?: boolean;
39
+ /**
40
+ * @deprecated Use `stable` prop instead (Will be removed in v0.11)
41
+ *
42
+ * Default: `true`
43
+ *
44
+ * The inverse of the `stable` prop. If both `fickle` and `stable` are
45
+ * defined then `stable` takes precedence.
46
+ */
47
+ fickle?: boolean;
48
+ /** Convenience callback that runs as soon as the `open` flag flips to `true` – including on initial opening.
49
+ *
50
+ * However, the initial `onOpen` is skipped `startOpen` is set to `true`.
51
+ */
52
+ onOpen?: () => void;
53
+ /**
54
+ * Convenience callback that runs as soon as the `open` flag flips to `false`
55
+ */
56
+ onClose?: () => void;
57
+ /**
58
+ * Callback that runs when the modal closes – **after** `closeDelay` has elaped.
59
+ */
60
+ onClosed: () => void;
61
+ /**
62
+ * Default:
63
+ * ```
64
+ * {
65
+ * closeButton: 'Close',
66
+ * closeButtonLabel: 'Close this window',
67
+ * }
68
+ * ```
69
+ */
70
+ texts?: Readonly<{
71
+ closeButton: string;
72
+ closeButtonLabel?: string;
73
+ }>;
74
+ lang?: string;
75
+ /**
76
+ * Should the modal be mounted in a Portal component `<div/>`
77
+ * located outside the ReactDOM.render root element?
78
+ *
79
+ * Default: `true`
80
+ */
81
+ portal?: boolean;
82
+ } & EitherObj<{
83
+ /** Render function that receives a `closeModal` action dispatcher. */
84
+ render: (props: {
85
+ closeModal(): void;
86
+ }) => ReactNode;
87
+ }, {
88
+ children: ReactNode;
89
+ }> & WrapperElmProps<'div', 'hidden' | 'role'> & SSRSupportProps;
90
+ type AbstractModalProps_private = AbstractModalProps & BemProps<true>;
91
+ export declare const AbstractModal: (props: AbstractModalProps_private) => JSX.Element;
92
+ export {};
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AbstractModal = exports.defaultAbstractModalTexts = 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 focusElm_1 = require("@hugsmidjan/qj/focusElm");
8
+ const i18n_1 = require("@reykjavik/hanna-utils/i18n");
9
+ const utils_js_1 = require("../utils.js");
10
+ const useCallbackOnEsc_js_1 = require("../utils/useCallbackOnEsc.js");
11
+ const _FocusTrap_js_1 = require("./_FocusTrap.js");
12
+ const _Portal_js_1 = require("./_Portal.js");
13
+ const MODAL_OPEN_CLASS = 'modal-open';
14
+ // ---------------------------------------------------------------------------
15
+ // Methods to manage the modalStack and the setting/unsetting modalOpenClass
16
+ //
17
+ const win = typeof window !== 'undefined'
18
+ ? window
19
+ : undefined;
20
+ const modalStack = win ? win.$$modalStack || (win.$$modalStack = []) : [];
21
+ const addToModalStack = (domid) => {
22
+ document.documentElement.classList.add(MODAL_OPEN_CLASS); // Always set this, even on startOpen === true
23
+ modalStack.unshift(domid);
24
+ };
25
+ const removeFromModalStack = (domid) => {
26
+ const stackPos = modalStack.indexOf(domid);
27
+ if (stackPos > -1) {
28
+ modalStack.splice(stackPos, 1);
29
+ }
30
+ if (modalStack.length <= 0) {
31
+ document.documentElement.classList.remove(MODAL_OPEN_CLASS);
32
+ }
33
+ };
34
+ exports.defaultAbstractModalTexts = {
35
+ is: {
36
+ closeButton: 'Loka',
37
+ closeButtonLabel: 'Loka þessum glugga',
38
+ },
39
+ en: {
40
+ closeButton: 'Close',
41
+ closeButtonLabel: 'Close this window',
42
+ },
43
+ pl: {
44
+ closeButton: 'Zamknij',
45
+ closeButtonLabel: 'Zamknij to okno',
46
+ },
47
+ };
48
+ // eslint-disable-next-line complexity
49
+ const AbstractModal = (props) => {
50
+ var _a;
51
+ const { bem, modifier, closeDelay = 500, wrapperProps = {}, ssr } = props;
52
+ const isFickle = !((_a = props.stable) !== null && _a !== void 0 ? _a : props.fickle === false) || undefined;
53
+ const txt = (0, i18n_1.getTexts)(props, exports.defaultAbstractModalTexts);
54
+ const isBrowser = (0, utils_js_1.useIsBrowserSide)(ssr);
55
+ const privateDomId = (0, utils_js_1.useDomid)();
56
+ const domid = wrapperProps.id || privateDomId;
57
+ const modalElmRef = (0, react_1.useRef)(null);
58
+ const [open, setOpen] = (0, react_1.useState)(() => !!props.startOpen && props.open !== false);
59
+ const openModal = () => {
60
+ if (!open) {
61
+ addToModalStack(privateDomId);
62
+ setTimeout(() => {
63
+ setOpen(true);
64
+ props.onOpen && props.onOpen();
65
+ (0, focusElm_1.focusElm)(modalElmRef.current);
66
+ }, 100);
67
+ }
68
+ };
69
+ const closeModal = () => {
70
+ if (open) {
71
+ setOpen(false);
72
+ removeFromModalStack(privateDomId);
73
+ props.onClose && props.onClose();
74
+ setTimeout(props.onClosed, closeDelay);
75
+ }
76
+ };
77
+ const lastPropsOpen = (0, react_1.useRef)(props.open);
78
+ // Update state when props.open changes. Icky but simple.
79
+ if (props.open !== lastPropsOpen.current && props.open !== open) {
80
+ lastPropsOpen.current = props.open;
81
+ props.open ? openModal() : closeModal();
82
+ }
83
+ const closeOnCurtainClick = isFickle &&
84
+ ((e) => {
85
+ if (e.target === e.currentTarget) {
86
+ closeModal();
87
+ }
88
+ });
89
+ (0, useCallbackOnEsc_js_1.useCallbackOnEsc)(isFickle &&
90
+ (() => {
91
+ if (modalStack[0] === domid) {
92
+ closeModal();
93
+ }
94
+ }));
95
+ // On initial mount (and final unmount)
96
+ (0, react_1.useEffect)(() => {
97
+ if (open) {
98
+ // The modal did `startOpen` so we need to add it to the "modal-stack".
99
+ addToModalStack(privateDomId);
100
+ }
101
+ else if (props.open) {
102
+ // The modal should transition to open.
103
+ openModal();
104
+ }
105
+ return () => removeFromModalStack(privateDomId);
106
+ }, [] // eslint-disable-line react-hooks/exhaustive-deps
107
+ );
108
+ const PortalOrFragment = props.portal !== false ? _Portal_js_1.Portal : react_1.Fragment;
109
+ const children = props.render ? props.render({ closeModal }) : props.children;
110
+ const closeButtonLabel = txt.closeButtonLabel || txt.closeButton;
111
+ const { onClick, className } = wrapperProps;
112
+ return (react_1.default.createElement(PortalOrFragment, null,
113
+ react_1.default.createElement("div", Object.assign({}, wrapperProps, { className: (0, classUtils_1.modifiedClass)(`${bem}wrapper`, [modifier, className]), hidden: !open, role: "dialog", onClick: closeOnCurtainClick && onClick
114
+ ? (e) => {
115
+ closeOnCurtainClick(e);
116
+ onClick(e);
117
+ }
118
+ : closeOnCurtainClick || onClick, id: domid }),
119
+ isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { atTop: true }),
120
+ react_1.default.createElement("div", { className: (0, classUtils_1.modifiedClass)(bem, modifier), ref: modalElmRef },
121
+ children,
122
+ 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))),
123
+ isBrowser && react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { atTop: true }))));
124
+ };
125
+ exports.AbstractModal = AbstractModal;
@@ -0,0 +1,14 @@
1
+ import { BemProps } from '../utils/types.js';
2
+ export type CarouselPagingProps = {
3
+ current: number;
4
+ itemCount: number;
5
+ setCurrent: (idx: number) => void;
6
+ 'aria-controls'?: string;
7
+ texts: Readonly<{
8
+ next: string;
9
+ prev: string;
10
+ unit?: string;
11
+ }>;
12
+ } & BemProps;
13
+ declare const CarouselPaging: (props: CarouselPagingProps) => JSX.Element;
14
+ export default CarouselPaging;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const react_1 = tslib_1.__importDefault(require("react"));
5
+ const classUtils_1 = require("@hugsmidjan/qj/classUtils");
6
+ const CarouselPaging = (props) => {
7
+ const { bem = 'CarouselPaging', modifier, current, itemCount, setCurrent } = props;
8
+ const { next, prev, unit = '' } = props.texts;
9
+ return (react_1.default.createElement("div", { className: (0, classUtils_1.modifiedClass)(bem, modifier) },
10
+ react_1.default.createElement("button", { className: `${bem}__button ${bem}__button--next`, type: "button", disabled: current >= itemCount - 1, onClick: () => setCurrent(current + 1), "aria-controls": props['aria-controls'], "aria-label": `${next} ${unit}`, title: `${next} ${unit}` }, next),
11
+ ' ',
12
+ react_1.default.createElement("button", { className: `${bem}__button ${bem}__button--prev`, type: "button", disabled: current === 0, onClick: () => setCurrent(current - 1), "aria-controls": props['aria-controls'], "aria-label": `${prev} ${unit}`, title: `${prev} ${unit}` }, prev)));
13
+ };
14
+ exports.default = CarouselPaging;
@@ -9,7 +9,7 @@ const FocusTrap = (props) => {
9
9
  return (react_1.default.createElement(Tag, { tabIndex: 0, onFocus: (e) => {
10
10
  var _a;
11
11
  let container = e.currentTarget;
12
- let depth = Math.max(props.depth || 0, 1);
12
+ let depth = Math.max(props.depth || 1, 1);
13
13
  while (depth-- && container) {
14
14
  container = container.parentElement;
15
15
  }
@@ -1,4 +1,5 @@
1
1
  export declare const renderLayoutHomeLink: (bem: string, logoLink: string, siteName?: string) => JSX.Element;
2
+ export declare const renderLegacyLayoutHomeLink: (bem: string, logoLink: string, siteName?: string) => JSX.Element;
2
3
  export declare const issueSiteNameWarningInDev: (props: {
3
4
  siteName?: string;
4
5
  }) => void;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.issueSiteNameWarningInDev = exports.renderLayoutHomeLink = void 0;
3
+ exports.issueSiteNameWarningInDev = exports.renderLegacyLayoutHomeLink = exports.renderLayoutHomeLink = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const react_1 = tslib_1.__importDefault(require("react"));
6
6
  const assets_1 = require("@reykjavik/hanna-utils/assets");
@@ -15,6 +15,14 @@ const renderLayoutHomeLink = (bem, logoLink, siteName) => (react_1.default.creat
15
15
  ' '));
16
16
  exports.renderLayoutHomeLink = renderLayoutHomeLink;
17
17
  // ---------------------------------------------------------------------------
18
+ const renderLegacyLayoutHomeLink = (bem, logoLink, siteName) => (react_1.default.createElement(_Link_js_1.Link, { className: "Layout__header__logo", href: logoLink },
19
+ ' ',
20
+ react_1.default.createElement(_Image_js_1.Image, { bem: undefined, inline: true, src: (0, assets_1.getRvkLogoUrl)('reykjavik-logo.svg'), altText: "Reykjav\u00EDk" }),
21
+ ' ',
22
+ siteName,
23
+ ' '));
24
+ exports.renderLegacyLayoutHomeLink = renderLegacyLayoutHomeLink;
25
+ // ---------------------------------------------------------------------------
18
26
  const issueSiteNameWarningInDev = (props) => {
19
27
  if (process.env.NODE_ENV !== 'production') {
20
28
  if (!('siteName' in props)) {
@@ -0,0 +1,7 @@
1
+ import { ReactNode } from 'react';
2
+ type PortalProps = {
3
+ getRoot?: () => Element;
4
+ children: ReactNode;
5
+ };
6
+ export declare const Portal: ({ getRoot, children }: PortalProps) => import("react").ReactPortal | null;
7
+ export {};