@elementor/editor-site-navigation 0.7.0 → 0.8.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 CHANGED
@@ -3,6 +3,28 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.8.1](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.8.0...@elementor/editor-site-navigation@0.8.1) (2023-06-25)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * comment out pages panel [ED-10871] ([#64](https://github.com/elementor/elementor-packages/issues/64)) ([3a8b890](https://github.com/elementor/elementor-packages/commit/3a8b890d8f21ac816d3c0b961b37372bcb91caff))
12
+
13
+
14
+
15
+
16
+
17
+ # [0.8.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.7.0...@elementor/editor-site-navigation@0.8.0) (2023-06-25)
18
+
19
+
20
+ ### Features
21
+
22
+ * **site-navigation:** pages navigation panel UI [ED-10871] ([#51](https://github.com/elementor/elementor-packages/issues/51)) ([594427c](https://github.com/elementor/elementor-packages/commit/594427c3c296fe664577319bba29bf711f6bbdde))
23
+
24
+
25
+
26
+
27
+
6
28
  # [0.7.0](https://github.com/elementor/elementor-packages/compare/@elementor/editor-site-navigation@0.6.2...@elementor/editor-site-navigation@0.7.0) (2023-06-25)
7
29
 
8
30
 
package/dist/index.js CHANGED
@@ -248,6 +248,84 @@ function RecentlyEdited() {
248
248
 
249
249
  // src/init.ts
250
250
  var import_editor_app_bar = require("@elementor/editor-app-bar");
251
+ var import_editor = require("@elementor/editor");
252
+
253
+ // src/components/panel/shell.tsx
254
+ var React18 = __toESM(require("react"));
255
+ var import_ui12 = require("@elementor/ui");
256
+ var import_icons13 = require("@elementor/icons");
257
+
258
+ // src/components/panel/pages-list/pages-collapsible-list.tsx
259
+ var React17 = __toESM(require("react"));
260
+
261
+ // src/components/panel/pages-list/collapsible-list.tsx
262
+ var React6 = __toESM(require("react"));
263
+ var import_react4 = require("react");
264
+ var import_ui6 = require("@elementor/ui");
265
+ var import_icons5 = require("@elementor/icons");
266
+ var RotateIcon = (0, import_ui6.styled)(import_icons5.ChevronDownIcon, {
267
+ shouldForwardProp: (prop) => prop !== "isOpen"
268
+ })(({ theme, isOpen }) => ({
269
+ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)",
270
+ transition: theme.transitions.create("transform", {
271
+ duration: theme.transitions.duration.standard
272
+ })
273
+ }));
274
+
275
+ // src/components/panel/pages-list/pages-collapsible-list.tsx
276
+ var import_icons12 = require("@elementor/icons");
277
+
278
+ // src/components/panel/pages-list/page-list-item.tsx
279
+ var React16 = __toESM(require("react"));
280
+ var import_ui11 = require("@elementor/ui");
281
+ var import_icons11 = require("@elementor/icons");
282
+ var import_editor_documents5 = require("@elementor/editor-documents");
283
+
284
+ // src/components/shared/page-title-and-status.tsx
285
+ var React7 = __toESM(require("react"));
286
+ var import_ui7 = require("@elementor/ui");
287
+
288
+ // src/components/panel/actions-menu/page-actions-menu.tsx
289
+ var React15 = __toESM(require("react"));
290
+ var import_ui10 = require("@elementor/ui");
291
+
292
+ // src/components/panel/pages-actions/rename.tsx
293
+ var React10 = __toESM(require("react"));
294
+ var import_icons6 = require("@elementor/icons");
295
+ var import_i18n3 = require("@wordpress/i18n");
296
+
297
+ // src/components/panel/actions-menu/action-menu-item.tsx
298
+ var React9 = __toESM(require("react"));
299
+
300
+ // src/components/panel/actions-menu/action-list-item.tsx
301
+ var React8 = __toESM(require("react"));
302
+ var import_ui8 = require("@elementor/ui");
303
+
304
+ // src/components/panel/actions-menu/action-menu-item.tsx
305
+ var import_ui9 = require("@elementor/ui");
306
+
307
+ // src/components/panel/pages-actions/duplicate.tsx
308
+ var React11 = __toESM(require("react"));
309
+ var import_i18n4 = require("@wordpress/i18n");
310
+ var import_icons7 = require("@elementor/icons");
311
+
312
+ // src/components/panel/pages-actions/delete.tsx
313
+ var React12 = __toESM(require("react"));
314
+ var import_icons8 = require("@elementor/icons");
315
+ var import_editor_documents4 = require("@elementor/editor-documents");
316
+ var import_i18n5 = require("@wordpress/i18n");
317
+
318
+ // src/components/panel/pages-actions/view.tsx
319
+ var React13 = __toESM(require("react"));
320
+ var import_icons9 = require("@elementor/icons");
321
+ var import_i18n6 = require("@wordpress/i18n");
322
+
323
+ // src/components/panel/pages-actions/set-home.tsx
324
+ var React14 = __toESM(require("react"));
325
+ var import_icons10 = require("@elementor/icons");
326
+ var import_i18n7 = require("@wordpress/i18n");
327
+
328
+ // src/init.ts
251
329
  function init() {
252
330
  registerTopBarMenuItems();
253
331
  }
package/dist/index.mjs CHANGED
@@ -230,6 +230,93 @@ function RecentlyEdited() {
230
230
 
231
231
  // src/init.ts
232
232
  import { injectIntoPageIndication } from "@elementor/editor-app-bar";
233
+ import { injectIntoTop } from "@elementor/editor";
234
+
235
+ // src/components/panel/shell.tsx
236
+ import * as React18 from "react";
237
+ import { Box as Box3, Button as Button2, Divider as Divider3, Grid, List as List2, Paper, ThemeProvider, Typography as Typography4 } from "@elementor/ui";
238
+ import { PlusIcon as PlusIcon2 } from "@elementor/icons";
239
+
240
+ // src/components/panel/pages-list/pages-collapsible-list.tsx
241
+ import * as React17 from "react";
242
+
243
+ // src/components/panel/pages-list/collapsible-list.tsx
244
+ import * as React6 from "react";
245
+ import { useState as useState3 } from "react";
246
+ import { Collapse, IconButton, List, ListItem, ListItemIcon as ListItemIcon2, ListItemText, styled } from "@elementor/ui";
247
+ import { ChevronDownIcon as ChevronDownIcon2 } from "@elementor/icons";
248
+ var RotateIcon = styled(ChevronDownIcon2, {
249
+ shouldForwardProp: (prop) => prop !== "isOpen"
250
+ })(({ theme, isOpen }) => ({
251
+ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)",
252
+ transition: theme.transitions.create("transform", {
253
+ duration: theme.transitions.duration.standard
254
+ })
255
+ }));
256
+
257
+ // src/components/panel/pages-list/pages-collapsible-list.tsx
258
+ import { PageTypeIcon as PageTypeIcon2 } from "@elementor/icons";
259
+
260
+ // src/components/panel/pages-list/page-list-item.tsx
261
+ import * as React16 from "react";
262
+ import {
263
+ bindMenu as bindMenu2,
264
+ bindTrigger as bindTrigger2,
265
+ ListItem as ListItem2,
266
+ ListItemButton as ListItemButton2,
267
+ ListItemIcon as ListItemIcon4,
268
+ ListItemText as ListItemText3,
269
+ ToggleButton,
270
+ usePopupState as usePopupState2
271
+ } from "@elementor/ui";
272
+ import { DotsVerticalIcon, HomeIcon as HomeIcon2 } from "@elementor/icons";
273
+ import { useActiveDocument as useActiveDocument3, useNavigateToDocument as useNavigateToDocument3 } from "@elementor/editor-documents";
274
+
275
+ // src/components/shared/page-title-and-status.tsx
276
+ import * as React7 from "react";
277
+ import { Box as Box2, Typography as Typography3 } from "@elementor/ui";
278
+
279
+ // src/components/panel/actions-menu/page-actions-menu.tsx
280
+ import * as React15 from "react";
281
+ import { Divider as Divider2, Menu as Menu2 } from "@elementor/ui";
282
+
283
+ // src/components/panel/pages-actions/rename.tsx
284
+ import * as React10 from "react";
285
+ import { EraseIcon } from "@elementor/icons";
286
+ import { __ as __3 } from "@wordpress/i18n";
287
+
288
+ // src/components/panel/actions-menu/action-menu-item.tsx
289
+ import * as React9 from "react";
290
+
291
+ // src/components/panel/actions-menu/action-list-item.tsx
292
+ import * as React8 from "react";
293
+ import { ListItemButton, ListItemIcon as ListItemIcon3, ListItemText as ListItemText2 } from "@elementor/ui";
294
+
295
+ // src/components/panel/actions-menu/action-menu-item.tsx
296
+ import { MenuItem as MenuItem3 } from "@elementor/ui";
297
+
298
+ // src/components/panel/pages-actions/duplicate.tsx
299
+ import * as React11 from "react";
300
+ import { __ as __4 } from "@wordpress/i18n";
301
+ import { CopyIcon } from "@elementor/icons";
302
+
303
+ // src/components/panel/pages-actions/delete.tsx
304
+ import * as React12 from "react";
305
+ import { TrashIcon } from "@elementor/icons";
306
+ import { useActiveDocument as useActiveDocument2 } from "@elementor/editor-documents";
307
+ import { __ as __5 } from "@wordpress/i18n";
308
+
309
+ // src/components/panel/pages-actions/view.tsx
310
+ import * as React13 from "react";
311
+ import { EyeIcon } from "@elementor/icons";
312
+ import { __ as __6 } from "@wordpress/i18n";
313
+
314
+ // src/components/panel/pages-actions/set-home.tsx
315
+ import * as React14 from "react";
316
+ import { HomeIcon } from "@elementor/icons";
317
+ import { __ as __7 } from "@wordpress/i18n";
318
+
319
+ // src/init.ts
233
320
  function init() {
234
321
  registerTopBarMenuItems();
235
322
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elementor/editor-site-navigation",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "private": false,
5
5
  "author": "Elementor Team",
6
6
  "homepage": "https://elementor.com/",
@@ -32,10 +32,11 @@
32
32
  "dev": "tsup --config=../../tsup.dev.ts"
33
33
  },
34
34
  "dependencies": {
35
- "@elementor/editor-app-bar": "^0.6.2",
35
+ "@elementor/editor": "^0.6.1",
36
+ "@elementor/editor-app-bar": "^0.6.3",
36
37
  "@elementor/editor-documents": "^0.8.2",
37
- "@elementor/icons": "^0.4.0",
38
- "@elementor/ui": "^1.4.50",
38
+ "@elementor/icons": "^0.5.0",
39
+ "@elementor/ui": "^1.4.53",
39
40
  "@wordpress/api-fetch": "^6.27.0",
40
41
  "@wordpress/i18n": "^4.31.0",
41
42
  "@wordpress/url": "^3.31.0"
@@ -46,5 +47,5 @@
46
47
  "elementor": {
47
48
  "type": "extension"
48
49
  },
49
- "gitHead": "ad9032e095915dbef0f8543d83f73b9288adc8ad"
50
+ "gitHead": "ada7204a85dbd362c29518f8ab28defe6942a4b7"
50
51
  }
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import { ComponentType } from 'react';
3
+ import { ListItemButton, ListItemIcon, ListItemText } from '@elementor/ui';
4
+ import { Page } from '../../../types';
5
+
6
+ export type Props = {
7
+ title: string;
8
+ icon: ComponentType;
9
+ disabled?: boolean;
10
+ onClick: ( page: Page ) => void;
11
+ }
12
+
13
+ export default function ActionListItem( { title, icon: Icon, disabled, onClick }: Props ) {
14
+ return (
15
+ <ListItemButton
16
+ disabled={ disabled }
17
+ onClick={ onClick }
18
+ >
19
+ <ListItemIcon>
20
+ <Icon />
21
+ </ListItemIcon>
22
+ <ListItemText>
23
+ { title }
24
+ </ListItemText>
25
+ </ListItemButton>
26
+ );
27
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ import ActionListItem, { Props as ActionListItemProps } from './action-list-item';
3
+ import { MenuItem } from '@elementor/ui';
4
+
5
+ export default function ActionMenuItem( props: ActionListItemProps ) {
6
+ return (
7
+ <MenuItem disableGutters>
8
+ <ActionListItem { ...props } />
9
+ </MenuItem>
10
+ );
11
+ }
@@ -0,0 +1,29 @@
1
+ import * as React from 'react';
2
+ import { Divider, Menu, MenuProps } from '@elementor/ui';
3
+ import Rename from '../pages-actions/rename';
4
+ import Duplicate from '../pages-actions/duplicate';
5
+ import Delete from '../pages-actions/delete';
6
+ import { Page } from '../../../types';
7
+ import View from '../pages-actions/view';
8
+ import SetHome from '../pages-actions/set-home';
9
+
10
+ type Props = MenuProps & {
11
+ page: Page
12
+ };
13
+
14
+ export default function PageActionsMenu( { page, ...props }: Props ) {
15
+ return (
16
+ <Menu
17
+ PaperProps={ { sx: { mt: 4, width: 200 } } }
18
+ MenuListProps={ { dense: true } }
19
+ { ...props }
20
+ >
21
+ <Rename />
22
+ <Duplicate />
23
+ <Delete page={ page } />
24
+ <View />
25
+ <Divider />
26
+ <SetHome page={ page } />
27
+ </Menu>
28
+ );
29
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { TrashIcon } from '@elementor/icons';
3
+ import { Page } from '../../../types';
4
+ import { useActiveDocument } from '@elementor/editor-documents';
5
+ import { __ } from '@wordpress/i18n';
6
+ import ActionMenuItem from '../actions-menu/action-menu-item';
7
+
8
+ export default function Delete( { page }: { page: Page } ) {
9
+ const activeDocument = useActiveDocument();
10
+
11
+ const isActive = activeDocument?.id === page.id;
12
+
13
+ return (
14
+ <ActionMenuItem
15
+ title={ __( 'Delete', 'elementor' ) }
16
+ icon={ TrashIcon }
17
+ disabled={ page.isHome || isActive }
18
+ onClick={ () => null }
19
+ />
20
+ );
21
+ }
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import { __ } from '@wordpress/i18n';
3
+ import { CopyIcon } from '@elementor/icons';
4
+ import ActionMenuItem from '../actions-menu/action-menu-item';
5
+
6
+ export default function Duplicate() {
7
+ return (
8
+ <ActionMenuItem
9
+ title={ __( 'Duplicate', 'elementor' ) }
10
+ icon={ CopyIcon }
11
+ onClick={ () => null }
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import { EraseIcon } from '@elementor/icons';
3
+ import { __ } from '@wordpress/i18n';
4
+ import ActionMenuItem from '../actions-menu/action-menu-item';
5
+
6
+ export default function Rename() {
7
+ return (
8
+ <ActionMenuItem
9
+ title={ __( 'Rename', 'elementor' ) }
10
+ icon={ EraseIcon }
11
+ onClick={ () => null }
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { Page } from '../../../types';
3
+ import { HomeIcon } from '@elementor/icons';
4
+ import { __ } from '@wordpress/i18n';
5
+ import ActionMenuItem from '../actions-menu/action-menu-item';
6
+
7
+ export default function SetHome( { page }: { page: Page } ) {
8
+ return (
9
+ <ActionMenuItem
10
+ title={ __( 'Set as homepage', 'elementor' ) }
11
+ icon={ HomeIcon }
12
+ disabled={ !! page.isHome }
13
+ onClick={ () => null }
14
+ />
15
+ );
16
+ }
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import { EyeIcon } from '@elementor/icons';
3
+ import { __ } from '@wordpress/i18n';
4
+ import ActionMenuItem from '../actions-menu/action-menu-item';
5
+
6
+ export default function View() {
7
+ return (
8
+ <ActionMenuItem
9
+ title={ __( 'View Page', 'elementor' ) } // TODO 21/06/2023 : post label should come from post type
10
+ icon={ EyeIcon }
11
+ onClick={ () => null }
12
+ />
13
+ );
14
+ }
@@ -0,0 +1,110 @@
1
+ import * as React from 'react';
2
+ import { act, render } from '@testing-library/react';
3
+ import { Page } from '../../../../types';
4
+ import PageListItem from '../page-list-item';
5
+ import { useNavigateToDocument } from '@elementor/editor-documents';
6
+
7
+ jest.mock( '@elementor/editor-documents', () => ( {
8
+ useActiveDocument: jest.fn(),
9
+ useNavigateToDocument: jest.fn(),
10
+ } ) );
11
+
12
+ describe( '@elementor/editor-site-navigation - PageListItem', () => {
13
+ afterAll( () => {
14
+ jest.clearAllMocks();
15
+ } );
16
+
17
+ it( 'should render a published post', () => {
18
+ // Arrange.
19
+ const page: Page = {
20
+ id: 1,
21
+ title: 'Test Post',
22
+ status: 'publish',
23
+ type: 'page',
24
+ };
25
+
26
+ // Act.
27
+ const { getByText, queryByText } = render( <PageListItem page={ page } /> );
28
+
29
+ // Assert.
30
+ const label = getByText( 'Test Post' );
31
+ const publishedLabel = queryByText( 'publish', { exact: false } );
32
+
33
+ expect( label ).toBeInTheDocument();
34
+ expect( publishedLabel ).toBeNull();
35
+ } );
36
+
37
+ it( 'should show the post status for non-published posts', () => {
38
+ // Arrange.
39
+ const page: Page = {
40
+ id: 1,
41
+ title: 'Test Post',
42
+ status: 'draft',
43
+ type: 'page',
44
+ };
45
+
46
+ // Act.
47
+ const { getByText } = render( <PageListItem page={ page } /> );
48
+
49
+ // Assert.
50
+ const label = getByText( 'draft', { exact: false } );
51
+ expect( label ).toBeInTheDocument();
52
+ } );
53
+
54
+ it( 'should render actions menu', () => {
55
+ // Arrange.
56
+ const page: Page = {
57
+ id: 1,
58
+ title: 'Test Post',
59
+ status: 'draft',
60
+ type: 'page',
61
+ };
62
+
63
+ const actions = [ 'View Page', 'Rename', 'Duplicate', 'Delete', 'Set as homepage' ];
64
+
65
+ // Act.
66
+ const { getByText, getAllByRole } = render( <PageListItem page={ page } /> );
67
+ const buttons = getAllByRole( 'button' );
68
+ // Button to open menu
69
+ const button = buttons[ 1 ];
70
+
71
+ act( () => {
72
+ // Open menu
73
+ button.click();
74
+ } );
75
+
76
+ // Assert.
77
+ actions.forEach( ( action ) => {
78
+ const label = getByText( action );
79
+ expect( label ).toBeInTheDocument();
80
+ } );
81
+ } );
82
+
83
+ it( 'should navigate to document on click', () => {
84
+ // Arrange.
85
+ const navigateToDocument = jest.fn();
86
+ jest.mocked( useNavigateToDocument ).mockReturnValue( navigateToDocument );
87
+
88
+ const id = 10;
89
+
90
+ const page: Page = {
91
+ id,
92
+ title: 'Test Post',
93
+ status: 'draft',
94
+ type: 'page',
95
+ };
96
+
97
+ // Act.
98
+ const { getAllByRole } = render( <PageListItem page={ page } /> );
99
+ const buttons = getAllByRole( 'button' );
100
+ const button = buttons[ 0 ];
101
+
102
+ act( () => {
103
+ button.click();
104
+ } );
105
+
106
+ // Assert.
107
+ expect( navigateToDocument ).toHaveBeenCalledTimes( 1 );
108
+ expect( navigateToDocument ).toHaveBeenCalledWith( id );
109
+ } );
110
+ } );
@@ -0,0 +1,48 @@
1
+ import * as React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { Page } from '../../../../types';
4
+ import PagesCollapsibleList from '../pages-collapsible-list';
5
+
6
+ jest.mock( '@elementor/editor-documents' );
7
+
8
+ describe( '@elementor/editor-site-navigation - PagesCollapsibleList', () => {
9
+ const mockPosts: Page[] = [
10
+ { id: 1, type: 'page', title: 'Home', status: 'draft' },
11
+ { id: 2, type: 'page', title: 'About', status: 'publish' },
12
+ { id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
13
+ { id: 4, type: 'page', title: 'Contact', status: 'draft' },
14
+ { id: 5, type: 'page', title: 'FAQ', status: 'publish' },
15
+ ];
16
+
17
+ afterAll( () => {
18
+ jest.clearAllMocks();
19
+ } );
20
+
21
+ it( 'should render closed list', () => {
22
+ // Act.
23
+ const { getByText, queryByText } = render(
24
+ <PagesCollapsibleList pages={ mockPosts } isOpenByDefault={ false } />,
25
+ );
26
+
27
+ // Assert.
28
+ const label = getByText( `Pages (${ mockPosts.length })` );
29
+ expect( label ).toBeInTheDocument();
30
+
31
+ const postInList = queryByText( 'Services' );
32
+ expect( postInList ).toBeNull();
33
+ } );
34
+
35
+ it( 'should render open list', () => {
36
+ // Act.
37
+ const { getByText } = render(
38
+ <PagesCollapsibleList pages={ mockPosts } isOpenByDefault={ true } />,
39
+ );
40
+
41
+ // Assert.
42
+ const label = getByText( `Pages (${ mockPosts.length })` );
43
+ expect( label ).toBeInTheDocument();
44
+
45
+ const postInList = getByText( 'Services' );
46
+ expect( postInList ).toBeInTheDocument();
47
+ } );
48
+ } );
@@ -0,0 +1,63 @@
1
+ import * as React from 'react';
2
+ import { ComponentType, PropsWithChildren, useState } from 'react';
3
+ import { Collapse, IconButton, List, ListItem, ListItemIcon, ListItemText, styled, SvgIconProps } from '@elementor/ui';
4
+ import { ChevronDownIcon } from '@elementor/icons';
5
+
6
+ type Props = {
7
+ label: string
8
+ Icon: ComponentType;
9
+ isOpenByDefault?: boolean;
10
+ }
11
+
12
+ interface RotateIconProps extends SvgIconProps {
13
+ isOpen: boolean;
14
+ }
15
+
16
+ // TODO 21/06/2023 : Should replace this with future Rotate component that will be implemented in elementor-ui
17
+ const RotateIcon = styled( ChevronDownIcon, {
18
+ shouldForwardProp: ( prop ) => prop !== 'isOpen',
19
+ } )<RotateIconProps>( ( { theme, isOpen } ) => ( {
20
+ transform: isOpen ? 'rotate(0deg)' : 'rotate(-90deg)',
21
+ transition: theme.transitions.create( 'transform', {
22
+ duration: theme.transitions.duration.standard,
23
+ } ),
24
+ } ) );
25
+
26
+ export default function CollapsibleList(
27
+ {
28
+ label,
29
+ Icon,
30
+ isOpenByDefault = false,
31
+ children,
32
+ }: PropsWithChildren<Props>
33
+ ) {
34
+ const [ isOpen, setIsOpen ] = useState( isOpenByDefault );
35
+
36
+ return (
37
+ <>
38
+ <ListItem disableGutters>
39
+ <ListItemIcon>
40
+ <IconButton
41
+ onClick={ () => setIsOpen( ( prev ) => ! prev ) }
42
+ sx={ { color: 'inherit' } }
43
+ size="small"
44
+ >
45
+ <RotateIcon fontSize="small" isOpen={ isOpen } />
46
+ </IconButton>
47
+ </ListItemIcon>
48
+ <ListItemIcon>
49
+ <Icon />
50
+ </ListItemIcon>
51
+ <ListItemText>{ label }</ListItemText>
52
+ </ListItem>
53
+ <Collapse
54
+ in={ isOpen }
55
+ timeout="auto"
56
+ unmountOnExit>
57
+ <List dense>
58
+ { children }
59
+ </List>
60
+ </Collapse>
61
+ </>
62
+ );
63
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from 'react';
2
+ import {
3
+ bindMenu,
4
+ bindTrigger,
5
+ ListItem,
6
+ ListItemButton,
7
+ ListItemIcon,
8
+ ListItemText,
9
+ ToggleButton,
10
+ usePopupState,
11
+ } from '@elementor/ui';
12
+ import { DotsVerticalIcon, HomeIcon } from '@elementor/icons';
13
+ import { Page } from '../../../types';
14
+ import { useActiveDocument, useNavigateToDocument } from '@elementor/editor-documents';
15
+ import PageTitleAndStatus from '../../shared/page-title-and-status';
16
+ import PageActionsMenu from '../actions-menu/page-actions-menu';
17
+
18
+ export default function PageListItem( { page }: { page: Page } ) {
19
+ const popupState = usePopupState( {
20
+ variant: 'popover',
21
+ popupId: 'page-actions',
22
+ } );
23
+
24
+ const activeDocument = useActiveDocument();
25
+ const navigateToDocument = useNavigateToDocument();
26
+
27
+ const isActive = activeDocument?.id === page.id;
28
+
29
+ return (
30
+ <>
31
+ <ListItem
32
+ disablePadding
33
+ secondaryAction={
34
+ <ToggleButton
35
+ value
36
+ color="secondary"
37
+ size="small"
38
+ selected={ popupState.isOpen }
39
+ { ...bindTrigger( popupState ) }
40
+ >
41
+ <DotsVerticalIcon fontSize="small" />
42
+ </ToggleButton>
43
+ }
44
+ >
45
+ <ListItemButton
46
+ selected={ isActive }
47
+ onClick={ () => navigateToDocument( page.id ) }
48
+ dense
49
+ >
50
+ <ListItemIcon />
51
+ <ListItemText
52
+ disableTypography={ true }
53
+ >
54
+ <PageTitleAndStatus page={ page } />
55
+ </ListItemText>
56
+ { page.isHome &&
57
+ <ListItemIcon>
58
+ <HomeIcon color="disabled" />
59
+ </ListItemIcon>
60
+ }
61
+ </ListItemButton>
62
+ </ListItem>
63
+ <PageActionsMenu page={ page } { ...bindMenu( popupState ) } />
64
+ </>
65
+ );
66
+ }
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+ import CollapsibleList from './collapsible-list';
3
+ import { Page } from '../../../types';
4
+ import { PageTypeIcon } from '@elementor/icons';
5
+ import PageListItem from './page-list-item';
6
+
7
+ type Props = {
8
+ pages: Page[];
9
+ isOpenByDefault?: boolean;
10
+ }
11
+
12
+ export default function PagesCollapsibleList( { pages, isOpenByDefault = false }: Props ) {
13
+ const label = `Pages (${ pages.length })`; // TODO 21/06/2023 : This label should come from the backend
14
+
15
+ return (
16
+ <CollapsibleList
17
+ label={ label }
18
+ Icon={ PageTypeIcon }
19
+ isOpenByDefault={ isOpenByDefault }
20
+ >
21
+ { pages.map( ( page ) => <PageListItem key={ page.id } page={ page } /> ) }
22
+ </CollapsibleList>
23
+ );
24
+ }
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import { Box, Button, Divider, Grid, List, Paper, ThemeProvider, Typography } from '@elementor/ui';
3
+ import { PlusIcon } from '@elementor/icons';
4
+ import { Page } from '../../types';
5
+ import PagesCollapsibleList from './pages-list/pages-collapsible-list';
6
+
7
+ // TODO: Remove once connected to real data fetching.
8
+ const mockPages: Page[] = [
9
+ {
10
+ id: 1,
11
+ type: 'page',
12
+ title: 'This is a very long title that somebody wrote, a very very long line',
13
+ status: 'pending approval',
14
+ },
15
+ { id: 2, type: 'page', title: 'About', status: 'publish' },
16
+ { id: 3, type: 'page', title: 'Services', status: 'publish', isHome: true },
17
+ { id: 4, type: 'page', title: 'Contact', status: 'draft' },
18
+ { id: 5, type: 'page', title: 'FAQ', status: 'publish' },
19
+ ];
20
+
21
+ const Shell = () => {
22
+ return (
23
+ <ThemeProvider colorScheme="light">
24
+ <Box sx={ { width: '100%', maxWidth: 360 } }>
25
+ <Paper>
26
+ <Grid
27
+ container
28
+ justifyContent="center"
29
+ alignItems="center"
30
+ sx={ { height: 51 } }>
31
+ <Typography variant="h6">Pages</Typography>
32
+ </Grid>
33
+ <Divider />
34
+ <Box
35
+ display="flex"
36
+ justifyContent="flex-end"
37
+ alignItems="center"
38
+ >
39
+ <Button
40
+ sx={ { mt: 4, mb: 4, mr: 5 } }
41
+ startIcon={ <PlusIcon /> }
42
+ >
43
+ Add New
44
+ </Button>
45
+ </Box>
46
+ <Box sx={ { width: '100%', maxWidth: 360 } }>
47
+ <List dense>
48
+ <PagesCollapsibleList pages={ mockPages } isOpenByDefault={ true } />
49
+ </List>
50
+ </Box>
51
+ <Divider />
52
+ </Paper>
53
+ </Box>
54
+ </ThemeProvider>
55
+ );
56
+ };
57
+
58
+ export default Shell;
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { Box, Typography } from '@elementor/ui';
3
+ import { Page } from '../../types';
4
+ import { useReverseHtmlEntities } from '../../hooks/use-reverse-html-entities';
5
+
6
+ const PageStatus = ( { status }: { status: string } ) => {
7
+ if ( 'publish' === status ) {
8
+ return null;
9
+ }
10
+
11
+ return (
12
+ <Typography
13
+ component="span"
14
+ variant="body1"
15
+ sx={ {
16
+ textTransform: 'capitalize',
17
+ fontStyle: 'italic',
18
+ whiteSpace: 'nowrap',
19
+ flexBasis: 'content',
20
+ } }
21
+ >
22
+ ({ status })
23
+ </Typography>
24
+ );
25
+ };
26
+
27
+ const PageTitle = ( { title }: { title: string } ) => {
28
+ const modifiedTitle = useReverseHtmlEntities( title );
29
+
30
+ return (
31
+ <Typography
32
+ component="span"
33
+ variant="body1"
34
+ noWrap
35
+ sx={ {
36
+ flexBasis: 'auto',
37
+ } }
38
+ >
39
+ { modifiedTitle }
40
+ </Typography>
41
+ );
42
+ };
43
+
44
+ export default function PageTitleAndStatus( { page }: { page: Page } ) {
45
+ return (
46
+ <Box display="flex">
47
+ <PageTitle title={ page.title } />&nbsp;<PageStatus status={ page.status } />
48
+ </Box>
49
+ );
50
+ }
package/src/init.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import RecentlyEdited from './components/top-bar/recently-edited';
2
2
  import { injectIntoPageIndication } from '@elementor/editor-app-bar';
3
+ import { injectIntoTop } from '@elementor/editor';
4
+ import Shell from './components/panel/shell';
3
5
 
4
6
  export default function init() {
5
7
  registerTopBarMenuItems();
8
+ // TODO 06/06/2023 : uncomment registerPanel() when we are ready to release
9
+ // registerPanel();
6
10
  }
7
11
 
8
12
  function registerTopBarMenuItems() {
@@ -11,3 +15,11 @@ function registerTopBarMenuItems() {
11
15
  filler: RecentlyEdited,
12
16
  } );
13
17
  }
18
+
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ function registerPanel() {
21
+ injectIntoTop( {
22
+ id: 'navigation-panel',
23
+ filler: Shell,
24
+ } );
25
+ }
package/src/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ export type Page = {
2
+ isHome?: boolean;
3
+ id: number;
4
+ title: string;
5
+ status: string;
6
+ type: string;
7
+ };