@geotab/zenith 3.15.0 → 3.16.0-beta.0
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/dist/absolute/absolute.js +1 -1
- package/dist/betaPill/betaPill.js +9 -9
- package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
- package/dist/commonHelpers/arrowNavigation/arrowNavigationHelper.js +5 -1
- package/dist/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
- package/dist/commonHelpers/hooks/useTrapFocus.js +66 -14
- package/dist/controlledPopup/controlledPopup.d.ts +1 -1
- package/dist/controlledPopup/controlledPopup.js +1 -1
- package/dist/dropdownRaw/dropdownSearchableTrigger.js +1 -1
- package/dist/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
- package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
- package/dist/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
- package/dist/filtersBar/filtersContainer/filtersContainer.js +7 -4
- package/dist/header/header.js +9 -2
- package/dist/header/headerHelpers.js +8 -5
- package/dist/index.css +142 -11
- package/dist/index.d.ts +6 -1
- package/dist/index.js +12 -3
- package/dist/modal/modal.d.ts +48 -0
- package/dist/modal/modal.js +37 -7
- package/dist/nav/navHeader/navHeader.d.ts +1 -1
- package/dist/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
- package/dist/nav/navItem/navItem.d.ts +15 -2
- package/dist/nav/navItem/navItem.js +49 -7
- package/dist/nav/utils/navUtils.d.ts +2 -2
- package/dist/pillExpandable/pillExpandable.js +6 -3
- package/dist/rangeRaw/components/rangeField.d.ts +2 -1
- package/dist/rangeRaw/components/rangeField.js +4 -3
- package/dist/rangeRaw/rangeRaw.js +8 -5
- package/dist/rangeRaw/types.d.ts +1 -0
- package/dist/sidePanel/sidePanel.js +3 -2
- package/dist/table/selectable/interfaces.d.ts +13 -0
- package/dist/table/selectable/useSelectableRows.js +46 -12
- package/dist/table/selectable/utils/calculateSelectionState.js +35 -0
- package/dist/table/selectable/utils/isRowChecked.js +12 -1
- package/dist/table/table.d.ts +2 -0
- package/dist/table/table.js +3 -1
- package/dist/tabs/tabs.js +14 -5
- package/dist/textInputRaw/textInputRaw.js +1 -1
- package/dist/textInputRaw/types.d.ts +1 -0
- package/dist/tooltip/components/inlineTrigger.d.ts +43 -0
- package/dist/tooltip/components/inlineTrigger.js +42 -0
- package/dist/tooltip/tooltip.d.ts +5 -2
- package/dist/tooltip/tooltip.js +15 -4
- package/dist/trendIndicator/trendIndicator.d.ts +47 -0
- package/dist/trendIndicator/trendIndicator.js +47 -0
- package/dist/trendIndicator/trendIndicatorUtils.d.ts +9 -0
- package/dist/trendIndicator/trendIndicatorUtils.js +18 -0
- package/dist/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
- package/dist/utils/experimentalFeatures/experimentalContext.js +29 -0
- package/dist/utils/experimentalFeatures/index.d.ts +4 -0
- package/dist/utils/experimentalFeatures/index.js +9 -0
- package/dist/utils/experimentalFeatures/registry.d.ts +13 -0
- package/dist/utils/experimentalFeatures/registry.js +11 -0
- package/dist/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
- package/dist/utils/experimentalFeatures/useExperimentalFeature.js +25 -0
- package/dist/utils/localization/translations/ar.json +1 -1
- package/dist/utils/localization/translations/bg-BG.json +1 -1
- package/dist/utils/localization/translations/cs.json +1 -1
- package/dist/utils/localization/translations/da-DK.json +1 -1
- package/dist/utils/localization/translations/de.json +1 -1
- package/dist/utils/localization/translations/el-GR.json +1 -1
- package/dist/utils/localization/translations/en.json +1 -1
- package/dist/utils/localization/translations/es.json +1 -1
- package/dist/utils/localization/translations/et-EE.json +1 -1
- package/dist/utils/localization/translations/fi-FI.json +1 -1
- package/dist/utils/localization/translations/fr-FR.json +1 -1
- package/dist/utils/localization/translations/fr.json +1 -1
- package/dist/utils/localization/translations/hu-HU.json +1 -1
- package/dist/utils/localization/translations/id.json +1 -1
- package/dist/utils/localization/translations/it.json +1 -1
- package/dist/utils/localization/translations/ja.json +1 -1
- package/dist/utils/localization/translations/ka-GE.json +1 -1
- package/dist/utils/localization/translations/ko-KR.json +1 -1
- package/dist/utils/localization/translations/lt-LT.json +1 -1
- package/dist/utils/localization/translations/lv-LV.json +1 -1
- package/dist/utils/localization/translations/ms.json +1 -1
- package/dist/utils/localization/translations/nb-NO.json +1 -1
- package/dist/utils/localization/translations/nl.json +1 -1
- package/dist/utils/localization/translations/pl.json +1 -1
- package/dist/utils/localization/translations/pt-BR.json +1 -1
- package/dist/utils/localization/translations/pt-PT.json +1 -1
- package/dist/utils/localization/translations/ro-RO.json +1 -1
- package/dist/utils/localization/translations/sk-SK.json +1 -1
- package/dist/utils/localization/translations/sv.json +1 -1
- package/dist/utils/localization/translations/th.json +1 -1
- package/dist/utils/localization/translations/tr.json +1 -1
- package/dist/utils/localization/translations/zh-Hans.json +1 -1
- package/dist/utils/localization/translations/zh-TW.json +1 -1
- package/esm/absolute/absolute.js +1 -1
- package/esm/betaPill/betaPill.js +9 -9
- package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.d.ts +1 -0
- package/esm/commonHelpers/arrowNavigation/arrowNavigationHelper.js +3 -0
- package/esm/commonHelpers/hooks/useTrapFocus.d.ts +9 -0
- package/esm/commonHelpers/hooks/useTrapFocus.js +67 -15
- package/esm/controlledPopup/controlledPopup.d.ts +1 -1
- package/esm/controlledPopup/controlledPopup.js +1 -1
- package/esm/dropdownRaw/dropdownSearchableTrigger.js +1 -1
- package/esm/filtersBar/components/filtersBarRange/filtersBarRange.d.ts +1 -1
- package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.d.ts +1 -1
- package/esm/filtersBar/filtersBarSidePanel/components/filtersBarSidePanelRange/filtersBarSidePanelRange.js +8 -5
- package/esm/filtersBar/filtersContainer/filtersContainer.js +7 -4
- package/esm/header/header.js +9 -2
- package/esm/header/headerHelpers.js +8 -5
- package/esm/index.d.ts +6 -1
- package/esm/index.js +5 -1
- package/esm/modal/modal.d.ts +48 -0
- package/esm/modal/modal.js +37 -7
- package/esm/nav/navHeader/navHeader.d.ts +1 -1
- package/esm/nav/navHeader/navHeaderSearch/navHeaderSearch.js +1 -1
- package/esm/nav/navItem/navItem.d.ts +15 -2
- package/esm/nav/navItem/navItem.js +49 -7
- package/esm/nav/utils/navUtils.d.ts +2 -2
- package/esm/pillExpandable/pillExpandable.js +6 -3
- package/esm/rangeRaw/components/rangeField.d.ts +2 -1
- package/esm/rangeRaw/components/rangeField.js +4 -3
- package/esm/rangeRaw/rangeRaw.js +8 -5
- package/esm/rangeRaw/types.d.ts +1 -0
- package/esm/sidePanel/sidePanel.js +3 -2
- package/esm/table/selectable/interfaces.d.ts +13 -0
- package/esm/table/selectable/useSelectableRows.js +46 -12
- package/esm/table/selectable/utils/calculateSelectionState.js +35 -0
- package/esm/table/selectable/utils/isRowChecked.js +12 -1
- package/esm/table/table.d.ts +2 -0
- package/esm/table/table.js +2 -0
- package/esm/tabs/tabs.js +14 -5
- package/esm/testHelpers/renderWithFeatures.d.ts +4 -0
- package/esm/testHelpers/renderWithFeatures.js +6 -0
- package/esm/textInputRaw/textInputRaw.js +1 -1
- package/esm/textInputRaw/types.d.ts +1 -0
- package/esm/tooltip/components/inlineTrigger.d.ts +43 -0
- package/esm/tooltip/components/inlineTrigger.js +38 -0
- package/esm/tooltip/tooltip.d.ts +5 -2
- package/esm/tooltip/tooltip.js +15 -4
- package/esm/trendIndicator/trendIndicator.d.ts +47 -0
- package/esm/trendIndicator/trendIndicator.js +43 -0
- package/esm/trendIndicator/trendIndicatorUtils.d.ts +9 -0
- package/esm/trendIndicator/trendIndicatorUtils.js +14 -0
- package/esm/utils/experimentalFeatures/experimentalContext.d.ts +17 -0
- package/esm/utils/experimentalFeatures/experimentalContext.js +25 -0
- package/esm/utils/experimentalFeatures/index.d.ts +4 -0
- package/esm/utils/experimentalFeatures/index.js +3 -0
- package/esm/utils/experimentalFeatures/registry.d.ts +13 -0
- package/esm/utils/experimentalFeatures/registry.js +8 -0
- package/esm/utils/experimentalFeatures/useExperimentalFeature.d.ts +3 -0
- package/esm/utils/experimentalFeatures/useExperimentalFeature.js +21 -0
- package/esm/utils/localization/translations/ar.json +1 -1
- package/esm/utils/localization/translations/bg-BG.json +1 -1
- package/esm/utils/localization/translations/cs.json +1 -1
- package/esm/utils/localization/translations/da-DK.json +1 -1
- package/esm/utils/localization/translations/de.json +1 -1
- package/esm/utils/localization/translations/el-GR.json +1 -1
- package/esm/utils/localization/translations/en.json +1 -1
- package/esm/utils/localization/translations/es.json +1 -1
- package/esm/utils/localization/translations/et-EE.json +1 -1
- package/esm/utils/localization/translations/fi-FI.json +1 -1
- package/esm/utils/localization/translations/fr-FR.json +1 -1
- package/esm/utils/localization/translations/fr.json +1 -1
- package/esm/utils/localization/translations/hu-HU.json +1 -1
- package/esm/utils/localization/translations/id.json +1 -1
- package/esm/utils/localization/translations/it.json +1 -1
- package/esm/utils/localization/translations/ja.json +1 -1
- package/esm/utils/localization/translations/ka-GE.json +1 -1
- package/esm/utils/localization/translations/ko-KR.json +1 -1
- package/esm/utils/localization/translations/lt-LT.json +1 -1
- package/esm/utils/localization/translations/lv-LV.json +1 -1
- package/esm/utils/localization/translations/ms.json +1 -1
- package/esm/utils/localization/translations/nb-NO.json +1 -1
- package/esm/utils/localization/translations/nl.json +1 -1
- package/esm/utils/localization/translations/pl.json +1 -1
- package/esm/utils/localization/translations/pt-BR.json +1 -1
- package/esm/utils/localization/translations/pt-PT.json +1 -1
- package/esm/utils/localization/translations/ro-RO.json +1 -1
- package/esm/utils/localization/translations/sk-SK.json +1 -1
- package/esm/utils/localization/translations/sv.json +1 -1
- package/esm/utils/localization/translations/th.json +1 -1
- package/esm/utils/localization/translations/tr.json +1 -1
- package/esm/utils/localization/translations/zh-Hans.json +1 -1
- package/esm/utils/localization/translations/zh-TW.json +1 -1
- package/package.json +36 -16
- package/dist/banner/utils/isBannerTarget.d.ts +0 -1
- package/dist/banner/utils/isBannerTarget.js +0 -11
- package/esm/banner/utils/isBannerTarget.d.ts +0 -1
- package/esm/banner/utils/isBannerTarget.js +0 -7
|
@@ -3,6 +3,41 @@ export const calculateSelectionState = (entities, selections, all, checkboxVisib
|
|
|
3
3
|
if (selections.size === 0) {
|
|
4
4
|
return all ? SelectionState.All : SelectionState.None;
|
|
5
5
|
}
|
|
6
|
+
if (all) {
|
|
7
|
+
// When `all` is true, `selections` holds the ids explicitly excluded from the
|
|
8
|
+
// otherwise-fully-selected set (see ISelectionValue), rather than included ids.
|
|
9
|
+
const isExcluded = (entity) => {
|
|
10
|
+
if (strategy === "nested" && entity.children) {
|
|
11
|
+
if (entity.children.length === 0) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return entity.children.every(child => {
|
|
15
|
+
if (!checkboxVisible(child)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (checkboxDisabled(child)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return selections.has(child.id);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return selections.has(entity.id);
|
|
25
|
+
};
|
|
26
|
+
const isPartiallyExcluded = (entity) => {
|
|
27
|
+
if (strategy === "nested" && entity.children) {
|
|
28
|
+
return entity.children.some(child => selections.has(child.id));
|
|
29
|
+
}
|
|
30
|
+
return selections.has(entity.id);
|
|
31
|
+
};
|
|
32
|
+
const relevantEntities = entities.filter(entity => checkboxVisible(entity) && !checkboxDisabled(entity));
|
|
33
|
+
if (relevantEntities.length === 0) {
|
|
34
|
+
return SelectionState.All;
|
|
35
|
+
}
|
|
36
|
+
if (relevantEntities.every(entity => !isPartiallyExcluded(entity))) {
|
|
37
|
+
return SelectionState.All;
|
|
38
|
+
}
|
|
39
|
+
return relevantEntities.every(entity => isExcluded(entity)) ? SelectionState.None : SelectionState.Partial;
|
|
40
|
+
}
|
|
6
41
|
return entities.every(entity => {
|
|
7
42
|
if (!checkboxVisible(entity)) {
|
|
8
43
|
return true;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { SelectionState } from "../interfaces";
|
|
2
2
|
export const isRowChecked = (selections, state, strategy, entity, isNested) => {
|
|
3
3
|
if (state === SelectionState.All) {
|
|
4
|
-
|
|
4
|
+
// When the overall selection is "all", `selections` holds the ids that have been
|
|
5
|
+
// explicitly excluded from that selection (see ISelectionValue), not included ids.
|
|
6
|
+
if (isNested) {
|
|
7
|
+
return { indeterminate: false, checked: !selections.has(entity.id) };
|
|
8
|
+
}
|
|
9
|
+
const mainEntity = entity;
|
|
10
|
+
if (strategy === "nested" && mainEntity.children && mainEntity.children.length > 0) {
|
|
11
|
+
const allChildrenChecked = mainEntity.children.every(child => !selections.has(child.id));
|
|
12
|
+
const someChildrenChecked = mainEntity.children.some(child => !selections.has(child.id));
|
|
13
|
+
return { indeterminate: someChildrenChecked && !allChildrenChecked, checked: allChildrenChecked };
|
|
14
|
+
}
|
|
15
|
+
return { indeterminate: false, checked: !selections.has(entity.id) };
|
|
5
16
|
}
|
|
6
17
|
if (isNested) {
|
|
7
18
|
return { indeterminate: false, checked: selections.has(entity.id) };
|
package/esm/table/table.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { IRowEntity } from "../dataGrid/row/row";
|
|
|
25
25
|
import { IEntityWithId } from "../commonHelpers/entity";
|
|
26
26
|
import "../cardContainer/cardContainer.less";
|
|
27
27
|
import { ITableDetailPanel } from "./children/tableDetailPanel";
|
|
28
|
+
import { ITrendIndicator } from "../trendIndicator/trendIndicator";
|
|
28
29
|
export interface IListColumn<T extends IRowEntity<N>, N extends IEntityWithId = T> {
|
|
29
30
|
readonly id: string;
|
|
30
31
|
readonly title: string;
|
|
@@ -92,6 +93,7 @@ interface ITableComponents {
|
|
|
92
93
|
BulkButton: React.FC<IBulkActionButton>;
|
|
93
94
|
BulkLink: React.FC<IBulkActionLink>;
|
|
94
95
|
DetailPanel: React.FC<ITableDetailPanel>;
|
|
96
|
+
TrendIndicator: React.FC<ITrendIndicator>;
|
|
95
97
|
}
|
|
96
98
|
declare const TableInner: <T extends IRowEntity<N>, N extends IEntityWithId = T>({ entities, columns, selectable, sortable, flexible, isLoading, loading, children, className, height, onFeedEnd, onScrollEnd, rowClassName, feedExpandable, expandedRows, onExpand, active, onClick }: ITable<T, N>) => import("react/jsx-runtime").JSX.Element;
|
|
97
99
|
declare const Table: typeof TableInner & ITableComponents;
|
package/esm/table/table.js
CHANGED
|
@@ -37,6 +37,7 @@ import { FeedExpandControl } from "../dataFeed/feedExpandControl/feedExpandContr
|
|
|
37
37
|
import { getGridClasses } from "../gridLayout/utils/getGridClasses";
|
|
38
38
|
import { useNestedRows } from "./nested/useNestedRows";
|
|
39
39
|
import { TableDetailPanel } from "./children/tableDetailPanel";
|
|
40
|
+
import { TrendIndicator } from "../trendIndicator/trendIndicator";
|
|
40
41
|
import { useLanguage } from "../utils/localization/useLanguage";
|
|
41
42
|
// Interpolation placeholder used in the "{count} more rows loaded" translation key.
|
|
42
43
|
// `translate()` has no interpolation overload, so the count is substituted at runtime via
|
|
@@ -402,4 +403,5 @@ Table.Fullscreen = TableFullscreen;
|
|
|
402
403
|
Table.BulkButton = BulkActionButton;
|
|
403
404
|
Table.BulkLink = BulkActionLink;
|
|
404
405
|
Table.DetailPanel = TableDetailPanel;
|
|
406
|
+
Table.TrendIndicator = TrendIndicator;
|
|
405
407
|
export { Table, getEmptySelection, ColumnSortDirection, getSortableValue, ActionsColumn, MainColumn, CheckboxColumn, ActionLinkColumn };
|
package/esm/tabs/tabs.js
CHANGED
|
@@ -11,6 +11,7 @@ import { IconChevronLeft } from "../icons/iconChevronLeft";
|
|
|
11
11
|
import { useDirectionalIcon } from "../utils/localization/useDirectionalIcon";
|
|
12
12
|
import { ButtonIconPosition, TextIconButton } from "../textIconButton/textIconButton";
|
|
13
13
|
import { tabsKeyboardHandler, tabsKeyboardTabHandler } from "../commonHelpers/tabsKeyboard";
|
|
14
|
+
import { getDirectionalScrollDelta } from "../commonHelpers/arrowNavigation/arrowNavigationHelper";
|
|
14
15
|
import { useMobileClassName } from "../utils/theme/useMobileClassName";
|
|
15
16
|
import "../utils/globalThisPolyfill";
|
|
16
17
|
import { zen } from "../utils/zen";
|
|
@@ -89,13 +90,15 @@ const getScrollButtonType = (element, isRTL) => {
|
|
|
89
90
|
return "";
|
|
90
91
|
}
|
|
91
92
|
if (isRTL) {
|
|
93
|
+
// scrollLeft is 0 at the start (right edge) and negative toward the end (left edge),
|
|
94
|
+
// so the button to offer is the mirror of the LTR case below.
|
|
92
95
|
if (scrollLeft < -1 && scrollLeft > clientWidth - scrollWidth + 1) {
|
|
93
96
|
return "both";
|
|
94
97
|
}
|
|
95
98
|
if (scrollLeft >= -1) {
|
|
96
|
-
return "
|
|
99
|
+
return "end";
|
|
97
100
|
}
|
|
98
|
-
return "
|
|
101
|
+
return "start";
|
|
99
102
|
}
|
|
100
103
|
if (scrollLeft > 0 && scrollWidth > clientWidth + scrollLeft + 1) {
|
|
101
104
|
return "both";
|
|
@@ -129,6 +132,7 @@ export const Tabs = ({
|
|
|
129
132
|
const scrollableRef = useRef(null);
|
|
130
133
|
const scrollRef = useRef(0);
|
|
131
134
|
const focusableTabIdRef = useRef("");
|
|
135
|
+
const userInitiatedTabChangeRef = useRef(false);
|
|
132
136
|
const handleButtonClick = e => {
|
|
133
137
|
if (disabled) {
|
|
134
138
|
return;
|
|
@@ -136,6 +140,7 @@ export const Tabs = ({
|
|
|
136
140
|
const newState = e.currentTarget.id;
|
|
137
141
|
focusableTabIdRef.current = newState;
|
|
138
142
|
if (newState !== activeTabId) {
|
|
143
|
+
userInitiatedTabChangeRef.current = true;
|
|
139
144
|
onTabChange(newState);
|
|
140
145
|
}
|
|
141
146
|
};
|
|
@@ -152,6 +157,7 @@ export const Tabs = ({
|
|
|
152
157
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
153
158
|
if (target && target.id && tabs.some(tab => tab.id === target.id)) {
|
|
154
159
|
focusableTabIdRef.current = target.id;
|
|
160
|
+
userInitiatedTabChangeRef.current = true;
|
|
155
161
|
onTabChange(target.id);
|
|
156
162
|
}
|
|
157
163
|
return;
|
|
@@ -233,7 +239,10 @@ export const Tabs = ({
|
|
|
233
239
|
const activeTab = scrollableRef.current.querySelector(`.zen-tab-item[id='${activeTabId}']`);
|
|
234
240
|
if (activeTab instanceof HTMLElement) {
|
|
235
241
|
focusableTabIdRef.current = activeTab.getAttribute("id") || "";
|
|
236
|
-
|
|
242
|
+
if (userInitiatedTabChangeRef.current) {
|
|
243
|
+
userInitiatedTabChangeRef.current = false;
|
|
244
|
+
activeTab.focus();
|
|
245
|
+
}
|
|
237
246
|
}
|
|
238
247
|
}, [activeTabId]);
|
|
239
248
|
return _jsx("div", {
|
|
@@ -249,7 +258,7 @@ export const Tabs = ({
|
|
|
249
258
|
icon: ScrollStartIcon,
|
|
250
259
|
type: "tertiary",
|
|
251
260
|
iconPosition: ButtonIconPosition.Start,
|
|
252
|
-
onClick: handleScrollButtonClick(scrollableRef.current, -scrollableStep),
|
|
261
|
+
onClick: handleScrollButtonClick(scrollableRef.current, getDirectionalScrollDelta(-scrollableStep, isRTL)),
|
|
253
262
|
iconClasses: "zen-tabs__start-icon"
|
|
254
263
|
})]
|
|
255
264
|
}) : null, _jsx("div", {
|
|
@@ -282,7 +291,7 @@ export const Tabs = ({
|
|
|
282
291
|
className: classNames(["zen-tabs__scroller", driveClassNameScroller || mobileClassNameScroller || "", "zen-tabs__end"]),
|
|
283
292
|
icon: ScrollEndIcon,
|
|
284
293
|
type: "tertiary",
|
|
285
|
-
onClick: handleScrollButtonClick(scrollableRef.current, scrollableStep),
|
|
294
|
+
onClick: handleScrollButtonClick(scrollableRef.current, getDirectionalScrollDelta(scrollableStep, isRTL)),
|
|
286
295
|
iconPosition: ButtonIconPosition.End
|
|
287
296
|
})]
|
|
288
297
|
}) : null]
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { RenderResult } from "@testing-library/react";
|
|
3
|
+
import { ExperimentalFeatureId } from "../utils/experimentalFeatures/registry";
|
|
4
|
+
export declare const renderWithFeatures: (ui: ReactNode, features?: Partial<Record<ExperimentalFeatureId, boolean>>) => RenderResult;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { ZenithExperimentalProvider } from "../utils/experimentalFeatures/experimentalContext";
|
|
4
|
+
export const renderWithFeatures = (ui, features) => {
|
|
5
|
+
return render(_jsx(ZenithExperimentalProvider, { features: features, children: ui }));
|
|
6
|
+
};
|
|
@@ -18,6 +18,6 @@ export const TextInputRaw = (props) => {
|
|
|
18
18
|
props.isError ? "zen-text-input--error" : ""
|
|
19
19
|
]), value: maxLength ? value.substring(0, maxLength) : value, placeholder: props.placeholder, disabled: disabled, onChange: onChange, onKeyDown: props.onKeyDown, onBlur: onBlur, maxLength: maxLength, autoComplete: props.autocomplete,
|
|
20
20
|
// TODO: add tests
|
|
21
|
-
min: min, max: max, "aria-labelledby": props.ariaLabelledby, "aria-label": props.ariaLabel, autoFocus: props.autoFocus, required: !!required, onFocus: props.onFocus, minLength: minLength, name: name, pattern: pattern, role: props.role, "aria-expanded": props.ariaExpanded === undefined ? undefined : props.ariaExpanded === "true" || props.ariaExpanded === true, "aria-haspopup": props.ariaHaspopup, "aria-controls": props.ariaControls, "aria-autocomplete": props.ariaAutocomplete, "aria-describedby": props.ariaDescribedby }));
|
|
21
|
+
min: min, max: max, step: props.step, "aria-labelledby": props.ariaLabelledby, "aria-label": props.ariaLabel, autoFocus: props.autoFocus, required: !!required, onFocus: props.onFocus, minLength: minLength, name: name, pattern: pattern, role: props.role, "aria-expanded": props.ariaExpanded === undefined ? undefined : props.ariaExpanded === "true" || props.ariaExpanded === true, "aria-haspopup": props.ariaHaspopup, "aria-controls": props.ariaControls, "aria-autocomplete": props.ariaAutocomplete, "aria-describedby": props.ariaDescribedby }));
|
|
22
22
|
};
|
|
23
23
|
TextInputRaw.displayName = "TextInputRaw";
|
|
@@ -31,6 +31,7 @@ export interface ITextInputDefaultRaw extends IZenComponentProps, ITextInputRawB
|
|
|
31
31
|
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
32
32
|
min?: number;
|
|
33
33
|
max?: number;
|
|
34
|
+
step?: number | "any";
|
|
34
35
|
minLength?: number;
|
|
35
36
|
maxLength?: number;
|
|
36
37
|
pattern?: RegExp;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ReactNode, Ref } from "react";
|
|
2
|
+
import "./inlineTrigger.less";
|
|
3
|
+
export interface IInlineTrigger {
|
|
4
|
+
/**
|
|
5
|
+
* The inline text that receives the dotted underline treatment.
|
|
6
|
+
*
|
|
7
|
+
* Accepts any `ReactNode`, but avoid placing interactive elements (buttons,
|
|
8
|
+
* links, inputs) inside — nesting interactive elements is invalid HTML and
|
|
9
|
+
* creates a poor keyboard and screen-reader experience.
|
|
10
|
+
*
|
|
11
|
+
* **For use within body-level text only.** Do not use inside headings, page
|
|
12
|
+
* titles, modal headers, or any non-body typographic context. Apply the
|
|
13
|
+
* desired body typography via `className` (e.g. `zen-body-s-700`).
|
|
14
|
+
*/
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
/** Additional CSS class names applied to the trigger button (e.g. a body typography class). */
|
|
17
|
+
className?: string;
|
|
18
|
+
ref?: Ref<HTMLButtonElement>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `Tooltip.InlineTrigger` is an inline, dotted-underline trigger for the `Tooltip` component.
|
|
22
|
+
* Pass it as the `trigger` prop of a `Tooltip` to annotate a term within body text.
|
|
23
|
+
*
|
|
24
|
+
* The dotted underline uses the surrounding font colour (`currentColor`) at 15% thickness.
|
|
25
|
+
* The trigger inherits its typography from the surrounding text by default; pass a body
|
|
26
|
+
* typography class via `className` (e.g. `zen-body-s-700`, `zen-body-s-400-italic`) to override
|
|
27
|
+
* it explicitly. All interaction
|
|
28
|
+
* (hover, focus, keyboard, positioning) is handled by the parent `Tooltip` — this component only
|
|
29
|
+
* renders the trigger element and forwards the event handlers and `ref` that `Tooltip` injects.
|
|
30
|
+
*
|
|
31
|
+
* Set an accessible name via the parent `Tooltip`'s `triggerAriaLabel` prop when `children`
|
|
32
|
+
* contains no readable text (e.g. a purely decorative image or icon), so the button is not
|
|
33
|
+
* left without an accessible name (WCAG 4.1.2).
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* <Tooltip alignment="top" trigger={<Tooltip.InlineTrigger className="zen-body-s-700">Inline text</Tooltip.InlineTrigger>}>
|
|
37
|
+
* The tooltip text.
|
|
38
|
+
* </Tooltip>
|
|
39
|
+
*/
|
|
40
|
+
export declare const InlineTrigger: {
|
|
41
|
+
({ children, className, ref, ...injectedProps }: IInlineTrigger): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
displayName: string;
|
|
43
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
13
|
+
import { classNames } from "../../commonHelpers/classNames/classNames";
|
|
14
|
+
/**
|
|
15
|
+
* `Tooltip.InlineTrigger` is an inline, dotted-underline trigger for the `Tooltip` component.
|
|
16
|
+
* Pass it as the `trigger` prop of a `Tooltip` to annotate a term within body text.
|
|
17
|
+
*
|
|
18
|
+
* The dotted underline uses the surrounding font colour (`currentColor`) at 15% thickness.
|
|
19
|
+
* The trigger inherits its typography from the surrounding text by default; pass a body
|
|
20
|
+
* typography class via `className` (e.g. `zen-body-s-700`, `zen-body-s-400-italic`) to override
|
|
21
|
+
* it explicitly. All interaction
|
|
22
|
+
* (hover, focus, keyboard, positioning) is handled by the parent `Tooltip` — this component only
|
|
23
|
+
* renders the trigger element and forwards the event handlers and `ref` that `Tooltip` injects.
|
|
24
|
+
*
|
|
25
|
+
* Set an accessible name via the parent `Tooltip`'s `triggerAriaLabel` prop when `children`
|
|
26
|
+
* contains no readable text (e.g. a purely decorative image or icon), so the button is not
|
|
27
|
+
* left without an accessible name (WCAG 4.1.2).
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* <Tooltip alignment="top" trigger={<Tooltip.InlineTrigger className="zen-body-s-700">Inline text</Tooltip.InlineTrigger>}>
|
|
31
|
+
* The tooltip text.
|
|
32
|
+
* </Tooltip>
|
|
33
|
+
*/
|
|
34
|
+
export const InlineTrigger = (_a) => {
|
|
35
|
+
var { children, className = "", ref } = _a, injectedProps = __rest(_a, ["children", "className", "ref"]);
|
|
36
|
+
return (_jsx("button", Object.assign({}, injectedProps, { ref: ref, type: "button", className: classNames(["zen-tooltip-inline-trigger", className]), children: children })));
|
|
37
|
+
};
|
|
38
|
+
InlineTrigger.displayName = "InlineTrigger";
|
package/esm/tooltip/tooltip.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React, { PropsWithChildren, ReactElement } from "react";
|
|
1
|
+
import React, { FC, PropsWithChildren, ReactElement } from "react";
|
|
2
2
|
import { IZenComponentProps } from "../commonHelpers/zenComponent";
|
|
3
3
|
import "./tooltip.less";
|
|
4
4
|
import { TAlignment } from "../absolute/absolute";
|
|
5
|
+
import { IInlineTrigger } from "./components/inlineTrigger";
|
|
5
6
|
export type TooltipSize = "large" | "small" | "auto";
|
|
6
7
|
export type TMobileView = "default" | "mobileSheet";
|
|
7
8
|
interface ITooltip extends IZenComponentProps {
|
|
@@ -33,5 +34,7 @@ interface ITooltip extends IZenComponentProps {
|
|
|
33
34
|
*/
|
|
34
35
|
mobileSheetTitle?: ReactElement | string;
|
|
35
36
|
}
|
|
36
|
-
export declare const Tooltip:
|
|
37
|
+
export declare const Tooltip: FC<ITooltip> & {
|
|
38
|
+
InlineTrigger: FC<IInlineTrigger>;
|
|
39
|
+
};
|
|
37
40
|
export {};
|
package/esm/tooltip/tooltip.js
CHANGED
|
@@ -19,6 +19,7 @@ import { IconInfoCircle } from "../icons/iconInfoCircle";
|
|
|
19
19
|
import { useDrive } from "../utils/theme/useDrive";
|
|
20
20
|
import { Shield } from "../shield/shield";
|
|
21
21
|
import { zen } from "../utils/zen";
|
|
22
|
+
import { InlineTrigger } from "./components/inlineTrigger";
|
|
22
23
|
injectString("cs", "Information", "Informace");
|
|
23
24
|
injectString("da-DK", "Information", "Information");
|
|
24
25
|
injectString("de", "Information", "Information");
|
|
@@ -161,6 +162,7 @@ const EventWrapper = ({
|
|
|
161
162
|
const propsToApply = isCurrentDeviceDesktop ? desktopEvents : mobileEvents;
|
|
162
163
|
return React.cloneElement(children, propsToApply);
|
|
163
164
|
};
|
|
165
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
164
166
|
export const Tooltip = ({
|
|
165
167
|
trigger,
|
|
166
168
|
triggerClassName,
|
|
@@ -193,7 +195,7 @@ export const Tooltip = ({
|
|
|
193
195
|
const {
|
|
194
196
|
dark
|
|
195
197
|
} = useContext(themeContext);
|
|
196
|
-
const tooltipAlignment = useMemo(() => isMobile ? "bottom" : alignment ||
|
|
198
|
+
const tooltipAlignment = useMemo(() => isMobile ? "bottom" : alignment || "end", [isMobile, alignment]);
|
|
197
199
|
const normalizedTooltipAlignment = useMemo(() => normalizeLogicalAlignment(tooltipAlignment, direction), [tooltipAlignment, direction]);
|
|
198
200
|
const TOOLTIP_ARROW_HEIGHT = useMemo(() => isMobile || isDrive ? 0 : 6, [isMobile, isDrive]);
|
|
199
201
|
const tooltipAlignmentRef = useRef(normalizedTooltipAlignment);
|
|
@@ -231,9 +233,17 @@ export const Tooltip = ({
|
|
|
231
233
|
}
|
|
232
234
|
}
|
|
233
235
|
}, [trigger === null || trigger === void 0 ? void 0 : trigger.props, deviceType]);
|
|
234
|
-
|
|
236
|
+
// Apply `triggerAriaLabel` to a `Tooltip.InlineTrigger` trigger (it has no `ariaLabel` prop
|
|
237
|
+
// of its own); the default info-icon trigger below handles `triggerAriaLabel` separately.
|
|
238
|
+
// An `aria-label` set directly on the `Tooltip.InlineTrigger` element takes priority over
|
|
239
|
+
// `triggerAriaLabel`, so we only fall back to `triggerAriaLabel` when the trigger has none.
|
|
240
|
+
const isInlineTrigger = (trigger === null || trigger === void 0 ? void 0 : trigger.type) === InlineTrigger;
|
|
241
|
+
const triggerOwnAriaLabel = trigger === null || trigger === void 0 ? void 0 : trigger.props["aria-label"];
|
|
242
|
+
const triggerCloneProps = useMemo(() => Object.assign(Object.assign({}, isTooltipVisible ? {
|
|
235
243
|
"aria-describedby": tooltipId
|
|
236
|
-
} : {},
|
|
244
|
+
} : {}), isInlineTrigger && triggerAriaLabel && !triggerOwnAriaLabel ? {
|
|
245
|
+
"aria-label": triggerAriaLabel
|
|
246
|
+
} : {}), [isTooltipVisible, tooltipId, isInlineTrigger, triggerAriaLabel, triggerOwnAriaLabel]);
|
|
237
247
|
const tooltipTrigger = useMemo(() => trigger ? trigger : _jsx("button", {
|
|
238
248
|
tabIndex: 0,
|
|
239
249
|
type: "button",
|
|
@@ -446,4 +456,5 @@ export const Tooltip = ({
|
|
|
446
456
|
return _jsxs(_Fragment, {
|
|
447
457
|
children: [portal, triggerClone]
|
|
448
458
|
});
|
|
449
|
-
};
|
|
459
|
+
};
|
|
460
|
+
Tooltip.InlineTrigger = InlineTrigger;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "./trendIndicator.less";
|
|
2
|
+
import { IZenComponentProps } from "../commonHelpers/zenComponent";
|
|
3
|
+
export type TTrendIndicatorDirection = "up" | "down" | "neutral";
|
|
4
|
+
export type TTrendIndicatorTone = "positive" | "negative" | "neutral";
|
|
5
|
+
export interface ITrendIndicator extends Pick<IZenComponentProps, "className"> {
|
|
6
|
+
/** Primary measured value displayed next to the direction icon, e.g. `"12.4km"`. */
|
|
7
|
+
value: string;
|
|
8
|
+
/**
|
|
9
|
+
* Change delta displayed after the value, e.g. `"+5%"` or `"+12km"`. When
|
|
10
|
+
* {@link ITrendIndicator.direction} is omitted, its sign determines the direction
|
|
11
|
+
* (and therefore the arrow and the default tone).
|
|
12
|
+
*/
|
|
13
|
+
delta: string;
|
|
14
|
+
/**
|
|
15
|
+
* Trend direction — controls the arrow icon. When omitted, it is derived from the
|
|
16
|
+
* sign of {@link ITrendIndicator.delta}.
|
|
17
|
+
*/
|
|
18
|
+
direction?: TTrendIndicatorDirection;
|
|
19
|
+
/**
|
|
20
|
+
* Color tone for the icon and delta. Defaults to the direction (`up` → positive,
|
|
21
|
+
* `down` → negative, `neutral` → neutral). Set it explicitly to decouple color from
|
|
22
|
+
* the arrow — e.g. `direction="up"` with `tone="negative"` for metrics where a rise
|
|
23
|
+
* is undesirable.
|
|
24
|
+
*/
|
|
25
|
+
tone?: TTrendIndicatorTone;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A directional trend indicator: a direction icon, a value, and a change delta.
|
|
29
|
+
* Example: ▲ 12.4km +5%.
|
|
30
|
+
*
|
|
31
|
+
* Built for the `Table` and exposed only as `Table.TrendIndicator`, for use
|
|
32
|
+
* inside a column's inline `render` (or the table's mobile card form).
|
|
33
|
+
* Presentational — it conveys a value and its change and has no interactive
|
|
34
|
+
* behavior.
|
|
35
|
+
*
|
|
36
|
+
* The arrow icon reflects `direction`; the icon and delta take a color `tone`
|
|
37
|
+
* (positive / negative / neutral) from design tokens. Tone defaults to the
|
|
38
|
+
* direction but can be set independently for metrics where a rise is bad. The
|
|
39
|
+
* value uses the primary text color.
|
|
40
|
+
*
|
|
41
|
+
* The direction icon is decorative — direction is also conveyed by the sign of
|
|
42
|
+
* the delta text, so no accessible name is required.
|
|
43
|
+
*/
|
|
44
|
+
export declare const TrendIndicator: {
|
|
45
|
+
({ value, delta, direction, tone, className }: ITrendIndicator): import("react/jsx-runtime").JSX.Element;
|
|
46
|
+
displayName: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { classNames } from "../commonHelpers/classNames/classNames";
|
|
3
|
+
import { IconChevronTopSmall } from "../icons/iconChevronTopSmall";
|
|
4
|
+
import { IconChevronDownSmall } from "../icons/iconChevronDownSmall";
|
|
5
|
+
import { IconTrendNeutral } from "../icons/iconTrendNeutral";
|
|
6
|
+
import { getTrendIndicatorDirection } from "./trendIndicatorUtils";
|
|
7
|
+
const DIRECTION_TONE = {
|
|
8
|
+
up: "positive",
|
|
9
|
+
down: "negative",
|
|
10
|
+
neutral: "neutral"
|
|
11
|
+
};
|
|
12
|
+
const DirectionIcon = ({ direction }) => {
|
|
13
|
+
if (direction === "down") {
|
|
14
|
+
return _jsx(IconChevronDownSmall, { className: "zen-trend-indicator__icon", size: "large" });
|
|
15
|
+
}
|
|
16
|
+
if (direction === "neutral") {
|
|
17
|
+
return _jsx(IconTrendNeutral, { className: "zen-trend-indicator__icon", size: "large" });
|
|
18
|
+
}
|
|
19
|
+
return _jsx(IconChevronTopSmall, { className: "zen-trend-indicator__icon", size: "large" });
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A directional trend indicator: a direction icon, a value, and a change delta.
|
|
23
|
+
* Example: ▲ 12.4km +5%.
|
|
24
|
+
*
|
|
25
|
+
* Built for the `Table` and exposed only as `Table.TrendIndicator`, for use
|
|
26
|
+
* inside a column's inline `render` (or the table's mobile card form).
|
|
27
|
+
* Presentational — it conveys a value and its change and has no interactive
|
|
28
|
+
* behavior.
|
|
29
|
+
*
|
|
30
|
+
* The arrow icon reflects `direction`; the icon and delta take a color `tone`
|
|
31
|
+
* (positive / negative / neutral) from design tokens. Tone defaults to the
|
|
32
|
+
* direction but can be set independently for metrics where a rise is bad. The
|
|
33
|
+
* value uses the primary text color.
|
|
34
|
+
*
|
|
35
|
+
* The direction icon is decorative — direction is also conveyed by the sign of
|
|
36
|
+
* the delta text, so no accessible name is required.
|
|
37
|
+
*/
|
|
38
|
+
export const TrendIndicator = ({ value, delta, direction, tone, className }) => {
|
|
39
|
+
const resolvedDirection = direction !== null && direction !== void 0 ? direction : getTrendIndicatorDirection(delta);
|
|
40
|
+
const resolvedTone = tone !== null && tone !== void 0 ? tone : DIRECTION_TONE[resolvedDirection];
|
|
41
|
+
return (_jsxs("span", { className: classNames(["zen-trend-indicator", `zen-trend-indicator--${resolvedTone}`, className !== null && className !== void 0 ? className : ""]), children: [_jsx(DirectionIcon, { direction: resolvedDirection }), _jsx("span", { className: "zen-trend-indicator__value", children: value }), _jsx("span", { className: "zen-trend-indicator__delta", children: delta })] }));
|
|
42
|
+
};
|
|
43
|
+
TrendIndicator.displayName = "TrendIndicator";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TTrendIndicatorDirection } from "./trendIndicator";
|
|
2
|
+
/**
|
|
3
|
+
* Derives a {@link TTrendIndicatorDirection} from the sign of a delta string.
|
|
4
|
+
*
|
|
5
|
+
* Strips any non-numeric characters (currency symbols, `%`, units) before
|
|
6
|
+
* parsing, so inputs like `"+5%"`, `"-5%"`, and `"+0%"` are handled. A zero,
|
|
7
|
+
* empty, or unparsable delta resolves to `"neutral"`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const getTrendIndicatorDirection: (delta: string) => TTrendIndicatorDirection;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives a {@link TTrendIndicatorDirection} from the sign of a delta string.
|
|
3
|
+
*
|
|
4
|
+
* Strips any non-numeric characters (currency symbols, `%`, units) before
|
|
5
|
+
* parsing, so inputs like `"+5%"`, `"-5%"`, and `"+0%"` are handled. A zero,
|
|
6
|
+
* empty, or unparsable delta resolves to `"neutral"`.
|
|
7
|
+
*/
|
|
8
|
+
export const getTrendIndicatorDirection = (delta) => {
|
|
9
|
+
const numeric = parseFloat(delta.replace(/[^0-9.+-]/g, ""));
|
|
10
|
+
if (Number.isNaN(numeric) || numeric === 0) {
|
|
11
|
+
return "neutral";
|
|
12
|
+
}
|
|
13
|
+
return numeric > 0 ? "up" : "down";
|
|
14
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { ExperimentalFeatureId } from "./registry";
|
|
3
|
+
export type FeatureOverrides<TFeatureId extends string = ExperimentalFeatureId> = Partial<Record<TFeatureId, boolean>>;
|
|
4
|
+
type ContextFeatureOverrides = FeatureOverrides<string>;
|
|
5
|
+
interface IExperimentalContext {
|
|
6
|
+
features: ContextFeatureOverrides;
|
|
7
|
+
}
|
|
8
|
+
export declare const experimentalContext: import("react").Context<IExperimentalContext>;
|
|
9
|
+
interface IZenithExperimentalProviderProps {
|
|
10
|
+
features?: Partial<Record<ExperimentalFeatureId, boolean>>;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export declare const ZenithExperimentalProvider: {
|
|
14
|
+
({ features, children }: IZenithExperimentalProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
displayName: string;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { experimentalFeatures, FeatureState } from "./registry";
|
|
4
|
+
const buildDefaultFeatures = () => {
|
|
5
|
+
const defaults = {};
|
|
6
|
+
for (const feature of experimentalFeatures) {
|
|
7
|
+
if (feature.state !== FeatureState.Killed) {
|
|
8
|
+
defaults[feature.id] = feature.state === FeatureState.Enabled;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return defaults;
|
|
12
|
+
};
|
|
13
|
+
const DEFAULT_FEATURES = buildDefaultFeatures();
|
|
14
|
+
const EMPTY_FEATURES = {};
|
|
15
|
+
export const experimentalContext = createContext({
|
|
16
|
+
features: {}
|
|
17
|
+
});
|
|
18
|
+
export const ZenithExperimentalProvider = ({ features = EMPTY_FEATURES, children }) => {
|
|
19
|
+
const parentContext = useContext(experimentalContext);
|
|
20
|
+
const contextValue = useMemo(() => ({
|
|
21
|
+
features: Object.assign(Object.assign(Object.assign({}, DEFAULT_FEATURES), parentContext.features), features)
|
|
22
|
+
}), [features, parentContext.features]);
|
|
23
|
+
return _jsx(experimentalContext.Provider, { value: contextValue, children: children });
|
|
24
|
+
};
|
|
25
|
+
ZenithExperimentalProvider.displayName = "ZenithExperimentalProvider";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare enum FeatureState {
|
|
2
|
+
Preview = 0,// OFF by default — consumers may opt in via provider/prop
|
|
3
|
+
Enabled = 1,// ON by default — consumers may opt out locally
|
|
4
|
+
Killed = 2
|
|
5
|
+
}
|
|
6
|
+
export interface IExperimentalFeatureDefinition {
|
|
7
|
+
id: string;
|
|
8
|
+
state: FeatureState;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
export declare const experimentalFeatures: readonly [];
|
|
12
|
+
export type ExperimentalFeatureId = (typeof experimentalFeatures)[number]["id"];
|
|
13
|
+
export declare const EXPERIMENTAL_FLAGS: { [K in ExperimentalFeatureId]: K; };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export var FeatureState;
|
|
2
|
+
(function (FeatureState) {
|
|
3
|
+
FeatureState[FeatureState["Preview"] = 0] = "Preview";
|
|
4
|
+
FeatureState[FeatureState["Enabled"] = 1] = "Enabled";
|
|
5
|
+
FeatureState[FeatureState["Killed"] = 2] = "Killed"; // OFF, locked — all overrides ignored; awaiting code deletion
|
|
6
|
+
})(FeatureState || (FeatureState = {}));
|
|
7
|
+
export const experimentalFeatures = [];
|
|
8
|
+
export const EXPERIMENTAL_FLAGS = Object.fromEntries(experimentalFeatures.map(({ id }) => [id, id]));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { experimentalContext } from "./experimentalContext";
|
|
3
|
+
import { experimentalFeatures, FeatureState } from "./registry";
|
|
4
|
+
const experimentalFeatureById = new Map(experimentalFeatures.map(feature => [feature.id, feature]));
|
|
5
|
+
const getFeatureOverride = (overrides, featureId) => overrides === null || overrides === void 0 ? void 0 : overrides[featureId];
|
|
6
|
+
export const useExperimentalFeature = (key, localOverrides) => {
|
|
7
|
+
const context = useContext(experimentalContext);
|
|
8
|
+
const feature = experimentalFeatureById.get(key);
|
|
9
|
+
if ((feature === null || feature === void 0 ? void 0 : feature.state) === FeatureState.Killed) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const localOverride = getFeatureOverride(localOverrides, key);
|
|
13
|
+
if (localOverride !== undefined) {
|
|
14
|
+
return localOverride;
|
|
15
|
+
}
|
|
16
|
+
const contextOverride = context.features[key];
|
|
17
|
+
if (contextOverride !== undefined) {
|
|
18
|
+
return contextOverride;
|
|
19
|
+
}
|
|
20
|
+
return (feature === null || feature === void 0 ? void 0 : feature.state) === FeatureState.Enabled;
|
|
21
|
+
};
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"Go to My Reports": "الذهاب إلى تقاريري",
|
|
91
91
|
"There was an issue preparing your report": "حدثت مشكلة في إعداد تقريرك",
|
|
92
92
|
"Your report is being prepared": "يتم إعداد تقريرك",
|
|
93
|
-
"
|
|
93
|
+
"You'll be notified when your report is ready. Track the progress on the My Reports page.": "سنخطرك عندما يصبح تقريرك جاهزًا. يمكنك تتبع التقدم بالذهاب إلى صفحة تقاريري.",
|
|
94
94
|
"Your report is ready to download": "تقريرك جاهز للتنزيل",
|
|
95
95
|
"Download now": "تنزيل الآن",
|
|
96
96
|
"Download": "تنزيل",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"Go to My Reports": "Към Моите отчети",
|
|
93
93
|
"There was an issue preparing your report": "Възникна проблем при подготовката на вашия отчет",
|
|
94
94
|
"Your report is being prepared": "Вашият отчет се подготвя",
|
|
95
|
-
"
|
|
95
|
+
"You'll be notified when your report is ready. Track the progress on the My Reports page.": "Ще ви уведомим, когато отчетът е готов. Можете да проследите напредъка от страницата Моите отчети.",
|
|
96
96
|
"Your report is ready to download": "Вашият отчет е готов за изтегляне",
|
|
97
97
|
"Download now": "Изтеглете сега",
|
|
98
98
|
"Download": "Изтегляне",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"All": "Vše",
|
|
82
82
|
"There was an issue preparing your report": "Při připravování výkazu došlo k problému",
|
|
83
83
|
"Your report is being prepared": "Váš výkaz se připravuje",
|
|
84
|
-
"
|
|
84
|
+
"You'll be notified when your report is ready. Track the progress on the My Reports page.": "Až bude váš výkaz připraven, upozorníme vás. Průběh můžete sledovat na stránce Moje výkazy.",
|
|
85
85
|
"Your report is ready to download": "Váš výkaz je připraven ke stažení",
|
|
86
86
|
"Download now": "Stáhnout",
|
|
87
87
|
"Download": "Stáhnout",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"Go to My Reports": "Gå til Mine rapporter",
|
|
89
89
|
"There was an issue preparing your report": "Der opstod et problem med at lave din rapport.",
|
|
90
90
|
"Your report is being prepared": "Din rapport er ved at blive lavet.",
|
|
91
|
-
"
|
|
91
|
+
"You'll be notified when your report is ready. Track the progress on the My Reports page.": "Vi giver dig besked, når din rapport er klar. Du kan tjekke status på siden 'Mine rapporter'.",
|
|
92
92
|
"Your report is ready to download": "Din rapport er klar til download",
|
|
93
93
|
"Download now": "Download nu",
|
|
94
94
|
"Download": "Download",
|