Skip to content

Commit

Permalink
Merge pull request #21614 from Yoast/493-add-navigation-sidebar-with-…
Browse files Browse the repository at this point in the history
…alert-center-and-first-time-configuration-links

493 add navigation sidebar with alert center and first time configuration links
  • Loading branch information
igorschoester authored Sep 11, 2024
2 parents 0e731b5 + e09b1e0 commit 25aab53
Show file tree
Hide file tree
Showing 17 changed files with 299 additions and 55 deletions.
4 changes: 2 additions & 2 deletions admin/class-config.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use Yoast\WP\SEO\Actions\Alert_Dismissal_Action;
use Yoast\WP\SEO\Dashboard\User_Interface\New_Dashboard_Page_Integration;
use Yoast\WP\SEO\Integrations\Academy_Integration;
use Yoast\WP\SEO\Integrations\Settings_Integration;
use Yoast\WP\SEO\Integrations\Support_Integration;
Expand Down Expand Up @@ -53,7 +54,6 @@ public function init() {
// Bail, this is managed in the applicable integration.
return;
}

add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'config_page_styles' ] );
}
Expand Down Expand Up @@ -105,7 +105,7 @@ public function config_page_scripts() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
$page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';

if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, 'wpseo_workouts' ], true ) ) {
if ( in_array( $page, [ WPSEO_Admin::PAGE_IDENTIFIER, New_Dashboard_Page_Integration::PAGE, 'wpseo_workouts' ], true ) ) {
wp_enqueue_media();

$script_data['userEditUrl'] = add_query_arg( 'user_id', '{user_id}', admin_url( 'user-edit.php' ) );
Expand Down
4 changes: 1 addition & 3 deletions css/src/first-time-configuration.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#wpseo-first-time-configuration {
.yst-root {
#yoast-configuration {
/* Override aggressive WordPress inputs styling */

.yst-input {
Expand Down Expand Up @@ -60,5 +59,4 @@
.yst-checkbox__input {
@apply before:yst-content-none !important;
}
}
}
86 changes: 86 additions & 0 deletions css/src/new-dashboard.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
body.seo_page_wpseo_page_new_dashboard {
@apply yst-bg-slate-100;

/* Move WP footer behind our content. */
z-index: -1;

#wpcontent {
padding-left: 0 !important;
}

#wpfooter {
@apply yst-pr-4;

@media (min-width: 768px) {
@apply yst-pr-8;

padding-left: 17rem;
}
}

.wp-responsive-open #wpbody {
@media screen and (max-width: 782px) {
right: -190px; /* Override to not leave space between the mobile menu and the content. */
}
}

#modal-search .yst-modal__close {
margin-top: -0.25rem;
}

&.sticky-menu {
.yst-root .yst-notifications--bottom-left {
@media (min-width: 783px) and (max-width: 962px) {
left: calc(160px + 2rem); /* Next to admin menu. 160px is the admin menu width. */
}
}

&.folded, &.auto-fold {
.yst-root .yst-notifications--bottom-left {
@media (min-width: 783px) and (max-width: 963px) {
left: calc(32px + 2rem); /* Next to admin menu. 32px is the collapsed admin menu width. */
}
}
}

&.folded {
.yst-root .yst-notifications--bottom-left {
@media (min-width: 962px) {
left: calc(32px + 2rem); /* Next to admin menu. 32px is the collapsed admin menu width. */
}
}
}
}

&:not(.sticky-menu) .wp-responsive-open .yst-root {
.yst-notifications--bottom-left {
@media (max-width: 783px) {
left: calc(190px + 2rem); /* Next to admin menu. 190px is the responsive admin menu width. */
}
}
}

.yst-root {

.yst-mobile-navigation__top {
@media (min-width: 601px) and (max-width: 768px) {
top: 46px;
}

@media (min-width: 783px) {
@apply yst-hidden;
}
}

.yst-mobile-navigation__dialog {
z-index: 99999;
}
}
/* RTL */

&.rtl {
.yst-root .yst-replacevar .emoji-select-popover {
@apply yst-left-0 yst-right-auto;
}
}
}
120 changes: 104 additions & 16 deletions packages/js/src/dashboard/app.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,118 @@
/* eslint-disable complexity */

