@itwin/saved-views-react 0.1.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.
Files changed (77) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +131 -0
  3. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.css +11 -0
  4. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.d.ts +39 -0
  5. package/lib/LayeredDropdownMenu/LayeredDropdownMenu.js +53 -0
  6. package/lib/SavedView.d.ts +18 -0
  7. package/lib/SavedView.js +1 -0
  8. package/lib/SavedViewTile/SavedViewOptions.css +26 -0
  9. package/lib/SavedViewTile/SavedViewOptions.d.ts +139 -0
  10. package/lib/SavedViewTile/SavedViewOptions.js +173 -0
  11. package/lib/SavedViewTile/SavedViewTile.css +89 -0
  12. package/lib/SavedViewTile/SavedViewTile.d.ts +55 -0
  13. package/lib/SavedViewTile/SavedViewTile.js +110 -0
  14. package/lib/SavedViewTile/SavedViewTileContext.d.ts +14 -0
  15. package/lib/SavedViewTile/SavedViewTileContext.js +20 -0
  16. package/lib/SavedViewsClient/ITwinSavedViewsClient.d.ts +30 -0
  17. package/lib/SavedViewsClient/ITwinSavedViewsClient.js +132 -0
  18. package/lib/SavedViewsClient/SavedViewsClient.d.ts +72 -0
  19. package/lib/SavedViewsClient/SavedViewsClient.js +1 -0
  20. package/lib/SavedViewsContext.d.ts +13 -0
  21. package/lib/SavedViewsContext.js +38 -0
  22. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupOptions.d.ts +9 -0
  23. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupOptions.js +14 -0
  24. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupTile.d.ts +14 -0
  25. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupTile.js +37 -0
  26. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupTileContext.d.ts +14 -0
  27. package/lib/SavedViewsWidget/SavedViewGroupTile/SavedViewGroupTileContext.js +20 -0
  28. package/lib/SavedViewsWidget/SavedViewsExpandableBlockWidget.css +50 -0
  29. package/lib/SavedViewsWidget/SavedViewsExpandableBlockWidget.d.ts +36 -0
  30. package/lib/SavedViewsWidget/SavedViewsExpandableBlockWidget.js +36 -0
  31. package/lib/SavedViewsWidget/SavedViewsFolderWidget.d.ts +14 -0
  32. package/lib/SavedViewsWidget/SavedViewsFolderWidget.js +60 -0
  33. package/lib/StickyExpandableBlock/StickyExpandableBlock.css +20 -0
  34. package/lib/StickyExpandableBlock/StickyExpandableBlock.d.ts +29 -0
  35. package/lib/StickyExpandableBlock/StickyExpandableBlock.js +63 -0
  36. package/lib/TileGrid/TileGrid.css +28 -0
  37. package/lib/TileGrid/TileGrid.d.ts +48 -0
  38. package/lib/TileGrid/TileGrid.js +32 -0
  39. package/lib/api/clients/IModelQueryClient.d.ts +10 -0
  40. package/lib/api/clients/IModelQueryClient.js +45 -0
  41. package/lib/api/clients/ISavedViewsClient.d.ts +9 -0
  42. package/lib/api/clients/ISavedViewsClient.js +16 -0
  43. package/lib/api/utilities/SavedViewTypes.d.ts +48 -0
  44. package/lib/api/utilities/SavedViewTypes.js +1 -0
  45. package/lib/api/utilities/translation/ModelsAndCategoriesHelper.d.ts +3 -0
  46. package/lib/api/utilities/translation/ModelsAndCategoriesHelper.js +57 -0
  47. package/lib/api/utilities/translation/RgbColor.d.ts +29 -0
  48. package/lib/api/utilities/translation/RgbColor.js +1 -0
  49. package/lib/api/utilities/translation/SavedViewTranslation.d.ts +22 -0
  50. package/lib/api/utilities/translation/SavedViewTranslation.js +246 -0
  51. package/lib/api/utilities/translation/SavedViewsExtensionHandlers.d.ts +13 -0
  52. package/lib/api/utilities/translation/SavedViewsExtensionHandlers.js +42 -0
  53. package/lib/api/utilities/translation/clipVectorsExtractor.d.ts +5 -0
  54. package/lib/api/utilities/translation/clipVectorsExtractor.js +56 -0
  55. package/lib/api/utilities/translation/displayStyleExtractor.d.ts +17 -0
  56. package/lib/api/utilities/translation/displayStyleExtractor.js +499 -0
  57. package/lib/api/utilities/translation/extensionExtractor.d.ts +18 -0
  58. package/lib/api/utilities/translation/extensionExtractor.js +79 -0
  59. package/lib/api/utilities/translation/extractionUtilities.d.ts +209 -0
  60. package/lib/api/utilities/translation/extractionUtilities.js +515 -0
  61. package/lib/api/utilities/translation/urlConverter.d.ts +7 -0
  62. package/lib/api/utilities/translation/urlConverter.js +42 -0
  63. package/lib/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.d.ts +35 -0
  64. package/lib/api/utilities/translation/viewExtractorSavedViewToLegacySavedView.js +298 -0
  65. package/lib/experimental.d.ts +4 -0
  66. package/lib/experimental.js +8 -0
  67. package/lib/index.d.ts +12 -0
  68. package/lib/index.js +14 -0
  69. package/lib/localization.d.ts +52 -0
  70. package/lib/localization.js +51 -0
  71. package/lib/ui/viewlist/ModelCategoryOverrideProvider.d.ts +31 -0
  72. package/lib/ui/viewlist/ModelCategoryOverrideProvider.js +88 -0
  73. package/lib/useSavedViews.d.ts +52 -0
  74. package/lib/useSavedViews.js +514 -0
  75. package/lib/utils.d.ts +1 -0
  76. package/lib/utils.js +7 -0
  77. package/package.json +75 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # MIT License
