@financial-times/dotcom-ui-header 9.0.0-beta.9 → 9.0.2

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.
@@ -1,114 +0,0 @@
1
- /**
2
- * @jest-environment jsdom
3
- */
4
- import 'jest-enzyme'
5
- import React from 'react'
6
- import { mount } from 'enzyme'
7
-
8
- import navigationData from '../../__stories__/story-data/index'
9
- import { Drawer as Subject } from '../../'
10
-
11
- const fixture = {
12
- data: { ...navigationData.data, currentPath: '/world' }
13
- }
14
-
15
- const loggedInUserFixture = {
16
- ...fixture,
17
- userIsAnonymous: false,
18
- userIsLoggedIn: true
19
- }
20
-
21
- const anonymousUserFixture = {
22
- ...fixture,
23
- userIsAnonymous: true,
24
- userIsLoggedIn: false
25
- }
26
-
27
- describe('dotcom-ui-header/src/components/drawer', () => {
28
- describe('editions', () => {
29
- let result
30
-
31
- beforeAll(() => {
32
- result = mount(<Subject {...fixture} />)
33
- })
34
-
35
- it('renders the current edition text', () => {
36
- expect(result.find('.o-header__drawer-current-edition')).toHaveText('UK Edition')
37
- })
38
-
39
- it('renders the alternative edition link', () => {
40
- expect(result.find('[data-trackable="edition-switcher"] a')).toHaveText(
41
- 'Switch to International Edition'
42
- )
43
- })
44
- })
45
-
46
- describe('navigation links', () => {
47
- let result
48
-
49
- beforeAll(() => {
50
- result = mount(<Subject {...fixture} />)
51
- })
52
-
53
- it('renders the primary link section title', () => {
54
- expect(result.find('.o-header__drawer-menu-item--heading').at(0)).toHaveText('Top sections')
55
- })
56
-
57
- it('renders the secondary link section title', () => {
58
- expect(result.find('.o-header__drawer-menu-item--heading').at(1)).toHaveText('FT recommends')
59
- })
60
-
61
- it('renders the tertiary link section divider', () => {
62
- expect(result.find('.o-header__drawer-menu-list--divide > li:first-child')).toHaveText('myFT')
63
- })
64
-
65
- it('renders primary link subsections', () => {
66
- const section = result
67
- .find('.o-header__drawer-menu-item')
68
- .findWhere((node) => node.key() === '/companies')
69
- .at(0)
70
-
71
- expect(section.find('.o-header__drawer-menu-link--parent')).toHaveText('Companies')
72
- expect(section.find('.o-header__drawer-menu-toggle')).toHaveText('Show more Companies')
73
- expect(section.find('.o-header__drawer-menu-link--child').length).toBe(10)
74
- })
75
-
76
- it('highlights the current page', () => {
77
- expect(result.find('[aria-current="page"]')).toHaveText('World')
78
- })
79
- })
80
-
81
- describe('user menu', () => {
82
- describe('for a logged in user', () => {
83
- let result
84
-
85
- beforeAll(() => {
86
- result = mount(<Subject {...loggedInUserFixture} />)
87
- })
88
-
89
- it('renders sign out link', () => {
90
- expect(result.find('a[data-trackable="Sign Out"]')).toExist()
91
- })
92
-
93
- it('renders settings and account link', () => {
94
- expect(result.find('a[data-trackable="Settings & Account"]')).toExist()
95
- })
96
- })
97
-
98
- describe('for an anonymous user', () => {
99
- let result
100
-
101
- beforeAll(() => {
102
- result = mount(<Subject {...anonymousUserFixture} />)
103
- })
104
-
105
- it('renders sign in link', () => {
106
- expect(result.find('a[data-trackable="Sign In"]')).toExist()
107
- })
108
-
109
- it('renders subscribe link', () => {
110
- expect(result.find('a[data-trackable="Subscribe"]')).toExist()
111
- })
112
- })
113
- })
114
- })
@@ -1,168 +0,0 @@
1
- import BaseRenderer from 'n-topic-search/src/renderers/base-renderer'
2
-
3
- const DISPLAY_ITEMS = 5
4
-
5
- const headingMapping = {
6
- concepts: 'Related topics',
7
- equities: 'Securities'
8
- }
9
-
10
- class CustomSuggestionList extends BaseRenderer {
11
- constructor(container, options, enhancedSearchUrl) {
12
- super(container, options)
13
- this.renderSuggestionGroup = this.renderSuggestionGroup.bind(this)
14
- this.enhancedSearchUrl = enhancedSearchUrl
15
- this.createHtml()
16
- this.render()
17
- }
18
-
19
- renderSuggestionChip = (term) => {
20
- return `<a
21
- tabindex="0"
22
- data-trackable="link"
23
- data-suggestion-id="${term}"
24
- href="${this.enhancedSearchUrl}${term}"
25
- class="n-topic-search__target enhanced-search__chip">
26
- <span class="enhanced-search__chip-text">${term}</span>
27
- </a>`
28
- }
29
-
30
- renderDefaultSuggestionsChips() {
31
- return `
32
- <div class="enhanced-search__default-results">
33
- ${['Will Trump win the next election?', 'Investing in AI', 'Ukraine counteroffensive']
34
- .map(this.renderSuggestionChip)
35
- .join('')}
36
- </div>`
37
- }
38
-
39
- renderSuggestionLink(suggestion, group) {
40
- return `<li class="n-topic-search__item">
41
- <a class="n-topic-search__target enhanced-search__target ${group.linkClassName}"
42
- href="${suggestion.url}"
43
- tabindex="0"
44
- data-trackable="link"
45
- data-suggestion-id="${suggestion.id}"
46
- data-suggestion-type="${suggestion.type}"
47
- >${suggestion.html}</a>
48
- </li>`
49
- }
50
-
51
- renderSuggestionGroup(group) {
52
- if (group.suggestions?.length || group.emptyHtml) {
53
- return `
54
- <div class="enhanced-search__group ${group.linkClassName}" data-trackable="${group.trackable}">
55
- <h3 class="enhanced-search__title">${group.heading}</h3>
56
- <ul class="n-topic-search__item-list">
57
- ${group.suggestions.map((suggestion) => this.renderSuggestionLink(suggestion, group)).join('')}
58
- </ul>
59
- </div>`
60
- }
61
- return ''
62
- }
63
-
64
- renderError() {
65
- return `
66
- <div
67
- class="o-message o-message--alert o-message--error enhanced-search__margin-top"
68
- data-o-component="o-message">
69
- <div class="o-message__container">
70
- <div class="o-message__content">
71
- <p class="o-message__content-main">Something went wrong!</p>
72
- </div>
73
- </div>
74
- </div>
75
- `
76
- }
77
-
78
- createHtml() {
79
- const term = this.state?.searchTerm
80
- const loading = this.state?.loading
81
- const error = this.state?.error !== undefined
82
- const hasConcepts = this.state?.suggestions?.concepts && this.state.suggestions.concepts.length
83
- const hasEquities = this.state?.suggestions?.equities && this.state.suggestions.equities.length
84
- const hasSuggestions = hasConcepts || hasEquities
85
- const suggestions = []
86
- this.items = []
87
- if (this.options.categories.includes('concepts')) {
88
- suggestions.push({
89
- heading: headingMapping['concepts'],
90
- linkClassName: 'enhanced-search__target--news',
91
- trackable: 'enhanced-search-news',
92
- suggestions: this.state.suggestions.concepts?.slice(0, DISPLAY_ITEMS).map((suggestion) => ({
93
- html: this.highlight(suggestion.prefLabel),
94
- url: suggestion.url,
95
- id: suggestion.id,
96
- type: 'enhanced-search-tag'
97
- }))
98
- })
99
- }
100
-
101
- if (this.options.categories.includes('equities')) {
102
- suggestions.push({
103
- heading: headingMapping['equities'],
104
- trackable: 'enhanced-search-equities',
105
- linkClassName: 'enhanced-search__target--equities',
106
- emptyHtml: '<div className="enhanced-search__no-results-message">No equities found</div>',
107
- suggestions: this.state.suggestions.equities?.slice(0, DISPLAY_ITEMS).map((suggestion) => ({
108
- html: `<span class="enhanced-search__target__equity-name">${this.highlight(
109
- suggestion.name
110
- )}</span><abbr>${this.highlight(suggestion.symbol)}</abbr>`,
111
- url: suggestion.url,
112
- id: suggestion.symbol,
113
- type: 'enhanced-search-equity'
114
- }))
115
- })
116
- }
117
-
118
- this.newHtml = `
119
- <div aria-live="assertive">
120
- ${
121
- hasSuggestions
122
- ? `
123
- <div
124
- class="o-normalise-visually-hidden n-topic-search__suggestions_explanation"
125
- >
126
- Search results have been displayed. To jump to the list of suggestions press
127
- the down arrow key.
128
- </div>
129
- `
130
- : ''
131
- }
132
- <div
133
- class="n-topic-search n-topic-search__suggestions enhanced-search enhanced-search__suggestions"
134
- data-trackable="typeahead"
135
- >
136
- <div class="enhanced-search__wrapper">
137
- <h3 class="enhanced-search__title">${
138
- term ? 'Get top results for...' : 'Search tip: Try using questions or phrases like...'
139
- }</h3>
140
- ${term ? this.renderSuggestionChip(term) : this.renderDefaultSuggestionsChips()}
141
- <div class="o-normalise-visually-hidden">Suggestions include</div>
142
- ${error ? this.renderError() : ''}
143
- ${
144
- loading && !error
145
- ? `<div class="o-loading o-loading--dark o-loading--small enhanced-search__margin-top"></div>`
146
- : ''
147
- }
148
- ${
149
- hasSuggestions && term && !loading && !error
150
- ? `<div class="enhanced-search__suggestions-items">${suggestions
151
- .map(this.renderSuggestionGroup)
152
- .join('')}</div>
153
- `
154
- : ''
155
- }
156
- </div>
157
- </div>
158
- </div>`
159
- }
160
-
161
- handleSelection(el, ev) {
162
- ev.stopPropagation()
163
- // we don't prevent default as the link's url is a link to the relevant stream page
164
- return
165
- }
166
- }
167
-
168
- export default CustomSuggestionList
@@ -1,59 +0,0 @@
1
- import TopicSearch from 'n-topic-search'
2
- import CustomSuggestionList from './customList'
3
-
4
- class EnhancedSearch extends TopicSearch {
5
- constructor(containerEl, options) {
6
- super(containerEl, {
7
- ...options,
8
- listComponent: (...args) => new CustomSuggestionList(...args.concat(options?.enhancedSearchUrl)),
9
- errorCallback: (error) => {
10
- this.suggestionsView.setState({
11
- error,
12
- searchTerm: this.searchEl.value,
13
- suggestions: {}
14
- })
15
- }
16
- })
17
-
18
- this.updateEnhancedSearchAttributes(options)
19
- }
20
-
21
- updateEnhancedSearchAttributes(options) {
22
- const inputs = [
23
- document.querySelector('#o-header-search-term-primary'),
24
- document.querySelector('#o-header-search-term-sticky'),
25
- document.querySelector('#o-header-drawer-search-term')
26
- ]
27
-
28
- inputs.forEach((input, index) => {
29
- input.setAttribute('placeholder', 'Search for stories, topics or securities')
30
- input.parentElement.setAttribute('action', options?.enhancedSearchUrl ?? '/search')
31
-
32
- if (index === 0) input.parentElement.setAttribute('data-attribute-enhanced-search', 'true')
33
- })
34
-
35
- this.hide()
36
- }
37
-
38
- onFocus(ev) {
39
- super.onFocus(ev)
40
- this.show()
41
- this.suggestionTargets = Array.from(
42
- this.suggestionListContainer.querySelectorAll('.n-topic-search__target')
43
- )
44
- }
45
-
46
- onType(ev) {
47
- // This is to update the suggestion chip on keyup
48
- this.suggestionsView.setState({
49
- searchTerm: this.searchEl.value,
50
- loading: this.searchEl.value && this.searchEl.value.length >= this.minLength,
51
- suggestions: {}
52
- })
53
- super.onType(ev)
54
- // this is show the flyout for less than minimum length
55
- this.show()
56
- }
57
- }
58
-
59
- export default EnhancedSearch
@@ -1,146 +0,0 @@
1
- @use '@financial-times/o-colors/main' as colors;
2
- @use '@financial-times/o-spacing/main' as spacing;
3
- @use '@financial-times/o-typography/main' as typography;
4
-
5
- @mixin enhancedSearch {
6
- // Hide search-bar on small screens
7
- .o-header__search[aria-hidden='false'] {
8
- display: none;
9
- @include oGridRespondTo('L') {
10
- display: block;
11
- }
12
- }
13
-
14
- .enhanced-search {
15
- &__suggestions {
16
- @include oGridRespondTo('L') {
17
- max-width: 540px;
18
- left: calc(-1 * spacing.oSpacingByName('s3'));
19
- }
20
- }
21
-
22
- &__title {
23
- color: colors.oColorsByName('black');
24
- @include typography.oTypographySans($scale: 0, $style: 'normal', $weight: 'semibold');
25
- margin: 0 0 spacing.oSpacingByName('s3') 0;
26
- }
27
-
28
- &__margin-top {
29
- margin-top: spacing.oSpacingByName('s4');
30
- }
31
-
32
- &__wrapper {
33
- display: flex;
34
- flex-direction: column;
35
- width: 100%;
36
- padding: spacing.oSpacingByName('s6');
37
- }
38
-
39
- &__title {
40
- color: colors.oColorsByName('black');
41
- @include typography.oTypographySans($scale: 0, $style: 'normal', $weight: 'semibold');
42
- margin: 0 0 spacing.oSpacingByName('s3') 0;
43
- }
44
-
45
- &__suggestions-items {
46
- display: flex;
47
- gap: spacing.oSpacingByName('m16');
48
- padding-top: spacing.oSpacingByName('s6');
49
- flex-direction: column;
50
-
51
- @include oGridRespondTo('L') {
52
- flex-direction: row;
53
- }
54
- }
55
-
56
- &__target {
57
- padding: 0;
58
- padding-block: spacing.oSpacingByName('s2');
59
- }
60
-
61
- &__target::before,
62
- &__chip::before {
63
- border-bottom: unset !important;
64
- }
65
-
66
- &__target:hover,
67
- &__target:focus {
68
- background-color: transparent;
69
- color: oColorsByName('teal');
70
- }
71
-
72
- &__target:hover:before,
73
- &__target:focus:before {
74
- opacity: 0;
75
- }
76
-
77
- &__target--news {
78
- min-width: spacing.oSpacingByIncrement(25);
79
- color: colors.oColorsByName('claret-70');
80
- }
81
-
82
- &__target--news:hover,
83
- &__target--news:focus {
84
- color: colors.oColorsByName('claret-30');
85
- }
86
-
87
- &__target--equities {
88
- color: colors.oColorsByName('black');
89
- }
90
-
91
- a.enhanced-search__target--equities{
92
- display: flex;
93
- align-items: flex-start;
94
- gap: spacing.oSpacingByName('s3');
95
- }
96
-
97
- &__target--equities:hover,
98
- &__target--equities:focus {
99
- color: colors.oColorsByName('slate');
100
- }
101
-
102
- &__chip {
103
- border-radius: spacing.oSpacingByName('s1');
104
- background: colors.oColorsMix('ft-grey', 'white', 10);
105
- padding: spacing.oSpacingByName('s2') spacing.oSpacingByName('s3');
106
- width: fit-content;
107
- cursor: pointer;
108
- text-decoration: none;
109
- display: block;
110
- }
111
-
112
- &__chip:hover,
113
- &__chip:focus {
114
- background: colors.oColorsMix('ft-grey', 'white', 20);
115
- }
116
-
117
- &__chip:focus-visible {
118
- box-shadow: 0 0 0 spacing.oSpacingByIncrement(0.5) colors.oColorsByName('white'),
119
- 0 0 0 spacing.oSpacingByName('s1') colors.oColorsByName('black-70');
120
- color: colors.oColorsByName('white');
121
- border-color: transparent;
122
- border-radius: 0;
123
- outline: none;
124
- }
125
-
126
- &__chip-text {
127
- margin: 0;
128
- color: colors.oColorsByName('black');
129
- @include typography.oTypographySans($scale: -1, $style: 'normal', $weight: 'regular');
130
- outline: none;
131
- }
132
-
133
- &__default-results {
134
- display: flex;
135
- justify-content: flex-start;
136
- align-items: flex-start;
137
- gap: spacing.oSpacingByName('s2');
138
- flex-direction: column;
139
-
140
- @include oGridRespondTo('L') {
141
- flex-direction: row;
142
- align-items: center;
143
- }
144
- }
145
- }
146
- }