import { Transition } from "@headlessui/react";
import { AdjustmentsIcon, BellIcon } from "@heroicons/react/outline";
import { __ } from "@wordpress/i18n";
import { Paper, Title } from "@yoast/ui-library";
import { Paper, SidebarNavigation, Title, useSvgAria } from "@yoast/ui-library";
import classNames from "classnames";
import PropTypes from "prop-types";
import { Link, Route, Routes, useLocation } from "react-router-dom";
import FirstTimeConfigurationSteps from "../first-time-configuration/first-time-configuration-steps";
import { MenuItemLink, YoastLogo } from "../shared-admin/components";
import { useSelectDashboard } from "./hooks";

/**
* @param {string} [idSuffix] Extra id suffix. Can prevent double IDs on the page.
* @returns {JSX.Element} The menu element.
*/
const Menu = ( { idSuffix = "" } ) => {
const svgAriaProps = useSvgAria();
const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" );

return <>
<header className="yst-px-3 yst-mb-6 yst-space-y-6">
<Link
id={ `link-yoast-logo${ idSuffix }` }
to="/"
className="yst-inline-block yst-rounded-md focus:yst-ring-primary-500"
aria-label={ `Yoast SEO${ isPremium ? " Premium" : "" }` }
>
<YoastLogo className="yst-w-40" { ...svgAriaProps } />
</Link>
</header>
<div className="yst-px-0.5 yst-space-y-6">
<ul className="yst-mt-1 yst-space-y-1">
<MenuItemLink icon={ BellIcon } to="/" label={ __( "Alert center", "wordpress-seo" ) } idSuffix={ idSuffix } />
<MenuItemLink
icon={ AdjustmentsIcon } to="/first-time-configuration"
label={ __( "First-time configuration", "wordpress-seo" ) }
idSuffix={ idSuffix }
/>
</ul>
</div>
</>;
};
Menu.propTypes = {
idSuffix: PropTypes.string,
};
/**
* @returns {JSX.Element} The dashboard content placeholder.
*/
const Content = () => {
return <>
<header className="yst-p-8 yst-border-b yst-border-slate-200">
<div className="yst-max-w-screen-sm">
<Title>{ __( "Alert center", "wordpress-seo" ) }</Title>
<p className="yst-text-tiny yst-mt-3">
{ __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) }
</p>
</div>
</header>
<div className="yst-h-full yst-p-8">
<div
className="yst-max-w-6xl yst-grid yst-gap-6 yst-grid-cols-1 sm:yst-grid-cols-2 min-[783px]:yst-grid-cols-1 lg:yst-grid-cols-2 xl:yst-grid-cols-4"
>
Content
</div>
</div>
</>;
};
/**
* @returns {JSX.Element} The app component.
*/
const App = () => {
const { pathname } = useLocation();
const isPremium = useSelectDashboard( "selectPreference", [], "isPremium" );
return (
<div className="yst-p-4 min-[783px]:yst-p-8 yst-mb-8 xl:yst-mb-0">
<Paper as="main">
<header className="yst-p-8 yst-border-b yst-border-slate-200">
<div className="yst-max-w-screen-sm">
<Title>{ __( "Alert center", "wordpress-seo" ) }</Title>
<p className="yst-text-tiny yst-mt-3">
{ __( "Monitor and manage potential SEO problems affecting your site and stay informed with important notifications and updates.", "wordpress-seo" ) }
</p>
</div>
</header>
<div className="yst-h-full yst-p-8">
<div className="yst-max-w-6xl yst-grid yst-gap-6 yst-grid-cols-1 sm:yst-grid-cols-2 min-[783px]:yst-grid-cols-1 lg:yst-grid-cols-2 xl:yst-grid-cols-4">
Content
<SidebarNavigation activePath={ pathname }>
<SidebarNavigation.Mobile
openButtonId="button-open-dashboard-navigation-mobile"
closeButtonId="button-close-dashboard-navigation-mobile"
/* translators: Hidden accessibility text. */
openButtonScreenReaderText={ __( "Open dashboard navigation", "wordpress-seo" ) }
/* translators: Hidden accessibility text. */
closeButtonScreenReaderText={ __( "Close dashboard navigation", "wordpress-seo" ) }
aria-label={ __( "Dashboard navigation", "wordpress-seo" ) }
>
<Menu idSuffix="-mobile" />
</SidebarNavigation.Mobile>
<div className="yst-p-4 min-[783px]:yst-p-8 yst-flex yst-gap-4">
<aside className="yst-sidebar yst-sidebar-nav yst-shrink-0 yst-hidden min-[783px]:yst-block yst-pb-6 yst-bottom-0 yst-w-56">
<SidebarNavigation.Sidebar>
<Menu />
</SidebarNavigation.Sidebar>
</aside>
<div className={ classNames( "yst-flex yst-grow yst-flex-wrap", ! isPremium && "xl:yst-pr-[17.5rem]" ) }>
<div className="yst-grow yst-space-y-6 yst-mb-8 xl:yst-mb-0">
<Paper as="main">
<Transition
key={ pathname }
appear={ true }
show={ true }
enter="yst-transition-opacity yst-delay-100 yst-duration-300"
enterFrom="yst-opacity-0"
enterTo="yst-opacity-100"
>
<Routes>
<Route path="/" element={ <Content /> } />
<Route path="/first-time-configuration" element={ <FirstTimeConfigurationSteps /> } />
</Routes>
</Transition>
</Paper>
</div>
</div>
</Paper>
</div>
</div>
</SidebarNavigation>
);
};

