Skip to content

Commit 844d60c

Browse files
Merge pull request #184 from canonical/feat-views
Feat: Project Views in content system
2 parents f04a874 + 9a082b7 commit 844d60c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1274
-81
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,4 +395,4 @@ Or if you prefer running tests in UI mode:
395395
yarn playwright test --ui
396396
```
397397

398-
**Note:** Please make sure the `BASE_URL` in `tests/config.ts` is correct and reflects your webserver. For example, if your project is running on localhost:8104, it should be `BASE_URL: http://localhost:8104`
398+
**Note:** Please make sure the `BASE_URL` in `tests/config.ts` is correct and reflects your webserver. For example, if your project is running on localhost:8104, it should be `BASE_URL: http://localhost:8104`

static/client/App.scss

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
@include vf-p-icons-common;
2222
@include vf-p-icon-edit;
2323
@include vf-p-icon-archive;
24+
@include vf-p-icon-switcher-dashboard;
25+
@include vf-p-icon-switcher-environments;
26+
@include vf-p-icon-filter;
27+
@include vf-p-icon-repository;
2428

2529
// Utilities
2630
@include vf-u-align;
@@ -56,4 +60,23 @@
5660
// allow SearchAndFilter component increase height when there are several options selected (in Reviewers)
5761
.p-search-and-filter__search-container {
5862
height: auto !important;
63+
}
64+
65+
.p-accordion {
66+
.p-accordion__heading {
67+
position: sticky !important;
68+
top: -1.5rem;
69+
z-index: 9;
70+
71+
& button[aria-expanded="true"]{
72+
background: var(--vf-color-background-neutral-hover) !important;
73+
}
74+
}
75+
.p-accordion__panel {
76+
padding-left: 0 !important;
77+
}
78+
}
79+
80+
.p-table__row--highlight{
81+
background: var(--vf-color-background-neutral-hover) !important;
5982
}

static/client/components/Breadcrumbs/Breadcrumbs.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, { useEffect, useState } from "react";
22

3-
import { useLocation } from "react-router-dom";
3+
import { useLocation, useNavigate } from "react-router-dom";
44

55
import { type IBreadcrumb } from "./Breadcrumbs.types";
66