2
+
3
+ Copyright © 2024 Bentley Systems, Incorporated. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @itwin/saved-views-react
2
+
3
+ ## About
4
+
5
+ A collection of React components for building applications that deal with [Saved Views](https://developer.bentley.com/apis/savedviews/overview/).
6
+
7
+ ## Documentation
8
+
9
+ ### React components
10
+
11
+ * [SavedViewTile](./src/SavedViewTile/SavedViewTile.tsx)
12
+ * [SavedViewOptions](./src/SavedViewTile/SavedViewOptions.tsx)
13
+ * [LayeredDropdownMenu](./src/LayeredDropdownMenu/LayeredDropdownMenu.tsx)
14
+ * [TileGrid](./src/TileGrid/TileGrid.tsx)
15
+ * [StickyExpandableBlock](./src/StickyExpandableBlock/StickyExpandableBlock.tsx)
16
+
17
+ You can use these components however you see fit, however, below is the suggested component arrangement.
18
+
19
+ ```tsx
20
+ import { LayeredMenuItem, SavedViewTile, StickyExpandableBlock, TileGrid } from "@itwin/saved-views-react";
21
+
22
+ export function SavedViewsWidget(props) {
23
+ return (
24
+ <StickyExpandableBlock title="Saved Views">
25
+ <TileGrid gridItems={props.savedViews}>
26
+ {
27
+ (savedView) => (
28
+ <SavedViewTile
29
+ savedView={savedView}
30
+ options={[
31
+ <SavedViewOptions.Delete key="delete" deleteSavedView={props.deleteSavedView} />,
32
+ <LayeredMenuItem key="menu-item" content={<MyMenuItemContent />}>
33
+ Custom menu item
34
+ </LayeredMenuItem>,
35
+ ]}
36
+ />
37
+ )
38
+ }
39
+ </TileGrid>
40
+ </StickyExpandableBlock>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### useSavedViews
46
+
47
+ [useSavedViews](./src/useSavedViews.tsx) React hook provides basic functionality to jump-start your Saved Views widget. It accepts [`ITwinSavedViewsClient`](./src/SavedViewsClient/ITwinSavedViewsClient.ts) which is used to pull Saved View data and synchronize it back to the [Saved Views service](https://developer.bentley.com/apis/savedviews/overview/).
48
+
49
+ ```tsx
50
+ import { useSavedViews, ITwinSavedViewsClient } from "@itwin/saved-views-react";
51
+
52
+ const client = new ITwinSavedViewsClient({
53
+ // auth_token should have access to savedviews:read and savedviews:modify OIDC scopes
54
+ getAccessToken: async () => "auth_token",
55
+ });
56
+
57
+ export function SavedViewsWidget(props) {
58
+ const savedViews = useSavedViews({ iTwinId: props.iTwinId, iModelId: props.iModelId, client });
59
+ if (savedViews === undefined) {
60
+ return "loading";
61
+ }
62
+
63
+ return (
64
+ <MyWidgetContent
65
+ savedViews={savedViews.savedViews}
66
+ groups={savedViews.groups}
67
+ tags={savedViews.tags}
68
+ actions={savedViews.actions}
69
+ />
70
+ );
71
+ }
72
+ ```
73
+
74
+ ### Localization
75
+
76
+ In order to localize text across various provided components, mount [`<SavedViewsContextProvider />`](./src/SavedViewsContext.tsx) and supply `localization` object containing string replacements.
77
+
78
+ ```tsx
79
+ import { SavedViewsContextProvider, type LocalizationStrings } from "@itwin/saved-views-react";
80
+
81
+ const localization: LocalizationStrings = { delete: "🗑️" };
82
+
83
+ export function SavedViewsWidget() {
84
+ return (
85
+ <SavedViewsContextProvider localization={localization}>
86
+ <MyWidgetContent />
87
+ </SavedViewsContextProvider>
88
+ );
89
+ }
90
+
91
+ ```
92
+
93
+ ### Custom tile options
94
+
95
+ [`<SavedViewTile />`](./src/SavedViewTile/SavedViewTile.tsx) accepts an array of options to display in its context menu. This package exposes a number of ready-to-use options to use with your tiles, accessible through [`SavedViewOptions`](./src/SavedViewTile//SavedViewOptions.tsx) export, and you are free to create your own ones.
96
+
97
+ ```tsx
98
+ import { MenuDivider, MenuItem } from "@itwin/itwinui-react";
99
+ import { SavedViewOptions, SavedViewTile, useSavedViewTileContext } from "@itwin/saved-views-react";
100
+
101
+ export function SavedViewsWidget(props) {
102
+ return (
103
+ <SavedViewTile
104
+ savedView={props.savedView}
105
+ options={(close) => [
106
+ <SavedViewOptions.Rename key="rename" onClick={close} />,
107
+ <MenuDivider key="divider" />,
108
+ <OpenSavedView key="custom" onClick={props.openSavedView} />,
109
+ ]}
110
+ />
111
+ );
112
+ }
113
+
114
+ function OpenSavedView(props) {
115
+ const { savedView } = useSavedViewTileContext();
116
+
117
+ return (
118
+ <MenuItem onClick={() => props.onClick(savedView)}>
119
+ Open {savedView.displayName}
120
+ </MenuItem>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ## Contributing
126
+
127
+ We welcome contributions to make this package better. You can submit feature requests or report bugs by creating an [issue](https://github.com/iTwin/saved-views/issues).
128
+
129
+ ---
130
+
131
+ Copyright © Bentley Systems, Incorporated. All rights reserved. See [LICENSE.md](./LICENSE.md) for license terms and full copyright notice.
@@ -0,0 +1,11 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+ .svr-layered-dropdown--back {
6
+ display: flex;
7
+ gap: var(--iui-size-s);
8
+ align-items: center;
9
+ }
10
+
11
+ /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9MYXllcmVkRHJvcGRvd25NZW51L0xheWVyZWREcm9wZG93bk1lbnUuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIiwiZmlsZSI6IkxheWVyZWREcm9wZG93bk1lbnUuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiogQ29weXJpZ2h0IChjKSBCZW50bGV5IFN5c3RlbXMsIEluY29ycG9yYXRlZC4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiogU2VlIExJQ0VOU0UubWQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSB0ZXJtcyBhbmQgZnVsbCBjb3B5cmlnaHQgbm90aWNlLlxuKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi5zdnItbGF5ZXJlZC1kcm9wZG93bi0tYmFjayB7XG4gIGRpc3BsYXk6IGZsZXg7XG4gIGdhcDogdmFyKC0taXVpLXNpemUtcyk7XG4gIGFsaWduLWl0ZW1zOiBjZW50ZXI7XG59XG4iXX0= */
@@ -0,0 +1,39 @@
1
+ import { type ReactElement, type ReactNode } from "react";
2
+ import "./LayeredDropdownMenu.css";
3
+ interface LayeredDropdownMenuProps {
4
+ /**
5
+ * Items to pass to iTwinUI `<DropdownMenu />` component. It is preferable for items to include at least one
6
+ * `<LayeredMenuItem />`. Otherwise, it is more efficient to use plain `<DropdownMenu />`.
7
+ */
8
+ menuItems: ReactElement[] | ((close: () => void) => ReactElement[]);
9
+ /** Dropdown menu trigger to pass to iTwinUI `<DropdownMenu />` component. */
10
+ children: ReactNode;
11
+ }
12
+ /**
13
+ * An extended version of iTwinUI `<DropdownMenu />` component with support for layered dropdown menu items.
14
+ *
15
+ * @example
16
+ * <LayeredDropdownMenu
17
+ * menuItems={[
18
+ * <LayeredMenuItem key="layered" content={<MyLayeredMenu />}>
19
+ * Layered menu
20
+ * </LayeredMenuItem>,
21
+ * <MenuDivider key="divider" />,
22
+ * <MenuItem ley="regural">Regular menu item</MenuItem>,
23
+ * ]}
24
+ * >
25
+ * <Button>Open dropdown menu</Button>
26
+ * </LayeredDropdownMenu>
27
+ */
28
+ export declare function LayeredDropdownMenu(props: LayeredDropdownMenuProps): ReactElement;
29
+ interface LayeredDropdownMenuItemProps {
30
+ /** Content of the nested menu. */
31
+ content: ReactNode;
32
+ /** Menu item icon. */
33
+ icon?: ReactNode | undefined;
34
+ /** Menu item label. */
35
+ children: ReactNode;
36
+ }
37
+ /** Behaves much like iTwinUI `<MenuItem />` but upon activation advances menu to a nested layer. */
38
+ export declare function LayeredMenuItem(props: LayeredDropdownMenuItemProps): ReactElement;
39
+ export {};
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ import { SvgChevronLeft, SvgChevronRight } from "@itwin/itwinui-icons-react";
7
+ import { DropdownMenu, IconButton, ListItem, MenuDivider, MenuExtraContent } from "@itwin/itwinui-react";
8
+ import { createContext, useContext, useMemo, useState } from "react";
9
+ import "./LayeredDropdownMenu.css";
10
+ /**
11
+ * An extended version of iTwinUI `<DropdownMenu />` component with support for layered dropdown menu items.
12
+ *
13
+ * @example
14
+ * <LayeredDropdownMenu
15
+ * menuItems={[
16
+ * <LayeredMenuItem key="layered" content={<MyLayeredMenu />}>
17
+ * Layered menu
18
+ * </LayeredMenuItem>,
19
+ * <MenuDivider key="divider" />,
20
+ * <MenuItem ley="regural">Regular menu item</MenuItem>,
21
+ * ]}
22
+ * >
23
+ * <Button>Open dropdown menu</Button>
24
+ * </LayeredDropdownMenu>
25
+ */
26
+ export function LayeredDropdownMenu(props) {
27
+ const [activeMenuItem, setActiveMenuItem] = useState(undefined);
28
+ const menuItems = useMemo(() => {
29
+ const menuItemsCallback = activeMenuItem === undefined
30
+ ? (menuItems) => menuItems.map((item, i) => (_jsx(layeredMenuItemIdentifierContext.Provider, { value: i, children: item }, item.key ?? i)))
31
+ : (menuItems) => [
32
+ _jsx(layeredMenuItemIdentifierContext.Provider, { value: activeMenuItem, children: menuItems[activeMenuItem] }, menuItems[activeMenuItem].key ?? activeMenuItem),
33
+ ];
34
+ return getMenuItems(props.menuItems, menuItemsCallback);
35
+ }, [activeMenuItem, props.menuItems]);
36
+ return (_jsx(layeredDropdownMenuContext.Provider, { value: { activeMenuItem, setActiveMenuItem }, children: _jsx(DropdownMenu, { menuItems: menuItems, onVisibleChange: (visible) => !visible && setActiveMenuItem(undefined), children: props.children }) }));
37
+ }
38
+ function getMenuItems(menuItems, callback) {
39
+ return (close) => callback(typeof menuItems === "function" ? menuItems(close) : menuItems);
40
+ }
41
+ /** Behaves much like iTwinUI `<MenuItem />` but upon activation advances menu to a nested layer. */
42
+ export function LayeredMenuItem(props) {
43
+ const itemIdentifier = useContext(layeredMenuItemIdentifierContext);
44
+ const { activeMenuItem, setActiveMenuItem } = useContext(layeredDropdownMenuContext);
45
+ if (itemIdentifier === activeMenuItem) {
46
+ return (_jsxs(_Fragment, { children: [_jsx(MenuExtraContent, { children: _jsx("div", { children: _jsx("div", { onClick: () => setActiveMenuItem(undefined), children: _jsxs(IconButton, { styleType: "borderless", iconProps: { className: "svr-layered-dropdown--back" }, children: [_jsx(SvgChevronLeft, {}), props.children] }) }) }) }, "header"), _jsx(MenuDivider, {}, "separator"), _jsx(MenuExtraContent, { children: props.content }, "content")] }));
47
+ }
48
+ return (_jsxs(ListItem, { tabIndex: -1, onClick: () => setActiveMenuItem(itemIdentifier), actionable: true, children: [props.icon && _jsx(ListItem.Icon, { children: props.icon }), _jsx(ListItem.Content, { children: props.children }), _jsx(ListItem.Icon, { children: _jsx(SvgChevronRight, {}) })] }));
49
+ }
50
+ const layeredDropdownMenuContext = createContext({ activeMenuItem: undefined, setActiveMenuItem: () => { } });
51
+ layeredDropdownMenuContext.displayName = "LayeredDropdownMenuContext";
52
+ const layeredMenuItemIdentifierContext = createContext(undefined);
53
+ layeredMenuItemIdentifierContext.displayName = "LayeredDropdownMenuIdentifier";
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from "react";
2
+ export interface SavedView {
3
+ id: string;
4
+ displayName: string;
5
+ groupId?: string | undefined;
6
+ tagIds?: string[] | undefined;
7
+ shared?: boolean | undefined;
8
+ thumbnail?: ReactNode | string | undefined;
9
+ }
10
+ export interface SavedViewTag {
11
+ id: string;
12
+ displayName: string;
13
+ }
14
+ export interface SavedViewGroup {
15
+ id: string;
16
+ displayName: string;
17
+ shared?: boolean | undefined;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /*---------------------------------------------------------------------------------------------
2
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
3
+ * See LICENSE.md in the project root for license terms and full copyright notice.
4
+ *--------------------------------------------------------------------------------------------*/
5
+
6
+ .svr-searchable-submenu {
7
+ display: grid;
8
+ grid: auto minmax(0, 1fr) / 1fr;
9
+ gap: var(--iui-size-xs);
10
+ height: calc(var(--iui-menu-max-height) - 2 * var(--iui-component-height) - 1px);
11
+ user-select: none;
12
+ }
13
+
14
+ .svr-searchable-submenu > div {
15
+ overflow: auto;
16
+ }
17
+
18
+ .svr-searchable-submenu-item {
19
+ width: calc(10 * var(--iui-size-l));
20
+ }
21
+
22
+ .svr-semibold {
23
+ font-weight: var(--iui-font-weight-semibold);
24
+ }
25
+
26
+ /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9TYXZlZFZpZXdUaWxlL1NhdmVkVmlld09wdGlvbnMuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIiwiZmlsZSI6IlNhdmVkVmlld09wdGlvbnMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiogQ29weXJpZ2h0IChjKSBCZW50bGV5IFN5c3RlbXMsIEluY29ycG9yYXRlZC4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiogU2VlIExJQ0VOU0UubWQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSB0ZXJtcyBhbmQgZnVsbCBjb3B5cmlnaHQgbm90aWNlLlxuKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuLnN2ci1zZWFyY2hhYmxlLXN1Ym1lbnUge1xuICBkaXNwbGF5OiBncmlkO1xuICBncmlkOiBhdXRvIG1pbm1heCgwLCAxZnIpIC8gMWZyO1xuICBnYXA6IHZhcigtLWl1aS1zaXplLXhzKTtcbiAgaGVpZ2h0OiBjYWxjKHZhcigtLWl1aS1tZW51LW1heC1oZWlnaHQpIC0gMiAqIHZhcigtLWl1aS1jb21wb25lbnQtaGVpZ2h0KSAtIDFweCk7XG4gIHVzZXItc2VsZWN0OiBub25lO1xufVxuXG4uc3ZyLXNlYXJjaGFibGUtc3VibWVudSA+IGRpdiB7XG4gIG92ZXJmbG93OiBhdXRvO1xufVxuXG4uc3ZyLXNlYXJjaGFibGUtc3VibWVudS1pdGVtIHtcbiAgd2lkdGg6IGNhbGMoMTAgKiB2YXIoLS1pdWktc2l6ZS1sKSk7XG59XG5cbi5zdnItc2VtaWJvbGQge1xuICBmb250LXdlaWdodDogdmFyKC0taXVpLWZvbnQtd2VpZ2h0LXNlbWlib2xkKTtcbn1cbiJdfQ== */
@@ -0,0 +1,139 @@
1
+ import { type ComponentProps, type ReactElement } from "react";
2
+ import type { SavedViewGroup, SavedViewTag } from "../SavedView.js";
3
+ import "./SavedViewOptions.css";
4
+ /**
5
+ * A collection of useful menu items for {@linkcode SavedViewTile}.
6
+ *
7
+ * @example
8
+ * <SavedViewTile
9
+ * savedView={savedView}
10
+ * options={(close) => [<SavedViewOptions.Rename key="rename" icon={<SvgBlank />} onClick={close} />]}
11
+ * onRename={handleRename}
12
+ * />
13
+ */
14
+ export declare const SavedViewOptions: {
15
+ /**
16
+ * When activated, makes Saved View title enter editable state. Has no effect if the {@linkcode SavedViewTile} does
17
+ * not receive `onRename` prop.
18
+ *
19
+ * @example
20
+ * <SavedViewTile
21
+ * savedView={savedView}
22
+ * options={(close) => [<SavedViewOptions.Rename key="rename" icon={<SvgBlank />} onClick={close} />]}
23
+ * onRename={handleRename}
24
+ * />
25
+ */
26
+ Rename: typeof Rename;
27
+ /**
28
+ * Displays a control for moving Saved View to a group.
29
+ *
30
+ * @example
31
+ * <SavedViewOptions.MoveToGroup
32
+ * groups={groups}
33
+ * moveToGroup={handleMoveToGroup}
34
+ * moveToNewGroup={handleMoveTo}
35
+ * icon={<SvgBlank />}
36
+ * />
37
+ */
38
+ MoveToGroup: typeof MoveToGroup;
39
+ /**
40
+ * Displays a control for managing Saved View tags.
41
+ *
42
+ * @example
43
+ * <SavedViewOptions.ManageTags
44
+ * tags={tags}
45
+ * addTag={handleAddTag}
46
+ * addNewTag={handleAddNewTag}
47
+ * removeTag={handleRemoveTag}
48
+ * icon={<SvgBlank />}
49
+ * />
50
+ */
51
+ ManageTags: typeof ManageTags;
52
+ /**
53
+ * Displays option for Saved View deletion.
54
+ *
55
+ * @example
56
+ * <SavedViewOptions.Delete
57
+ * icon={<SvgBlank />}
58
+ * deleteSavedView={handleDeleteSavedView}
59
+ * />
60
+ */
61
+ Delete: typeof Delete;
62
+ };
63
+ export interface CreateSavedViewOptionsParams {
64
+ /** When `true`, the returned options contain a `Rename` entry. */
65
+ renameSavedView?: boolean | undefined;
66
+ /** When set, the returned options contain a `Move to group` entry. */
67
+ groupActions?: OmitCommonOptionProps<ComponentProps<typeof SavedViewOptions.MoveToGroup>>;
68
+ /** When set, the returned options contain a `Tags` entry. */
69
+ tagActions?: OmitCommonOptionProps<ComponentProps<typeof SavedViewOptions.ManageTags>>;
70
+ /** When set, the returned options contain a `Delete` entry. */
71
+ deleteSavedView?: ComponentProps<typeof SavedViewOptions.Delete>["deleteSavedView"];
72
+ }
73
+ type OmitCommonOptionProps<T> = Omit<T, "icon">;
74
+ /**
75
+ * Convenience function that returns an array populated with options to be used on {@linkcode SavedViewTile}. Available
76
+ * options are determined by what data is present in the {@linkcode args} argument.
77
+ *
78
+ * @example
79
+ * <SavedViewTile
80
+ * savedView={savedView}
81
+ * options={createSavedViewOptions({ tagActions, groupActions })}
82
+ * editable
83
+ * />
84
+ */
85
+ export declare function createSavedViewOptions(args: CreateSavedViewOptionsParams): (close: () => void) => ReactElement[];
86
+ interface RenameProps extends CommonOptionProps {
87
+ /** Invoked after triggering the rename action. Often used to close the tile dropdown menu. */
88
+ onClick?: (() => void) | undefined;
89
+ }
90
+ declare function Rename(props: RenameProps): ReactElement;
91
+ interface MoveToGroupProps extends CommonOptionProps {
92
+ /** Available Saved View groups to choose from. */
93
+ groups: SavedViewGroup[];
94
+ /**
95
+ * Invoked when user selects a group from the supplied {@linkcode groups} list.
96
+ * @param savedViewId Id of the associated Saved View.
97
+ * @param groupId Id of the user-selected group.
98
+ */
99
+ moveToGroup: (savedViewId: string, groupId: string) => void;
100
+ /**
101
+ * When set, allows creation of new Saved View groups. Invoked when user submits a name for the new group.
102
+ * @param savedViewId Id of the associated Saved View.
103
+ * @param groupName User-submitted string for the new Saved View group.
104
+ */
105
+ moveToNewGroup?: ((savedViewId: string, groupName: string) => void) | undefined;
106
+ }
107
+ declare function MoveToGroup(props: MoveToGroupProps): ReactElement;
108
+ interface ManageTagsProps extends CommonOptionProps {
109
+ /** Available Saved View tags to choose from. */
110
+ tags: SavedViewTag[];
111
+ /**
112
+ * Invoked when user selects a tag from the supplied {@linkcode tags} list.
113
+ * @param savedViewId Id of the associated Saved View.
114
+ * @param tagId Id of the user-selected tag.
115
+ */
116
+ addTag: (savedViewId: string, tagId: string) => void;
117
+ /**
118
+ * When set, allows creation of new Saved View tags. Invoked when user submits a name for the new tag.
119
+ * @param savedViewId Id of the associated Saved View.
120
+ * @param tagName User-submitted string for the new Saved View tag.
121
+ */
122
+ addNewTag?: ((savedViewId: string, tagName: string) => void) | undefined;
123
+ /**
124
+ * Invoked when user selects a tag from the supplied {@linkcode tags} list.
125
+ * @param savedViewId Id of the associated Saved View.
126
+ * @param tagId Id of the user-selected tag.
127
+ */
128
+ removeTag: (savedViewId: string, tagId: string) => void;
129
+ }
130
+ declare function ManageTags(props: ManageTagsProps): ReactElement;
131
+ interface DeleteProps extends CommonOptionProps {
132
+ deleteSavedView: (savedViewId: string) => void;
133
+ }
134
+ declare function Delete(props: DeleteProps): ReactElement;
135
+ interface CommonOptionProps {
136
+ /** Icon to the left of this menu item. */
137
+ icon?: ReactElement | undefined;
138
+ }
139
+ export {};
@@ -0,0 +1,173 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ import { SvgAdd, SvgBlank, SvgCheckmarkSmall, SvgRename, SvgShare } from "@itwin/itwinui-icons-react";
7
+ import { Input, MenuItem, Text } from "@itwin/itwinui-react";
8
+ import Fuse from "fuse.js";
9
+ import { useMemo, useState } from "react";
10
+ import { LayeredMenuItem } from "../LayeredDropdownMenu/LayeredDropdownMenu.js";
11
+ import { useSavedViewsContext } from "../SavedViewsContext.js";
12
+ import { trimInputString } from "../utils.js";
13
+ import { useSavedViewTileContext } from "./SavedViewTileContext.js";
14
+ import "./SavedViewOptions.css";
15
+ /**
16
+ * A collection of useful menu items for {@linkcode SavedViewTile}.
17
+ *
18
+ * @example
19
+ * <SavedViewTile
20
+ * savedView={savedView}
21
+ * options={(close) => [<SavedViewOptions.Rename key="rename" icon={<SvgBlank />} onClick={close} />]}
22
+ * onRename={handleRename}
23
+ * />
24
+ */
25
+ export const SavedViewOptions = {
26
+ /**
27
+ * When activated, makes Saved View title enter editable state. Has no effect if the {@linkcode SavedViewTile} does
28
+ * not receive `onRename` prop.
29
+ *
30
+ * @example
31
+ * <SavedViewTile
32
+ * savedView={savedView}
33
+ * options={(close) => [<SavedViewOptions.Rename key="rename" icon={<SvgBlank />} onClick={close} />]}
34
+ * onRename={handleRename}
35
+ * />
36
+ */
37
+ Rename,
38
+ /**
39
+ * Displays a control for moving Saved View to a group.
40
+ *
41
+ * @example
42
+ * <SavedViewOptions.MoveToGroup
43
+ * groups={groups}
44
+ * moveToGroup={handleMoveToGroup}
45
+ * moveToNewGroup={handleMoveTo}
46
+ * icon={<SvgBlank />}
47
+ * />
48
+ */
49
+ MoveToGroup,
50
+ /**
51
+ * Displays a control for managing Saved View tags.
52
+ *
53
+ * @example
54
+ * <SavedViewOptions.ManageTags
55
+ * tags={tags}
56
+ * addTag={handleAddTag}
57
+ * addNewTag={handleAddNewTag}
58
+ * removeTag={handleRemoveTag}
59
+ * icon={<SvgBlank />}
60
+ * />
61
+ */
62
+ ManageTags,
63
+ /**
64
+ * Displays option for Saved View deletion.
65
+ *
66
+ * @example
67
+ * <SavedViewOptions.Delete
68
+ * icon={<SvgBlank />}
69
+ * deleteSavedView={handleDeleteSavedView}
70
+ * />
71
+ */
72
+ Delete,
73
+ };
74
+ /**
75
+ * Convenience function that returns an array populated with options to be used on {@linkcode SavedViewTile}. Available
76
+ * options are determined by what data is present in the {@linkcode args} argument.
77
+ *
78
+ * @example
79
+ * <SavedViewTile
80
+ * savedView={savedView}
81
+ * options={createSavedViewOptions({ tagActions, groupActions })}
82
+ * editable
83
+ * />
84
+ */
85
+ export function createSavedViewOptions(args) {
86
+ return (close) => {
87
+ const options = [];
88
+ if (args.renameSavedView) {
89
+ options.push(_jsx(SavedViewOptions.Rename, { icon: _jsx(SvgRename, {}), onClick: close }, "rename"));
90
+ }
91
+ if (args.groupActions && args.groupActions) {
92
+ options.push(_jsx(SavedViewOptions.MoveToGroup, { groups: args.groupActions.groups, moveToGroup: args.groupActions.moveToGroup, moveToNewGroup: args.groupActions.moveToNewGroup, icon: _jsx(SvgBlank, {}) }, "move"));
93
+ }
94
+ if (args.tagActions && args.tagActions) {
95
+ options.push(_jsx(SavedViewOptions.ManageTags, { tags: args.tagActions.tags, addTag: args.tagActions.addTag, addNewTag: args.tagActions.addNewTag, removeTag: args.tagActions.removeTag, icon: _jsx(SvgBlank, {}) }, "tags"));
96
+ }
97
+ if (args.deleteSavedView) {
98
+ options.push(_jsx(SavedViewOptions.Delete, { icon: _jsx(SvgBlank, {}), deleteSavedView: args.deleteSavedView }, "delete"));
99
+ }
100
+ return options;
101
+ };
102
+ }
103
+ function Rename(props) {
104
+ const { setEditingName } = useSavedViewTileContext();
105
+ const { localization } = useSavedViewsContext();
106
+ const handleClick = () => {
107
+ setEditingName(true);
108
+ props.onClick?.();
109
+ };
110
+ return (_jsx(MenuItem, { startIcon: props.icon, onClick: handleClick, children: localization.rename }));
111
+ }
112
+ function MoveToGroup(props) {
113
+ const { savedView } = useSavedViewTileContext();
114
+ const { localization } = useSavedViewsContext();
115
+ return (_jsx(LayeredMenuItem, { icon: _jsx(SvgBlank, {}), content: _jsx(MoveToGroupSubmenu, { savedView: savedView, groups: props.groups, moveToGroup: props.moveToGroup, moveToNewGroup: props.moveToNewGroup }, "move"), children: localization.moveToGroupMenu.moveToGroup }));
116
+ }
117
+ function MoveToGroupSubmenu(props) {
118
+ const { localization: { moveToGroupMenu } } = useSavedViewsContext();
119
+ const handleMoveToGroup = (groupId) => {
120
+ props.moveToGroup(props.savedView.id, groupId);
121
+ };
122
+ const { moveToNewGroup } = props;
123
+ const handleCreate = moveToNewGroup && ((groupName) => {
124
+ moveToNewGroup(props.savedView.id, groupName);
125
+ });
126
+ return (_jsx(SearchableSubmenu, { collection: props.groups, placeholder: props.moveToNewGroup ? moveToGroupMenu.findOrCreateGroup : moveToGroupMenu.findGroup, creationLabel: moveToGroupMenu.createGroup, onCreate: handleCreate, children: (searchResults) => searchResults.map((group) => (_jsxs(MenuItem, { className: "svr-searchable-submenu-item", startIcon: group.shared ? _jsx(SvgShare, {}) : _jsx(SvgBlank, {}), onClick: () => handleMoveToGroup(group.id), disabled: group.id === props.savedView.groupId, children: [group.displayName, group.id === props.savedView.groupId &&
127
+ _jsx(Text, { style: { marginLeft: "var(--iui-size-xs)" }, as: "span", children: moveToGroupMenu.current })] }, group.id))) }));
128
+ }
129
+ function ManageTags(props) {
130
+ const { savedView } = useSavedViewTileContext();
131
+ const { localization } = useSavedViewsContext();
132
+ return (_jsx(LayeredMenuItem, { icon: props.icon, content: _jsx(ManageTagsSubmenu, { savedView: savedView, tags: props.tags, addTag: props.addTag, addNewTag: props.addNewTag, removeTag: props.removeTag }, "tags"), children: localization.tagsMenu.tags }));
133
+ }
134
+ function ManageTagsSubmenu(props) {
135
+ const { localization } = useSavedViewsContext();
136
+ const handleTagClick = (tagId) => {
137
+ if (props.savedView.tagIds?.includes(tagId)) {
138
+ props.removeTag(props.savedView.id, tagId);
139
+ }
140
+ else {
141
+ props.addTag(props.savedView.id, tagId);
142
+ }
143
+ };
144
+ const { addNewTag } = props;
145
+ const handleCreate = addNewTag && ((tagName) => {
146
+ addNewTag(props.savedView.id, tagName);
147
+ });
148
+ return (_jsx(SearchableSubmenu, { collection: props.tags, placeholder: props.addNewTag ? localization.tagsMenu.findOrCreateTag : localization.tagsMenu.findTag, creationLabel: localization.tagsMenu.createTag, onCreate: handleCreate, children: (searchResults) => searchResults.map((tag) => (_jsx(MenuItem, { className: "svr-searchable-submenu-item", startIcon: props.savedView.tagIds?.includes(tag.id) ? _jsx(SvgCheckmarkSmall, {}) : _jsx(SvgBlank, {}), onClick: () => handleTagClick(tag.id), children: tag.displayName }, tag.id))) }));
149
+ }
150
+ function SearchableSubmenu(props) {
151
+ const { localization } = useSavedViewsContext();
152
+ const fuse = useMemo(() => new Fuse(props.collection, { keys: ["displayName"], threshold: 0.5 }), [props.collection]);
153
+ const [inputValue, setInputValue] = useState("");
154
+ const searchResults = useMemo(() => inputValue.length === 0 ? props.collection : fuse.search(inputValue, { limit: 6 }).map(({ item }) => item), [inputValue, props.collection, fuse]);
155
+ const handleSearchChange = (event) => {
156
+ setInputValue(event.target.value);
157
+ };
158
+ const { onCreate } = props;
159
+ const trimmedValue = trimInputString(inputValue);
160
+ const offerCreate = onCreate && trimmedValue && !searchResults.find((result) => result.displayName === trimmedValue);
161
+ const handleCreate = onCreate && (() => {
162
+ setInputValue("");
163
+ onCreate(trimmedValue);
164
+ });
165
+ return (_jsxs("div", { className: "svr-searchable-submenu", children: [_jsx(Input, { className: "svr-searchable-submenu-item", placeholder: props.placeholder, value: inputValue, onChange: handleSearchChange }), _jsxs("div", { children: [offerCreate &&
166
+ _jsxs(MenuItem, { className: "svr-searchable-submenu-item", startIcon: _jsx(SvgAdd, {}), onClick: handleCreate, children: [props.creationLabel, "\u00A0", _jsx(Text, { className: "svr-semibold", as: "span", children: trimmedValue })] }), props.children(searchResults), searchResults.length === 0 &&
167
+ _jsx(MenuItem, { sublabel: localization.searchableMenu.noSearchResults, size: "default", disabled: true })] })] }));
168
+ }
169
+ function Delete(props) {
170
+ const { savedView } = useSavedViewTileContext();
171
+ const { localization } = useSavedViewsContext();
172
+ return (_jsx(MenuItem, { startIcon: props.icon, onClick: () => props.deleteSavedView(savedView.id), children: localization.delete }));
173
+ }