@reykjavik/hanna-react 0.10.109 → 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.
- package/CHANGELOG.md +14 -0
- package/CheckboxButtonsGroup.d.ts +2 -0
- package/CheckboxButtonsGroup.js +1 -1
- package/FormField.d.ts +1 -1
- package/Gallery/_GalleryModal.js +11 -12
- package/Gallery.js +7 -7
- package/Heading.js +1 -0
- package/Modal.d.ts +9 -5
- package/Modal.js +12 -10
- package/Multiselect/_Multiselect.search.d.ts +1 -1
- package/Multiselect/_Multiselect.search.js +15 -3
- package/Multiselect.d.ts +3 -1
- package/Multiselect.js +9 -2
- package/RadioButtonsGroup.d.ts +2 -0
- package/RadioButtonsGroup.js +1 -1
- package/_abstract/_AbstractModal.d.ts +92 -0
- package/_abstract/_AbstractModal.js +125 -0
- package/_abstract/_CarouselPaging.d.ts +14 -0
- package/_abstract/_CarouselPaging.js +14 -0
- package/_abstract/_FocusTrap.js +1 -1
- package/_abstract/_Portal.d.ts +7 -0
- package/_abstract/_Portal.js +20 -0
- package/_abstract/_TogglerGroup.d.ts +6 -6
- package/_abstract/_TogglerGroupField.d.ts +2 -2
- package/esm/CheckboxButtonsGroup.d.ts +2 -0
- package/esm/CheckboxButtonsGroup.js +1 -1
- package/esm/FormField.d.ts +1 -1
- package/esm/Gallery/_GalleryModal.js +11 -12
- package/esm/Gallery.js +7 -7
- package/esm/Heading.js +1 -0
- package/esm/Modal.d.ts +9 -5
- package/esm/Modal.js +11 -9
- package/esm/Multiselect/_Multiselect.search.d.ts +1 -1
- package/esm/Multiselect/_Multiselect.search.js +15 -3
- package/esm/Multiselect.d.ts +3 -1
- package/esm/Multiselect.js +10 -3
- package/esm/RadioButtonsGroup.d.ts +2 -0
- package/esm/RadioButtonsGroup.js +1 -1
- package/esm/_abstract/_AbstractModal.d.ts +92 -0
- package/esm/_abstract/_AbstractModal.js +120 -0
- package/esm/_abstract/_CarouselPaging.d.ts +14 -0
- package/esm/_abstract/_CarouselPaging.js +11 -0
- package/esm/_abstract/_FocusTrap.js +1 -1
- package/esm/_abstract/_Portal.d.ts +7 -0
- package/esm/_abstract/_Portal.js +16 -0
- package/esm/_abstract/_TogglerGroup.d.ts +6 -6
- package/esm/_abstract/_TogglerGroupField.d.ts +2 -2
- package/esm/utils/browserSide.js +2 -1
- package/esm/utils/types.d.ts +5 -2
- package/esm/utils/useDomid.d.ts +3 -1
- package/esm/utils/useDomid.js +3 -1
- package/package.json +1 -1
- package/utils/browserSide.js +2 -1
- package/utils/types.d.ts +5 -2
- package/utils/useDomid.d.ts +3 -1
- package/utils/useDomid.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
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
|
+
|
|
7
21
|
## 0.10.109
|
|
8
22
|
|
|
9
23
|
_2023-10-25_
|
|
@@ -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) */
|
package/CheckboxButtonsGroup.js
CHANGED
|
@@ -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>;
|
package/Gallery/_GalleryModal.js
CHANGED
|
@@ -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
|
|
9
|
-
const
|
|
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(
|
|
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(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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: '
|
|
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: '
|
|
25
|
+
modalCloseLabel: 'Zamknij zdjęcie',
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
const Gallery = (props) => {
|
package/Heading.js
CHANGED
package/Modal.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
}
|
|
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
|
|
6
|
+
const _AbstractModal_js_1 = require("./_abstract/_AbstractModal.js");
|
|
7
|
+
exports.defaultModalTexts = _AbstractModal_js_1.defaultAbstractModalTexts;
|
|
7
8
|
const Modal = (props) => {
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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/RadioButtonsGroup.d.ts
CHANGED
|
@@ -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) */
|
package/RadioButtonsGroup.js
CHANGED
|
@@ -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;
|
package/_abstract/_FocusTrap.js
CHANGED
|
@@ -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 ||
|
|
12
|
+
let depth = Math.max(props.depth || 1, 1);
|
|
13
13
|
while (depth-- && container) {
|
|
14
14
|
container = container.parentElement;
|
|
15
15
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Portal = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_dom_1 = require("react-dom");
|
|
6
|
+
const defaultGetRoot = () => {
|
|
7
|
+
const rootElm = document.createElement('div');
|
|
8
|
+
document.body.appendChild(rootElm);
|
|
9
|
+
return rootElm;
|
|
10
|
+
};
|
|
11
|
+
const Portal = ({ getRoot, children }) => {
|
|
12
|
+
const [rootElm, setRootElm] = (0, react_1.useState)(null);
|
|
13
|
+
(0, react_1.useEffect)(() => {
|
|
14
|
+
const newRoot = (getRoot || defaultGetRoot)();
|
|
15
|
+
setRootElm(newRoot);
|
|
16
|
+
return () => newRoot.remove();
|
|
17
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
18
|
+
return rootElm && (0, react_dom_1.createPortal)(children, rootElm);
|
|
19
|
+
};
|
|
20
|
+
exports.Portal = Portal;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { FormFieldInputProps } from '../FormField.js';
|
|
2
2
|
import { HTMLProps } from '../utils.js';
|
|
3
3
|
import { TogglerInputProps } from './_TogglerInput.js';
|
|
4
|
-
export type TogglerGroupOption<T = 'default'> = {
|
|
4
|
+
export type TogglerGroupOption<T = 'default', Extras = {}> = {
|
|
5
5
|
value: string;
|
|
6
6
|
label?: T extends 'default' ? string | JSX.Element : T;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
id?: string;
|
|
9
|
-
}
|
|
10
|
-
export type TogglerGroupOptions<T = 'default'> = Array<TogglerGroupOption<T>>;
|
|
9
|
+
} & Partial<Extras>;
|
|
10
|
+
export type TogglerGroupOptions<T = 'default', Extras = {}> = Array<TogglerGroupOption<T, Extras>>;
|
|
11
11
|
type RestrictedInputProps = Omit<HTMLProps<'input'>, 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'className' | 'id' | 'name' | 'children'>;
|
|
12
|
-
export type TogglerGroupProps<T = 'default'> = {
|
|
13
|
-
options: Array<string> | TogglerGroupOptions<T>;
|
|
12
|
+
export type TogglerGroupProps<T = 'default', Extras = {}> = {
|
|
13
|
+
options: Array<string> | TogglerGroupOptions<T, Extras>;
|
|
14
14
|
className?: string;
|
|
15
15
|
name?: string;
|
|
16
16
|
disabled?: boolean | ReadonlyArray<number>;
|
|
@@ -21,7 +21,7 @@ export type TogglerGroupProps<T = 'default'> = {
|
|
|
21
21
|
/** The new checked state of the selected value */
|
|
22
22
|
checked: boolean;
|
|
23
23
|
/** The option object being selected */
|
|
24
|
-
option: TogglerGroupOption<T>;
|
|
24
|
+
option: TogglerGroupOption<T, Extras>;
|
|
25
25
|
/** The updated value array */
|
|
26
26
|
selectedValues: Array<string>;
|
|
27
27
|
}) => void;
|
|
@@ -3,9 +3,9 @@ import { FormFieldGroupWrappingProps } from '../FormField.js';
|
|
|
3
3
|
import { BemModifierProps } from '../utils/types.js';
|
|
4
4
|
import { TogglerGroupOption, TogglerGroupOptions, TogglerGroupProps } from './_TogglerGroup.js';
|
|
5
5
|
import { TogglerInputProps } from './_TogglerInput.js';
|
|
6
|
-
export type TogglerGroupFieldProps<T = 'default'> = {
|
|
6
|
+
export type TogglerGroupFieldProps<T = 'default', Extras = {}> = {
|
|
7
7
|
className?: string;
|
|
8
|
-
} & Omit<FormFieldGroupWrappingProps, 'disabled'> & TogglerGroupProps<T>;
|
|
8
|
+
} & Omit<FormFieldGroupWrappingProps, 'disabled'> & TogglerGroupProps<T, Extras>;
|
|
9
9
|
type _TogglerGroupFieldProps = {
|
|
10
10
|
Toggler: (props: TogglerInputProps) => ReactElement;
|
|
11
11
|
isRadio?: true;
|
|
@@ -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) */
|
|
@@ -8,6 +8,6 @@ export const CheckboxButtonsGroup = (props) => {
|
|
|
8
8
|
if (props.columns /* eslint-disable-line deprecation/deprecation */) {
|
|
9
9
|
console.warn('`CheckboxButtonsGroupProps.columns` is deprecated.');
|
|
10
10
|
}
|
|
11
|
-
return (React.createElement(TogglerGroupField, Object.assign({}, props, { bem: "CheckboxButtonsGroup", Toggler: CheckboxButton })));
|
|
11
|
+
return (React.createElement(TogglerGroupField, Object.assign({}, props, { bem: "CheckboxButtonsGroup", modifier: props.stacked && 'stacked', Toggler: CheckboxButton })));
|
|
12
12
|
};
|
|
13
13
|
export default CheckboxButtonsGroup;
|