You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-11-24 23:01:05 +03:00
frontend: make the navbar better and push it under the user avatar/name
This commit is contained in:
@@ -19,13 +19,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
margin-top: var(--cpd-space-6x);
|
||||||
|
padding: var(--cpd-space-6x) 0;
|
||||||
|
border-top: 1px solid var(--cpd-color-border-interactive-secondary);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.footer-links {
|
||||||
border: 0;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--cpd-color-border-interactive-secondary);
|
|
||||||
margin: var(--cpd-space-4x) 0;
|
margin: var(--cpd-space-4x) 0;
|
||||||
}
|
|
||||||
|
|
||||||
|
& > ul {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--cpd-space-2x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,26 +12,42 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Link } from "@vector-im/compound-web";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
|
import { currentUserIdAtom } from "../atoms";
|
||||||
|
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||||
|
|
||||||
|
import GraphQLError from "./GraphQLError";
|
||||||
import styles from "./Layout.module.css";
|
import styles from "./Layout.module.css";
|
||||||
import NavBar from "./NavBar";
|
import NavBar from "./NavBar";
|
||||||
import NavItem, { ExternalLink } from "./NavItem";
|
import NavItem from "./NavItem";
|
||||||
|
import NotLoggedIn from "./NotLoggedIn";
|
||||||
const Separator: React.FC = () => <hr className={styles.separator} />;
|
import UserGreeting from "./UserGreeting";
|
||||||
|
|
||||||
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||||
|
const result = useAtomValue(currentUserIdAtom);
|
||||||
|
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||||
|
|
||||||
|
const userId = unwrapOk(result);
|
||||||
|
if (userId === null)
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
|
<NotLoggedIn />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<UserGreeting userId={userId} />
|
||||||
|
|
||||||
<NavBar>
|
<NavBar>
|
||||||
<NavItem route={{ type: "home" }}>Sessions</NavItem>
|
<NavItem route={{ type: "home" }}>Sessions</NavItem>
|
||||||
<NavItem route={{ type: "account" }}>Emails</NavItem>
|
<NavItem route={{ type: "account" }}>Emails</NavItem>
|
||||||
</NavBar>
|
</NavBar>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
<a href="https://matrix.org" target="_blank" rel="noreferrer noopener">
|
||||||
<img
|
<img
|
||||||
@@ -43,17 +59,15 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<NavBar>
|
<nav className={styles.footerLinks}>
|
||||||
<ExternalLink href="https://matrix.org/legal/copyright-notice">
|
<ul>
|
||||||
Info
|
<Link href="https://matrix.org/legal/copyright-notice">Info</Link>
|
||||||
</ExternalLink>
|
<Link href="https://matrix.org/legal/privacy-notice">Privacy</Link>
|
||||||
<ExternalLink href="https://matrix.org/legal/privacy-notice">
|
<Link href="https://matrix.org/legal/terms-and-conditions">
|
||||||
Privacy
|
|
||||||
</ExternalLink>
|
|
||||||
<ExternalLink href="https://matrix.org/legal/terms-and-conditions">
|
|
||||||
Terms & Conditions
|
Terms & Conditions
|
||||||
</ExternalLink>
|
</Link>
|
||||||
</NavBar>
|
</ul>
|
||||||
|
</nav>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,9 +13,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.nav-bar {
|
||||||
|
border-bottom: var(--cpd-border-width-1) solid var(--cpd-color-gray-400);
|
||||||
|
margin: var(--cpd-space-6x) 0;
|
||||||
|
padding: 0 var(--cpd-space-10x);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-bar-items {
|
.nav-bar-items {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
gap: var(--cpd-space-4x);
|
align-items: center;
|
||||||
|
gap: var(--cpd-space-3x);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
import styles from "./NavBar.module.css";
|
import styles from "./NavBar.module.css";
|
||||||
|
|
||||||
const NavBar: React.FC<React.PropsWithChildren<{}>> = ({ children }) => (
|
const NavBar: React.FC<React.PropsWithChildren<{}>> = ({ children }) => (
|
||||||
<nav>
|
<nav className={styles.navBar}>
|
||||||
<ul className={styles.navBarItems}>{children}</ul>
|
<ul className={styles.navBarItems}>{children}</ul>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,11 +13,41 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.nav-tab {
|
||||||
|
padding: var(--cpd-space-4x) 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Underline effect */
|
||||||
|
.nav-tab::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 0;
|
||||||
|
border-radius: var(--cpd-radius-pill-effect) var(--cpd-radius-pill-effect) 0 0;
|
||||||
|
background-color: var(--cpd-color-bg-action-primary-rest);
|
||||||
|
transition: height 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tab[data-current]::before {
|
||||||
|
/* This is not exactly right: designs says 3px, but there are no variables for that */
|
||||||
|
height: var(--cpd-border-width-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
padding: var(--cpd-space-1x) var(--cpd-space-2x);
|
padding: var(--cpd-space-1x) var(--cpd-space-2x);
|
||||||
color: var(--cpd-color-text-secondary);
|
color: var(--cpd-color-text-secondary);
|
||||||
line-height: var(--cpd-space-6x);
|
line-height: var(--cpd-space-6x);
|
||||||
border-radius: var(--cpd-radius-pill-effect);
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
|
border: transparent var(--cpd-border-width-2) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item.external-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-item:hover {
|
.nav-item:hover {
|
||||||
@@ -25,6 +55,16 @@
|
|||||||
background-color: var(--cpd-color-bg-subtle-secondary);
|
background-color: var(--cpd-color-bg-subtle-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-item:active {
|
||||||
|
color: var(--cpd-color-text-primary);
|
||||||
|
background-color: var(--cpd-color-bg-subtle-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--cpd-color-border-focused);
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item[aria-current="page"] {
|
.nav-item[aria-current="page"] {
|
||||||
color: var(--cpd-color-text-primary);
|
color: var(--cpd-color-text-primary);
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Link as CpdLink } from "@vector-im/compound-web";
|
import classNames from "classnames";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
|
|
||||||
import { Link, Route, routeAtom } from "../../Router";
|
import { Link, Route, routeAtom } from "../../Router";
|
||||||
@@ -24,12 +24,13 @@ const NavItem: React.FC<React.PropsWithChildren<{ route: Route }>> = ({
|
|||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const currentRoute = useAtomValue(routeAtom);
|
const currentRoute = useAtomValue(routeAtom);
|
||||||
|
const active = currentRoute.type === route.type;
|
||||||
return (
|
return (
|
||||||
<li>
|
<li className={styles.navTab} data-current={active ? true : undefined}>
|
||||||
<Link
|
<Link
|
||||||
className={styles.navItem}
|
className={styles.navItem}
|
||||||
route={route}
|
route={route}
|
||||||
aria-current={currentRoute.type === route.type ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -40,8 +41,14 @@ const NavItem: React.FC<React.PropsWithChildren<{ route: Route }>> = ({
|
|||||||
export const ExternalLink: React.FC<
|
export const ExternalLink: React.FC<
|
||||||
React.PropsWithChildren<{ href: string }>
|
React.PropsWithChildren<{ href: string }>
|
||||||
> = ({ href, children }) => (
|
> = ({ href, children }) => (
|
||||||
<li>
|
<li className={styles.navTab}>
|
||||||
<CpdLink href={href}>{children}</CpdLink>
|
<a
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
className={classNames(styles.navItem, styles.externalLink)}
|
||||||
|
href={href}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`NavItem > render an active <NavItem /> 1`] = `
|
exports[`NavItem > render an active <NavItem /> 1`] = `
|
||||||
<li>
|
<li
|
||||||
|
className="_navTab_8603fc"
|
||||||
|
data-current={true}
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
aria-current="page"
|
aria-current="page"
|
||||||
className="_navItem_8603fc"
|
className="_navItem_8603fc"
|
||||||
@@ -14,7 +17,9 @@ exports[`NavItem > render an active <NavItem /> 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`NavItem > render an inactive <NavItem /> 1`] = `
|
exports[`NavItem > render an inactive <NavItem /> 1`] = `
|
||||||
<li>
|
<li
|
||||||
|
className="_navTab_8603fc"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
className="_navItem_8603fc"
|
className="_navItem_8603fc"
|
||||||
href=""
|
href=""
|
||||||
@@ -26,7 +31,9 @@ exports[`NavItem > render an inactive <NavItem /> 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`NavItem > renders a different route 1`] = `
|
exports[`NavItem > renders a different route 1`] = `
|
||||||
<li>
|
<li
|
||||||
|
className="_navTab_8603fc"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
className="_navItem_8603fc"
|
className="_navItem_8603fc"
|
||||||
href="account"
|
href="account"
|
||||||
|
|||||||
@@ -18,13 +18,11 @@ import { currentUserIdAtom } from "../atoms";
|
|||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
import NotLoggedIn from "../components/NotLoggedIn";
|
||||||
import UserEmailList from "../components/UserEmailList";
|
import UserEmailList from "../components/UserEmailList";
|
||||||
import UserGreeting from "../components/UserGreeting";
|
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||||
|
|
||||||
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
const UserAccount: React.FC<{ id: string }> = ({ id }) => {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
<UserGreeting userId={id} />
|
|
||||||
<UserEmailList userId={id} />
|
<UserEmailList userId={id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import CompatSessionList from "../components/CompatSessionList";
|
|||||||
import GraphQLError from "../components/GraphQLError";
|
import GraphQLError from "../components/GraphQLError";
|
||||||
import NotLoggedIn from "../components/NotLoggedIn";
|
import NotLoggedIn from "../components/NotLoggedIn";
|
||||||
import OAuth2SessionList from "../components/OAuth2SessionList";
|
import OAuth2SessionList from "../components/OAuth2SessionList";
|
||||||
import UserGreeting from "../components/UserGreeting";
|
|
||||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
@@ -32,7 +31,6 @@ const Home: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UserGreeting userId={currentUserId} />
|
|
||||||
<div className="mt-4 grid gap-8">
|
<div className="mt-4 grid gap-8">
|
||||||
<OAuth2SessionList userId={currentUserId} />
|
<OAuth2SessionList userId={currentUserId} />
|
||||||
<CompatSessionList userId={currentUserId} />
|
<CompatSessionList userId={currentUserId} />
|
||||||
|
|||||||
Reference in New Issue
Block a user