@equinor/fusion-framework-react-components-bookmark 2.0.4 → 2.0.5

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 (33) hide show
  1. package/dist/esm/version.js +1 -1
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/dist/types/version.d.ts +1 -1
  4. package/package.json +10 -7
  5. package/CHANGELOG.md +0 -1108
  6. package/src/__tests__/AppNameField.test.tsx +0 -39
  7. package/src/components/Bookmark.tsx +0 -98
  8. package/src/components/BookmarkProvider.tsx +0 -129
  9. package/src/components/create-bookmark/CreateBookmarkModal.tsx +0 -130
  10. package/src/components/create-bookmark/index.ts +0 -1
  11. package/src/components/edit-bookmark/AppNameField.tsx +0 -27
  12. package/src/components/edit-bookmark/EditBookmarkModal.tsx +0 -203
  13. package/src/components/edit-bookmark/index.ts +0 -1
  14. package/src/components/filter/BookmarkFilter.tsx +0 -44
  15. package/src/components/import-bookmark/ImportBookmarkModal.tsx +0 -100
  16. package/src/components/import-bookmark/index.ts +0 -1
  17. package/src/components/loading/Loading.tsx +0 -20
  18. package/src/components/messages/Message.tsx +0 -89
  19. package/src/components/row/MoreMenu.tsx +0 -41
  20. package/src/components/row/Row.tsx +0 -105
  21. package/src/components/section/Section.tsx +0 -63
  22. package/src/components/sectionList/SectionList.tsx +0 -186
  23. package/src/components/shared/SharedIcon.tsx +0 -51
  24. package/src/hooks/index.ts +0 -1
  25. package/src/hooks/useBookmarkGrouping.ts +0 -65
  26. package/src/index.ts +0 -12
  27. package/src/utils/append-bookmark-id-to-url.ts +0 -5
  28. package/src/utils/filter-empty-groups.ts +0 -5
  29. package/src/utils/sort-by-name.ts +0 -4
  30. package/src/utils/to-human-readable.ts +0 -6
  31. package/src/version.ts +0 -2
  32. package/tsconfig.json +0 -18
  33. package/vitest.config.ts +0 -10
