@financial-times/dotcom-ui-header 4.2.0 → 6.0.0

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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.EditionsSwitcher = exports.DrawerSpecialItem = exports.DrawerSingleItem = exports.DrawerParentItem = void 0;
6
+ exports.SubscribeButton = exports.EditionsSwitcher = exports.DrawerSpecialItem = exports.DrawerSingleItem = exports.DrawerParentItem = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const utils_1 = require("../../utils");
9
9
  exports.DrawerParentItem = ({ item, idSuffix }) => {
@@ -34,3 +34,5 @@ exports.EditionsSwitcher = (editions) => (react_1.default.createElement("ul", {
34
34
  name,
35
35
  " Edition")));
36
36
  })));
37
+ exports.SubscribeButton = (action) => (react_1.default.createElement("div", { className: "o-header__drawer-actions" },
38
+ react_1.default.createElement("a", { className: "o-header__drawer-button", role: "button", href: action.url, "data-trackable": "subscribe-button" }, action.name)));
@@ -10,11 +10,13 @@ const IncludeDrawer = (props) => react_1.default.createElement(Drawer, Object.as
10
10
  exports.IncludeDrawer = IncludeDrawer;
11
11
  const Drawer = (props) => {
12
12
  const editions = props.data.editions;
13
+ const subscribeAction = props.data.subscribeAction;
13
14
  const [primary, secondary, tertiary] = props.data.drawer.items;
14
15
  const user = props.userIsLoggedIn ? props.data.user : props.data.anon;
15
16
  return (react_1.default.createElement("div", { className: "o-header__drawer", id: "o-header-drawer", role: "navigation", "aria-label": "Drawer menu", "data-o-header-drawer": true, "data-o-header-drawer--no-js": true, "data-trackable": "drawer", "data-trackable-terminate": true },
16
17
  react_1.default.createElement("div", { className: "o-header__drawer-inner" },
17
18
  react_1.default.createElement(DrawerTools, Object.assign({}, editions)),
19
+ !props.userIsSubscribed && subscribeAction && react_1.default.createElement(additionalPartials_1.SubscribeButton, Object.assign({}, subscribeAction)),
18
20
  react_1.default.createElement(Search, null),
19
21
  react_1.default.createElement("nav", { className: "o-header__drawer-menu o-header__drawer-menu--primary o-header__drawer-menu--border" },
20
22
  editions && react_1.default.createElement(additionalPartials_1.EditionsSwitcher, Object.assign({}, editions)),
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.MobileNav = exports.UserActionsNav = exports.NavListRightAnon = exports.NavListRight = exports.NavListLeft = exports.NavDesktop = void 0;
6
+ exports.MobileNav = exports.UserActionsNav = exports.NavListRight = exports.NavListLeft = exports.NavDesktop = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const utils_1 = require("../../utils");
9
9
  const MobileNav = (props) => {
@@ -25,32 +25,13 @@ const NavListLeft = (props) => (react_1.default.createElement("ul", { className:
25
25
  props.showMegaNav && Array.isArray(item.meganav) ? (react_1.default.createElement(MegaNav, { meganav: item.meganav, label: item.label, index: index })) : null)))));
26
26
  exports.NavListLeft = NavListLeft;
27
27
  const NavListRight = (props) => {
28
- if (props.userIsLoggedIn) {
29
- return react_1.default.createElement(NavListRightLoggedIn, { items: props.data['navbar-right'].items });
30
- }
31
- else {
32
- return react_1.default.createElement(NavListRightAnon, { items: props.data['navbar-right-anon'].items });
33
- }
28
+ return props.userIsLoggedIn ? react_1.default.createElement(NavListRightLoggedIn, { items: props.data['navbar-right'].items }) : null;
34
29
  };
35
30
  exports.NavListRight = NavListRight;
36
31
  const NavListRightLoggedIn = ({ items }) => {
37
32
  return (react_1.default.createElement("ul", { className: "o-header__nav-list o-header__nav-list--right", "data-trackable": "user-nav" }, items.map((item, index) => (react_1.default.createElement("li", { className: "o-header__nav-item", key: `link-${index}` },
38
33
  react_1.default.createElement("a", { className: "o-header__nav-link", href: item.url, "data-trackable": item.label }, item.label))))));
39
34
  };
40
- const NavListRightAnon = ({ items, variant }) => {
41
- // If user is anonymous the second list item is styled as a button
42
- const [first, second] = items;
43
- const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null;
44
- return (react_1.default.createElement("ul", { className: "o-header__nav-list o-header__nav-list--right", "data-trackable": "user-nav" },
45
- react_1.default.createElement("li", { className: "o-header__nav-item" },
46
- react_1.default.createElement("a", Object.assign({ className: "o-header__nav-link", href: first.url, "data-trackable": first.label }, setTabIndex), first.label)),
47
- react_1.default.createElement("li", { className: "o-header__nav-item o-header__nav-item--hide-s" },
48
- react_1.default.createElement("a", Object.assign({ className: "o-header__nav-button",
49
- // Added as the result of a DAC audit. This will be confusing for users of voice activation software
50
- // as it looks like a button but behaves like a link without this role.
51
- role: "button", href: second.url, "data-trackable": second.label }, setTabIndex), second.label))));
52
- };
53
- exports.NavListRightAnon = NavListRightAnon;
54
35
  const MegaNav = ({ label, meganav, index }) => {
55
36
  const sections = meganav.find(({ component }) => component === 'sectionlist');
56
37
  const articles = meganav.find(({ component }) => component === 'articlelist');
@@ -7,12 +7,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.TopColumnRightSticky = exports.TopColumnCenterSticky = exports.TopColumnLeftSticky = exports.TopWrapperSticky = exports.StickyHeaderWrapper = void 0;
9
9
  const react_1 = __importDefault(require("react"));
10
- const partials_1 = require("../navigation/partials");
10
+ const partials_1 = require("../top/partials");
11
11
  const StickyHeaderWrapper = (props) => (react_1.default.createElement("header", { className: `o-header o-header--simple o-header--sticky o--if-js`, "data-o-component": "o-header", "data-o-header--sticky": true, "aria-hidden": "true" }, props.children));
12
12
  exports.StickyHeaderWrapper = StickyHeaderWrapper;
13
- const DrawerIconSticky = () => (react_1.default.createElement("a", { href: "#", className: "o-header__top-link o-header__top-link--menu", "aria-controls": "o-header-drawer", "data-trackable": "drawer-toggle", tabIndex: -1 },
13
+ const DrawerIconSticky = () => (react_1.default.createElement("a", { href: "#", className: "o-header__top-icon-link o-header__top-icon-link--menu", "aria-controls": "o-header-drawer", "data-trackable": "drawer-toggle", tabIndex: -1 },
14
14
  react_1.default.createElement("span", { className: "o-header__top-link-label" }, "Menu")));
15
- const SearchIconSticky = () => (react_1.default.createElement("a", { href: "#", className: "o-header__top-link o-header__top-link--search", "aria-controls": "o-header-search-sticky", "data-trackable": "search-toggle", tabIndex: -1 },
15
+ const SearchIconSticky = () => (react_1.default.createElement("a", { href: "#", className: "o-header__top-icon-link o-header__top-icon-link--search", "aria-controls": "o-header-search-sticky", "data-trackable": "search-toggle", tabIndex: -1 },
16
16
  react_1.default.createElement("span", { className: "o-header__top-link-label" }, "Search")));
17
17
  const Navigation = (props) => (react_1.default.createElement("div", { className: "o-header__top-takeover" },
18
18
  react_1.default.createElement("div", { className: "o-header__nav" },
@@ -21,11 +21,14 @@ const Navigation = (props) => (react_1.default.createElement("div", { className:
21
21
  const Logo = () => (react_1.default.createElement("a", { className: "o-header__top-logo o-header__hide--L", "data-trackable": "logo", href: "/", title: "Go to Financial Times homepage", tabIndex: -1 },
22
22
  react_1.default.createElement("span", { className: "o-header__visually-hidden" }, "Financial Times")));
23
23
  const NavListRightAnonSticky = (props) => {
24
- const navbarItems = props.data['navbar-right-anon'].items;
24
+ // If user is anonymous the second list item is styled as a button
25
+ const [signInAction, subscribeAction] = props.data['navbar-right-anon'].items;
25
26
  return (react_1.default.createElement("div", { className: "o-header__nav" },
26
- react_1.default.createElement(partials_1.NavListRightAnon, { items: navbarItems, variant: "sticky" })));
27
+ react_1.default.createElement("div", { className: "o-header__top-column o-header__top-column--right" },
28
+ subscribeAction && (react_1.default.createElement(partials_1.SubscribeButton, { item: subscribeAction, variant: "sticky", className: "o-header__top-button--hide-m" })),
29
+ signInAction && react_1.default.createElement(partials_1.SignInLink, { item: signInAction, variant: "sticky", className: "" }))));
27
30
  };
28
- const MyFtSticky = () => (react_1.default.createElement("a", { className: "o-header__top-link o-header__top-link--myft", href: "/myft", "data-trackable": "my-ft", tabIndex: -1 },
31
+ const MyFtSticky = ({ className }) => (react_1.default.createElement("a", { className: `o-header__top-icon-link o-header__top-icon-link--myft ${className}`, href: "/myft", "data-trackable": "my-ft", tabIndex: -1 },
29
32
  react_1.default.createElement("span", { className: "o-header__visually-hidden" }, "myFT")));
30
33
  const TopWrapperSticky = (props) => (react_1.default.createElement("div", { className: "o-header__row o-header__top", "data-trackable": "header-sticky" },
31
34
  react_1.default.createElement("div", { className: "o-header__container" },
@@ -43,13 +46,20 @@ const TopColumnCenterSticky = (props) => {
43
46
  react_1.default.createElement(Logo, null)));
44
47
  };
45
48
  exports.TopColumnCenterSticky = TopColumnCenterSticky;
49
+ const NavListRightLoggedInSticky = (props) => {
50
+ var _a;
51
+ const subscribeAction = (_a = props.data['navbar-right-anon'].items) === null || _a === void 0 ? void 0 : _a[1];
52
+ return (react_1.default.createElement("div", { className: "o-header__top-column o-header__top-column--right" },
53
+ !props.userIsSubscribed && subscribeAction && (react_1.default.createElement(partials_1.SubscribeButton, { item: subscribeAction, variant: props.variant, className: "o-header__top-button--hide-m" })),
54
+ react_1.default.createElement(MyFtSticky, { className: "" })));
55
+ };
46
56
  // This behaviour is similar to `NavListRight` in '../navigation/partials' but:
47
57
  // - The sticky header renders either the `navbar-right-anon` data or the myFT component
48
58
  // - The normal header renders either the `navbar-right-anon` or the `navbar-right` data
49
59
  const TopColumnRightSticky = (props) => {
50
60
  let children = null;
51
61
  if (props.userIsLoggedIn) {
52
- children = react_1.default.createElement(MyFtSticky, null);
62
+ children = react_1.default.createElement(NavListRightLoggedInSticky, Object.assign({}, props));
53
63
  }
54
64
  else if (props.showUserNavigation) {
55
65
  children = react_1.default.createElement(NavListRightAnonSticky, Object.assign({}, props));
@@ -3,16 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.TopColumnRight = exports.TopColumnCenterNoLink = exports.TopColumnCenter = exports.TopColumnLeft = exports.TopWrapper = exports.HeaderWrapper = void 0;
6
+ exports.SignInLink = exports.SubscribeButton = exports.TopColumnRightAnon = exports.TopColumnRight = exports.TopColumnCenterNoLink = exports.TopColumnCenter = exports.TopColumnLeft = exports.TopWrapper = exports.HeaderWrapper = void 0;
7
7
  const react_1 = __importDefault(require("react"));
8
8
  const BrandFtMasthead_1 = __importDefault(require("../svg-components/BrandFtMasthead"));
9
9
  const HeaderWrapper = (props) => (react_1.default.createElement("header", { id: "site-navigation", className: `o-header o-header--${props.variant || 'simple'}`, "data-o-component": "o-header", "data-o-header--no-js": true, tabIndex: -1 }, props.children));
10
10
  exports.HeaderWrapper = HeaderWrapper;
11
- const DrawerIcon = () => (react_1.default.createElement("a", { href: "#o-header-drawer", className: "o-header__top-link o-header__top-link--menu", "aria-controls": "o-header-drawer", title: "Open side navigation menu", "data-trackable": "drawer-toggle" },
11
+ const DrawerIcon = () => (react_1.default.createElement("a", { href: "#o-header-drawer", className: "o-header__top-icon-link o-header__top-icon-link--menu", "aria-controls": "o-header-drawer", title: "Open side navigation menu", "data-trackable": "drawer-toggle" },
12
12
  react_1.default.createElement("span", { className: "o-header__top-link-label" }, "Open side navigation menu")));
13
- const SearchIcon = () => (react_1.default.createElement("a", { href: `#o-header-search-primary`, className: "o-header__top-link o-header__top-link--search", "aria-controls": `o-header-search-primary`, title: "Open search bar", "data-trackable": "search-toggle" },
13
+ const SearchIcon = () => (react_1.default.createElement("a", { href: `#o-header-search-primary`, className: "o-header__top-icon-link o-header__top-icon-link--search", "aria-controls": `o-header-search-primary`, title: "Open search bar", "data-trackable": "search-toggle" },
14
14
  react_1.default.createElement("span", { className: "o-header__top-link-label" }, "Open search bar")));
15
- const MyFt = () => (react_1.default.createElement("a", { className: "o-header__top-link o-header__top-link--myft", id: "o-header-top-link-myft", href: "/myft", "data-trackable": "my-ft", "data-tour-stage": "myFt", "aria-label": "My F T" },
15
+ const MyFt = ({ className }) => (react_1.default.createElement("a", { className: `o-header__top-icon-link o-header__top-icon-link--myft ${className}`, id: "o-header-top-link-myft", href: "/myft", "data-trackable": "my-ft", "data-tour-stage": "myFt", "aria-label": "My F T" },
16
16
  react_1.default.createElement("span", { className: "o-header__visually-hidden" }, "myFT")));
17
17
  const TopWrapper = (props) => (react_1.default.createElement("div", { className: "o-header__row o-header__top", "data-trackable": "header-top" },
18
18
  react_1.default.createElement("div", { className: "o-header__container" },
@@ -30,6 +30,42 @@ const TopColumnCenterNoLink = () => (react_1.default.createElement("div", { clas
30
30
  react_1.default.createElement("div", { className: "o-header__top-logo", style: { backgroundImage: 'none' } },
31
31
  react_1.default.createElement(BrandFtMasthead_1.default, { title: "Financial Times" }))));
32
32
  exports.TopColumnCenterNoLink = TopColumnCenterNoLink;
33
- const TopColumnRight = () => (react_1.default.createElement("div", { className: "o-header__top-column o-header__top-column--right" },
34
- react_1.default.createElement(MyFt, null)));
33
+ const TopColumnRightLoggedIn = (props) => {
34
+ var _a, _b;
35
+ const subscribeAction = (_b = (_a = props.data['navbar-right-anon']) === null || _a === void 0 ? void 0 : _a.items) === null || _b === void 0 ? void 0 : _b[1];
36
+ return (react_1.default.createElement("div", { className: "o-header__top-column o-header__top-column--right" },
37
+ !props.userIsSubscribed && subscribeAction && (react_1.default.createElement(SubscribeButton, { item: subscribeAction, variant: props.variant, className: "o-header__top-button--hide-m" })),
38
+ react_1.default.createElement(MyFt, { className: "" })));
39
+ };
40
+ const SignInLink = ({ item, variant, className }) => {
41
+ const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null;
42
+ return (react_1.default.createElement("a", Object.assign({ className: `o-header__top-link ${className}`, href: item.url, "data-trackable": item.label }, setTabIndex), item.label));
43
+ };
44
+ exports.SignInLink = SignInLink;
45
+ const SubscribeButton = ({ item, variant, className }) => {
46
+ const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null;
47
+ return (react_1.default.createElement("a", Object.assign({ className: `o-header__top-button ${className}`,
48
+ // Added as the result of a DAC audit. This will be confusing for users of voice activation software
49
+ // as it looks like a button but behaves like a link without this role.
50
+ role: "button", href: item.url, "data-trackable": item.label }, setTabIndex), item.label));
51
+ };
52
+ exports.SubscribeButton = SubscribeButton;
53
+ const TopColumnRightAnon = ({ items, variant }) => {
54
+ // If user is anonymous the second list item is styled as a button
55
+ const [signInAction, subscribeAction] = items;
56
+ return (react_1.default.createElement("div", { className: "o-header__top-column o-header__top-column--right" },
57
+ subscribeAction && (react_1.default.createElement(SubscribeButton, { item: subscribeAction, variant: variant, className: "o-header__top-button--hide-m" })),
58
+ signInAction && (react_1.default.createElement(SignInLink, { item: signInAction, variant: variant, className: "o-header__top-link--hide-m" })),
59
+ react_1.default.createElement(MyFt, { className: "o-header__top-icon-link--show-m" })));
60
+ };
61
+ exports.TopColumnRightAnon = TopColumnRightAnon;
62
+ const TopColumnRight = (props) => {
63
+ if (props.userIsLoggedIn) {
64
+ return react_1.default.createElement(TopColumnRightLoggedIn, Object.assign({}, props));
65
+ }
66
+ else {
67
+ const userNavAnonItems = props.data['navbar-right-anon'].items;
68
+ return react_1.default.createElement(TopColumnRightAnon, { items: userNavAnonItems, variant: props.variant });
69
+ }
70
+ };
35
71
  exports.TopColumnRight = TopColumnRight;
@@ -27,7 +27,7 @@ function MainHeader(props) {
27
27
  react_1.default.createElement(partials_1.TopWrapper, null,
28
28
  react_1.default.createElement(partials_1.TopColumnLeft, null),
29
29
  props.showLogoLink ? react_1.default.createElement(partials_1.TopColumnCenter, null) : react_1.default.createElement(partials_1.TopColumnCenterNoLink, null),
30
- react_1.default.createElement(partials_1.TopColumnRight, null)),
30
+ react_1.default.createElement(partials_1.TopColumnRight, Object.assign({}, props))),
31
31
  react_1.default.createElement(partials_5.Search, { instance: "primary" }),
32
32
  react_1.default.createElement(partials_2.MobileNav, Object.assign({}, props)),
33
33
  react_1.default.createElement(partials_2.NavDesktop, null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/dotcom-ui-header",
3
- "version": "4.2.0",
3
+ "version": "6.0.0",
4
4
  "description": "",
5
5
  "browser": "browser.js",
6
6
  "main": "component.js",
@@ -23,7 +23,7 @@
23
23
  "author": "",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@financial-times/dotcom-types-navigation": "^4.2.0",
26
+ "@financial-times/dotcom-types-navigation": "^6.0.0",
27
27
  "n-topic-search": "^3.2.3",
28
28
  "n-ui-foundations": "^7.0.0"
29
29
  },
@@ -32,11 +32,11 @@
32
32
  "@svgr/core": "^5.0.0",
33
33
  "camelcase": "^6.0.0",
34
34
  "check-engine": "^1.10.1",
35
- "@financial-times/o-header": "^9.2.0",
35
+ "@financial-times/o-header": "^10.0.0",
36
36
  "react": "^16.8.6"
37
37
  },
38
38
  "peerDependencies": {
39
- "@financial-times/o-header": "^9.2.0",
39
+ "@financial-times/o-header": "^10.0.0",
40
40
  "@financial-times/logo-images": "^1.10.1",
41
41
  "react": "16.x || 17.x"
42
42
  },
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { ariaSelected } from '../../utils'
3
- import { TNavMenuItem, TNavEditions } from '@financial-times/dotcom-types-navigation'
3
+ import { TNavMenuItem, TNavEditions, TNavAction } from '@financial-times/dotcom-types-navigation'
4
4
 
5
5
  export type TDrawerParentItemProps = {
6
6
  item: TNavMenuItem
@@ -95,3 +95,11 @@ export const EditionsSwitcher = (editions: TNavEditions) => (
95
95
  })}
96
96
  </ul>
97
97
  )
98
+
99
+ export const SubscribeButton = (action: TNavAction) => (
100
+ <div className="o-header__drawer-actions">
101
+ <a className="o-header__drawer-button" role="button" href={action.url} data-trackable="subscribe-button">
102
+ {action.name}
103
+ </a>
104
+ </div>
105
+ )
@@ -1,5 +1,11 @@
1
1
  import React from 'react'
2
- import { DrawerParentItem, DrawerSingleItem, DrawerSpecialItem, EditionsSwitcher } from './additionalPartials'
2
+ import {
3
+ DrawerParentItem,
4
+ DrawerSingleItem,
5
+ DrawerSpecialItem,
6
+ EditionsSwitcher,
7
+ SubscribeButton
8
+ } from './additionalPartials'
3
9
  import { THeaderProps } from '../../interfaces'
4
10
  import { TNavMenuItem, TNavMenu, TNavEditions } from '@financial-times/dotcom-types-navigation'
5
11
 
@@ -7,6 +13,7 @@ const IncludeDrawer = (props) => <Drawer {...props} />
7
13
 
8
14
  const Drawer = (props: THeaderProps) => {
9
15
  const editions = props.data.editions
16
+ const subscribeAction = props.data.subscribeAction
10
17
  const [primary, secondary, tertiary] = props.data.drawer.items
11
18
  const user = props.userIsLoggedIn ? props.data.user : props.data.anon
12
19
 
@@ -23,6 +30,7 @@ const Drawer = (props: THeaderProps) => {
23
30
  >
24
31
  <div className="o-header__drawer-inner">
25
32
  <DrawerTools {...editions} />
33
+ {!props.userIsSubscribed && subscribeAction && <SubscribeButton {...subscribeAction} />}
26
34
  <Search />
27
35
  <nav className="o-header__drawer-menu o-header__drawer-menu--primary o-header__drawer-menu--border">
28
36
  {editions && <EditionsSwitcher {...editions} />}
@@ -23,7 +23,8 @@ const NavMobile = ({ items }: { items: TNavMenuItem[] }) => {
23
23
  id="o-header-nav-mobile"
24
24
  className="o-header__row o-header__nav o-header__nav--mobile"
25
25
  aria-hidden="true"
26
- data-trackable="header-nav:mobile">
26
+ data-trackable="header-nav:mobile"
27
+ >
27
28
  <ul className="o-header__nav-list">
28
29
  {items.map((item, index) => (
29
30
  <li className="o-header__nav-item" key={`link-${index}`}>
@@ -31,7 +32,8 @@ const NavMobile = ({ items }: { items: TNavMenuItem[] }) => {
31
32
  className="o-header__nav-link o-header__nav-link--primary"
32
33
  href={item.url}
33
34
  {...ariaSelected(item)}
34
- data-trackable={item.label}>
35
+ data-trackable={item.label}
36
+ >
35
37
  {item.label}
36
38
  </a>
37
39
  </li>
@@ -47,7 +49,8 @@ const NavDesktop = (props) => (
47
49
  className="o-header__row o-header__nav o-header__nav--desktop"
48
50
  role="navigation"
49
51
  aria-label="Primary navigation"
50
- data-trackable="header-nav:desktop">
52
+ data-trackable="header-nav:desktop"
53
+ >
51
54
  <div className="o-header__container">{props.children}</div>
52
55
  </nav>
53
56
  )
@@ -61,7 +64,8 @@ const NavListLeft = (props: THeaderProps) => (
61
64
  href={item.url}
62
65
  id={`o-header-link-${index}`}
63
66
  {...ariaSelected(item)}
64
- data-trackable={item.label}>
67
+ data-trackable={item.label}
68
+ >
65
69
  {item.label}
66
70
  </a>
67
71
  {props.showMegaNav && Array.isArray(item.meganav) ? (
@@ -73,11 +77,7 @@ const NavListLeft = (props: THeaderProps) => (
73
77
  )
74
78
 
75
79
  const NavListRight = (props: THeaderProps) => {
76
- if (props.userIsLoggedIn) {
77
- return <NavListRightLoggedIn items={props.data['navbar-right'].items} />
78
- } else {
79
- return <NavListRightAnon items={props.data['navbar-right-anon'].items} />
80
- }
80
+ return props.userIsLoggedIn ? <NavListRightLoggedIn items={props.data['navbar-right'].items} /> : null
81
81
  }
82
82
 
83
83
  const NavListRightLoggedIn = ({ items }: { items: TNavMenuItem[] }) => {
@@ -94,33 +94,6 @@ const NavListRightLoggedIn = ({ items }: { items: TNavMenuItem[] }) => {
94
94
  )
95
95
  }
96
96
 
97
- const NavListRightAnon = ({ items, variant }: { items: TNavMenuItem[]; variant?: string }) => {
98
- // If user is anonymous the second list item is styled as a button
99
- const [first, second] = items
100
- const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null
101
- return (
102
- <ul className="o-header__nav-list o-header__nav-list--right" data-trackable="user-nav">
103
- <li className="o-header__nav-item">
104
- <a className="o-header__nav-link" href={first.url} data-trackable={first.label} {...setTabIndex}>
105
- {first.label}
106
- </a>
107
- </li>
108
- <li className="o-header__nav-item o-header__nav-item--hide-s">
109
- <a
110
- className="o-header__nav-button"
111
- // Added as the result of a DAC audit. This will be confusing for users of voice activation software
112
- // as it looks like a button but behaves like a link without this role.
113
- role="button"
114
- href={second.url}
115
- data-trackable={second.label}
116
- {...setTabIndex}>
117
- {second.label}
118
- </a>
119
- </li>
120
- </ul>
121
- )
122
- }
123
-
124
97
  const MegaNav = ({ label, meganav, index }: { label: string; meganav: TNavMeganav[]; index: number }) => {
125
98
  const sections = meganav.find(({ component }) => component === 'sectionlist')
126
99
  const articles = meganav.find(({ component }) => component === 'articlelist')
@@ -132,7 +105,8 @@ const MegaNav = ({ label, meganav, index }: { label: string; meganav: TNavMegana
132
105
  role="group"
133
106
  aria-labelledby={`o-header-link-${index}`}
134
107
  data-o-header-mega
135
- data-trackable={`meganav | ${label}`}>
108
+ data-trackable={`meganav | ${label}`}
109
+ >
136
110
  <div className="o-header__container">
137
111
  <div className="o-header__mega-wrapper">
138
112
  {sections ? <SectionList {...(sections as INavMeganavSections)} /> : null}
@@ -156,7 +130,8 @@ const SectionList = ({ title, data }: INavMeganavSections) => {
156
130
  className="o-header__mega-link"
157
131
  href={item.url}
158
132
  {...ariaSelected(item)}
159
- data-trackable="link">
133
+ data-trackable="link"
134
+ >
160
135
  {item.label}
161
136
  </a>
162
137
  </li>
@@ -180,7 +155,8 @@ const ArticleList = ({ title, data }: INavMeganavArticles) => {
180
155
  className="o-header__mega-link"
181
156
  href={item.url}
182
157
  {...ariaSelected(item)}
183
- data-trackable="link">
158
+ data-trackable="link"
159
+ >
184
160
  {item.label}
185
161
  </a>
186
162
  </li>
@@ -209,4 +185,4 @@ const UserActionsNav = (props: THeaderProps) => {
209
185
  )
210
186
  }
211
187
 
212
- export { NavDesktop, NavListLeft, NavListRight, NavListRightAnon, UserActionsNav, MobileNav }
188
+ export { NavDesktop, NavListLeft, NavListRight, UserActionsNav, MobileNav }
@@ -2,7 +2,7 @@
2
2
  /* This is the sticky header variant */
3
3
 
4
4
  import React from 'react'
5
- import { NavListRightAnon } from '../navigation/partials'
5
+ import { SubscribeButton, SignInLink } from '../top/partials'
6
6
  import { THeaderProps } from '../../interfaces'
7
7
 
8
8
  const StickyHeaderWrapper = (props: THeaderProps & { children: React.ReactNode }) => (
@@ -10,7 +10,8 @@ const StickyHeaderWrapper = (props: THeaderProps & { children: React.ReactNode }
10
10
  className={`o-header o-header--simple o-header--sticky o--if-js`}
11
11
  data-o-component="o-header"
12
12
  data-o-header--sticky
13
- aria-hidden="true">
13
+ aria-hidden="true"
14
+ >
14
15
  {props.children}
15
16
  </header>
16
17
  )
@@ -18,10 +19,11 @@ const StickyHeaderWrapper = (props: THeaderProps & { children: React.ReactNode }
18
19
  const DrawerIconSticky = () => (
19
20
  <a
20
21
  href="#"
21
- className="o-header__top-link o-header__top-link--menu"
22
+ className="o-header__top-icon-link o-header__top-icon-link--menu"
22
23
  aria-controls="o-header-drawer"
23
24
  data-trackable="drawer-toggle"
24
- tabIndex={-1}>
25
+ tabIndex={-1}
26
+ >
25
27
  <span className="o-header__top-link-label">Menu</span>
26
28
  </a>
27
29
  )
@@ -29,10 +31,11 @@ const DrawerIconSticky = () => (
29
31
  const SearchIconSticky = () => (
30
32
  <a
31
33
  href="#"
32
- className="o-header__top-link o-header__top-link--search"
34
+ className="o-header__top-icon-link o-header__top-icon-link--search"
33
35
  aria-controls="o-header-search-sticky"
34
36
  data-trackable="search-toggle"
35
- tabIndex={-1}>
37
+ tabIndex={-1}
38
+ >
36
39
  <span className="o-header__top-link-label">Search</span>
37
40
  </a>
38
41
  )
@@ -47,7 +50,8 @@ const Navigation = (props: THeaderProps) => (
47
50
  className="o-header__nav-link o-header__nav-link--primary"
48
51
  href={item.url}
49
52
  data-trackable={item.label}
50
- tabIndex={-1}>
53
+ tabIndex={-1}
54
+ >
51
55
  {item.label}
52
56
  </a>
53
57
  </li>
@@ -63,27 +67,34 @@ const Logo = () => (
63
67
  data-trackable="logo"
64
68
  href="/"
65
69
  title="Go to Financial Times homepage"
66
- tabIndex={-1}>
70
+ tabIndex={-1}
71
+ >
67
72
  <span className="o-header__visually-hidden">Financial Times</span>
68
73
  </a>
69
74
  )
70
75
 
71
76
  const NavListRightAnonSticky = (props: THeaderProps) => {
72
- const navbarItems = props.data['navbar-right-anon'].items
73
-
77
+ // If user is anonymous the second list item is styled as a button
78
+ const [signInAction, subscribeAction] = props.data['navbar-right-anon'].items
74
79
  return (
75
80
  <div className="o-header__nav">
76
- <NavListRightAnon items={navbarItems} variant="sticky" />
81
+ <div className="o-header__top-column o-header__top-column--right">
82
+ {subscribeAction && (
83
+ <SubscribeButton item={subscribeAction} variant="sticky" className="o-header__top-button--hide-m" />
84
+ )}
85
+ {signInAction && <SignInLink item={signInAction} variant="sticky" className="" />}
86
+ </div>
77
87
  </div>
78
88
  )
79
89
  }
80
90
 
81
- const MyFtSticky = () => (
91
+ const MyFtSticky = ({ className }: { className?: string }) => (
82
92
  <a
83
- className="o-header__top-link o-header__top-link--myft"
93
+ className={`o-header__top-icon-link o-header__top-icon-link--myft ${className}`}
84
94
  href="/myft"
85
95
  data-trackable="my-ft"
86
- tabIndex={-1}>
96
+ tabIndex={-1}
97
+ >
87
98
  <span className="o-header__visually-hidden">myFT</span>
88
99
  </a>
89
100
  )
@@ -113,6 +124,22 @@ const TopColumnCenterSticky = (props: THeaderProps) => {
113
124
  )
114
125
  }
115
126
 
127
+ const NavListRightLoggedInSticky = (props: THeaderProps) => {
128
+ const subscribeAction = props.data['navbar-right-anon'].items?.[1]
129
+ return (
130
+ <div className="o-header__top-column o-header__top-column--right">
131
+ {!props.userIsSubscribed && subscribeAction && (
132
+ <SubscribeButton
133
+ item={subscribeAction}
134
+ variant={props.variant}
135
+ className="o-header__top-button--hide-m"
136
+ />
137
+ )}
138
+ <MyFtSticky className="" />
139
+ </div>
140
+ )
141
+ }
142
+
116
143
  // This behaviour is similar to `NavListRight` in '../navigation/partials' but:
117
144
  // - The sticky header renders either the `navbar-right-anon` data or the myFT component
118
145
  // - The normal header renders either the `navbar-right-anon` or the `navbar-right` data
@@ -120,7 +147,7 @@ const TopColumnRightSticky = (props: THeaderProps) => {
120
147
  let children = null
121
148
 
122
149
  if (props.userIsLoggedIn) {
123
- children = <MyFtSticky />
150
+ children = <NavListRightLoggedInSticky {...props} />
124
151
  } else if (props.showUserNavigation) {
125
152
  children = <NavListRightAnonSticky {...props} />
126
153
  }
@@ -1,5 +1,7 @@
1
1
  import React from 'react'
2
+ import { THeaderProps } from '../../interfaces'
2
3
  import BrandFtMastheadSvg from '../svg-components/BrandFtMasthead'
4
+ import { TNavMenuItem } from '@financial-times/dotcom-types-navigation'
3
5
 
4
6
  const HeaderWrapper = (props) => (
5
7
  <header
@@ -16,7 +18,7 @@ const HeaderWrapper = (props) => (
16
18
  const DrawerIcon = () => (
17
19
  <a
18
20
  href="#o-header-drawer"
19
- className="o-header__top-link o-header__top-link--menu"
21
+ className="o-header__top-icon-link o-header__top-icon-link--menu"
20
22
  aria-controls="o-header-drawer"
21
23
  title="Open side navigation menu"
22
24
  data-trackable="drawer-toggle"
@@ -28,7 +30,7 @@ const DrawerIcon = () => (
28
30
  const SearchIcon = () => (
29
31
  <a
30
32
  href={`#o-header-search-primary`}
31
- className="o-header__top-link o-header__top-link--search"
33
+ className="o-header__top-icon-link o-header__top-icon-link--search"
32
34
  aria-controls={`o-header-search-primary`}
33
35
  title="Open search bar"
34
36
  data-trackable="search-toggle"
@@ -37,9 +39,9 @@ const SearchIcon = () => (
37
39
  </a>
38
40
  )
39
41
 
40
- const MyFt = () => (
42
+ const MyFt = ({ className }: { className?: string }) => (
41
43
  <a
42
- className="o-header__top-link o-header__top-link--myft"
44
+ className={`o-header__top-icon-link o-header__top-icon-link--myft ${className}`}
43
45
  id="o-header-top-link-myft"
44
46
  href="/myft"
45
47
  data-trackable="my-ft"
@@ -87,10 +89,101 @@ const TopColumnCenterNoLink = () => (
87
89
  </div>
88
90
  )
89
91
 
90
- const TopColumnRight = () => (
91
- <div className="o-header__top-column o-header__top-column--right">
92
- <MyFt />
93
- </div>
94
- )
92
+ const TopColumnRightLoggedIn = (props: THeaderProps) => {
93
+ const subscribeAction = props.data['navbar-right-anon']?.items?.[1]
94
+ return (
95
+ <div className="o-header__top-column o-header__top-column--right">
96
+ {!props.userIsSubscribed && subscribeAction && (
97
+ <SubscribeButton
98
+ item={subscribeAction}
99
+ variant={props.variant}
100
+ className="o-header__top-button--hide-m"
101
+ />
102
+ )}
103
+ <MyFt className="" />
104
+ </div>
105
+ )
106
+ }
107
+
108
+ const SignInLink = ({
109
+ item,
110
+ variant,
111
+ className
112
+ }: {
113
+ item: TNavMenuItem
114
+ variant?: string
115
+ className?: string
116
+ }) => {
117
+ const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null
118
+ return (
119
+ <a
120
+ className={`o-header__top-link ${className}`}
121
+ href={item.url}
122
+ data-trackable={item.label}
123
+ {...setTabIndex}
124
+ >
125
+ {item.label}
126
+ </a>
127
+ )
128
+ }
129
+ const SubscribeButton = ({
130
+ item,
131
+ variant,
132
+ className
133
+ }: {
134
+ item: TNavMenuItem
135
+ variant?: string
136
+ className?: string
137
+ }) => {
138
+ const setTabIndex = variant === 'sticky' ? { tabIndex: -1 } : null
139
+ return (
140
+ <a
141
+ className={`o-header__top-button ${className}`}
142
+ // Added as the result of a DAC audit. This will be confusing for users of voice activation software
143
+ // as it looks like a button but behaves like a link without this role.
144
+ role="button"
145
+ href={item.url}
146
+ data-trackable={item.label}
147
+ {...setTabIndex}
148
+ >
149
+ {item.label}
150
+ </a>
151
+ )
152
+ }
153
+
154
+ const TopColumnRightAnon = ({ items, variant }: { items: TNavMenuItem[]; variant?: string }) => {
155
+ // If user is anonymous the second list item is styled as a button
156
+ const [signInAction, subscribeAction] = items
157
+ return (
158
+ <div className="o-header__top-column o-header__top-column--right">
159
+ {subscribeAction && (
160
+ <SubscribeButton item={subscribeAction} variant={variant} className="o-header__top-button--hide-m" />
161
+ )}
162
+ {signInAction && (
163
+ <SignInLink item={signInAction} variant={variant} className="o-header__top-link--hide-m" />
164
+ )}
165
+ <MyFt className="o-header__top-icon-link--show-m" />
166
+ </div>
167
+ )
168
+ }
169
+
170
+ const TopColumnRight = (props: THeaderProps) => {
171
+ if (props.userIsLoggedIn) {
172
+ return <TopColumnRightLoggedIn {...props} />
173
+ } else {
174
+ const userNavAnonItems = props.data['navbar-right-anon'].items
175
+ return <TopColumnRightAnon items={userNavAnonItems} variant={props.variant} />
176
+ }
177
+ }
95
178
 
96
- export { HeaderWrapper, TopWrapper, TopColumnLeft, TopColumnCenter, TopColumnCenterNoLink, TopColumnRight }
179
+ export {
180
+ HeaderWrapper,
181
+ TopWrapper,
182
+ TopColumnLeft,
183
+ TopColumnCenter,
184
+ TopColumnCenterNoLink,
185
+ TopColumnRight,
186
+ TopColumnRightAnon,
187
+ SubscribeButton,
188
+ SignInLink
189
+ }
package/src/index.tsx CHANGED
@@ -46,7 +46,7 @@ function MainHeader(props: THeaderProps) {
46
46
  <TopWrapper>
47
47
  <TopColumnLeft />
48
48
  {props.showLogoLink ? <TopColumnCenter /> : <TopColumnCenterNoLink />}
49
- <TopColumnRight />
49
+ <TopColumnRight {...props} />
50
50
  </TopWrapper>
51
51
  <Search instance="primary" />
52
52
  <MobileNav {...props} />
@@ -4,6 +4,7 @@ export type THeaderOptions = {
4
4
  variant?: THeaderVariant
5
5
  userIsAnonymous?: boolean
6
6
  userIsLoggedIn?: boolean
7
+ userIsSubscribed?: boolean
7
8
  showSubNavigation?: boolean
8
9
  showUserNavigation?: boolean
9
10
  showStickyHeader?: boolean