Expand Down
1 change: 1 addition & 0 deletions packages/js/src/dashboard/components/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as RouteLayout } from "./route-layout";
48 changes: 48 additions & 0 deletions packages/js/src/dashboard/components/route-layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { __, sprintf } from "@wordpress/i18n";
import { Title } from "@yoast/ui-library";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";
import { LiveAnnouncer, LiveMessage } from "react-aria-live";

/**
* @param {Object} props The properties.
* @param {JSX.node} children The children.
* @param {string} title The title.
* @param {JSX.node} [description] The description.
* @returns {JSX.Element} The route layout component.
*/
const RouteLayout = ( {
children,
title,
description,
} ) => {
const ariaLiveTitle = sprintf(
/* translators: 1: Settings' section title, 2: Yoast SEO */
__( "%1$s Dashboard - %2$s", "wordpress-seo" ),
title,
"Yoast SEO"
);
return (
<LiveAnnouncer>
<LiveMessage message={ ariaLiveTitle } aria-live="polite" />
<Helmet>
<title>Dashboard</title>
</Helmet>
<header className="yst-p-8 yst-border-b yst-border-slate-200">
<div className="yst-max-w-screen-sm">
<Title>{ title }</Title>
{ description && <p className="yst-text-tiny yst-mt-3">{ description }</p> }
</div>
</header>
{ children }
</LiveAnnouncer>
);
};

RouteLayout.propTypes = {
children: PropTypes.node.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.node,
};

export default RouteLayout;
2 changes: 2 additions & 0 deletions packages/js/src/dashboard/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

export { default as useSelectDashboard } from "./use-select-dashboard";
12 changes: 12 additions & 0 deletions packages/js/src/dashboard/hooks/use-select-dashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useSelect } from "@wordpress/data";
import { STORE_NAME } from "../constants";

/**
* @param {string} selector The name of the selector.
* @param {array} [deps] List of dependencies.
* @param {*} [args] Selector arguments.
* @returns {*} The result.
*/
const useSelectDashboard = ( selector, deps = [], ...args ) => useSelect( select => select( STORE_NAME )[ selector ]?.( ...args ), deps );

export default useSelectDashboard;
5 changes: 4 additions & 1 deletion packages/js/src/dashboard/initialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { render } from "@wordpress/element";
import { Root } from "@yoast/ui-library";
import { get } from "lodash";
import { LINK_PARAMS_NAME } from "../shared-admin/store";
import { HashRouter } from "react-router-dom";
import App from "./app";
import { STORE_NAME } from "./constants";
import registerStore from "./store";
Expand All @@ -24,7 +25,9 @@ domReady( () => {
render(
<Root context={ { isRtl } }>
<SlotFillProvider>
<App />
<HashRouter>
<App />
</HashRouter>
</SlotFillProvider>
</Root>,
root
Expand Down
Loading

0 comments on commit 25aab53

Please sign in to comment.