@@ -1,39 +0,0 @@
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,98 +0,0 @@
1
- import { EMPTY, map } from 'rxjs';
2
- import { useBookmarkGrouping } from '../hooks';
3
- import { BookmarkFilter } from './filter/BookmarkFilter';
4
- import { SectionList } from './sectionList/SectionList';
5
- import { useMemo } from 'react';
6
-
7
- import { Icon } from '@equinor/eds-core-react';
8
- import { chevron_down, chevron_right, share, more_vertical, add } from '@equinor/eds-icons';
9
-
10
- import styled from 'styled-components';
11
- import { Message } from './messages/Message';
12
- import { Loading } from './loading/Loading';
13
- import { useBookmarkComponentContext } from './BookmarkProvider';
14
- import { useObservableState } from '@equinor/fusion-observable/react';
15
-
16
- Icon.add({
17
- chevron_down,
18
- chevron_right,
19
- share,
20
- more_vertical,
21
- add,
22
- });
23
-
24
- const Styled = {
25
- Wrapper: styled.div`
26
- padding-right: 1rem;
27
- display: flex;
28
- flex-direction: column;
29
- gap: 1rem;
30
- `,
31
- List: styled.div`
32
- overflow-y: auto;
33
- overflow-x: hidden;
34
- height: calc((100vh - 85px) - 3rem);
35
- `,
36
- NoContentWrapper: styled.div`
37
- position: absolute;
38
- top: 150px;
39
- bottom: 200px;
40
- left: 0px;
41
- right: 0px;
42
- `,
43
- };
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
- */
50
- export const Bookmark = () => {
51
- const { provider } = useBookmarkComponentContext();
52
-
53
- const { value: bookmarks } = useObservableState(
54
- useMemo(() => provider?.bookmarks$ || EMPTY, [provider]),
55
- );
56
-
57
- const { value: isLoading } = useObservableState(
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]),
62
- { initial: true },
63
- );
64
-
65
- const { bookmarkGroups, groupingModes, searchText, setGroupBy, setSearchText, groupByKey } =
66
- useBookmarkGrouping(bookmarks);
67
-
68
- const content = useMemo(() => {
69
- // Show the loading placeholder while bookmarks are still being fetched
70
- if (isLoading) {
71
- return <Loading />;
72
- }
73
- // Show an empty-state message when there are no bookmark groups to display
74
- if (bookmarkGroups.length === 0) {
75
- return (
76
- <Message title="No Bookmarks" type="NoContent">
77
- You have not created any bookmarks yet.
78
- </Message>
79
- );
80
- }
81
- return <SectionList bookmarkGroups={bookmarkGroups} />;
82
- }, [isLoading, bookmarkGroups]);
83
-
84
- return (
85
- <Styled.Wrapper>
86
- <BookmarkFilter
87
- groupBy={groupByKey}
88
- groupingModes={Object.keys(groupingModes)}
89
- searchText={searchText ?? ''}
90
- setGroupBy={setGroupBy}
91
- setSearchText={setSearchText}
92
- />
93
- <Styled.List>{content}</Styled.List>
94
- </Styled.Wrapper>
95
- );
96
- };
97
-
98
- export default Bookmark;
@@ -1,129 +0,0 @@
1
- import { Snackbar } from '@equinor/eds-core-react';
2
- import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framework-module-event';
3
- import { type PropsWithChildren, createContext, useCallback, useContext, useState } from 'react';
4
- import { CreateBookmarkModal } from './create-bookmark';
5
- import { EditBookmarkModal } from './edit-bookmark';
6
- import { ImportBookmarkModal } from './import-bookmark';
7
- import type { IBookmarkProvider } from '@equinor/fusion-framework-module-bookmark';
8
-
9
- type BookmarkApp = {
10
- appKey: string;
11
- name?: string;
12
- };
13
-
14
- type BookmarkUser = {
15
- id: string;
16
- name?: string;
17
- };
18
-
19
- type ProviderState = {
20
- provider?: IBookmarkProvider;
21
- currentApp?: BookmarkApp | null;
22
- currentUser?: BookmarkUser | null;
23
- showCreateBookmark: () => void;
24
- showEditBookmark: (bookmarkId: string) => void;
25
- addBookmarkToClipboard: (bookmarkId: string) => void;
26
- };
27
-
28
- const bookmarkProviderContext = createContext<ProviderState | null>(null);
29
-
30
- type BookmarkProviderProps = {
31
- readonly provider?: IBookmarkProvider;
32
- readonly currentApp?: BookmarkApp;
33
- readonly currentUser?: BookmarkUser;
34
- };
35
-
36
- /**
37
- * Hook for accessing the bookmark component context provided by `BookmarkProvider`.
38
- *
39
- * @returns The current bookmark provider state
40
- */
41
- export const useBookmarkComponentContext = () =>
42
- useContext(bookmarkProviderContext) as ProviderState;
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
- */
54
- export const BookmarkProvider = (props: PropsWithChildren<BookmarkProviderProps>) => {
55
- const { provider, currentApp, currentUser, children } = props;
56
-
57
- const [isCreateBookmarkOpen, setIsCreateBookmarkOpen] = useState(false);
58
- const [editBookmarkId, setEditBookmarkId] = useState<string | undefined>();
59
-
60
- const showCreateBookmark = useCallback(() => {
61
- setIsCreateBookmarkOpen(true);
62
- }, []);
63
-
64
- const [snackbarContent, setSnackbarContent] = useState('');
65
-
66
- const addBookmarkToClipboard = useCallback((bookmarkId: string) => {
67
- const url = new URL(window.location.toString());
68
- url.searchParams.set('bookmarkId', bookmarkId);
69
- navigator.clipboard.writeText(String(url));
70
- setSnackbarContent('Bookmark url copied to clipboard');
71
- }, []);
72
-
73
- // Render children without a live provider context when no provider is configured
74
- if (!provider) {
75
- return (
76
- <bookmarkProviderContext.Provider
77
- value={{
78
- provider: undefined,
79
- currentApp,
80
- currentUser,
81
- showCreateBookmark,
82
- showEditBookmark: setEditBookmarkId,
83
- addBookmarkToClipboard,
84
- }}
85
- >
86
- {children}
87
- </bookmarkProviderContext.Provider>
88
- );
89
- }
90
-
91
- return (
92
- <bookmarkProviderContext.Provider
93
- value={{
94
- provider,
95
- currentApp,
96
- currentUser,
97
- showCreateBookmark,
98
- showEditBookmark: setEditBookmarkId,
99
- addBookmarkToClipboard,
100
- }}
101
- >
102
- <CreateBookmarkModal isOpen={isCreateBookmarkOpen} onClose={setIsCreateBookmarkOpen} />
103
- {editBookmarkId && (
104
- <EditBookmarkModal
105
- isOpen={!!editBookmarkId}
106
- onClose={() => setEditBookmarkId(undefined)}
107
- bookmarkId={editBookmarkId}
108
- />
109
- )}
110
- <ImportBookmarkModal />
111
- <Snackbar
112
- autoHideDuration={2000}
113
- onClose={() => setSnackbarContent('')}
114
- open={!!snackbarContent}
115
- >
116
- {snackbarContent}
117
- </Snackbar>
118
- {children}
119
- </bookmarkProviderContext.Provider>
120
- );
121
- };
122
-
123
- declare module '@equinor/fusion-framework-module-event' {
124
- interface FrameworkEventMap {
125
- // onBookmarkOpen: FrameworkEvent<FrameworkEventInit<boolean, unknown>>;
126
- onBookmarkEdit: FrameworkEvent<FrameworkEventInit<{ bookmarkId: string }, unknown>>;
127
- onBookmarkUrlCopy: FrameworkEvent<FrameworkEventInit<{ url: string }, unknown>>;
128
- }
129
- }
@@ -1,130 +0,0 @@
1
- import { type ChangeEvent, useCallback, useEffect, useId, useState } from 'react';
2
-
3
- import type { BookmarkCreateArgs } from '@equinor/fusion-framework-module-bookmark';
4
- import { useBookmarkComponentContext } from '../BookmarkProvider';
5
-
6
- import { Button, Checkbox, Dialog, Input, Label, Textarea } from '@equinor/eds-core-react';
7
- import styled from 'styled-components';
8
- import { from } from 'rxjs';
9
-
10
- const StyledContent = styled(Dialog.Content)`
11
- display: flex;
12
- flex-direction: column;
13
- gap: 1rem;
14
- `;
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
- */
24
- export const CreateBookmarkModal = ({
25
- isOpen,
26
- onClose,
27
- }: {
28
- readonly isOpen: boolean;
29
- readonly onClose: (b: boolean) => void;
30
- }) => {
31
- const { provider, currentApp } = useBookmarkComponentContext();
32
-
33
- const [state, setState] = useState<BookmarkCreateArgs<never>>({
34
- name: '',
35
- description: '',
36
- isShared: false,
37
- });
38
-
39
- const nameId = useId();
40
- const descriptionId = useId();
41
-
42
- useEffect(() => {
43
- setState((s) => ({ ...s, appKey: currentApp?.appKey || '' }));
44
- }, [currentApp]);
45
-
46
- const createBookmark = useCallback(
47
- async (args: BookmarkCreateArgs<never>) => {
48
- // Cannot create a bookmark without a provider to persist it
49
- if (!provider) {
50
- console.error('Provider not available');
51
- return;
52
- }
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?
56
- const sub = from(provider.createBookmark(args)).subscribe({
57
- next: (bookmark) => {
58
- console.debug('Bookmark created', bookmark);
59
- },
60
- error: (error) => {
61
- console.error('Failed to create bookmark', error);
62
- },
63
- complete() {
64
- onClose(false);
65
- },
66
- });
67
- return () => sub.unsubscribe();
68
- },
69
- [onClose, provider],
70
- );
71
-
72
- return (
73
- <Dialog style={{ width: '400px' }} open={isOpen}>
74
- <Dialog.Header>Create bookmark</Dialog.Header>
75
- <StyledContent>
76
- <div>
77
- <Label htmlFor={nameId} label="Name" />
78
- <Input
79
- id={nameId}
80
- autoComplete="off"
81
- value={state?.name}
82
- onChange={(event: ChangeEvent<HTMLInputElement>) => {
83
- setState((s) => ({ ...s, name: event.target.value }));
84
- }}
85
- />
86
- </div>
87
- <div>
88
- <Textarea
89
- id={descriptionId}
90
- label="Description"
91
- rows={3}
92
- rowsMax={10}
93
- onChange={(event: ChangeEvent<HTMLTextAreaElement>) => {
94
- setState((s) => ({ ...s, description: event.target.value }));
95
- }}
96
- />
97
- </div>
98
- <div>
99
- <Label htmlFor="app" label="App" />
100
- <Input readOnly={true} value={currentApp?.name || currentApp?.appKey || ''} />
101
- </div>
102
-
103
- <div>
104
- <Checkbox
105
- label="Is Shared"
106
- checked={state.isShared}
107
- onChange={(event: ChangeEvent<HTMLInputElement>) => {
108
- setState((s) => ({ ...s, isShared: event.target.checked }));
109
- }}
110
- />
111
- </div>
112
- </StyledContent>
113
- <Dialog.Actions>
114
- <div style={{ display: 'flex', gap: '0.2em' }}>
115
- <Button onClick={() => onClose(false)} variant="ghost">
116
- Cancel
117
- </Button>
118
- <Button
119
- disabled={!currentApp || !state.name}
120
- onClick={() => {
121
- createBookmark(state);
122
- }}
123
- >
124
- Create
125
- </Button>
126
- </div>
127
- </Dialog.Actions>
128
- </Dialog>
129
- );
130
- };
@@ -1 +0,0 @@
1
- export { CreateBookmarkModal } from './CreateBookmarkModal';
@@ -1,27 +0,0 @@
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
- );
@@ -1,203 +0,0 @@
1
- import { type ChangeEvent, useCallback, useEffect, useId, useMemo, useState } from 'react';
2
-
3
- import { EMPTY, from, of } from 'rxjs';
4
-
5
- import { useObservableState } from '@equinor/fusion-observable/react';
6
-
7
- import { useFrameworkModule } from '@equinor/fusion-framework-react';
8
- import type { AppModule } from '@equinor/fusion-framework-module-app';
9
- import type { BookmarkUpdate } from '@equinor/fusion-framework-module-bookmark';
10
-
11
- import { Button, Checkbox, Dialog, Input, Label, Textarea } from '@equinor/eds-core-react';
12
- import styled from 'styled-components';
13
-
14
- import { useBookmarkComponentContext } from '../BookmarkProvider';
15
- import { AppNameField } from './AppNameField';
16
-
17
- const Styled = {
18
- Dialog: styled(Dialog)`
19
- width: 500px;
20
- `,
21
- DialogContent: styled(Dialog.Content)`
22
- display: flex;
23
- flex-direction: column;
24
- gap: 1rem;
25
- `,
26
- CheckboxWrapper: styled.div`
27
- display: flex;
28
- gap: 1rem;
29
- `,
30
- Actions: styled.div`
31
- display: flex;
32
- gap: 0.2em;
33
- `,
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
- */
45
- export const EditBookmarkModal = ({
46
- isOpen,
47
- onClose,
48
- bookmarkId,
49
- }: {
50
- readonly isOpen: boolean;
51
- readonly onClose: (b: boolean) => void;
52
- readonly bookmarkId: string;
53
- }) => {
54
- const { provider, addBookmarkToClipboard, currentApp } = useBookmarkComponentContext();
55
-
56
- const [state, setState] = useState<BookmarkUpdate>({
57
- name: '',
58
- description: '',
59
- isShared: false,
60
- });
61
-
62
- const nameId = useId();
63
- const descriptionId = useId();
64
- const appId = useId();
65
-
66
- const [updatePayload, setUpdatePayload] = useState(false);
67
-
68
- const bookmark$ = useMemo(
69
- () => from(provider ? provider.getBookmark(bookmarkId) : EMPTY),
70
- [provider, bookmarkId],
71
- );
72
-
73
- const { value: bookmark } = useObservableState(bookmark$);
74
-
75
- // set the state when the bookmark is loaded
76
- useEffect(() => {
77
- // Only populate local state once the bookmark has actually loaded
78
- if (bookmark) {
79
- const { name, description, isShared } = bookmark;
80
- setState({ name, description, isShared });
81
- }
82
- }, [bookmark]);
83
-
84
- // TODO(#5091): this should be on the bookmark object
85
- const appProvider = useFrameworkModule<AppModule>('app');
86
- const { value: appName, error: appNameError } = useObservableState(
87
- useMemo(
88
- () => (bookmark && appProvider ? appProvider.getAppManifest(bookmark.appKey) : of(undefined)),
89
- [appProvider, bookmark],
90
- ),
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
- );
96
-
97
- const updateBookmark = useCallback(
98
- async (updates: BookmarkUpdate) => {
99
- // Cannot update a bookmark without a provider to persist the change
100
- if (!provider) {
101
- console.error('Provider not available');
102
- return;
103
- }
104
- from(
105
- provider.updateBookmark(bookmarkId, updates, {
106
- excludePayloadGeneration: !updatePayload,
107
- }),
108
- ).subscribe({
109
- next: (updatedBookmark) => {
110
- console.debug('Bookmark updated', updatedBookmark);
111
- },
112
- error: (error) => {
113
- console.error('Failed to update bookmark', error);
114
- },
115
- complete: () => {
116
- onClose(false);
117
- },
118
- });
119
- // TODO(#5089): Show success message
120
- // TODO(#5090): should this call onUpdated, with the updated bookmark?
121
- onClose(false);
122
- },
123
- [onClose, provider, bookmarkId, updatePayload],
124
- );
125
-
126
- return (
127
- <Styled.Dialog open={isOpen}>
128
- <Dialog.Header>Edit bookmark</Dialog.Header>
129
- <Styled.DialogContent>
130
- <div>
131
- <Label htmlFor={nameId} label="Name" />
132
- <Input
133
- id={nameId}
134
- autoComplete="off"
135
- value={state?.name}
136
- onChange={(event: ChangeEvent<HTMLInputElement>) => {
137
- setState((s) => ({ ...s, name: event.target.value }));
138
- }}
139
- />
140
- </div>
141
- <div>
142
- <Textarea
143
- id={descriptionId}
144
- label="Description"
145
- value={state?.description}
146
- rows={3}
147
- rowsMax={10}
148
- onChange={(event: ChangeEvent<HTMLTextAreaElement>) => {
149
- setState((s) => ({ ...s, description: event.target.value }));
150
- }}
151
- />
152
- </div>
153
- <div>
154
- <Label htmlFor={appId} label="App" />
155
- <AppNameField
156
- id={appId}
157
- displayName={appName?.displayName}
158
- isLoading={isAppNameLoading}
159
- />
160
- </div>
161
-
162
- <Styled.CheckboxWrapper>
163
- <Checkbox
164
- label="Is Shared"
165
- checked={state.isShared}
166
- onChange={(changeEvent: ChangeEvent<HTMLInputElement>) => {
167
- const isShared = changeEvent.target.checked;
168
- // Copy the bookmark URL when the user shares it so it's easy to send along
169
- if (isShared) {
170
- addBookmarkToClipboard(bookmarkId);
171
- }
172
- setState((s) => ({ ...s, isShared }));
173
- }}
174
- />
175
- {/* only allow updating payload if the app is the same as the creator of the app */}
176
- {bookmark?.appKey === currentApp?.name && provider?.canCreateBookmarks && (
177
- <Checkbox
178
- label="Update bookmark with current view"
179
- checked={updatePayload}
180
- onChange={() => {
181
- setUpdatePayload((s) => !s);
182
- }}
183
- />
184
- )}
185
- </Styled.CheckboxWrapper>
186
- </Styled.DialogContent>
187
- <Dialog.Actions>
188
- <Styled.Actions>
189
- <Button onClick={() => onClose(false)} variant="ghost">
190
- Cancel
191
- </Button>
192
- <Button
193
- onClick={() => {
194
- updateBookmark(state);
195
- }}
196
- >
197
- Save
198
- </Button>
199
- </Styled.Actions>
200
- </Dialog.Actions>
201
- </Styled.Dialog>
202
- );
203
- };
@@ -1 +0,0 @@
1
- export * from './EditBookmarkModal';
@@ -1,44 +0,0 @@
1
- import { Search } from '@equinor/eds-core-react';
2
- import type { GroupingKeys } from '../../hooks/useBookmarkGrouping';
3
-
4
- import styled from 'styled-components';
5
-
6
- type BookmarkFilterProps = {
7
- readonly searchText: string;
8
- readonly setSearchText: (newVal: string | null) => void;
9
- readonly setGroupBy: (groupBy: GroupingKeys) => void;
10
- readonly groupingModes: string[];
11
- readonly groupBy: string;
12
- };
13
-
14
- const _Styled = {
15
- Root: styled.div`
16
- display: flex;
17
- align-items: center;
18
- justify-content: space-between;
19
- `,
20
- };
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
- */
28
- export const BookmarkFilter = ({
29
- searchText,
30
- setGroupBy: _setGroupBy,
31
- setSearchText,
32
- groupingModes: _groupingModes,
33
- groupBy: _groupBy,
34
- }: BookmarkFilterProps) => {
35
- return (
36
- <Search
37
- placeholder="Search in my bookmarks"
38
- value={searchText ?? ''}
39
- onChange={(e) => {
40
- setSearchText(e.currentTarget.value.length ? e.currentTarget.value : null);
41
- }}
42
- />
43
- );
44
- };