@reykjavik/hanna-react 0.10.92 → 0.10.93
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/ArticleCarousel/_ArticleCarouselCard.d.ts +2 -1
- package/CHANGELOG.md +16 -0
- package/ContactBubble.d.ts +2 -1
- package/ContactBubble.js +4 -6
- package/Datepicker.d.ts +31 -3
- package/Datepicker.js +25 -6
- package/FormField.d.ts +11 -2
- package/FormField.js +5 -5
- package/MainMenu/_PrimaryPanel.d.ts +2 -2
- package/MainMenu.d.ts +2 -1
- package/MainMenu.js +5 -6
- package/Multiselect/_Multiselect.search.d.ts +19 -0
- package/Multiselect/_Multiselect.search.js +80 -0
- package/Multiselect.d.ts +64 -0
- package/Multiselect.js +236 -0
- package/RelatedLinks.d.ts +2 -1
- package/Selectbox.d.ts +3 -3
- package/TextInput.d.ts +0 -1
- package/_abstract/_CardList.d.ts +2 -1
- package/_abstract/_CardList.js +2 -2
- package/_abstract/_FocusTrap.d.ts +14 -0
- package/_abstract/_FocusTrap.js +24 -0
- package/_abstract/_TogglerGroup.d.ts +11 -7
- package/_abstract/_TogglerGroup.js +11 -3
- package/_abstract/_TogglerGroupField.d.ts +4 -4
- package/_abstract/_TogglerGroupField.js +2 -2
- package/_abstract/_TogglerInput.d.ts +3 -1
- package/_abstract/_TogglerInput.js +7 -4
- package/esm/ArticleCarousel/_ArticleCarouselCard.d.ts +2 -1
- package/esm/ContactBubble.d.ts +2 -1
- package/esm/ContactBubble.js +4 -6
- package/esm/Datepicker.d.ts +31 -3
- package/esm/Datepicker.js +25 -6
- package/esm/FormField.d.ts +11 -2
- package/esm/FormField.js +5 -5
- package/esm/MainMenu/_PrimaryPanel.d.ts +2 -2
- package/esm/MainMenu.d.ts +2 -1
- package/esm/MainMenu.js +5 -6
- package/esm/Multiselect/_Multiselect.search.d.ts +19 -0
- package/esm/Multiselect/_Multiselect.search.js +75 -0
- package/esm/Multiselect.d.ts +64 -0
- package/esm/Multiselect.js +231 -0
- package/esm/RelatedLinks.d.ts +2 -1
- package/esm/Selectbox.d.ts +3 -3
- package/esm/TextInput.d.ts +0 -1
- package/esm/_abstract/_CardList.d.ts +2 -1
- package/esm/_abstract/_CardList.js +2 -2
- package/esm/_abstract/_FocusTrap.d.ts +14 -0
- package/esm/_abstract/_FocusTrap.js +19 -0
- package/esm/_abstract/_TogglerGroup.d.ts +11 -7
- package/esm/_abstract/_TogglerGroup.js +11 -3
- package/esm/_abstract/_TogglerGroupField.d.ts +4 -4
- package/esm/_abstract/_TogglerGroupField.js +2 -2
- package/esm/_abstract/_TogglerInput.d.ts +3 -1
- package/esm/_abstract/_TogglerInput.js +7 -4
- package/esm/index.d.ts +1 -0
- package/esm/utils/useFormatMonitor.d.ts +4 -2
- package/esm/utils/useFormatMonitor.js +4 -2
- package/index.d.ts +1 -0
- package/package.json +7 -3
- package/utils/useFormatMonitor.d.ts +4 -2
- package/utils/useFormatMonitor.js +4 -2
package/Multiselect.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Multiselect = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
6
|
+
const domid_1 = tslib_1.__importDefault(require("@hugsmidjan/qj/domid"));
|
|
7
|
+
const hooks_1 = require("@hugsmidjan/react/hooks");
|
|
8
|
+
const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
|
|
9
|
+
const hanna_utils_1 = require("@reykjavik/hanna-utils");
|
|
10
|
+
const i18n_1 = require("@reykjavik/hanna-utils/i18n");
|
|
11
|
+
const _FocusTrap_js_1 = require("./_abstract/_FocusTrap.js");
|
|
12
|
+
const _Multiselect_search_js_1 = require("./Multiselect/_Multiselect.search.js");
|
|
13
|
+
const Checkbox_js_1 = tslib_1.__importDefault(require("./Checkbox.js"));
|
|
14
|
+
const FormField_js_1 = tslib_1.__importDefault(require("./FormField.js"));
|
|
15
|
+
const TagPill_js_1 = tslib_1.__importDefault(require("./TagPill.js"));
|
|
16
|
+
const utils_js_1 = require("./utils.js");
|
|
17
|
+
const metaData = {
|
|
18
|
+
/**
|
|
19
|
+
* The item-count where the list becomes searchable.
|
|
20
|
+
*
|
|
21
|
+
* (The search UI, including the on-screen keyboard, takes up a lot of space
|
|
22
|
+
* on mobile devices, so there's a balance that we want to strike.)
|
|
23
|
+
*/
|
|
24
|
+
searchableLimit: 20,
|
|
25
|
+
/**
|
|
26
|
+
* The item-count above which we display a summary of "current" values
|
|
27
|
+
* at the top of the drop-down list.
|
|
28
|
+
*
|
|
29
|
+
* (This summary just gets in the way with ultra short option lists.)
|
|
30
|
+
*/
|
|
31
|
+
summaryLimit: 10,
|
|
32
|
+
};
|
|
33
|
+
const { searchableLimit, summaryLimit } = metaData;
|
|
34
|
+
const defaultTexts = {
|
|
35
|
+
pl: {
|
|
36
|
+
search: 'Wyszukaj opcje',
|
|
37
|
+
buttonShow: 'Pokaż opcje',
|
|
38
|
+
// buttonHide: 'Ukryj opcje',
|
|
39
|
+
currentValues: 'Wybrane wartości',
|
|
40
|
+
noneFoundMsg: 'Brak dopasowań',
|
|
41
|
+
},
|
|
42
|
+
en: {
|
|
43
|
+
search: 'Search options',
|
|
44
|
+
buttonShow: 'Show options',
|
|
45
|
+
// buttonHide: 'Hide options',
|
|
46
|
+
currentValues: 'Currently selected',
|
|
47
|
+
noneFoundMsg: 'No matches',
|
|
48
|
+
},
|
|
49
|
+
is: {
|
|
50
|
+
search: 'Leita í valkostum',
|
|
51
|
+
buttonShow: 'Birta valkosti',
|
|
52
|
+
// buttonHide: 'Fela valkosti',
|
|
53
|
+
currentValues: 'Valin gildi',
|
|
54
|
+
noneFoundMsg: 'Ekkert passar',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
const Multiselect = (props) => {
|
|
58
|
+
const { onSelected, options: _options, disabled: _disabled, readOnly } = props;
|
|
59
|
+
const disabled = _disabled === true;
|
|
60
|
+
const disableds = !disabled && _disabled;
|
|
61
|
+
const name = (0, hooks_1.useDomid)(props.name);
|
|
62
|
+
const [values, setValues] = (0, utils_js_1.useMixedControlState)(props, 'value', []);
|
|
63
|
+
const filled = values.length > 0;
|
|
64
|
+
const empty = !filled && !props.placeholder;
|
|
65
|
+
const placeholderText = !values.length ? props.placeholder : undefined;
|
|
66
|
+
const texts = (0, i18n_1.getTexts)(props, defaultTexts);
|
|
67
|
+
const inputRef = (0, react_1.useRef)(null);
|
|
68
|
+
const wrapperRef = (0, react_1.useRef)(null);
|
|
69
|
+
const [activeItemIndex, setActiveItemIndex] = (0, react_1.useState)(-1);
|
|
70
|
+
const [searchQuery, setSearchQuery] = (0, react_1.useState)('');
|
|
71
|
+
const [isOpen, setIsOpen] = (0, react_1.useState)(false);
|
|
72
|
+
const toggleOpen = (newIsOpen) => {
|
|
73
|
+
setIsOpen((isOpen) => {
|
|
74
|
+
newIsOpen = typeof newIsOpen === 'boolean' ? newIsOpen : !isOpen;
|
|
75
|
+
if (!newIsOpen) {
|
|
76
|
+
wrapperRef.current.querySelector('.Multiselect__choices').scrollTo(0, 0);
|
|
77
|
+
setSearchQuery('');
|
|
78
|
+
setActiveItemIndex(-1);
|
|
79
|
+
}
|
|
80
|
+
return newIsOpen;
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
(0, hooks_1.useOnClickOutside)(wrapperRef, () => toggleOpen(false));
|
|
84
|
+
const options = (0, react_1.useMemo)(() => _options.map((item) => (typeof item === 'string' ? { value: item } : item)), [_options]);
|
|
85
|
+
const isSearchable = props.forceSearchable || options.length >= searchableLimit;
|
|
86
|
+
/*
|
|
87
|
+
NOTE: he `.MultiSelect__currentvalues` should only be visible when
|
|
88
|
+
there are some items selected, and multiselect is either collapsed,
|
|
89
|
+
or the dropdown has reached `summaryLimit` number of items.
|
|
90
|
+
(For fewer items, the "summary" is just in the way.)
|
|
91
|
+
The `forceSummary` prop overrides this default.
|
|
92
|
+
*/
|
|
93
|
+
const showCurrentValues = values.length > 0 &&
|
|
94
|
+
(props.forceSummary || !isOpen || options.length >= summaryLimit);
|
|
95
|
+
const filteredOptions = (0, react_1.useMemo)(() => (0, _Multiselect_search_js_1.filterItems)(options, searchQuery, props.searchScoring), [searchQuery, options, props.searchScoring]);
|
|
96
|
+
const handleCheckboxSelection = (0, react_1.useCallback)((selectedItem) => {
|
|
97
|
+
const selValue = selectedItem.value;
|
|
98
|
+
const isAdding = values.indexOf(selValue) === -1;
|
|
99
|
+
const _newValues = isAdding
|
|
100
|
+
? [...values, selValue]
|
|
101
|
+
: values.filter((value) => value !== selValue);
|
|
102
|
+
const selectedValues = options
|
|
103
|
+
.filter((item) => _newValues.includes(item.value))
|
|
104
|
+
.map((item) => item.value);
|
|
105
|
+
setValues(selectedValues);
|
|
106
|
+
if (onSelected) {
|
|
107
|
+
onSelected({
|
|
108
|
+
value: selectedItem.value,
|
|
109
|
+
checked: isAdding,
|
|
110
|
+
option: selectedItem,
|
|
111
|
+
selectedValues,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}, [values, options, onSelected, setValues]);
|
|
115
|
+
const handleInputChange = (event) => {
|
|
116
|
+
const val = event.target.value;
|
|
117
|
+
const fixVal = val === ' ' ? '' : val;
|
|
118
|
+
setSearchQuery(fixVal);
|
|
119
|
+
toggleOpen(true);
|
|
120
|
+
};
|
|
121
|
+
const handleInputKeyDown = (event) => {
|
|
122
|
+
if (searchQuery.length === 0 && [' ', 'Delete', 'Backspace'].includes(event.key)) {
|
|
123
|
+
// setSearchQuery('');
|
|
124
|
+
toggleOpen(activeItemIndex > -1 ? true : !isOpen);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
// When the dropdown is open, add keydown handlers
|
|
128
|
+
(0, react_1.useEffect)(() => {
|
|
129
|
+
if (!isOpen) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const handleKeyDown = (e) => {
|
|
133
|
+
const inputElm = inputRef.current;
|
|
134
|
+
if (e.key === 'ArrowUp') {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
inputElm.focus();
|
|
137
|
+
setActiveItemIndex((prevIndex) => prevIndex === 0 ? filteredOptions.length - 1 : prevIndex - 1);
|
|
138
|
+
}
|
|
139
|
+
else if (e.key === 'ArrowDown') {
|
|
140
|
+
e.preventDefault();
|
|
141
|
+
inputElm.focus();
|
|
142
|
+
setActiveItemIndex((prevIndex) => prevIndex === filteredOptions.length - 1 ? 0 : prevIndex + 1);
|
|
143
|
+
}
|
|
144
|
+
else if (e.key === 'Escape') {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
inputElm.blur();
|
|
147
|
+
inputElm.focus();
|
|
148
|
+
toggleOpen(false);
|
|
149
|
+
}
|
|
150
|
+
else if (e.key === 'Enter' || e.key === ' ') {
|
|
151
|
+
if (e.target.closest('.MultiSelect__currentvalues')) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const focusInRange = activeItemIndex >= 0 && activeItemIndex < filteredOptions.length;
|
|
155
|
+
if (focusInRange) {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
const selItem = filteredOptions[activeItemIndex];
|
|
158
|
+
if (selItem) {
|
|
159
|
+
handleCheckboxSelection(selItem);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
165
|
+
return () => {
|
|
166
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
167
|
+
};
|
|
168
|
+
}, [activeItemIndex, filteredOptions, isOpen, handleCheckboxSelection, inputRef]);
|
|
169
|
+
// Auto-close the dropdown when focus has left the building
|
|
170
|
+
(0, react_1.useEffect)(() => {
|
|
171
|
+
const wrapperDiv = wrapperRef.current;
|
|
172
|
+
if (!wrapperDiv) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let closing;
|
|
176
|
+
const cancelClose = () => clearTimeout(closing);
|
|
177
|
+
const closeDropdown = () => {
|
|
178
|
+
closing = setTimeout(() => toggleOpen(false), 200);
|
|
179
|
+
};
|
|
180
|
+
wrapperDiv.addEventListener('focusin', cancelClose);
|
|
181
|
+
wrapperDiv.addEventListener('focusout', closeDropdown);
|
|
182
|
+
return () => {
|
|
183
|
+
wrapperDiv.removeEventListener('focusin', cancelClose);
|
|
184
|
+
wrapperDiv.removeEventListener('focusout', closeDropdown);
|
|
185
|
+
};
|
|
186
|
+
}, []);
|
|
187
|
+
(0, react_1.useEffect)(() => {
|
|
188
|
+
var _a, _b;
|
|
189
|
+
(_b = (_a = wrapperRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('.Multiselect__option')[activeItemIndex]) === null || _b === void 0 ? void 0 : _b.scrollIntoView({
|
|
190
|
+
behavior: 'smooth',
|
|
191
|
+
block: 'nearest',
|
|
192
|
+
});
|
|
193
|
+
}, [activeItemIndex]);
|
|
194
|
+
return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)('Multiselect', props.nowrap && 'nowrap', props.className), ssr: props.ssr, group: "inputlike", label: props.label, LabelTag: props.LabelTag, hideLabel: props.hideLabel, small: props.small, filled: filled, empty: empty, disabled: disabled, invalid: props.invalid, errorMessage: props.errorMessage, assistText: props.assistText, readOnly: readOnly, required: props.required, reqText: props.reqText, id: props.id, renderInput: (className, inputProps, addFocusProps, isBrowser) => {
|
|
195
|
+
const { id } = inputProps;
|
|
196
|
+
return (react_1.default.createElement("div", Object.assign({ className: (0, getBemClass_1.default)('Multiselect__input', [isOpen && 'open'], className.input) }, addFocusProps(), { "data-sprinkled": isBrowser, ref: wrapperRef }),
|
|
197
|
+
!isBrowser ? null : isSearchable ? (react_1.default.createElement("input", { className: "Multiselect__search", id: `toggler:${id}`, "aria-label": texts.search, "aria-controls": (0, domid_1.default)(), "data-expanded": isOpen || undefined, onChange: handleInputChange, onKeyDown: handleInputKeyDown, onClick: () => toggleOpen(), value: searchQuery,
|
|
198
|
+
// onFocus={handleInputFocus}
|
|
199
|
+
placeholder: placeholderText, disabled: disabled, ref: inputRef })) : (react_1.default.createElement("button", { className: "Multiselect__toggler", id: `toggler:${id}`, type: "button", "aria-label": texts.buttonShow, "aria-controls": (0, domid_1.default)(), "aria-expanded": isOpen, onClick: () => toggleOpen(), disabled: disabled,
|
|
200
|
+
// Seems like an innocent hack for visible "placeholder" value.
|
|
201
|
+
// For scren-readers aria-label should take precedence.
|
|
202
|
+
ref: inputRef }, placeholderText || ' ')),
|
|
203
|
+
react_1.default.createElement("div", { className: "Multiselect__choices", tabIndex: -1 },
|
|
204
|
+
isBrowser && showCurrentValues && (react_1.default.createElement("div", { className: "Multiselect__currentvalues", onClick: isOpen || disabled
|
|
205
|
+
? undefined
|
|
206
|
+
: () => {
|
|
207
|
+
var _a;
|
|
208
|
+
toggleOpen();
|
|
209
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
210
|
+
}, "aria-label": `${texts.currentValues}:` }, values
|
|
211
|
+
.map((value) => options.find((opt) => opt.value === value))
|
|
212
|
+
.filter(hanna_utils_1.notNully)
|
|
213
|
+
.map((item, idx) => (react_1.default.createElement(TagPill_js_1.default, Object.assign({ key: idx, large: true, label: item.label || item.value }, (isOpen && !readOnly
|
|
214
|
+
? {
|
|
215
|
+
removable: true,
|
|
216
|
+
onRemove: () => {
|
|
217
|
+
handleCheckboxSelection(item);
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
: { removable: false }))))))),
|
|
221
|
+
react_1.default.createElement("ul", { id: id, className: "Multiselect__options", "aria-expanded": isBrowser ? isOpen : undefined, hidden: isBrowser && !isOpen, role: "group", "aria-labelledby": inputProps['aria-labelledby'], "aria-describedby": inputProps['aria-describedby'], "aria-required": props.required },
|
|
222
|
+
filteredOptions.length ? (filteredOptions.map((item, idx) => {
|
|
223
|
+
const isDisabled = item.disabled != null
|
|
224
|
+
? item.disabled
|
|
225
|
+
: disableds && disableds.includes(idx);
|
|
226
|
+
const isChecked = values.includes(item.value);
|
|
227
|
+
return (react_1.default.createElement(Checkbox_js_1.default, Object.assign({ key: idx, className: (0, getBemClass_1.default)('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: {
|
|
228
|
+
onMouseEnter: () => setActiveItemIndex(idx),
|
|
229
|
+
} })));
|
|
230
|
+
})) : searchQuery ? (react_1.default.createElement("li", { className: "Multiselect__noresults" }, texts.noneFoundMsg)) : undefined,
|
|
231
|
+
react_1.default.createElement(_FocusTrap_js_1.FocusTrap, { Tag: "li" })))));
|
|
232
|
+
} }));
|
|
233
|
+
};
|
|
234
|
+
exports.Multiselect = Multiselect;
|
|
235
|
+
/** Configuration constants for the Multiselect components */
|
|
236
|
+
exports.Multiselect.meta = metaData;
|
package/RelatedLinks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
declare const types: {
|
|
2
3
|
readonly external: 1;
|
|
3
4
|
readonly document: 1;
|
|
@@ -8,7 +9,7 @@ export type RelatedLinkType = keyof typeof types;
|
|
|
8
9
|
export type RelatedLinkItem = {
|
|
9
10
|
href: string;
|
|
10
11
|
label: string;
|
|
11
|
-
target?:
|
|
12
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
12
13
|
type?: RelatedLinkType;
|
|
13
14
|
};
|
|
14
15
|
export type RelatedLinksProps = {
|
package/Selectbox.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { OptionOrValue, SelectboxProps as _SelectboxProps } from '@hugsmidjan/react/Selectbox';
|
|
1
|
+
import type { OptionOrValue, SelectboxOptions as _SelectboxOptions, SelectboxProps as _SelectboxProps } from '@hugsmidjan/react/Selectbox';
|
|
2
2
|
import { FormFieldWrappingProps } from './FormField.js';
|
|
3
|
-
export { type SelectboxOption, type SelectboxOptions as SelectboxOptionList,
|
|
3
|
+
export { type SelectboxOption, type SelectboxOptions as SelectboxOptionList, } from '@hugsmidjan/react/Selectbox';
|
|
4
4
|
/** @deprecated Use `SelectboxOptionList` instead (Will be removed in v0.11) */
|
|
5
|
-
type SelectboxOptions
|
|
5
|
+
export type SelectboxOptions = _SelectboxOptions;
|
|
6
6
|
export type SelectboxProps<O extends OptionOrValue = OptionOrValue> = FormFieldWrappingProps & Omit<_SelectboxProps<O>, 'bem'> & {
|
|
7
7
|
small?: boolean;
|
|
8
8
|
};
|
package/TextInput.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ type InputElmProps = JSX.IntrinsicElements['input'];
|
|
|
4
4
|
type TextareaElmProps = JSX.IntrinsicElements['textarea'];
|
|
5
5
|
export type TextInputProps = {
|
|
6
6
|
small?: boolean;
|
|
7
|
-
children?: never;
|
|
8
7
|
} & FormFieldWrappingProps & (({
|
|
9
8
|
type?: 'text' | 'email' | 'tel' | 'number' | 'date' | 'url' | 'password' | 'search';
|
|
10
9
|
inputRef?: RefObject<HTMLInputElement>;
|
package/_abstract/_CardList.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactElement, ReactNode } from 'react';
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
2
|
import { EitherObj } from '@reykjavik/hanna-utils';
|
|
3
3
|
import { ImageProps } from './_Image.js';
|
|
4
4
|
type BaseCardProps = {
|
|
@@ -12,6 +12,7 @@ export type ImageCardProps = BaseCardProps & {
|
|
|
12
12
|
};
|
|
13
13
|
export type TextCardProps = BaseCardProps & {
|
|
14
14
|
summary?: string;
|
|
15
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
15
16
|
};
|
|
16
17
|
export type CardListProps<T> = {
|
|
17
18
|
cards: Array<T>;
|
package/_abstract/_CardList.js
CHANGED
|
@@ -6,10 +6,10 @@ const react_1 = tslib_1.__importDefault(require("react"));
|
|
|
6
6
|
const _Button_js_1 = require("./_Button.js");
|
|
7
7
|
const _Image_js_1 = require("./_Image.js");
|
|
8
8
|
const Card = (props) => {
|
|
9
|
-
const { bem, href, title, imgPlaceholder, image, meta, summary } = props;
|
|
9
|
+
const { bem, href, title, imgPlaceholder, image, meta, summary, target } = props;
|
|
10
10
|
const cardClass = `${bem}__card`;
|
|
11
11
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
12
|
-
react_1.default.createElement(_Button_js_1.Button, { bem: cardClass, href: href },
|
|
12
|
+
react_1.default.createElement(_Button_js_1.Button, { bem: cardClass, href: href, target: target },
|
|
13
13
|
' ',
|
|
14
14
|
react_1.default.createElement(_Image_js_1.Image, Object.assign({ className: `${bem}__image` }, image, { placeholder: imgPlaceholder })),
|
|
15
15
|
react_1.default.createElement("span", { className: `${cardClass}__title` }, title),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type FocusTrapProps = {
|
|
2
|
+
/** The HTML tag to use for the trap element. (Default `<span />`) */
|
|
3
|
+
Tag?: `span` | `li`;
|
|
4
|
+
/** Set to `true` for focus traps positioned at the top of a container. */
|
|
5
|
+
atTop?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* How deep the trap is placed in the DOM tree beneath its container element.
|
|
8
|
+
*
|
|
9
|
+
* Default: `1`
|
|
10
|
+
*/
|
|
11
|
+
depth?: number;
|
|
12
|
+
};
|
|
13
|
+
/** A focus trap element that can be used to keep keyboard focus within a container block. */
|
|
14
|
+
export declare const FocusTrap: (props: FocusTrapProps) => JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FocusTrap = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
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. */
|
|
7
|
+
const FocusTrap = (props) => {
|
|
8
|
+
const Tag = props.Tag || 'span';
|
|
9
|
+
return (react_1.default.createElement(Tag, { tabIndex: 0, onFocus: (e) => {
|
|
10
|
+
var _a;
|
|
11
|
+
let container = e.currentTarget;
|
|
12
|
+
let depth = Math.max(props.depth || 0, 1);
|
|
13
|
+
while (depth-- && container) {
|
|
14
|
+
container = container.parentElement;
|
|
15
|
+
}
|
|
16
|
+
if (!container) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const focusables = container.querySelectorAll('a,input, select, textarea,button, [tabindex]');
|
|
20
|
+
const targetIdx = props.atTop ? focusables.length - 1 : 0;
|
|
21
|
+
(_a = focusables[targetIdx]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
22
|
+
} }));
|
|
23
|
+
};
|
|
24
|
+
exports.FocusTrap = FocusTrap;
|
|
@@ -1,24 +1,28 @@
|
|
|
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 = {
|
|
4
|
+
export type TogglerGroupOption<T = 'default'> = {
|
|
5
5
|
value: string;
|
|
6
|
-
label?: string | JSX.Element;
|
|
6
|
+
label?: T extends 'default' ? string | JSX.Element : T;
|
|
7
7
|
disabled?: boolean;
|
|
8
8
|
id?: string;
|
|
9
9
|
};
|
|
10
|
-
export type TogglerGroupOptions = Array<TogglerGroupOption
|
|
10
|
+
export type TogglerGroupOptions<T = 'default'> = Array<TogglerGroupOption<T>>;
|
|
11
11
|
type RestrictedInputProps = Omit<HTMLProps<'input'>, 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'className' | 'id' | 'name' | 'children'>;
|
|
12
|
-
export type TogglerGroupProps = {
|
|
13
|
-
options: TogglerGroupOptions
|
|
12
|
+
export type TogglerGroupProps<T = 'default'> = {
|
|
13
|
+
options: Array<string> | TogglerGroupOptions<T>;
|
|
14
14
|
className?: string;
|
|
15
|
-
name
|
|
15
|
+
name?: string;
|
|
16
16
|
disabled?: boolean | ReadonlyArray<number>;
|
|
17
17
|
inputProps?: RestrictedInputProps;
|
|
18
18
|
onSelected?: (payload: {
|
|
19
|
+
/** The value of being selected/updated */
|
|
19
20
|
value: string;
|
|
21
|
+
/** The new checked state of the selected value */
|
|
20
22
|
checked: boolean;
|
|
21
|
-
option
|
|
23
|
+
/** The option object being selected */
|
|
24
|
+
option: TogglerGroupOption<T>;
|
|
25
|
+
/** The updated value array */
|
|
22
26
|
selectedValues: Array<string>;
|
|
23
27
|
}) => void;
|
|
24
28
|
} & Omit<FormFieldInputProps, 'disabled'>;
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TogglerGroup = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const react_1 = tslib_1.
|
|
5
|
+
const react_1 = tslib_1.__importStar(require("react"));
|
|
6
|
+
const hooks_1 = require("@hugsmidjan/react/hooks");
|
|
6
7
|
const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
|
|
7
8
|
const utils_js_1 = require("../utils.js");
|
|
8
9
|
const TogglerGroup = (props) => {
|
|
9
10
|
const {
|
|
10
11
|
// id,
|
|
11
|
-
className, bem,
|
|
12
|
+
className, bem, disabled, readOnly, Toggler, onSelected, isRadio, inputProps = {}, } = props;
|
|
12
13
|
const [values, setValues] = (0, utils_js_1.useMixedControlState)(props, 'value', []);
|
|
14
|
+
const name = (0, hooks_1.useDomid)(props.name);
|
|
15
|
+
const options = (0, react_1.useMemo)(() => {
|
|
16
|
+
const _options = props.options;
|
|
17
|
+
return typeof _options[0] === 'string'
|
|
18
|
+
? _options.map((option) => ({ value: option }))
|
|
19
|
+
: _options;
|
|
20
|
+
}, [props.options]);
|
|
13
21
|
return (react_1.default.createElement("ul", { className: (0, getBemClass_1.default)(bem, null, className), role: "group", "aria-labelledby": props['aria-labelledby'], "aria-describedby": props['aria-describedby'], "aria-required": props.required }, options.map((option, i) => {
|
|
14
22
|
const isDisabled = option.disabled != null
|
|
15
23
|
? option.disabled
|
|
@@ -27,7 +35,7 @@ const TogglerGroup = (props) => {
|
|
|
27
35
|
}
|
|
28
36
|
setValues(selectedValues);
|
|
29
37
|
onSelected && onSelected({ value, checked, option, selectedValues });
|
|
30
|
-
}, disabled: isDisabled, "aria-invalid": props['aria-invalid'], checked: isChecked })));
|
|
38
|
+
}, disabled: isDisabled, readOnly: readOnly, "aria-invalid": props['aria-invalid'], checked: isChecked })));
|
|
31
39
|
})));
|
|
32
40
|
};
|
|
33
41
|
exports.TogglerGroup = TogglerGroup;
|
|
@@ -3,9 +3,9 @@ import { BemPropsModifier } from '@hugsmidjan/react/types';
|
|
|
3
3
|
import { FormFieldGroupWrappingProps } from '../FormField.js';
|
|
4
4
|
import { TogglerGroupOption, TogglerGroupOptions, TogglerGroupProps } from './_TogglerGroup.js';
|
|
5
5
|
import { TogglerInputProps } from './_TogglerInput.js';
|
|
6
|
-
export type TogglerGroupFieldProps = {
|
|
6
|
+
export type TogglerGroupFieldProps<T = 'default'> = {
|
|
7
7
|
className?: string;
|
|
8
|
-
} & FormFieldGroupWrappingProps & TogglerGroupProps
|
|
8
|
+
} & Omit<FormFieldGroupWrappingProps, 'disabled'> & TogglerGroupProps<T>;
|
|
9
9
|
type _TogglerGroupFieldProps = {
|
|
10
10
|
Toggler: (props: TogglerInputProps) => ReactElement;
|
|
11
11
|
isRadio?: true;
|
|
@@ -13,7 +13,7 @@ type _TogglerGroupFieldProps = {
|
|
|
13
13
|
defaultValue?: string | ReadonlyArray<string>;
|
|
14
14
|
bem: string;
|
|
15
15
|
} & BemPropsModifier;
|
|
16
|
-
export type TogglerGroupFieldOption = TogglerGroupOption
|
|
17
|
-
export type TogglerGroupFieldOptions = TogglerGroupOptions
|
|
16
|
+
export type TogglerGroupFieldOption<T = 'default'> = TogglerGroupOption<T>;
|
|
17
|
+
export type TogglerGroupFieldOptions<T = 'default'> = TogglerGroupOptions<T>;
|
|
18
18
|
export declare const TogglerGroupField: (props: TogglerGroupFieldProps & _TogglerGroupFieldProps) => JSX.Element;
|
|
19
19
|
export {};
|
|
@@ -14,8 +14,8 @@ const TogglerGroupField = (props) => {
|
|
|
14
14
|
: typeof defaultValue === 'string'
|
|
15
15
|
? [defaultValue]
|
|
16
16
|
: defaultValue, [defaultValue]);
|
|
17
|
-
return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)(bem, modifier, className), group: true, label: label, LabelTag: LabelTag, assistText: assistText, hideLabel: hideLabel, disabled: disabled, readOnly: readOnly, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, id: id, renderInput: (className, inputProps) => {
|
|
18
|
-
return (react_1.default.createElement(_TogglerGroup_js_1.TogglerGroup, Object.assign({ bem: className.options }, inputProps, togglerGroupProps, { value: _value, defaultValue: _defaultValue, Toggler: Toggler })));
|
|
17
|
+
return (react_1.default.createElement(FormField_js_1.default, { className: (0, getBemClass_1.default)(bem, modifier, className), group: true, label: label, LabelTag: LabelTag, assistText: assistText, hideLabel: hideLabel, disabled: !!disabled, readOnly: readOnly, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, id: id, renderInput: (className, inputProps) => {
|
|
18
|
+
return (react_1.default.createElement(_TogglerGroup_js_1.TogglerGroup, Object.assign({ bem: className.options }, inputProps, togglerGroupProps, { disabled: disabled, value: _value, defaultValue: _defaultValue, Toggler: Toggler })));
|
|
19
19
|
} }));
|
|
20
20
|
};
|
|
21
21
|
exports.TogglerGroupField = TogglerGroupField;
|
|
@@ -2,7 +2,6 @@ import { BemPropsModifier } from '@hugsmidjan/react/types';
|
|
|
2
2
|
export type TogglerInputProps = {
|
|
3
3
|
label: string | JSX.Element;
|
|
4
4
|
children?: never;
|
|
5
|
-
Wrapper?: 'div' | 'li';
|
|
6
5
|
invalid?: boolean;
|
|
7
6
|
/** Hidden label prefix text to indicate that the field is required.
|
|
8
7
|
*
|
|
@@ -13,6 +12,9 @@ export type TogglerInputProps = {
|
|
|
13
12
|
* */
|
|
14
13
|
reqText?: string | false;
|
|
15
14
|
errorMessage?: string | JSX.Element;
|
|
15
|
+
Wrapper?: 'div' | 'li';
|
|
16
|
+
wrapperProps?: JSX.IntrinsicElements['div'];
|
|
17
|
+
inputProps?: JSX.IntrinsicElements['input'];
|
|
16
18
|
} & BemPropsModifier & Omit<JSX.IntrinsicElements['input'], 'type'>;
|
|
17
19
|
type _TogglerInputProps = {
|
|
18
20
|
bem: string;
|
|
@@ -6,22 +6,25 @@ const react_1 = tslib_1.__importDefault(require("react"));
|
|
|
6
6
|
const hooks_1 = require("@hugsmidjan/react/hooks");
|
|
7
7
|
const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
|
|
8
8
|
const TogglerInput = (props) => {
|
|
9
|
-
const { bem, modifier, className, label, invalid, errorMessage, Wrapper = 'div', required, reqText, type, id, innerWrap } = props,
|
|
9
|
+
const { bem, modifier, className, label, invalid, errorMessage, Wrapper = 'div', required, reqText, type, id, innerWrap, wrapperProps, inputProps } = props, restInputProps = tslib_1.__rest(props, ["bem", "modifier", "className", "label", "invalid", "errorMessage", "Wrapper", "required", "reqText", "type", "id", "innerWrap", "wrapperProps", "inputProps"]);
|
|
10
10
|
const domid = (0, hooks_1.useDomid)(id);
|
|
11
11
|
const errorId = errorMessage && 'error' + domid;
|
|
12
12
|
const reqStar = required && reqText !== false && (react_1.default.createElement("abbr", { className: bem + '__label__reqstar',
|
|
13
13
|
// TODO: add mo-better i18n thinking
|
|
14
14
|
title: (reqText || 'Þarf að haka í') + ': ' }, "*"));
|
|
15
|
+
const readOnly = restInputProps.readOnly || (inputProps || {}).readOnly;
|
|
15
16
|
const labelContent = (react_1.default.createElement(react_1.default.Fragment, null,
|
|
16
17
|
' ',
|
|
17
18
|
reqStar,
|
|
18
19
|
" ",
|
|
19
20
|
label,
|
|
20
21
|
' '));
|
|
21
|
-
return (react_1.default.createElement(Wrapper, { className: (0, getBemClass_1.default)(bem, modifier, className) },
|
|
22
|
-
react_1.default.createElement("input", Object.assign({ className: bem + '__input', type: type, id: domid, "aria-invalid": invalid || !!errorMessage || undefined, "aria-describedby": errorId }, inputProps)),
|
|
22
|
+
return (react_1.default.createElement(Wrapper, Object.assign({}, wrapperProps, { className: (0, getBemClass_1.default)(bem, modifier, className) }),
|
|
23
|
+
react_1.default.createElement("input", Object.assign({ className: bem + '__input', type: type, id: domid, "aria-invalid": invalid || !!errorMessage || undefined, "aria-describedby": errorId }, restInputProps, inputProps, (readOnly && { disabled: true }))),
|
|
23
24
|
' ',
|
|
24
|
-
react_1.default.createElement("label", { className: bem + '__label', htmlFor: domid },
|
|
25
|
+
react_1.default.createElement("label", { className: bem + '__label', htmlFor: domid },
|
|
26
|
+
innerWrap ? (react_1.default.createElement("span", { className: bem + '__label__wrap' }, labelContent)) : (labelContent),
|
|
27
|
+
readOnly && (react_1.default.createElement("input", { type: "hidden", name: restInputProps.name, value: restInputProps.value }))),
|
|
25
28
|
errorMessage && (react_1.default.createElement("div", { className: bem + '__error', id: errorId }, errorMessage))));
|
|
26
29
|
};
|
|
27
30
|
exports.TogglerInput = TogglerInput;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import type { HannaColorTheme } from '@reykjavik/hanna-css';
|
|
2
3
|
import { Illustration } from '@reykjavik/hanna-utils/assets';
|
|
3
4
|
import { ImageProps } from '../_abstract/_Image.js';
|
|
@@ -10,7 +11,7 @@ export type ArticleCarouselCardProps = {
|
|
|
10
11
|
title: string;
|
|
11
12
|
summary: string;
|
|
12
13
|
href: string;
|
|
13
|
-
target?:
|
|
14
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
14
15
|
color?: ColorFamily;
|
|
15
16
|
/** NOTE: if both `color` and `theme` are specified
|
|
16
17
|
* then `color` takes precedence.
|
package/esm/ContactBubble.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import { DefaultTexts } from '@reykjavik/hanna-utils/i18n';
|
|
2
3
|
import { SSRSupport } from './utils.js';
|
|
3
4
|
export type ContactBubbleI18n = {
|
|
@@ -25,7 +26,7 @@ export type ContactBubbleItem = {
|
|
|
25
26
|
href: string;
|
|
26
27
|
/** Prevents default link behavior unless the handler function returns `true` */
|
|
27
28
|
onClick?: () => void | boolean;
|
|
28
|
-
target?:
|
|
29
|
+
target?: React.HTMLAttributeAnchorTarget;
|
|
29
30
|
} | {
|
|
30
31
|
onClick: () => void | boolean;
|
|
31
32
|
href?: undefined;
|
package/esm/ContactBubble.js
CHANGED
|
@@ -2,7 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { focusElm } from '@hugsmidjan/qj/focusElm';
|
|
3
3
|
import { useDomid } from '@hugsmidjan/react/hooks';
|
|
4
4
|
import getBemClass from '@hugsmidjan/react/utils/getBemClass';
|
|
5
|
-
import { getPageScrollElm } from '@reykjavik/hanna-utils';
|
|
6
5
|
import { getTexts } from '@reykjavik/hanna-utils/i18n';
|
|
7
6
|
import { Link } from './_abstract/_Link.js';
|
|
8
7
|
import { breakOnNL } from './_abstract/breakOnNL.js';
|
|
@@ -61,12 +60,11 @@ export const ContactBubble = (props) => {
|
|
|
61
60
|
wrapperElm.dataset.show = 'true';
|
|
62
61
|
return;
|
|
63
62
|
}
|
|
64
|
-
const scrollElm = getPageScrollElm();
|
|
65
63
|
let pending = 0;
|
|
66
64
|
const checkScroll = () => {
|
|
67
65
|
if (!pending) {
|
|
68
66
|
pending = requestAnimationFrame(() => {
|
|
69
|
-
const { scrollTop, scrollHeight, clientHeight } =
|
|
67
|
+
const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
|
|
70
68
|
const scrollLength = scrollHeight - clientHeight;
|
|
71
69
|
// const f = scrollLength > 600 ? 1 : (scrollLength - 200) / 600;
|
|
72
70
|
const f = 1;
|
|
@@ -78,7 +76,7 @@ export const ContactBubble = (props) => {
|
|
|
78
76
|
}
|
|
79
77
|
};
|
|
80
78
|
checkScroll();
|
|
81
|
-
// Set scroll-listeners on both the ´document` and the `
|
|
79
|
+
// Set scroll-listeners on both the ´document` and the `document.documentElement`
|
|
82
80
|
// because mobile browsers seem to handle CSS height and overflow
|
|
83
81
|
// rules on <html> and <body> differently from desktop browsers.
|
|
84
82
|
// Only one of these two handlers seems to trigger though,
|
|
@@ -86,10 +84,10 @@ export const ContactBubble = (props) => {
|
|
|
86
84
|
// and even if they did, the rAF throttling prevents that from
|
|
87
85
|
// becoming a problem.
|
|
88
86
|
document.addEventListener('scroll', checkScroll);
|
|
89
|
-
|
|
87
|
+
document.documentElement.addEventListener('scroll', checkScroll);
|
|
90
88
|
return () => {
|
|
91
89
|
document.removeEventListener('scroll', checkScroll);
|
|
92
|
-
|
|
90
|
+
document.documentElement.removeEventListener('scroll', checkScroll);
|
|
93
91
|
};
|
|
94
92
|
}, [isBrowser, alwaysShow, closeBubble]);
|
|
95
93
|
useEffect(() => {
|
package/esm/Datepicker.d.ts
CHANGED
|
@@ -4,22 +4,45 @@ export type DatepickerProps = {
|
|
|
4
4
|
small?: boolean;
|
|
5
5
|
placeholder?: string;
|
|
6
6
|
value?: Date;
|
|
7
|
+
/**
|
|
8
|
+
* Default value for "uncontrolled" mode.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: Even though defaultValue and the `onChange` value are both `Date`
|
|
11
|
+
* the `<input/>` element is still `type="text"` and it's `.value` is
|
|
12
|
+
* the human-readable (parsed) date `string`.
|
|
13
|
+
*
|
|
14
|
+
* Use this incombination with the `isoMode` prop to submit ISO-8601
|
|
15
|
+
* formatted input values
|
|
16
|
+
*/
|
|
17
|
+
defaultValue?: Date;
|
|
7
18
|
name?: string;
|
|
8
19
|
startDate?: Date;
|
|
9
20
|
endDate?: Date;
|
|
10
21
|
minDate?: Date;
|
|
11
22
|
maxDate?: Date;
|
|
23
|
+
/**
|
|
24
|
+
* Turn this on to generate a form <input/> that contains (and submits)
|
|
25
|
+
* an ISO-date formatted string value, instead of the default "human
|
|
26
|
+
* readable" format.
|
|
27
|
+
*
|
|
28
|
+
* NOTE: This will be the default mode in v0.11.
|
|
29
|
+
*/
|
|
30
|
+
isoMode?: boolean;
|
|
12
31
|
localeCode?: 'is' | 'en' | 'pl';
|
|
13
32
|
dateFormat?: string | Array<string>;
|
|
14
33
|
isStartDate?: boolean;
|
|
15
34
|
isEndDate?: boolean;
|
|
16
35
|
inputRef?: RefObject<HTMLInputElement>;
|
|
17
|
-
onChange
|
|
36
|
+
onChange?: (date?: Date) => void;
|
|
18
37
|
datepickerExtraProps?: Record<string, unknown>;
|
|
19
|
-
/** @deprecated
|
|
38
|
+
/** @deprecated Use `value` or `defaultValue` instead. (Will be removed in v0.11) */
|
|
20
39
|
initialDate?: Date;
|
|
21
40
|
} & FormFieldWrappingProps;
|
|
22
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Dumb utility function that returns a new Date that's `dayOffset` days away
|
|
43
|
+
* from the input `date`.
|
|
44
|
+
*/
|
|
45
|
+
export declare const getDateDiff: (refDate: Date, dayOffset: number) => Date;
|
|
23
46
|
export type DatepickerLocaleProps = {
|
|
24
47
|
nextMonthAriaLabel: string;
|
|
25
48
|
nextMonthButtonLabel: string;
|
|
@@ -36,5 +59,10 @@ export type DatepickerLocaleProps = {
|
|
|
36
59
|
chooseDayAriaLabelPrefix: string;
|
|
37
60
|
disabledDayAriaLabelPrefix: string;
|
|
38
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* A compo
|
|
64
|
+
*
|
|
65
|
+
* Internally, this component uses the [`react-datepicker`](https://reactdatepicker.com/) component.
|
|
66
|
+
*/
|
|
39
67
|
export declare const Datepicker: (props: DatepickerProps) => JSX.Element;
|
|
40
68
|
export default Datepicker;
|