@equinor/fusion-framework-react-components-bookmark 2.0.1 → 2.0.3
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 +18 -0
- package/README.md +69 -0
- package/dist/esm/components/Bookmark.js +12 -2
- package/dist/esm/components/Bookmark.js.map +1 -1
- package/dist/esm/components/BookmarkProvider.js +16 -0
- package/dist/esm/components/BookmarkProvider.js.map +1 -1
- package/dist/esm/components/create-bookmark/{CreateBookmark.js → CreateBookmarkModal.js} +13 -5
- package/dist/esm/components/create-bookmark/CreateBookmarkModal.js.map +1 -0
- package/dist/esm/components/create-bookmark/index.js +1 -1
- package/dist/esm/components/create-bookmark/index.js.map +1 -1
- package/dist/esm/components/edit-bookmark/AppNameField.js +10 -0
- package/dist/esm/components/edit-bookmark/AppNameField.js.map +1 -0
- package/dist/esm/components/edit-bookmark/{EditBookmark.js → EditBookmarkModal.js} +22 -6
- package/dist/esm/components/edit-bookmark/EditBookmarkModal.js.map +1 -0
- package/dist/esm/components/edit-bookmark/index.js +1 -1
- package/dist/esm/components/edit-bookmark/index.js.map +1 -1
- package/dist/esm/components/filter/{Filter.js → BookmarkFilter.js} +9 -3
- package/dist/esm/components/filter/BookmarkFilter.js.map +1 -0
- package/dist/esm/components/import-bookmark/{ImportBookmark.js → ImportBookmarkModal.js} +13 -1
- package/dist/esm/components/import-bookmark/ImportBookmarkModal.js.map +1 -0
- package/dist/esm/components/import-bookmark/index.js +1 -1
- package/dist/esm/components/import-bookmark/index.js.map +1 -1
- package/dist/esm/components/loading/Loading.js +5 -0
- package/dist/esm/components/loading/Loading.js.map +1 -1
- package/dist/esm/components/messages/Message.js +7 -0
- package/dist/esm/components/messages/Message.js.map +1 -1
- package/dist/esm/components/row/MoreMenu.js +15 -7
- package/dist/esm/components/row/MoreMenu.js.map +1 -1
- package/dist/esm/components/row/Row.js +9 -4
- package/dist/esm/components/row/Row.js.map +1 -1
- package/dist/esm/components/section/Section.js +6 -0
- package/dist/esm/components/section/Section.js.map +1 -1
- package/dist/esm/components/sectionList/SectionList.js +16 -5
- package/dist/esm/components/sectionList/SectionList.js.map +1 -1
- package/dist/esm/components/shared/SharedIcon.js +7 -1
- package/dist/esm/components/shared/SharedIcon.js.map +1 -1
- package/dist/esm/hooks/useBookmarkGrouping.js +23 -10
- package/dist/esm/hooks/useBookmarkGrouping.js.map +1 -1
- package/dist/esm/index.js +10 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/{append-bookmark-to-uri.js → append-bookmark-id-to-url.js} +1 -1
- package/dist/esm/utils/append-bookmark-id-to-url.js.map +1 -0
- package/dist/esm/utils/filter-empty-groups.js +4 -0
- package/dist/esm/utils/filter-empty-groups.js.map +1 -0
- package/dist/esm/utils/sort-by-name.js +2 -0
- package/dist/esm/utils/sort-by-name.js.map +1 -0
- package/dist/esm/utils/to-human-readable.js +6 -0
- package/dist/esm/utils/to-human-readable.js.map +1 -0
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/components/Bookmark.d.ts +6 -1
- package/dist/types/components/BookmarkProvider.d.ts +16 -1
- package/dist/types/components/create-bookmark/CreateBookmarkModal.d.ts +12 -0
- package/dist/types/components/create-bookmark/index.d.ts +1 -1
- package/dist/types/components/edit-bookmark/AppNameField.d.ts +11 -0
- package/dist/types/components/edit-bookmark/EditBookmarkModal.d.ts +14 -0
- package/dist/types/components/edit-bookmark/index.d.ts +1 -1
- package/dist/types/components/filter/BookmarkFilter.d.ts +16 -0
- package/dist/types/components/import-bookmark/ImportBookmarkModal.d.ts +6 -0
- package/dist/types/components/import-bookmark/index.d.ts +1 -1
- package/dist/types/components/loading/Loading.d.ts +6 -1
- package/dist/types/components/messages/Message.d.ts +7 -1
- package/dist/types/components/row/MoreMenu.d.ts +7 -1
- package/dist/types/components/row/Row.d.ts +7 -1
- package/dist/types/components/section/Section.d.ts +7 -1
- package/dist/types/components/sectionList/SectionList.d.ts +7 -1
- package/dist/types/components/shared/SharedIcon.d.ts +7 -1
- package/dist/types/hooks/useBookmarkGrouping.d.ts +6 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/utils/filter-empty-groups.d.ts +5 -0
- package/dist/types/utils/sort-by-name.d.ts +2 -0
- package/dist/types/utils/to-human-readable.d.ts +1 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +11 -9
- package/src/__tests__/AppNameField.test.tsx +39 -0
- package/src/components/Bookmark.tsx +12 -5
- package/src/components/BookmarkProvider.tsx +16 -0
- package/src/components/create-bookmark/{CreateBookmark.tsx → CreateBookmarkModal.tsx} +12 -4
- package/src/components/create-bookmark/index.ts +1 -1
- package/src/components/edit-bookmark/AppNameField.tsx +27 -0
- package/src/components/edit-bookmark/{EditBookmark.tsx → EditBookmarkModal.tsx} +28 -7
- package/src/components/edit-bookmark/index.ts +1 -1
- package/src/components/filter/{Filter.tsx → BookmarkFilter.tsx} +11 -5
- package/src/components/import-bookmark/{ImportBookmark.tsx → ImportBookmarkModal.tsx} +12 -0
- package/src/components/import-bookmark/index.ts +1 -1
- package/src/components/loading/Loading.tsx +5 -1
- package/src/components/messages/Message.tsx +7 -0
- package/src/components/row/MoreMenu.tsx +24 -17
- package/src/components/row/Row.tsx +8 -1
- package/src/components/section/Section.tsx +6 -0
- package/src/components/sectionList/SectionList.tsx +16 -5
- package/src/components/shared/SharedIcon.tsx +6 -0
- package/src/hooks/useBookmarkGrouping.ts +29 -18
- package/src/index.ts +10 -0
- package/src/utils/filter-empty-groups.ts +5 -0
- package/src/utils/sort-by-name.ts +4 -0
- package/src/utils/to-human-readable.ts +6 -0
- package/src/version.ts +1 -1
- package/tsconfig.json +1 -1
- package/vitest.config.ts +10 -0
- package/dist/esm/components/create-bookmark/CreateBookmark.js.map +0 -1
- package/dist/esm/components/edit-bookmark/EditBookmark.js.map +0 -1
- package/dist/esm/components/filter/Filter.js.map +0 -1
- package/dist/esm/components/import-bookmark/ImportBookmark.js.map +0 -1
- package/dist/esm/utils/append-bookmark-to-uri.js.map +0 -1
- package/dist/esm/utils/utils.js +0 -9
- package/dist/esm/utils/utils.js.map +0 -1
- package/dist/types/components/create-bookmark/CreateBookmark.d.ts +0 -4
- package/dist/types/components/edit-bookmark/EditBookmark.d.ts +0 -5
- package/dist/types/components/filter/Filter.d.ts +0 -10
- package/dist/types/components/import-bookmark/ImportBookmark.d.ts +0 -1
- package/dist/types/utils/utils.d.ts +0 -7
- package/src/utils/utils.ts +0 -13
- /package/dist/types/utils/{append-bookmark-to-uri.d.ts → append-bookmark-id-to-url.d.ts} +0 -0
- /package/src/utils/{append-bookmark-to-uri.ts → append-bookmark-id-to-url.ts} +0 -0
|
@@ -1,2 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Renders the list of bookmark sections and the current bookmark's loading/message state.
|
|
3
|
+
*
|
|
4
|
+
* @returns The bookmark list UI
|
|
5
|
+
*/
|
|
6
|
+
export declare const Bookmark: () => import("react").JSX.Element;
|
|
2
7
|
export default Bookmark;
|
|
@@ -22,8 +22,23 @@ type BookmarkProviderProps = {
|
|
|
22
22
|
readonly currentApp?: BookmarkApp;
|
|
23
23
|
readonly currentUser?: BookmarkUser;
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Hook for accessing the bookmark component context provided by `BookmarkProvider`.
|
|
27
|
+
*
|
|
28
|
+
* @returns The current bookmark provider state
|
|
29
|
+
*/
|
|
25
30
|
export declare const useBookmarkComponentContext: () => ProviderState;
|
|
26
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Provides bookmark state and modals (create/edit/import) to descendant bookmark components.
|
|
33
|
+
*
|
|
34
|
+
* @param props - The provider's props
|
|
35
|
+
* @param props.provider - The bookmark provider used to fetch/manage bookmarks
|
|
36
|
+
* @param props.currentApp - The app the bookmarks belong to
|
|
37
|
+
* @param props.currentUser - The current user, used to determine bookmark ownership
|
|
38
|
+
* @param props.children - The descendant components that can access the bookmark context
|
|
39
|
+
* @returns The provider with its context and modals
|
|
40
|
+
*/
|
|
41
|
+
export declare const BookmarkProvider: (props: PropsWithChildren<BookmarkProviderProps>) => import("react").JSX.Element;
|
|
27
42
|
declare module '@equinor/fusion-framework-module-event' {
|
|
28
43
|
interface FrameworkEventMap {
|
|
29
44
|
onBookmarkEdit: FrameworkEvent<FrameworkEventInit<{
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal for creating a new bookmark.
|
|
3
|
+
*
|
|
4
|
+
* @param props - The component's props
|
|
5
|
+
* @param props.isOpen - Whether the modal is open
|
|
6
|
+
* @param props.onClose - Callback invoked to close the modal
|
|
7
|
+
* @returns The create bookmark modal
|
|
8
|
+
*/
|
|
9
|
+
export declare const CreateBookmarkModal: ({ isOpen, onClose, }: {
|
|
10
|
+
readonly isOpen: boolean;
|
|
11
|
+
readonly onClose: (b: boolean) => void;
|
|
12
|
+
}) => import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { CreateBookmarkModal } from './
|
|
1
|
+
export { CreateBookmarkModal } from './CreateBookmarkModal';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the bookmark's app name while making manifest resolution visible.
|
|
3
|
+
*
|
|
4
|
+
* @param props - The resolved display name and whether the manifest request is pending.
|
|
5
|
+
* @returns A read-only app name field with an accessible loading indicator when needed.
|
|
6
|
+
*/
|
|
7
|
+
export declare const AppNameField: ({ id, displayName, isLoading, }: {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly displayName?: string;
|
|
10
|
+
readonly isLoading: boolean;
|
|
11
|
+
}) => import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Modal for editing an existing bookmark.
|
|
3
|
+
*
|
|
4
|
+
* @param props - The component's props
|
|
5
|
+
* @param props.isOpen - Whether the modal is open
|
|
6
|
+
* @param props.onClose - Callback invoked to close the modal
|
|
7
|
+
* @param props.bookmarkId - The id of the bookmark to edit
|
|
8
|
+
* @returns The edit bookmark modal
|
|
9
|
+
*/
|
|
10
|
+
export declare const EditBookmarkModal: ({ isOpen, onClose, bookmarkId, }: {
|
|
11
|
+
readonly isOpen: boolean;
|
|
12
|
+
readonly onClose: (b: boolean) => void;
|
|
13
|
+
readonly bookmarkId: string;
|
|
14
|
+
}) => import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './EditBookmarkModal';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GroupingKeys } from '../../hooks/useBookmarkGrouping';
|
|
2
|
+
type BookmarkFilterProps = {
|
|
3
|
+
readonly searchText: string;
|
|
4
|
+
readonly setSearchText: (newVal: string | null) => void;
|
|
5
|
+
readonly setGroupBy: (groupBy: GroupingKeys) => void;
|
|
6
|
+
readonly groupingModes: string[];
|
|
7
|
+
readonly groupBy: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Search and grouping filter controls for the bookmark list.
|
|
11
|
+
*
|
|
12
|
+
* @param props - The component's props
|
|
13
|
+
* @returns The filter controls
|
|
14
|
+
*/
|
|
15
|
+
export declare const BookmarkFilter: ({ searchText, setGroupBy: _setGroupBy, setSearchText, groupingModes: _groupingModes, groupBy: _groupBy, }: BookmarkFilterProps) => import("react").JSX.Element;
|
|
16
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './ImportBookmarkModal';
|
|
@@ -6,5 +6,11 @@ type MessageProps = {
|
|
|
6
6
|
readonly type?: PortalMessageType;
|
|
7
7
|
readonly color?: string;
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Displays a titled message with an icon matching its type.
|
|
11
|
+
*
|
|
12
|
+
* @param props - The component's props
|
|
13
|
+
* @returns The message
|
|
14
|
+
*/
|
|
15
|
+
export declare const Message: ({ title, type, color, children, }: PropsWithChildren<MessageProps>) => import("react").JSX.Element;
|
|
10
16
|
export {};
|
|
@@ -6,5 +6,11 @@ type MenuProps = {
|
|
|
6
6
|
readonly open: boolean;
|
|
7
7
|
readonly options: MenuOption[];
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* A dropdown menu of row actions anchored to a reference element.
|
|
11
|
+
*
|
|
12
|
+
* @param props - The component's props
|
|
13
|
+
* @returns The menu
|
|
14
|
+
*/
|
|
15
|
+
export declare const MoreMenu: ({ pRef, onClose, open, options }: MenuProps) => import("react").JSX.Element;
|
|
10
16
|
export {};
|
|
@@ -13,5 +13,11 @@ type RowProps = {
|
|
|
13
13
|
readonly menuOptions: MenuOption[];
|
|
14
14
|
readonly children?: ReactNode;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A single bookmark row, with actions and click-to-set-current behavior.
|
|
18
|
+
*
|
|
19
|
+
* @param props - The component's props
|
|
20
|
+
* @returns The row
|
|
21
|
+
*/
|
|
22
|
+
export declare const Row: ({ name, menuOptions, children, id, menuOpen, onMenuOpen }: RowProps) => import("react").JSX.Element;
|
|
17
23
|
export {};
|
|
@@ -3,5 +3,11 @@ type SectionProps = {
|
|
|
3
3
|
readonly name: string;
|
|
4
4
|
readonly children: ReactNode;
|
|
5
5
|
};
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* A collapsible section with a named header and its contents.
|
|
8
|
+
*
|
|
9
|
+
* @param props - The component's props
|
|
10
|
+
* @returns The section
|
|
11
|
+
*/
|
|
12
|
+
export declare const Section: ({ children, name }: SectionProps) => import("react").JSX.Element;
|
|
7
13
|
export {};
|
|
@@ -2,5 +2,11 @@ import type { useBookmarkGrouping } from '../../hooks';
|
|
|
2
2
|
type SectionListProps = {
|
|
3
3
|
readonly bookmarkGroups: ReturnType<typeof useBookmarkGrouping>['bookmarkGroups'];
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Renders bookmark groups as sections of rows with edit/share/delete actions.
|
|
7
|
+
*
|
|
8
|
+
* @param props - The component's props
|
|
9
|
+
* @returns The section list
|
|
10
|
+
*/
|
|
11
|
+
export declare const SectionList: ({ bookmarkGroups }: SectionListProps) => import("react").JSX.Element;
|
|
6
12
|
export {};
|
|
@@ -2,5 +2,11 @@ type SharedIconProps = {
|
|
|
2
2
|
readonly createdBy: string;
|
|
3
3
|
readonly createdById: string;
|
|
4
4
|
};
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Displays a "shared by" indicator when a bookmark was created by another user.
|
|
7
|
+
*
|
|
8
|
+
* @param props - The component's props
|
|
9
|
+
* @returns The shared icon, or nothing when the current user created the bookmark
|
|
10
|
+
*/
|
|
11
|
+
export declare const SharedIcon: ({ createdBy, createdById }: SharedIconProps) => import("react").JSX.Element;
|
|
6
12
|
export {};
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Bookmarks, BookmarkWithoutData } from '@equinor/fusion-framework-module-bookmark';
|
|
2
2
|
export type GroupingKeys = 'Group by app' | 'Created by' | 'Group by Context';
|
|
3
|
+
/**
|
|
4
|
+
* Hook for grouping and searching bookmarks by app, creator, or context.
|
|
5
|
+
*
|
|
6
|
+
* @param bookmarks - The bookmarks to group and search
|
|
7
|
+
* @returns The grouped bookmarks along with the search text and grouping key state/setters
|
|
8
|
+
*/
|
|
3
9
|
export declare const useBookmarkGrouping: (bookmarks?: Bookmarks) => {
|
|
4
10
|
setGroupBy: import("react").Dispatch<import("react").SetStateAction<GroupingKeys>>;
|
|
5
11
|
setSearchText: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-built React UI components for bookmark management.
|
|
3
|
+
*
|
|
4
|
+
* Provides the {@link Bookmark} list component and {@link BookmarkProvider}
|
|
5
|
+
* context wrapper with built-in create, edit, and import modals plus filtering controls.
|
|
6
|
+
*
|
|
7
|
+
* For bookmark hooks without UI, see `@equinor/fusion-framework-react-app/bookmark`.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
1
11
|
export { Bookmark } from './components/Bookmark';
|
|
2
12
|
export { BookmarkProvider, useBookmarkComponentContext } from './components/BookmarkProvider';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const toHumanReadable: (val: string) => string;
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.
|
|
1
|
+
export declare const version = "2.0.3";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-react-components-bookmark",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"rxjs": "^7.8.1",
|
|
26
|
-
"@equinor/fusion-
|
|
27
|
-
"@equinor/fusion-framework-react": "^
|
|
28
|
-
"@equinor/fusion-
|
|
26
|
+
"@equinor/fusion-framework-react": "^8.0.1",
|
|
27
|
+
"@equinor/fusion-framework-react-module-bookmark": "^6.0.1",
|
|
28
|
+
"@equinor/fusion-observable": "^9.1.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@equinor/eds-core-react": "^2.3.7",
|
|
@@ -35,10 +35,11 @@
|
|
|
35
35
|
"@types/react": "^19.2.7",
|
|
36
36
|
"react": "^19.2.1",
|
|
37
37
|
"styled-components": "^6.3.11",
|
|
38
|
-
"typescript": "^
|
|
39
|
-
"
|
|
40
|
-
"@equinor/fusion-framework-module-
|
|
41
|
-
"@equinor/fusion-framework-module-
|
|
38
|
+
"typescript": "^7.0.2",
|
|
39
|
+
"vitest": "^4.1.0",
|
|
40
|
+
"@equinor/fusion-framework-module-app": "^8.0.3",
|
|
41
|
+
"@equinor/fusion-framework-module-event": "^6.0.1",
|
|
42
|
+
"@equinor/fusion-framework-module-bookmark": "^4.0.3"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"@equinor/eds-core-react": "^2.0.0",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
"scripts": {
|
|
58
|
-
"build": "tsc -b"
|
|
59
|
+
"build": "tsc -b",
|
|
60
|
+
"test": "vitest --run"
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isValidElement } from 'react';
|
|
2
|
+
import { Input, Progress } from '@equinor/eds-core-react';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { AppNameField } from '../components/edit-bookmark/AppNameField';
|
|
5
|
+
|
|
6
|
+
describe('AppNameField', () => {
|
|
7
|
+
it('shows an accessible progress indicator while the manifest is loading', () => {
|
|
8
|
+
const field = AppNameField({ id: 'app', isLoading: true });
|
|
9
|
+
const loadingIndicator = field.props.rightAdornments;
|
|
10
|
+
|
|
11
|
+
expect(field.type).toBe(Input);
|
|
12
|
+
expect(field.props.id).toBe('app');
|
|
13
|
+
expect(field.props['aria-busy']).toBe(true);
|
|
14
|
+
expect(isValidElement(loadingIndicator)).toBe(true);
|
|
15
|
+
|
|
16
|
+
// Guard the element shape before inspecting progress-specific props.
|
|
17
|
+
if (!isValidElement(loadingIndicator)) {
|
|
18
|
+
throw new Error('Expected a valid loading indicator');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
expect(loadingIndicator.type).toBe(Progress.Circular);
|
|
22
|
+
expect(loadingIndicator.props).toMatchObject({
|
|
23
|
+
'aria-label': 'Loading app name',
|
|
24
|
+
size: 16,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('shows the resolved display name without a progress indicator', () => {
|
|
29
|
+
const field = AppNameField({
|
|
30
|
+
id: 'app',
|
|
31
|
+
displayName: 'My app',
|
|
32
|
+
isLoading: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
expect(field.props.value).toBe('My app');
|
|
36
|
+
expect(field.props['aria-busy']).toBe(false);
|
|
37
|
+
expect(field.props.rightAdornments).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EMPTY, map } from 'rxjs';
|
|
2
2
|
import { useBookmarkGrouping } from '../hooks';
|
|
3
|
-
import { BookmarkFilter } from './filter/
|
|
3
|
+
import { BookmarkFilter } from './filter/BookmarkFilter';
|
|
4
4
|
import { SectionList } from './sectionList/SectionList';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
6
|
|
|
@@ -42,6 +42,11 @@ const Styled = {
|
|
|
42
42
|
`,
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Renders the list of bookmark sections and the current bookmark's loading/message state.
|
|
47
|
+
*
|
|
48
|
+
* @returns The bookmark list UI
|
|
49
|
+
*/
|
|
45
50
|
export const Bookmark = () => {
|
|
46
51
|
const { provider } = useBookmarkComponentContext();
|
|
47
52
|
|
|
@@ -50,10 +55,10 @@ export const Bookmark = () => {
|
|
|
50
55
|
);
|
|
51
56
|
|
|
52
57
|
const { value: isLoading } = useObservableState(
|
|
53
|
-
useMemo(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
),
|
|
58
|
+
useMemo(() => {
|
|
59
|
+
// Derive a boolean loading flag from whether 'fetch_bookmarks' is an active status
|
|
60
|
+
return (provider?.status$ || EMPTY).pipe(map((status) => !!status.has('fetch_bookmarks')));
|
|
61
|
+
}, [provider]),
|
|
57
62
|
{ initial: true },
|
|
58
63
|
);
|
|
59
64
|
|
|
@@ -61,9 +66,11 @@ export const Bookmark = () => {
|
|
|
61
66
|
useBookmarkGrouping(bookmarks);
|
|
62
67
|
|
|
63
68
|
const content = useMemo(() => {
|
|
69
|
+
// Show the loading placeholder while bookmarks are still being fetched
|
|
64
70
|
if (isLoading) {
|
|
65
71
|
return <Loading />;
|
|
66
72
|
}
|
|
73
|
+
// Show an empty-state message when there are no bookmark groups to display
|
|
67
74
|
if (bookmarkGroups.length === 0) {
|
|
68
75
|
return (
|
|
69
76
|
<Message title="No Bookmarks" type="NoContent">
|
|
@@ -33,9 +33,24 @@ type BookmarkProviderProps = {
|
|
|
33
33
|
readonly currentUser?: BookmarkUser;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Hook for accessing the bookmark component context provided by `BookmarkProvider`.
|
|
38
|
+
*
|
|
39
|
+
* @returns The current bookmark provider state
|
|
40
|
+
*/
|
|
36
41
|
export const useBookmarkComponentContext = () =>
|
|
37
42
|
useContext(bookmarkProviderContext) as ProviderState;
|
|
38
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Provides bookmark state and modals (create/edit/import) to descendant bookmark components.
|
|
46
|
+
*
|
|
47
|
+
* @param props - The provider's props
|
|
48
|
+
* @param props.provider - The bookmark provider used to fetch/manage bookmarks
|
|
49
|
+
* @param props.currentApp - The app the bookmarks belong to
|
|
50
|
+
* @param props.currentUser - The current user, used to determine bookmark ownership
|
|
51
|
+
* @param props.children - The descendant components that can access the bookmark context
|
|
52
|
+
* @returns The provider with its context and modals
|
|
53
|
+
*/
|
|
39
54
|
export const BookmarkProvider = (props: PropsWithChildren<BookmarkProviderProps>) => {
|
|
40
55
|
const { provider, currentApp, currentUser, children } = props;
|
|
41
56
|
|
|
@@ -55,6 +70,7 @@ export const BookmarkProvider = (props: PropsWithChildren<BookmarkProviderProps>
|
|
|
55
70
|
setSnackbarContent('Bookmark url copied to clipboard');
|
|
56
71
|
}, []);
|
|
57
72
|
|
|
73
|
+
// Render children without a live provider context when no provider is configured
|
|
58
74
|
if (!provider) {
|
|
59
75
|
return (
|
|
60
76
|
<bookmarkProviderContext.Provider
|
|
@@ -7,13 +7,20 @@ import { Button, Checkbox, Dialog, Input, Label, Textarea } from '@equinor/eds-c
|
|
|
7
7
|
import styled from 'styled-components';
|
|
8
8
|
import { from } from 'rxjs';
|
|
9
9
|
|
|
10
|
-
// TODO
|
|
11
10
|
const StyledContent = styled(Dialog.Content)`
|
|
12
11
|
display: flex;
|
|
13
12
|
flex-direction: column;
|
|
14
13
|
gap: 1rem;
|
|
15
14
|
`;
|
|
16
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Modal for creating a new bookmark.
|
|
18
|
+
*
|
|
19
|
+
* @param props - The component's props
|
|
20
|
+
* @param props.isOpen - Whether the modal is open
|
|
21
|
+
* @param props.onClose - Callback invoked to close the modal
|
|
22
|
+
* @returns The create bookmark modal
|
|
23
|
+
*/
|
|
17
24
|
export const CreateBookmarkModal = ({
|
|
18
25
|
isOpen,
|
|
19
26
|
onClose,
|
|
@@ -38,13 +45,14 @@ export const CreateBookmarkModal = ({
|
|
|
38
45
|
|
|
39
46
|
const createBookmark = useCallback(
|
|
40
47
|
async (args: BookmarkCreateArgs<never>) => {
|
|
48
|
+
// Cannot create a bookmark without a provider to persist it
|
|
41
49
|
if (!provider) {
|
|
42
50
|
console.error('Provider not available');
|
|
43
51
|
return;
|
|
44
52
|
}
|
|
45
|
-
// TODO: Show success message
|
|
46
|
-
// TODO: should this call onCreated, with the new bookmark?
|
|
47
|
-
// TODO: should current bookmark be updated?
|
|
53
|
+
// TODO(#5089): Show success message
|
|
54
|
+
// TODO(#5090): should this call onCreated, with the new bookmark?
|
|
55
|
+
// TODO(#5090): should current bookmark be updated?
|
|
48
56
|
const sub = from(provider.createBookmark(args)).subscribe({
|
|
49
57
|
next: (bookmark) => {
|
|
50
58
|
console.debug('Bookmark created', bookmark);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { CreateBookmarkModal } from './
|
|
1
|
+
export { CreateBookmarkModal } from './CreateBookmarkModal';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Input, Progress } from '@equinor/eds-core-react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Renders the bookmark's app name while making manifest resolution visible.
|
|
5
|
+
*
|
|
6
|
+
* @param props - The resolved display name and whether the manifest request is pending.
|
|
7
|
+
* @returns A read-only app name field with an accessible loading indicator when needed.
|
|
8
|
+
*/
|
|
9
|
+
export const AppNameField = ({
|
|
10
|
+
id,
|
|
11
|
+
displayName,
|
|
12
|
+
isLoading,
|
|
13
|
+
}: {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly displayName?: string;
|
|
16
|
+
readonly isLoading: boolean;
|
|
17
|
+
}) => (
|
|
18
|
+
<Input
|
|
19
|
+
id={id}
|
|
20
|
+
readOnly={true}
|
|
21
|
+
value={displayName ?? ''}
|
|
22
|
+
aria-busy={isLoading}
|
|
23
|
+
rightAdornments={
|
|
24
|
+
isLoading ? <Progress.Circular aria-label="Loading app name" size={16} /> : undefined
|
|
25
|
+
}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
@@ -12,6 +12,7 @@ import { Button, Checkbox, Dialog, Input, Label, Textarea } from '@equinor/eds-c
|
|
|
12
12
|
import styled from 'styled-components';
|
|
13
13
|
|
|
14
14
|
import { useBookmarkComponentContext } from '../BookmarkProvider';
|
|
15
|
+
import { AppNameField } from './AppNameField';
|
|
15
16
|
|
|
16
17
|
const Styled = {
|
|
17
18
|
Dialog: styled(Dialog)`
|
|
@@ -32,6 +33,15 @@ const Styled = {
|
|
|
32
33
|
`,
|
|
33
34
|
};
|
|
34
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Modal for editing an existing bookmark.
|
|
38
|
+
*
|
|
39
|
+
* @param props - The component's props
|
|
40
|
+
* @param props.isOpen - Whether the modal is open
|
|
41
|
+
* @param props.onClose - Callback invoked to close the modal
|
|
42
|
+
* @param props.bookmarkId - The id of the bookmark to edit
|
|
43
|
+
* @returns The edit bookmark modal
|
|
44
|
+
*/
|
|
35
45
|
export const EditBookmarkModal = ({
|
|
36
46
|
isOpen,
|
|
37
47
|
onClose,
|
|
@@ -51,6 +61,7 @@ export const EditBookmarkModal = ({
|
|
|
51
61
|
|
|
52
62
|
const nameId = useId();
|
|
53
63
|
const descriptionId = useId();
|
|
64
|
+
const appId = useId();
|
|
54
65
|
|
|
55
66
|
const [updatePayload, setUpdatePayload] = useState(false);
|
|
56
67
|
|
|
@@ -63,23 +74,29 @@ export const EditBookmarkModal = ({
|
|
|
63
74
|
|
|
64
75
|
// set the state when the bookmark is loaded
|
|
65
76
|
useEffect(() => {
|
|
77
|
+
// Only populate local state once the bookmark has actually loaded
|
|
66
78
|
if (bookmark) {
|
|
67
79
|
const { name, description, isShared } = bookmark;
|
|
68
80
|
setState({ name, description, isShared });
|
|
69
81
|
}
|
|
70
82
|
}, [bookmark]);
|
|
71
83
|
|
|
72
|
-
// TODO
|
|
84
|
+
// TODO(#5091): this should be on the bookmark object
|
|
73
85
|
const appProvider = useFrameworkModule<AppModule>('app');
|
|
74
|
-
const { value: appName } = useObservableState(
|
|
86
|
+
const { value: appName, error: appNameError } = useObservableState(
|
|
75
87
|
useMemo(
|
|
76
88
|
() => (bookmark && appProvider ? appProvider.getAppManifest(bookmark.appKey) : of(undefined)),
|
|
77
89
|
[appProvider, bookmark],
|
|
78
90
|
),
|
|
79
91
|
);
|
|
92
|
+
// Only report a pending manifest request; missing providers and failed requests are not loading.
|
|
93
|
+
const isAppNameLoading = Boolean(
|
|
94
|
+
bookmark && appProvider && appName === undefined && appNameError === null,
|
|
95
|
+
);
|
|
80
96
|
|
|
81
97
|
const updateBookmark = useCallback(
|
|
82
98
|
async (updates: BookmarkUpdate) => {
|
|
99
|
+
// Cannot update a bookmark without a provider to persist the change
|
|
83
100
|
if (!provider) {
|
|
84
101
|
console.error('Provider not available');
|
|
85
102
|
return;
|
|
@@ -99,8 +116,8 @@ export const EditBookmarkModal = ({
|
|
|
99
116
|
onClose(false);
|
|
100
117
|
},
|
|
101
118
|
});
|
|
102
|
-
// TODO: Show success message
|
|
103
|
-
// TODO: should this call onUpdated, with the updated bookmark?
|
|
119
|
+
// TODO(#5089): Show success message
|
|
120
|
+
// TODO(#5090): should this call onUpdated, with the updated bookmark?
|
|
104
121
|
onClose(false);
|
|
105
122
|
},
|
|
106
123
|
[onClose, provider, bookmarkId, updatePayload],
|
|
@@ -134,9 +151,12 @@ export const EditBookmarkModal = ({
|
|
|
134
151
|
/>
|
|
135
152
|
</div>
|
|
136
153
|
<div>
|
|
137
|
-
<Label htmlFor=
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
<Label htmlFor={appId} label="App" />
|
|
155
|
+
<AppNameField
|
|
156
|
+
id={appId}
|
|
157
|
+
displayName={appName?.displayName}
|
|
158
|
+
isLoading={isAppNameLoading}
|
|
159
|
+
/>
|
|
140
160
|
</div>
|
|
141
161
|
|
|
142
162
|
<Styled.CheckboxWrapper>
|
|
@@ -145,6 +165,7 @@ export const EditBookmarkModal = ({
|
|
|
145
165
|
checked={state.isShared}
|
|
146
166
|
onChange={(changeEvent: ChangeEvent<HTMLInputElement>) => {
|
|
147
167
|
const isShared = changeEvent.target.checked;
|
|
168
|
+
// Copy the bookmark URL when the user shares it so it's easy to send along
|
|
148
169
|
if (isShared) {
|
|
149
170
|
addBookmarkToClipboard(bookmarkId);
|
|
150
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './EditBookmarkModal';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Search
|
|
1
|
+
import { Search } from '@equinor/eds-core-react';
|
|
2
2
|
import type { GroupingKeys } from '../../hooks/useBookmarkGrouping';
|
|
3
3
|
|
|
4
4
|
import styled from 'styled-components';
|
|
@@ -11,7 +11,7 @@ type BookmarkFilterProps = {
|
|
|
11
11
|
readonly groupBy: string;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const _Styled = {
|
|
15
15
|
Root: styled.div`
|
|
16
16
|
display: flex;
|
|
17
17
|
align-items: center;
|
|
@@ -19,12 +19,18 @@ const Styled = {
|
|
|
19
19
|
`,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Search and grouping filter controls for the bookmark list.
|
|
24
|
+
*
|
|
25
|
+
* @param props - The component's props
|
|
26
|
+
* @returns The filter controls
|
|
27
|
+
*/
|
|
22
28
|
export const BookmarkFilter = ({
|
|
23
29
|
searchText,
|
|
24
|
-
setGroupBy,
|
|
30
|
+
setGroupBy: _setGroupBy,
|
|
25
31
|
setSearchText,
|
|
26
|
-
groupingModes,
|
|
27
|
-
groupBy,
|
|
32
|
+
groupingModes: _groupingModes,
|
|
33
|
+
groupBy: _groupBy,
|
|
28
34
|
}: BookmarkFilterProps) => {
|
|
29
35
|
return (
|
|
30
36
|
<Search
|