@xh/hoist 83.1.0 → 84.0.1
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 +76 -0
- package/admin/tabs/cluster/instances/logs/levels/LogLevelDialogModel.ts +106 -10
- package/admin/tabs/cluster/metrics/MetricsModel.ts +3 -3
- package/appcontainer/AppContainerModel.ts +1 -1
- package/appcontainer/README.md +20 -0
- package/assets.d.ts +34 -0
- package/build/types/cmp/ag-grid/AgGrid.d.ts +8 -19
- package/build/types/cmp/ag-grid/AgGridModel.d.ts +18 -5
- package/build/types/cmp/card/Card.d.ts +9 -4
- package/build/types/cmp/card/CardModel.d.ts +15 -2
- package/build/types/cmp/chart/Chart.d.ts +2 -2
- package/build/types/cmp/chart/ChartModel.d.ts +11 -1
- package/build/types/cmp/dataview/DataView.d.ts +4 -2
- package/build/types/cmp/dataview/DataViewModel.d.ts +16 -4
- package/build/types/cmp/filter/FilterChooserModel.d.ts +7 -1
- package/build/types/cmp/form/Form.d.ts +2 -1
- package/build/types/cmp/form/FormModel.d.ts +12 -0
- package/build/types/cmp/form/field/BaseFieldModel.d.ts +7 -0
- package/build/types/cmp/form/formfieldset/FormFieldSetModel.d.ts +7 -1
- package/build/types/cmp/grid/GridModel.d.ts +16 -1
- package/build/types/cmp/grid/GridSorter.d.ts +14 -0
- package/build/types/cmp/grid/Types.d.ts +18 -0
- package/build/types/cmp/grid/columns/Column.d.ts +40 -2
- package/build/types/cmp/grid/columns/ColumnGroup.d.ts +10 -0
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +9 -2
- package/build/types/cmp/layout/Box.d.ts +19 -7
- package/build/types/cmp/layout/Frame.d.ts +17 -5
- package/build/types/cmp/loadingindicator/LoadingIndicator.d.ts +6 -4
- package/build/types/cmp/pinpad/PinPadModel.d.ts +6 -1
- package/build/types/cmp/spinner/Spinner.d.ts +31 -10
- package/build/types/cmp/tab/TabContainerModel.d.ts +11 -0
- package/build/types/cmp/tab/TabModel.d.ts +7 -0
- package/build/types/cmp/tab/Types.d.ts +4 -0
- package/build/types/cmp/treemap/TreeMapModel.d.ts +3 -3
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +9 -0
- package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +22 -3
- package/build/types/cmp/zoneGrid/impl/ZoneMapperModel.d.ts +6 -0
- package/build/types/core/HoistComponent.d.ts +29 -8
- package/build/types/core/HoistProps.d.ts +9 -3
- package/build/types/core/load/LoadSpec.d.ts +1 -1
- package/build/types/core/persist/provider/ViewManagerProvider.d.ts +7 -0
- package/build/types/data/Store.d.ts +35 -1
- package/build/types/data/StoreSelectionModel.d.ts +18 -2
- package/build/types/data/cube/Cube.d.ts +26 -6
- package/build/types/data/cube/Query.d.ts +10 -0
- package/build/types/data/cube/View.d.ts +21 -2
- package/build/types/data/cube/aggregate/Aggregator.d.ts +13 -0
- package/build/types/data/cube/aggregate/AverageAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/AverageStrictAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/ChildCountAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/LeafCountAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/MaxAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/MinAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/NullAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/SingleAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/SumAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/SumStrictAggregator.d.ts +1 -0
- package/build/types/data/cube/aggregate/UniqueAggregator.d.ts +1 -0
- package/build/types/data/filter/BaseFilterFieldSpec.d.ts +9 -0
- package/build/types/data/filter/Types.d.ts +12 -0
- package/build/types/desktop/cmp/button/AppMenuButton.d.ts +5 -0
- package/build/types/desktop/cmp/button/Button.d.ts +5 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +12 -3
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +9 -0
- package/build/types/desktop/cmp/dock/DockViewModel.d.ts +7 -0
- package/build/types/desktop/cmp/filechooser/FileChooserModel.d.ts +8 -0
- package/build/types/desktop/cmp/grid/editors/BooleanEditor.d.ts +1 -0
- package/build/types/desktop/cmp/grid/editors/DateEditor.d.ts +1 -0
- package/build/types/desktop/cmp/grid/editors/NumberEditor.d.ts +1 -0
- package/build/types/desktop/cmp/grid/editors/SelectEditor.d.ts +1 -0
- package/build/types/desktop/cmp/grid/editors/TextAreaEditor.d.ts +1 -0
- package/build/types/desktop/cmp/grid/editors/TextEditor.d.ts +1 -0
- package/build/types/desktop/cmp/input/Picker.d.ts +1 -1
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +16 -2
- package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +7 -0
- package/build/types/desktop/cmp/modalsupport/ModalSupportModel.d.ts +28 -2
- package/build/types/desktop/cmp/panel/Panel.d.ts +5 -2
- package/build/types/desktop/cmp/panel/PanelModel.d.ts +12 -2
- package/build/types/desktop/cmp/rest/RestGrid.d.ts +10 -0
- package/build/types/desktop/cmp/rest/RestGridModel.d.ts +9 -1
- package/build/types/desktop/cmp/toolbar/Toolbar.d.ts +4 -1
- package/build/types/format/FormatDate.d.ts +4 -4
- package/build/types/icon/Icon.d.ts +3 -0
- package/build/types/kit/blueprint/Wrappers.d.ts +12 -1
- package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +8 -0
- package/build/types/mobile/cmp/navigator/PageModel.d.ts +7 -0
- package/build/types/mobile/cmp/panel/DialogPanel.d.ts +0 -2
- package/build/types/security/BaseOAuthClient.d.ts +9 -0
- package/build/types/security/authzero/AuthZeroClient.d.ts +6 -0
- package/build/types/security/msal/MsalClient.d.ts +6 -0
- package/build/types/svc/FetchService.d.ts +10 -7
- package/build/types/svc/TraceService.d.ts +17 -2
- package/build/types/utils/async/Timer.d.ts +6 -0
- package/build/types/utils/js/LangUtils.d.ts +1 -1
- package/build/types/utils/js/TestUtils.d.ts +1 -1
- package/build/types/utils/react/index.d.ts +0 -1
- package/build/types/utils/telemetry/Span.d.ts +12 -2
- package/cmp/ag-grid/AgGrid.ts +8 -19
- package/cmp/ag-grid/AgGridModel.ts +18 -5
- package/cmp/card/Card.ts +9 -4
- package/cmp/card/CardModel.ts +15 -2
- package/cmp/chart/Chart.ts +2 -2
- package/cmp/chart/ChartModel.ts +11 -1
- package/cmp/dataview/DataView.ts +4 -2
- package/cmp/dataview/DataViewModel.ts +16 -4
- package/cmp/filter/FilterChooserModel.ts +7 -1
- package/cmp/form/Form.ts +2 -1
- package/cmp/form/FormModel.ts +12 -0
- package/cmp/form/README.md +13 -0
- package/cmp/form/field/BaseFieldModel.ts +7 -0
- package/cmp/form/formfieldset/FormFieldSetModel.ts +7 -1
- package/cmp/grid/Grid.scss +14 -8
- package/cmp/grid/GridModel.ts +16 -1
- package/cmp/grid/GridSorter.ts +14 -0
- package/cmp/grid/README.md +12 -0
- package/cmp/grid/Types.ts +18 -0
- package/cmp/grid/columns/Column.ts +40 -2
- package/cmp/grid/columns/ColumnGroup.ts +10 -0
- package/cmp/grouping/GroupingChooserModel.ts +9 -2
- package/cmp/layout/Box.ts +19 -7
- package/cmp/layout/Frame.ts +17 -5
- package/cmp/layout/README.md +16 -21
- package/cmp/loadingindicator/LoadingIndicator.scss +1 -1
- package/cmp/loadingindicator/LoadingIndicator.ts +11 -9
- package/cmp/pinpad/PinPadModel.ts +6 -1
- package/cmp/spinner/Spinner.scss +13 -0
- package/cmp/spinner/Spinner.ts +58 -20
- package/cmp/tab/TabContainerModel.ts +11 -0
- package/cmp/tab/TabModel.ts +7 -0
- package/cmp/tab/Types.ts +4 -0
- package/cmp/treemap/TreeMapModel.ts +3 -3
- package/cmp/viewmanager/ViewManagerModel.ts +9 -0
- package/cmp/zoneGrid/ZoneGridModel.ts +22 -3
- package/cmp/zoneGrid/impl/ZoneMapperModel.ts +6 -0
- package/core/ExceptionHandler.ts +1 -1
- package/core/HoistComponent.ts +36 -11
- package/core/HoistProps.ts +9 -3
- package/core/README.md +68 -6
- package/core/impl/InstanceManager.ts +1 -0
- package/core/load/LoadSpec.ts +1 -1
- package/core/persist/provider/ViewManagerProvider.ts +7 -0
- package/data/README.md +48 -124
- package/data/Store.ts +35 -1
- package/data/StoreSelectionModel.ts +18 -2
- package/data/cube/Cube.ts +26 -6
- package/data/cube/Query.ts +10 -0
- package/data/cube/README.md +236 -0
- package/data/cube/View.ts +21 -2
- package/data/cube/aggregate/Aggregator.ts +13 -0
- package/data/cube/aggregate/AverageAggregator.ts +1 -0
- package/data/cube/aggregate/AverageStrictAggregator.ts +1 -0
- package/data/cube/aggregate/ChildCountAggregator.ts +1 -0
- package/data/cube/aggregate/LeafCountAggregator.ts +1 -0
- package/data/cube/aggregate/MaxAggregator.ts +1 -0
- package/data/cube/aggregate/MinAggregator.ts +1 -0
- package/data/cube/aggregate/NullAggregator.ts +1 -0
- package/data/cube/aggregate/SingleAggregator.ts +1 -0
- package/data/cube/aggregate/SumAggregator.ts +1 -0
- package/data/cube/aggregate/SumStrictAggregator.ts +1 -0
- package/data/cube/aggregate/UniqueAggregator.ts +1 -0
- package/data/filter/BaseFilterFieldSpec.ts +9 -0
- package/data/filter/Types.ts +12 -0
- package/desktop/README.md +131 -9
- package/desktop/appcontainer/AboutDialog.ts +2 -0
- package/desktop/appcontainer/Banner.ts +5 -2
- package/desktop/appcontainer/ChangelogDialog.ts +1 -0
- package/desktop/appcontainer/ExceptionDialog.ts +4 -0
- package/desktop/appcontainer/ExceptionDialogDetails.ts +4 -1
- package/desktop/appcontainer/FeedbackDialog.ts +4 -1
- package/desktop/appcontainer/ImpersonationBar.ts +4 -0
- package/desktop/appcontainer/LockoutPanel.ts +4 -1
- package/desktop/appcontainer/LoginPanel.ts +7 -3
- package/desktop/appcontainer/Message.ts +9 -3
- package/desktop/appcontainer/OptionsDialog.ts +3 -1
- package/desktop/appcontainer/VersionBar.ts +1 -0
- package/desktop/appcontainer/suspend/IdlePanel.ts +4 -4
- package/desktop/appcontainer/suspend/SuspendPanel.ts +3 -0
- package/desktop/cmp/button/AppMenuButton.ts +5 -0
- package/desktop/cmp/button/Button.ts +14 -4
- package/desktop/cmp/dash/README.md +14 -0
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +12 -3
- package/desktop/cmp/dash/container/DashContainerModel.ts +9 -0
- package/desktop/cmp/dock/DockViewModel.ts +7 -0
- package/desktop/cmp/filechooser/FileChooserModel.ts +9 -2
- package/desktop/cmp/grid/editors/BooleanEditor.ts +1 -0
- package/desktop/cmp/grid/editors/DateEditor.ts +1 -0
- package/desktop/cmp/grid/editors/NumberEditor.ts +1 -0
- package/desktop/cmp/grid/editors/SelectEditor.ts +1 -0
- package/desktop/cmp/grid/editors/TextAreaEditor.ts +1 -0
- package/desktop/cmp/grid/editors/TextEditor.ts +1 -0
- package/desktop/cmp/input/Picker.ts +2 -2
- package/desktop/cmp/input/SegmentedControl.ts +20 -2
- package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +7 -0
- package/desktop/cmp/modalsupport/ModalSupportModel.ts +31 -2
- package/desktop/cmp/panel/Panel.ts +29 -21
- package/desktop/cmp/panel/PanelModel.ts +12 -2
- package/desktop/cmp/panel/README.md +20 -0
- package/desktop/cmp/rest/RestGrid.ts +10 -0
- package/desktop/cmp/rest/RestGridModel.ts +9 -1
- package/desktop/cmp/toolbar/Toolbar.ts +9 -2
- package/desktop/cmp/viewmanager/ViewManager.ts +1 -1
- package/docs/README.md +9 -4
- package/docs/coding-conventions.md +29 -21
- package/docs/doc-registry.json +31 -15
- package/docs/planning/docs-roadmap-log.md +11 -0
- package/docs/planning/docs-roadmap.md +1 -0
- package/docs/upgrade-notes/v84-upgrade-notes.md +136 -0
- package/docs/version-compatibility.md +2 -0
- package/format/FormatDate.ts +4 -4
- package/icon/Icon.ts +9 -0
- package/icon/README.md +62 -22
- package/icon/index.ts +24 -0
- package/kit/README.md +8 -2
- package/kit/blueprint/Wrappers.ts +12 -1
- package/mcp/README.md +47 -26
- package/mcp/cli/ts.ts +39 -4
- package/mcp/data/ts-registry.ts +57 -17
- package/mcp/tools/typescript.ts +32 -4
- package/mobile/appcontainer/AboutDialog.ts +3 -0
- package/mobile/appcontainer/Banner.ts +2 -0
- package/mobile/appcontainer/ExceptionDialog.ts +4 -0
- package/mobile/appcontainer/ExceptionDialogDetails.ts +1 -0
- package/mobile/appcontainer/FeedbackDialog.ts +4 -1
- package/mobile/appcontainer/ImpersonationBar.ts +2 -0
- package/mobile/appcontainer/LockoutPanel.ts +2 -0
- package/mobile/appcontainer/LoginPanel.ts +7 -3
- package/mobile/appcontainer/Message.ts +9 -3
- package/mobile/appcontainer/OptionsDialog.ts +5 -1
- package/mobile/appcontainer/VersionBar.ts +1 -0
- package/mobile/appcontainer/suspend/IdlePanel.ts +5 -6
- package/mobile/appcontainer/suspend/SuspendPanel.ts +3 -0
- package/mobile/cmp/navigator/NavigatorModel.ts +8 -0
- package/mobile/cmp/navigator/PageModel.ts +7 -0
- package/mobile/cmp/panel/DialogPanel.ts +0 -2
- package/package.json +11 -11
- package/security/BaseOAuthClient.ts +9 -0
- package/security/authzero/AuthZeroClient.ts +6 -0
- package/security/msal/MsalClient.ts +6 -0
- package/styles/vars.scss +14 -0
- package/svc/FetchService.ts +25 -15
- package/svc/README.md +39 -9
- package/svc/TraceService.ts +69 -11
- package/utils/README.md +0 -1
- package/utils/async/Timer.ts +6 -0
- package/utils/js/LangUtils.ts +1 -1
- package/utils/js/TestUtils.ts +1 -1
- package/utils/react/index.ts +0 -1
- package/utils/telemetry/Span.ts +21 -4
- package/build/types/utils/react/ClassName.d.ts +0 -14
- package/utils/react/ClassName.ts +0 -24
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {Aggregator} from './Aggregator';
|
|
9
9
|
|
|
10
|
+
/** Returns the value if there is exactly one row, otherwise null. */
|
|
10
11
|
export class SingleAggregator extends Aggregator {
|
|
11
12
|
override aggregate(rows, fieldName) {
|
|
12
13
|
return rows.length === 1 ? rows[0].data[fieldName] : null;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import {Aggregator} from './Aggregator';
|
|
8
8
|
import {isEmpty, isEqual} from 'lodash';
|
|
9
9
|
|
|
10
|
+
/** Returns the value if all rows share the same value, otherwise null. */
|
|
10
11
|
export class UniqueAggregator extends Aggregator {
|
|
11
12
|
override aggregate(rows, fieldName) {
|
|
12
13
|
if (isEmpty(rows)) return null;
|
|
@@ -9,6 +9,15 @@ import {Field, FieldFilter, FieldType, FilterValueSource, genDisplayName} from '
|
|
|
9
9
|
import {compact, isArray, isEmpty} from 'lodash';
|
|
10
10
|
import {FieldFilterOperator} from './Types';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Base configuration for field-level filtering options - defines available operators, value
|
|
14
|
+
* enumeration, and display metadata. Not used directly by applications; extended by
|
|
15
|
+
* {@link GridFilterFieldSpecConfig} (for column-header filters via {@link GridFilterModelConfig})
|
|
16
|
+
* and {@link FilterChooserFieldSpecConfig} (for {@link FilterChooserModel}).
|
|
17
|
+
*
|
|
18
|
+
* @see GridFilterFieldSpec
|
|
19
|
+
* @see FilterChooserFieldSpec
|
|
20
|
+
*/
|
|
12
21
|
export interface BaseFilterFieldSpecConfig {
|
|
13
22
|
/** Identifying field name to filter on. */
|
|
14
23
|
field: string;
|
package/data/filter/Types.ts
CHANGED
|
@@ -13,6 +13,10 @@ export type FilterLike = Filter | FilterSpec | FilterTestFn | FilterLike[];
|
|
|
13
13
|
|
|
14
14
|
export type FilterSpec = FieldFilterSpec | FunctionFilterSpec | CompoundFilterSpec;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Plain-object form of a {@link FieldFilter} - a single field/operator/value condition.
|
|
18
|
+
* Can be passed anywhere a {@link FilterLike} is accepted.
|
|
19
|
+
*/
|
|
16
20
|
export interface FieldFilterSpec {
|
|
17
21
|
/** Name of Field to filter or Field instance. */
|
|
18
22
|
field: string | Field;
|
|
@@ -42,6 +46,10 @@ export type FieldFilterOperator =
|
|
|
42
46
|
| 'includes'
|
|
43
47
|
| 'excludes';
|
|
44
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Plain-object form of a {@link CompoundFilter} - a group of filters joined by AND/OR.
|
|
51
|
+
* Can be passed anywhere a {@link FilterLike} is accepted.
|
|
52
|
+
*/
|
|
45
53
|
export interface CompoundFilterSpec {
|
|
46
54
|
/** Collection of Filters or configs to create. */
|
|
47
55
|
filters: FilterLike[];
|
|
@@ -52,6 +60,10 @@ export interface CompoundFilterSpec {
|
|
|
52
60
|
|
|
53
61
|
export type CompoundFilterOperator = 'AND' | 'OR' | 'and' | 'or';
|
|
54
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Plain-object form of a {@link FunctionFilter} - a custom function-based filter.
|
|
65
|
+
* Can be passed anywhere a {@link FilterLike} is accepted.
|
|
66
|
+
*/
|
|
55
67
|
export interface FunctionFilterSpec {
|
|
56
68
|
/** Key used to identify this FunctionFilter.*/
|
|
57
69
|
key: string;
|
package/desktop/README.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Desktop Package
|
|
2
2
|
|
|
3
|
+
| Section | Description |
|
|
4
|
+
|---------|-------------|
|
|
5
|
+
| [Overview](#overview) | Desktop platform, Blueprint foundation |
|
|
6
|
+
| [Architecture](#architecture) | Directory structure and key sub-packages |
|
|
7
|
+
| [Relationship to /cmp/](#relationship-to-cmp) | Platform-specific vs. cross-platform components |
|
|
8
|
+
| [AppContainer](#appcontainer) | Desktop app shell and lifecycle |
|
|
9
|
+
| [Component Sub-Packages](#component-sub-packages) | Panel, Toolbar, Button, Grid, Dashboard, Tabs, Inputs, and more |
|
|
10
|
+
| [Dialogs](#dialogs) | Custom modal dialogs via Blueprint Kit export |
|
|
11
|
+
| [Desktop Hooks](#desktop-hooks) | useContextMenu |
|
|
12
|
+
| [Common Patterns](#common-patterns) | Blueprint wrappers, Popover, ContextMenu |
|
|
13
|
+
| [Related Packages](#related-packages) | Links to cross-platform and utility packages |
|
|
14
|
+
|
|
3
15
|
## Overview
|
|
4
16
|
|
|
5
17
|
The `/desktop/` package provides Hoist's desktop-specific UI components, incorporating the
|
|
@@ -169,20 +181,34 @@ covering toolbars, masks, collapse/resize, persistence, and modal support.
|
|
|
169
181
|
|
|
170
182
|
### Toolbar (`/cmp/toolbar/`)
|
|
171
183
|
|
|
172
|
-
|
|
184
|
+
Horizontal (or vertical) container for action buttons and controls. Toolbars are typically
|
|
185
|
+
placed in a Panel's `tbar` (top) or `bbar` (bottom) slots. Use `filler()` to push items to
|
|
186
|
+
the right side, and `'-'` (shortcut for `toolbarSep()`) for visual dividers.
|
|
173
187
|
|
|
174
188
|
```typescript
|
|
175
|
-
import {toolbar
|
|
189
|
+
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
190
|
+
import {filler} from '@xh/hoist/cmp/layout';
|
|
176
191
|
|
|
177
|
-
toolbar(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
)
|
|
192
|
+
toolbar({
|
|
193
|
+
items: [
|
|
194
|
+
button({text: 'Add', icon: Icon.add()}),
|
|
195
|
+
button({text: 'Edit', icon: Icon.edit()}),
|
|
196
|
+
'-', // separator token — shorthand for toolbarSep()
|
|
197
|
+
filler(),
|
|
198
|
+
searchField()
|
|
199
|
+
]
|
|
200
|
+
})
|
|
184
201
|
```
|
|
185
202
|
|
|
203
|
+
Key props:
|
|
204
|
+
- `compact` - reduced height and font size (useful in dense UIs)
|
|
205
|
+
- `vertical` - stack items vertically instead of horizontally
|
|
206
|
+
- `enableOverflowMenu` - collapse items that don't fit into a dropdown menu
|
|
207
|
+
- `collapseFrom` - `'start'` or `'end'` (default) for overflow direction
|
|
208
|
+
- `minVisibleItems` - minimum items to keep visible before overflowing
|
|
209
|
+
|
|
210
|
+
`Toolbar.defaults.compact` can be set at bootstrap for app-wide compact toolbars.
|
|
211
|
+
|
|
186
212
|
### Button (`/cmp/button/`)
|
|
187
213
|
|
|
188
214
|
Desktop buttons with Blueprint styling:
|
|
@@ -340,6 +366,102 @@ leftRightChooser({model})
|
|
|
340
366
|
| `/viewmanager/` | View save/load UI |
|
|
341
367
|
| `/zoneGrid/` | Zone mapper for ZoneGrid columns |
|
|
342
368
|
|
|
369
|
+
## Dialogs
|
|
370
|
+
|
|
371
|
+
For simple alerts and confirmations, use the built-in `XH.message()` and `XH.confirm()` methods
|
|
372
|
+
(see the [appcontainer README](../appcontainer/README.md#messages)).
|
|
373
|
+
|
|
374
|
+
For custom dialogs with rich content (forms, grids, etc.), use the Blueprint `dialog` element
|
|
375
|
+
factory from `@xh/hoist/kit/blueprint`. This is the standard Hoist pattern for modal dialogs -
|
|
376
|
+
Blueprint's Dialog is re-exported through Kit with transitions disabled for snappy rendering.
|
|
377
|
+
|
|
378
|
+
The typical pattern uses a dedicated model to manage the dialog's open/closed state and content,
|
|
379
|
+
with the parent component always rendering the dialog component alongside its other children.
|
|
380
|
+
The dialog renders as a Blueprint portal overlay when open and returns null when closed, so it
|
|
381
|
+
can be included unconditionally in the parent's item list.
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// TaskDialogModel.ts - manages dialog open/closed state and form data
|
|
385
|
+
export class TaskDialogModel extends HoistModel {
|
|
386
|
+
@observable isOpen = false;
|
|
387
|
+
|
|
388
|
+
@managed formModel = new FormModel({
|
|
389
|
+
fields: [{name: 'description', rules: [required]}]
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
@action
|
|
393
|
+
open(initialValues?) {
|
|
394
|
+
this.isOpen = true;
|
|
395
|
+
this.formModel.init(initialValues);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
@action
|
|
399
|
+
close() { this.isOpen = false; }
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// TaskDialog.ts - the dialog component, rendered by the parent
|
|
403
|
+
export const taskDialog = hoistCmp.factory({
|
|
404
|
+
model: uses(TaskDialogModel),
|
|
405
|
+
render({model}) {
|
|
406
|
+
if (!model.isOpen) return null;
|
|
407
|
+
return dialog({
|
|
408
|
+
title: 'Edit Task',
|
|
409
|
+
style: {width: 500},
|
|
410
|
+
isOpen: true,
|
|
411
|
+
onClose: () => model.close(),
|
|
412
|
+
item: panel({
|
|
413
|
+
item: form(
|
|
414
|
+
formField({field: 'description', item: textInput()})
|
|
415
|
+
),
|
|
416
|
+
bbar: toolbar({
|
|
417
|
+
items: [filler(), button({text: 'Save', intent: 'primary'})]
|
|
418
|
+
})
|
|
419
|
+
})
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// TodoPanel.ts - the parent component that owns and renders the dialog
|
|
425
|
+
export class TodoPanelModel extends HoistModel {
|
|
426
|
+
@managed taskDialogModel = new TaskDialogModel();
|
|
427
|
+
@managed gridModel = new GridModel({...});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export const todoPanel = hoistCmp.factory({
|
|
431
|
+
model: creates(TodoPanelModel),
|
|
432
|
+
render({model}) {
|
|
433
|
+
return panel({
|
|
434
|
+
tbar: toolbar({
|
|
435
|
+
items: [
|
|
436
|
+
button({
|
|
437
|
+
text: 'New Task',
|
|
438
|
+
icon: Icon.add(),
|
|
439
|
+
onClick: () => model.taskDialogModel.open()
|
|
440
|
+
})
|
|
441
|
+
]
|
|
442
|
+
}),
|
|
443
|
+
items: [
|
|
444
|
+
grid(),
|
|
445
|
+
taskDialog() // Always rendered - shows/hides based on isOpen
|
|
446
|
+
]
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
Key points:
|
|
453
|
+
- Import `dialog` from `@xh/hoist/kit/blueprint` (not from Blueprint directly) to get
|
|
454
|
+
Hoist's transition-disabled wrapper.
|
|
455
|
+
- The parent model owns the dialog model as a `@managed` property and calls `open()`/`close()`.
|
|
456
|
+
- The parent component renders the dialog factory in its `items` alongside other children -
|
|
457
|
+
the dialog shows/hides itself based on its model's `isOpen` state.
|
|
458
|
+
- Use `onClose` to handle the dialog's close button and outside-click-to-close.
|
|
459
|
+
- Wrap dialog content in a `panel()` when you need toolbars, masks, or standard layout.
|
|
460
|
+
|
|
461
|
+
Note: Hoist does not yet provide its own first-class Dialog component wrapper
|
|
462
|
+
([#861](https://github.com/xh/hoist-react/issues/861)) - the Blueprint Kit re-export is the
|
|
463
|
+
established pattern used throughout the framework and applications.
|
|
464
|
+
|
|
343
465
|
## Desktop Hooks
|
|
344
466
|
|
|
345
467
|
### useContextMenu
|
|
@@ -45,6 +45,7 @@ export const aboutDialog = hoistCmp.factory({
|
|
|
45
45
|
button({
|
|
46
46
|
text: 'Send Client Health Report',
|
|
47
47
|
icon: Icon.health(),
|
|
48
|
+
testId: 'xh-about-health-report-btn',
|
|
48
49
|
omit: !XH.clientHealthService.enabled,
|
|
49
50
|
onClick: async () => {
|
|
50
51
|
try {
|
|
@@ -60,6 +61,7 @@ export const aboutDialog = hoistCmp.factory({
|
|
|
60
61
|
text: 'Close',
|
|
61
62
|
intent: 'primary',
|
|
62
63
|
outlined: true,
|
|
64
|
+
testId: 'xh-about-close-btn',
|
|
63
65
|
onClick: onClose
|
|
64
66
|
})
|
|
65
67
|
)
|
|
@@ -26,9 +26,10 @@ export const banner = hoistCmp.factory({
|
|
|
26
26
|
model: uses(BannerModel),
|
|
27
27
|
|
|
28
28
|
render({model}) {
|
|
29
|
-
const {icon, message, intent, onClick, className} = model;
|
|
29
|
+
const {icon, message, intent, onClick, className, category} = model;
|
|
30
30
|
|
|
31
31
|
return toolbar({
|
|
32
|
+
testId: `xh-banner-${category}`,
|
|
32
33
|
className: classNames(
|
|
33
34
|
'xh-banner',
|
|
34
35
|
className,
|
|
@@ -57,12 +58,13 @@ export const banner = hoistCmp.factory({
|
|
|
57
58
|
});
|
|
58
59
|
|
|
59
60
|
const actionButton = hoistCmp.factory<BannerModel>(({model}) => {
|
|
60
|
-
const {actionButtonProps} = model;
|
|
61
|
+
const {actionButtonProps, category} = model;
|
|
61
62
|
if (isEmpty(actionButtonProps)) return null;
|
|
62
63
|
|
|
63
64
|
return button({
|
|
64
65
|
outlined: true,
|
|
65
66
|
className: 'xh-banner__action-button',
|
|
67
|
+
testId: `xh-banner-${category}-action-btn`,
|
|
66
68
|
...actionButtonProps
|
|
67
69
|
});
|
|
68
70
|
});
|
|
@@ -74,6 +76,7 @@ const dismissButton = hoistCmp.factory<BannerModel>(({model}) => {
|
|
|
74
76
|
return button({
|
|
75
77
|
icon: Icon.close(),
|
|
76
78
|
className: 'xh-banner__dismiss-button',
|
|
79
|
+
testId: `xh-banner-${category}-dismiss-btn`,
|
|
77
80
|
onClick: () => {
|
|
78
81
|
XH.hideBanner(category);
|
|
79
82
|
if (isFunction(onClose)) onClose(model);
|
|
@@ -62,12 +62,14 @@ const bbar = hoistCmp.factory<ExceptionDialogModel>(({model}) =>
|
|
|
62
62
|
omit: !XH.identityService?.isImpersonating,
|
|
63
63
|
icon: Icon.impersonate(),
|
|
64
64
|
text: 'End Impersonation',
|
|
65
|
+
testId: 'xh-exception-end-impersonation-btn',
|
|
65
66
|
onClick: () => XH.identityService.endImpersonateAsync()
|
|
66
67
|
}),
|
|
67
68
|
filler(),
|
|
68
69
|
button({
|
|
69
70
|
icon: Icon.search(),
|
|
70
71
|
text: 'Show/Report Details',
|
|
72
|
+
testId: 'xh-exception-details-btn',
|
|
71
73
|
onClick: () => model.openDetails(),
|
|
72
74
|
omit: !model.options.showAsError
|
|
73
75
|
}),
|
|
@@ -86,11 +88,13 @@ export const dismissButton = hoistCmp.factory<ExceptionDialogModel>(({model}) =>
|
|
|
86
88
|
? button({
|
|
87
89
|
icon: Icon.refresh(),
|
|
88
90
|
text: 'Reload App',
|
|
91
|
+
testId: 'xh-exception-dismiss-btn',
|
|
89
92
|
autoFocus: true,
|
|
90
93
|
onClick: () => XH.reloadApp({removeQueryParams: true})
|
|
91
94
|
})
|
|
92
95
|
: button({
|
|
93
96
|
text: 'Close',
|
|
97
|
+
testId: 'xh-exception-dismiss-btn',
|
|
94
98
|
autoFocus: true,
|
|
95
99
|
onClick: () => model.close()
|
|
96
100
|
});
|
|
@@ -62,6 +62,7 @@ export const exceptionDialogDetails = hoistCmp.factory<ExceptionDialogModel>(({m
|
|
|
62
62
|
placeholder: 'Add message here...',
|
|
63
63
|
width: '100%',
|
|
64
64
|
height: 120,
|
|
65
|
+
testId: 'xh-exception-details-message',
|
|
65
66
|
omit: !clientUserKnown
|
|
66
67
|
})
|
|
67
68
|
]
|
|
@@ -71,13 +72,15 @@ export const exceptionDialogDetails = hoistCmp.factory<ExceptionDialogModel>(({m
|
|
|
71
72
|
button({
|
|
72
73
|
icon: Icon.envelope(),
|
|
73
74
|
text: 'Send',
|
|
75
|
+
testId: 'xh-exception-details-send-btn',
|
|
74
76
|
disabled: !model.userMessage,
|
|
75
77
|
onClick: () => model.sendReportAsync(),
|
|
76
78
|
omit: !clientUserKnown
|
|
77
79
|
}),
|
|
78
80
|
clipboardButton({
|
|
79
81
|
getCopyText: () => errorStr,
|
|
80
|
-
successMessage: 'Error details copied to clipboard.'
|
|
82
|
+
successMessage: 'Error details copied to clipboard.',
|
|
83
|
+
testId: 'xh-exception-details-copy-btn'
|
|
81
84
|
}),
|
|
82
85
|
dismissButton()
|
|
83
86
|
])
|
|
@@ -37,12 +37,14 @@ export const feedbackDialog = hoistCmp.factory({
|
|
|
37
37
|
height: 250,
|
|
38
38
|
style: {marginBottom: 2},
|
|
39
39
|
commitOnChange: true,
|
|
40
|
-
bind: 'message'
|
|
40
|
+
bind: 'message',
|
|
41
|
+
testId: 'xh-feedback-message'
|
|
41
42
|
}),
|
|
42
43
|
toolbar(
|
|
43
44
|
filler(),
|
|
44
45
|
button({
|
|
45
46
|
text: 'Cancel',
|
|
47
|
+
testId: 'xh-feedback-cancel-btn',
|
|
46
48
|
onClick: () => model.hide()
|
|
47
49
|
}),
|
|
48
50
|
button({
|
|
@@ -50,6 +52,7 @@ export const feedbackDialog = hoistCmp.factory({
|
|
|
50
52
|
intent: 'success',
|
|
51
53
|
minimal: false,
|
|
52
54
|
disabled: !model.message,
|
|
55
|
+
testId: 'xh-feedback-send-btn',
|
|
53
56
|
onClick: () => model.submitAsync()
|
|
54
57
|
})
|
|
55
58
|
)
|
|
@@ -41,6 +41,7 @@ export const impersonationBar = hoistCmp.factory({
|
|
|
41
41
|
|
|
42
42
|
return toolbar({
|
|
43
43
|
className: 'xh-impersonation-bar',
|
|
44
|
+
testId: 'xh-impersonation-bar',
|
|
44
45
|
items: [
|
|
45
46
|
Icon.impersonate(),
|
|
46
47
|
span(msg),
|
|
@@ -49,6 +50,7 @@ export const impersonationBar = hoistCmp.factory({
|
|
|
49
50
|
text: 'Important Reminders',
|
|
50
51
|
icon: Icon.warning(),
|
|
51
52
|
outlined: true,
|
|
53
|
+
testId: 'xh-impersonation-reminders-btn',
|
|
52
54
|
onClick: showUseResponsiblyAlert
|
|
53
55
|
}),
|
|
54
56
|
hspacer(),
|
|
@@ -64,12 +66,14 @@ export const impersonationBar = hoistCmp.factory({
|
|
|
64
66
|
maxWidth: 350,
|
|
65
67
|
menuWidth: 350,
|
|
66
68
|
flex: 1,
|
|
69
|
+
testId: 'xh-impersonation-target',
|
|
67
70
|
onCommit: model.onCommit,
|
|
68
71
|
ref: model.inputRef
|
|
69
72
|
}),
|
|
70
73
|
button({
|
|
71
74
|
text: isImpersonating ? 'Exit Impersonation' : 'Cancel',
|
|
72
75
|
outlined: true,
|
|
76
|
+
testId: 'xh-impersonation-exit-btn',
|
|
73
77
|
onClick: model.onClose
|
|
74
78
|
})
|
|
75
79
|
]
|
|
@@ -27,6 +27,7 @@ export const lockoutPanel = hoistCmp.factory({
|
|
|
27
27
|
filler(),
|
|
28
28
|
box({
|
|
29
29
|
className: 'xh-lockout-panel',
|
|
30
|
+
testId: 'xh-lockout-panel',
|
|
30
31
|
item: unauthorizedMessage()
|
|
31
32
|
}),
|
|
32
33
|
filler()
|
|
@@ -60,7 +61,8 @@ const unauthorizedMessage = hoistCmp.factory<AppContainerModel>({
|
|
|
60
61
|
logoutButton({
|
|
61
62
|
text: 'Logout',
|
|
62
63
|
intent: null,
|
|
63
|
-
minimal: false
|
|
64
|
+
minimal: false,
|
|
65
|
+
testId: 'xh-lockout-logout-btn'
|
|
64
66
|
}),
|
|
65
67
|
hspacer(5),
|
|
66
68
|
button({
|
|
@@ -68,6 +70,7 @@ const unauthorizedMessage = hoistCmp.factory<AppContainerModel>({
|
|
|
68
70
|
icon: Icon.impersonate(),
|
|
69
71
|
text: 'End Impersonation',
|
|
70
72
|
minimal: false,
|
|
73
|
+
testId: 'xh-lockout-end-impersonation-btn',
|
|
71
74
|
onClick: () => identityService.endImpersonateAsync()
|
|
72
75
|
}),
|
|
73
76
|
filler()
|
|
@@ -39,6 +39,7 @@ export const loginPanel = hoistCmp.factory({
|
|
|
39
39
|
title: XH.clientAppName,
|
|
40
40
|
icon: Icon.login(),
|
|
41
41
|
className: 'xh-login',
|
|
42
|
+
testId: 'xh-login',
|
|
42
43
|
width: 300,
|
|
43
44
|
mask: loadObserver,
|
|
44
45
|
items: [
|
|
@@ -51,7 +52,8 @@ export const loginPanel = hoistCmp.factory({
|
|
|
51
52
|
autoFocus: true,
|
|
52
53
|
commitOnChange: true,
|
|
53
54
|
onKeyDown,
|
|
54
|
-
width: null
|
|
55
|
+
width: null,
|
|
56
|
+
testId: 'xh-login-username'
|
|
55
57
|
}),
|
|
56
58
|
textInput({
|
|
57
59
|
bind: 'password',
|
|
@@ -60,7 +62,8 @@ export const loginPanel = hoistCmp.factory({
|
|
|
60
62
|
type: 'password',
|
|
61
63
|
commitOnChange: true,
|
|
62
64
|
onKeyDown,
|
|
63
|
-
width: null
|
|
65
|
+
width: null,
|
|
66
|
+
testId: 'xh-login-password'
|
|
64
67
|
})
|
|
65
68
|
),
|
|
66
69
|
div({
|
|
@@ -81,7 +84,8 @@ export const loginPanel = hoistCmp.factory({
|
|
|
81
84
|
intent: 'primary',
|
|
82
85
|
icon: Icon.login(),
|
|
83
86
|
disabled: !isValid || loginInProgress,
|
|
84
|
-
onClick: () => model.submitAsync()
|
|
87
|
+
onClick: () => model.submitAsync(),
|
|
88
|
+
testId: 'xh-login-btn'
|
|
85
89
|
})
|
|
86
90
|
]
|
|
87
91
|
})
|
|
@@ -52,6 +52,7 @@ const inputsCmp = hoistCmp.factory<MessageModel>(({model}) => {
|
|
|
52
52
|
formField({
|
|
53
53
|
field: 'value',
|
|
54
54
|
label: null,
|
|
55
|
+
testId: 'xh-message-value',
|
|
55
56
|
item: withDefault(
|
|
56
57
|
input.item,
|
|
57
58
|
textInput({
|
|
@@ -70,6 +71,7 @@ const inputsCmp = hoistCmp.factory<MessageModel>(({model}) => {
|
|
|
70
71
|
formField({
|
|
71
72
|
label: extraConfirmLabel,
|
|
72
73
|
field: 'extraConfirm',
|
|
74
|
+
testId: 'xh-message-extra-confirm',
|
|
73
75
|
item: textInput({
|
|
74
76
|
autoFocus: true,
|
|
75
77
|
selectOnFocus: true,
|
|
@@ -95,7 +97,7 @@ const bbar = hoistCmp.factory<MessageModel>(({model}) => {
|
|
|
95
97
|
ret = [];
|
|
96
98
|
|
|
97
99
|
if (cancelProps) {
|
|
98
|
-
ret.push(button(cancelProps));
|
|
100
|
+
ret.push(button({testId: 'xh-message-cancel-btn', ...cancelProps}));
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
if (cancelAlign === 'left') {
|
|
@@ -108,8 +110,12 @@ const bbar = hoistCmp.factory<MessageModel>(({model}) => {
|
|
|
108
110
|
// Merge in formModel.isValid here in render stage to get reactivity.
|
|
109
111
|
ret.push(
|
|
110
112
|
formModel
|
|
111
|
-
? button({
|
|
112
|
-
|
|
113
|
+
? button({
|
|
114
|
+
testId: 'xh-message-confirm-btn',
|
|
115
|
+
...confirmProps,
|
|
116
|
+
disabled: !formModel.isValid
|
|
117
|
+
})
|
|
118
|
+
: button({testId: 'xh-message-confirm-btn', ...confirmProps})
|
|
113
119
|
);
|
|
114
120
|
}
|
|
115
121
|
|
|
@@ -51,10 +51,11 @@ export const optionsDialog = hoistCmp.factory({
|
|
|
51
51
|
})
|
|
52
52
|
),
|
|
53
53
|
bbar: [
|
|
54
|
-
restoreDefaultsButton(),
|
|
54
|
+
restoreDefaultsButton({testId: 'xh-options-restore-defaults-btn'}),
|
|
55
55
|
filler(),
|
|
56
56
|
button({
|
|
57
57
|
text: 'Cancel',
|
|
58
|
+
testId: 'xh-options-cancel-btn',
|
|
58
59
|
onClick: () => model.hide()
|
|
59
60
|
}),
|
|
60
61
|
button({
|
|
@@ -62,6 +63,7 @@ export const optionsDialog = hoistCmp.factory({
|
|
|
62
63
|
text: reloadRequired ? 'Save & Reload' : 'Save',
|
|
63
64
|
icon: reloadRequired ? Icon.refresh() : Icon.check(),
|
|
64
65
|
intent: 'success',
|
|
66
|
+
testId: 'xh-options-save-btn',
|
|
65
67
|
onClick: () => model.saveAsync()
|
|
66
68
|
})
|
|
67
69
|
]
|
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {div, filler, img, p, viewport} from '@xh/hoist/cmp/layout';
|
|
7
8
|
import {hoistCmp, XH} from '@xh/hoist/core';
|
|
8
|
-
import {viewport, div, img, p, filler} from '@xh/hoist/cmp/layout';
|
|
9
|
-
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
10
9
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
10
|
+
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
11
11
|
import {Icon} from '@xh/hoist/icon';
|
|
12
|
-
|
|
13
12
|
import './IdlePanel.scss';
|
|
14
|
-
// @ts-ignore
|
|
15
13
|
import idleImage from './IdlePanelImage.png';
|
|
16
14
|
|
|
17
15
|
/**
|
|
@@ -33,6 +31,7 @@ export const idlePanel = hoistCmp.factory({
|
|
|
33
31
|
title: `${XH.clientAppName} is sleeping`,
|
|
34
32
|
icon: Icon.moon(),
|
|
35
33
|
className: 'xh-idle-panel',
|
|
34
|
+
testId: 'xh-idle-panel',
|
|
36
35
|
item: div(
|
|
37
36
|
img({
|
|
38
37
|
src: idleImage,
|
|
@@ -49,6 +48,7 @@ export const idlePanel = hoistCmp.factory({
|
|
|
49
48
|
intent: 'primary',
|
|
50
49
|
minimal: false,
|
|
51
50
|
autoFocus: true,
|
|
51
|
+
testId: 'xh-idle-reactivate-btn',
|
|
52
52
|
onClick: onReactivate
|
|
53
53
|
})
|
|
54
54
|
]
|
|
@@ -59,6 +59,7 @@ export const suspendPanel = hoistCmp.factory<AppContainerModel>({
|
|
|
59
59
|
title,
|
|
60
60
|
icon,
|
|
61
61
|
className: 'xh-suspend-panel',
|
|
62
|
+
testId: 'xh-suspend-panel',
|
|
62
63
|
item: div({
|
|
63
64
|
className: 'xh-suspend-panel__inner',
|
|
64
65
|
items: [
|
|
@@ -71,6 +72,7 @@ export const suspendPanel = hoistCmp.factory<AppContainerModel>({
|
|
|
71
72
|
text: 'More Details',
|
|
72
73
|
icon: Icon.detail(),
|
|
73
74
|
minimal: true,
|
|
75
|
+
testId: 'xh-suspend-details-btn',
|
|
74
76
|
omit: !exception,
|
|
75
77
|
onClick: () => XH.exceptionHandler.showExceptionDetails(exception)
|
|
76
78
|
}),
|
|
@@ -81,6 +83,7 @@ export const suspendPanel = hoistCmp.factory<AppContainerModel>({
|
|
|
81
83
|
intent: 'primary',
|
|
82
84
|
minimal: false,
|
|
83
85
|
autoFocus: true,
|
|
86
|
+
testId: 'xh-suspend-reload-btn',
|
|
84
87
|
onClick: () => XH.reloadApp()
|
|
85
88
|
})
|
|
86
89
|
]
|
|
@@ -64,6 +64,11 @@ export interface AppMenuButtonProps extends ButtonProps {
|
|
|
64
64
|
|
|
65
65
|
type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Application-level menu button rendered in the AppBar. Provides a dropdown menu with standard
|
|
69
|
+
* items for About, Admin, Feedback, Options, Theme, Impersonation, Changelog, and Logout.
|
|
70
|
+
* Individual items can be hidden via props, and custom items added via `extraItems`.
|
|
71
|
+
*/
|
|
67
72
|
export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButtonProps>({
|
|
68
73
|
displayName: 'AppMenuButton',
|
|
69
74
|
model: false,
|