77
const Breadcrumbs = () => {
88
const location = useLocation();
99
const [breadcrumbs, setBreadcrumbs] = useState<IBreadcrumb[]>([]);
10+
const navigate = useNavigate();
1011

1112
useEffect(() => {
1213
const pageIndex = location.pathname.indexOf("app/webpage/");
@@ -28,12 +29,20 @@ const Breadcrumbs = () => {
2829
}
2930
}, [location]);
3031

32+
const goToPage = React.useCallback(
33+
(e: React.MouseEvent<HTMLAnchorElement>, path: string) => {
34+
e.preventDefault();
35+
navigate(path);
36+
},
37+
[navigate],
38+
);
39+
3140
return (
3241
<div className="l-breadcrumbs">
3342
{breadcrumbs.map((bc, index) => (
3443
<React.Fragment key={`bc-${index}`}>
3544
{index < breadcrumbs.length - 1 ? (
36-
<a className="p-text--small-caps" href={bc.link}>
45+
<a className="p-text--small-caps" href={bc.link} onClick={(e) => goToPage(e, bc.link)}>
3746
{bc.name}
3847
</a>
3948
) : (
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from "react";
2+
3+
import { Tooltip } from "@canonical/react-components";
4+
5+
interface IIconTextWithTooltipProps {
6+
text?: string;
7+
icon?: string;
8+
message?: string;
9+
}
10+
11+
const IconTextWithTooltip: React.FC<IIconTextWithTooltipProps> = ({ text, icon, message }) => {
12+
return (
13+
<Tooltip message={message} zIndex={999}>
14+
<span className="u-has-icon">
15+
{text}&nbsp;
16+
{icon && <i className={`p-icon--${icon}`} />}
17+
</span>
18+
</Tooltip>
19+
);
20+
};
21+
22+
export default IconTextWithTooltip;

static/client/components/MainLayout/MainLayout.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
import { NotificationConsumer } from "@canonical/react-components";
2-
import { useLocation } from "react-router-dom";
1+
import React from "react";
2+
3+
import { Button, NotificationConsumer } from "@canonical/react-components";
4+
import { Outlet, useLocation, useNavigate } from "react-router-dom";
35

4-
import Breadcrumbs from "@/components/Breadcrumbs";
56
import Navigation from "@/components/Navigation";
67
import Search from "@/components/Search";
8+
import FilterTableView from "@/components/Views/FilterTableView";
9+
import { VIEW_TREE } from "@/config";
10+
import { goBack } from "@/helpers/views";
11+
import { useViewsStore } from "@/store/views";
712

813
interface IMainLayoutProps {
914
children?: JSX.Element;
1015
}
1116

1217
const MainLayout = ({ children }: IMainLayoutProps): JSX.Element => {
1318
const location = useLocation();
19+
const navigate = useNavigate();
20+
const view = useViewsStore((state) => state.view);
21+
22+
function goPrev() {
23+
return goBack(location, navigate);
24+
}
1425

1526
return (
1627
<>
@@ -19,7 +30,13 @@ const MainLayout = ({ children }: IMainLayoutProps): JSX.Element => {
1930
<main className="l-main">
2031
<div className="row">
2132
<div className="col-7">
22-
<Breadcrumbs />
33+
{location.pathname.includes("/webpage") && view !== VIEW_TREE && (
34+
<Button hasIcon onClick={goPrev}>
35+
<React.Fragment key=".0">
36+
<i className="p-icon--chevron-left" /> <span>Back</span>
37+
</React.Fragment>
38+
</Button>
39+
)}
2340
</div>
2441
<div className="col-5">
2542
<Search />
@@ -29,11 +46,12 @@ const MainLayout = ({ children }: IMainLayoutProps): JSX.Element => {
2946
<div className="row">
3047
{location.pathname === "/app" && (
3148
<>
32-
<h2>Welcome to the Content System</h2>
33-
<h3 className="p-heading--4">Please select a page that you are looking for from the left sidebar</h3>
49+
<h2>All pages</h2>
50+
<FilterTableView />
3451
</>
3552
)}
3653
{children}
54+
<Outlet />
3755
</div>
3856
</main>
3957
<div className="l-notification__container">

static/client/components/Navigation/Navigation.tsx

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,29 @@ import React, { useCallback, useState } from "react";
22

33
import { Button } from "@canonical/react-components";
44
import classNames from "classnames";
5-
import { useNavigate } from "react-router-dom";
5+
import { useLocation, useNavigate } from "react-router-dom";
66

77
import NavigationBanner from "./NavigationBanner";
88
import NavigationItems from "./NavigationItems";
99

1010
import NavigationCollapseToggle from "@/components/Navigation/NavigationCollapseToggle";
1111
import SiteSelector from "@/components/SiteSelector";
12+
import { VIEW_OWNED, VIEW_REVIEWED, VIEW_TABLE, VIEW_TREE } from "@/config";
1213
import type { IUser } from "@/services/api/types/users";
14+
import type { TView } from "@/services/api/types/views";
1315
import { useStore } from "@/store";
16+
import { useViewsStore } from "@/store/views";
1417

1518
const Navigation = (): JSX.Element => {
1619
const navigate = useNavigate();
20+
const location = useLocation();
1721
const [isCollapsed, setIsCollapsed] = useState(true);
1822
const [user, setUser] = useStore((state) => [state.user, state.setUser]);
23+
const [view, setView, setExpandedProject] = useViewsStore((state) => [
24+
state.view,
25+
state.setView,
26+
state.setExpandedProject,
27+
]);
1928

2029
const logout = useCallback(() => {
2130
setUser({} as IUser);
@@ -26,6 +35,24 @@ const Navigation = (): JSX.Element => {
2635
navigate("/app/new-webpage");
2736
}, [navigate]);
2837

38+
const changeView = useCallback(
39+
(view: TView) => {
40+
setExpandedProject("");
41+
setView(view);
42+
if ([VIEW_OWNED, VIEW_REVIEWED].includes(view)) navigate(`/app/views/${view}`);
43+
if ([VIEW_TABLE, VIEW_TREE].includes(view)) navigate("/app");
44+
},
45+
[navigate, setExpandedProject, setView],
46+
);
47+
48+
const isViewActive = useCallback(
49+
(linkView: TView) => {
50+
if (view === VIEW_TREE) return linkView === view;
51+
return linkView === view && location.pathname === `/app/views/${linkView}`;
52+
},
53+
[location.pathname, view],
54+
);
55+
2956
return (
3057
<>
3158
<header className="l-navigation-bar">
@@ -48,15 +75,66 @@ const Navigation = (): JSX.Element => {
4875
<div className="l-navigation__controls">
4976
<NavigationCollapseToggle isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed} />
5077
</div>
51-
<SiteSelector />
52-
<Button appearance="" className="l-new-webpage-button" hasIcon onClick={handleNewPageClick}>
53-
<React.Fragment key=".0">
54-
<i className="p-icon--plus" /> <span>Request new page</span>
55-
</React.Fragment>
56-
</Button>
78+
<div>
79+
<Button appearance="" className="l-new-webpage-button" hasIcon onClick={handleNewPageClick}>
80+
<React.Fragment key=".0">
81+
<i className="p-icon--plus" /> <span>Request new page</span>
82+
</React.Fragment>
83+
</Button>
84+
</div>
5785
</div>
86+
<hr className="p-rule" />
5887
<div className="p-panel__content">
59-
<NavigationItems />
88+
<ul className="u-no-margin--left u-no-padding">
89+
<li
90+
className={`p-side-navigation__link ${location.pathname === "/app" && view !== VIEW_TREE && "is-active"}`}
91+
onClick={() => changeView(VIEW_TABLE)}
92+
>
93+
<span className="u-has-icon">
94+
<i className="p-icon--switcher-dashboard is-dark" />
95+
Table view
96+
</span>
97+
</li>
98+
<li
99+
className={`p-side-navigation__link ${isViewActive(VIEW_TREE) && "is-active"}`}
100+
onClick={() => changeView(VIEW_TREE)}
101+
>
102+
<span className="u-has-icon">
103+
<i className="p-icon--switcher-environments is-dark" />
104+
Tree view
105+
</span>
106+
</li>
107+
</ul>
108+
{isViewActive(VIEW_TREE) && (
109+
<>
110+
<SiteSelector />
111+
<NavigationItems />
112+
</>
113+
)}
114+
</div>
115+
<div className="p-panel__views">
116+
<hr className="p-rule" />
117+
<p className="p-muted-heading u-text--muted">Quick views</p>
118+
<ul className="u-no-margin u-no-padding">
119+
<li
120+
className={`p-side-navigation__link ${isViewActive(VIEW_OWNED) && "is-active"}`}
121+
onClick={() => changeView(VIEW_OWNED)}
122+
>
123+
<span className="u-has-icon">
124+
<i className="p-icon--user" />
125+
Owned by me
126+
</span>
127+
</li>
128+
<li
129+
className={`p-side-navigation__link ${isViewActive(VIEW_REVIEWED) && "is-active"}`}
130+
onClick={() => changeView(VIEW_REVIEWED)}
131+
>
132+
<span className="u-has-icon">
133+
<i className="p-icon--show" />
134+
Reviewed by me
135+
</span>
136+
</li>
137+
</ul>
60138
</div>
61139
<div className="p-panel__footer p-side-navigation--icons">
62140
<div className="u-no-margin u-truncate p-side-navigation__label">

static/client/components/Navigation/NavigationElement/NavigationElement.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ export interface INavigationElementProps {
99

1010
export interface INavigationElementBadgeProps {
1111
page: IPage;
12+
appearance?: "is-dark" | "is-light";
1213
}

static/client/components/Navigation/NavigationElement/NavigationElementBadge.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ import type { INavigationElementBadgeProps } from "./NavigationElement.types";
66

77
import { PageStatus } from "@/services/api/types/pages";
88

9-
const NavigationElementBadge = ({ page }: INavigationElementBadgeProps): JSX.Element => {
9+
const NavigationElementBadge = ({ page, appearance }: INavigationElementBadgeProps): JSX.Element => {
1010
const getIcon = useMemo(() => {
1111
switch (page.status) {
1212
case PageStatus.NEW:
13-
return <i className="p-icon--edit is-dark" />;
13+
return <i className={`p-icon--edit ${appearance}`} />;
1414
case PageStatus.TO_DELETE:
15-
return <i className="p-icon--archive is-dark" />;
15+
return <i className={`p-icon--archive ${appearance}`} />;
1616
default:
1717
return <></>;
1818
}
19-
}, [page.status]);
19+
}, [appearance, page.status]);
2020

2121
const getTitle = useMemo(() => {
2222
switch (page.status) {

static/client/components/Navigation/_Navigation.scss

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
.l-site-selector,
3030
.l-site-selector__label,
3131
.l-new-webpage-button {
32+
@media (max-width: $breakpoint-small) {
33+
margin-top: 2rem;
34+
}
3235
@media (min-width: $breakpoint-small) and (max-width: $breakpoint-large) {
3336
visibility: hidden;
3437
}
@@ -73,15 +76,19 @@
7376

7477
.l-navigation-collapse-toggle {
7578
position: absolute;
76-
right: 20px;
79+
right: 0;
7780
top: 20px;
7881
}
7982
}
8083

8184
.p-panel__content {
82-
height: calc(100vh - 440px);
85+
max-height: calc(100vh - 540px);
8386
overflow: auto;
87+
margin-bottom: 2rem;
88+
89+
}
8490

91+
.p-panel__content, .p-panel__views {
8592
@media (min-width: $breakpoint-small) and (max-width: $breakpoint-large) {
8693
visibility: hidden;
8794
}
@@ -101,11 +108,17 @@
101108
}
102109
}
103110
}
111+
112+
li.p-side-navigation__link {
113+
padding: 0.8rem 1rem;
114+
cursor: pointer;
115+
}
104116
}
105117

106118
&:hover {
107119
@media (min-width: $breakpoint-small) and (max-width: $breakpoint-large) {
108120
.p-panel__content,
121+
.p-panel__views,
109122
.p-panel__header .l-site-selector,
110123
.p-panel__header .l-site-selector__label,
111124
.p-panel__header .l-new-webpage-button {

0 commit comments

Comments
 (0)