-
Notifications
You must be signed in to change notification settings - Fork 2
/
PreviewFeatures.tsx
187 lines (176 loc) · 6.88 KB
/
PreviewFeatures.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Utilities
*/
import * as React from "react";
import { create } from "zustand";
import { Logger } from "@itwin/core-bentley";
import { UiFramework } from "../UiFramework";
import type { useTransientState } from "../widget-panels/useTransientState";
import type { WidgetDef } from "../widgets/WidgetDef";
/** List of known preview features. */
interface KnownPreviewFeatures {
/** If true, the panels and tool settings will always be rendered over the content.
* The content will never change size.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/672
*/
contentAlwaysMaxSize: boolean;
/** If true, the floating widget will have a "maximize" button. Use `enableMaximizedPanelWidget` to enable the feature for panel widgets.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/673
*/
enableMaximizedFloatingWidget: boolean;
/** If true, the panel widget will have a "maximize" button. Use `enableMaximizedFloatingWidget` to enable the feature for floating widgets.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/673
*/
enableMaximizedPanelWidget: boolean;
/** If true, the active tab of a dragged widget will become active when dropped in a container.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/679
*/
activateDroppedTab: boolean;
/** If true, the horizontal panels will have an additional "Align" button.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/706
*/
horizontalPanelAlignment: boolean;
/** If enabled, a dropdown menu will be rendered for widgets that exceed the specified threshold of title bar buttons.
*
* Discuss or upvote this feature: https://github.com/iTwin/appui/discussions/723
*/
widgetActionDropdown: { threshold: number };
/** If true, the [[Toolbar]] component will be replaced by a new iTwinUI based toolbar. */
newToolbars: boolean;
/** If `true`, popout widgets will not be rendered in a separate element tree, instead widget content will be re-parented to a popout content container.
* Alternatively, an array of widget ids can be provided to only re-parent specific widgets.
* @note Use {@link useTransientState} to save and restore DOM transient state when re-parenting widgets.
* @note There is a known limitation where iTwinUI v2 popover elements will be rendered in the main window. Prefer using iTwinUI v3 when using this feature.
*/
reparentPopoutWidgets: boolean | WidgetDef["id"][];
}
/** Object used trim to only known features at runtime.
* @internal
*/
const knownFeaturesObject: Record<keyof KnownPreviewFeatures, undefined> = {
activateDroppedTab: undefined,
contentAlwaysMaxSize: undefined,
enableMaximizedFloatingWidget: undefined,
enableMaximizedPanelWidget: undefined,
horizontalPanelAlignment: undefined,
widgetActionDropdown: undefined,
newToolbars: undefined,
reparentPopoutWidgets: undefined,
};
/** List of preview features that can be enabled/disabled.
* This list is expected to change over time, the interface is made
* so that new features can be added or removed without breaking existing code.
* A console warning will simply appear if unknown features are passed.
* @beta
*/
export interface PreviewFeatures extends Partial<KnownPreviewFeatures> {
[featureName: string]: any;
}
/** Trim an object to only contain known feature keys.
* @param previewFeatures object potentially containing unknown features
* @returns object containing only known features
* @internal
*/
function trimToKnownFeaturesOnly(previewFeatures: PreviewFeatures) {
const knownFeatureKeys = Object.keys(knownFeaturesObject);
const [knownFeatures, unknownFeatures] = Object.entries(
previewFeatures
).reduce(
([known, unknown], [key, value]) => {
if (knownFeatureKeys.includes(key)) {
known[key] = value;
} else {
unknown.push(key);
}
return [known, unknown];
},
[{}, []] as [PreviewFeatures, string[]]
);
if (Object.keys(unknownFeatures).length > 0) {
Logger.logWarning(
UiFramework.loggerCategory(trimToKnownFeaturesOnly),
`Features used in "setPreviewFeatures" are unknown or no longer in preview`,
{
unknownFeatures,
}
);
}
return knownFeatures;
}
interface PreviewFeaturesState {
previewFeatures: PreviewFeatures;
setPreviewFeatures: (previewFeatures: PreviewFeatures) => void;
}
/** Preview features store used by `UiFramework.previewFeatures` and `UiFramework.setPreviewFeatures()` APIs to manage the preview features.
* Use `usePreviewFeatures` hook to access the preview features set from a react component.
* Use `usePreviewFeaturesStore.getState().previewFeatures` to access the preview features set from a non-react component.
* Use `usePreviewFeaturesStore.subscribe()` to subscribe to changes to the preview features set from a non-react component.
* @internal
*/
const usePreviewFeaturesStore = create<PreviewFeaturesState>((set) => {
return {
previewFeatures: {},
setPreviewFeatures: (newPreviewFeatures: PreviewFeatures) => {
const previewFeatures = trimToKnownFeaturesOnly(newPreviewFeatures);
set({ previewFeatures });
},
};
});
/** Hook to access the preview features set.
* @internal
*/
export function usePreviewFeatures() {
return usePreviewFeaturesStore((state) => state.previewFeatures);
}
/** Props for PreviewFeaturesProvider.
* @beta
*/
export interface PreviewFeaturesProviderProps {
children?: React.ReactNode;
features?: PreviewFeatures;
}
/** Set which preview features are enabled. These features are not yet ready for production use nor have
* a proper API defined yet.
* The available set of features are defined in the [[PreviewFeatures]] interface.
*
* This component should wrap the Provider component.
*
* ```tsx
* <PreviewFeaturesProvider features={{ enableMaximizedFloatingWidget: true }}>
* <Provider store={UiFramework.store}>
* [...]
* <ConfigurableUIContent />
* [/...]
* </Provider>
* </PreviewFeaturesProvider>
* ```
* @beta
*/
export function PreviewFeaturesProvider({
children,
features,
}: PreviewFeaturesProviderProps) {
const setPreviewFeatures = usePreviewFeaturesStore(
(state) => state.setPreviewFeatures
);
React.useEffect(() => {
setPreviewFeatures(features ?? {});
}, [features, setPreviewFeatures]);
// Clear preview features when unmounting.
React.useEffect(
() => () => {
setPreviewFeatures({});
},
[setPreviewFeatures]
);
return <>{children}</>;
}