@financial-times/n-myft-ui 26.0.0 → 27.2.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.
Files changed (40) hide show
  1. package/.circleci/config.yml +42 -16
  2. package/.nvmrc +1 -1
  3. package/Makefile +0 -1
  4. package/README.md +2 -48
  5. package/build-state/npm-shrinkwrap.json +10147 -20187
  6. package/components/collections/collections.html +85 -0
  7. package/components/concept-list/concept-list.html +31 -0
  8. package/components/csrf-token/input.html +5 -0
  9. package/components/follow-button/follow-button.html +79 -0
  10. package/components/instant-alert/instant-alert.html +47 -0
  11. package/components/pin-button/pin-button.html +20 -0
  12. package/components/save-for-later/save-for-later.html +68 -0
  13. package/components/unread-articles-indicator/date-fns.js +6 -6
  14. package/demos/app.js +3 -26
  15. package/demos/templates/demo.html +11 -10
  16. package/myft/main.scss +145 -0
  17. package/myft/ui/lists.js +22 -0
  18. package/myft/ui/save-article-to-list-variant.js +376 -0
  19. package/package.json +16 -30
  20. package/components/collections/collections.jsx +0 -68
  21. package/components/collections/collections.test.js +0 -83
  22. package/components/concept-list/concept-list.jsx +0 -69
  23. package/components/concept-list/concept-list.test.js +0 -116
  24. package/components/csrf-token/input.jsx +0 -20
  25. package/components/csrf-token/input.test.js +0 -23
  26. package/components/follow-button/follow-button.jsx +0 -176
  27. package/components/follow-button/follow-button.test.js +0 -40
  28. package/components/index.js +0 -17
  29. package/components/instant-alert/instant-alert.jsx +0 -73
  30. package/components/instant-alert/instant-alert.test.js +0 -86
  31. package/components/pin-button/pin-button.jsx +0 -40
  32. package/components/pin-button/pin-button.test.js +0 -57
  33. package/components/save-for-later/save-for-later.jsx +0 -101
  34. package/components/save-for-later/save-for-later.test.js +0 -59
  35. package/demos/templates/demo-layout.html +0 -25
  36. package/demos/templates/demo.jsx +0 -125
  37. package/dist/bundles/bundle.js +0 -3232
  38. package/jest.config.js +0 -8
  39. package/jsx-migration.md +0 -16
  40. package/webpack.config.js +0 -34
@@ -1,57 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
- import '@testing-library/jest-dom';
4
- import PinButton from './pin-button';
5
-
6
- const flags = {
7
- myFtApi: true,
8
- myFtApiWrite: true
9
- };
10
-
11
- const fixtures = [
12
- {
13
- id: '00000000-0000-0000-0000-000000000022',
14
- name: 'myFT Enterprises',
15
- directType: 'http://www.ft.com/ontology/Topic',
16
- showPrioritiseButton: true
17
- },
18
- {
19
- id: '00000000-0000-0000-0000-000000000023',
20
- name: 'myFT Enterprises',
21
- directType: 'http://www.ft.com/ontology/Topic',
22
- showPrioritiseButton: false
23
- }
24
- ];
25
-
26
-
27
- describe('Pin Button', () => {
28
-
29
- test('It renders', () => {
30
- const { container } = render(<PinButton flags={flags} {...fixtures[0]} />);
31
- expect(container.querySelector(`button[id="myft-pin-button__${fixtures[0].id}"]`)).toBeTruthy();
32
- expect(container.querySelector('button[data-trackable="prioritised"]')).toBeTruthy();
33
- });
34
-
35
- test('It renders unprioritised', () => {
36
- const { container } = render(<PinButton flags={flags} {...fixtures[0]} />);
37
- expect(container.querySelector('button[aria-label="Pin myFT Enterprises in my F T"]')).toBeTruthy();
38
- expect(container.querySelector('button[title="Pin myFT Enterprises"]')).toBeTruthy();
39
- expect(container.querySelector('button[data-prioritised=false]')).toBeTruthy();
40
- expect(container.querySelector(`button[data-concept-id="${fixtures[0].id}"]`)).toBeTruthy();
41
- });
42
-
43
- test('It renders with prioritised', () => {
44
- const { container } = render(<PinButton flags={flags} prioritised={true} {...fixtures[0]} />);
45
- expect(container.querySelector('button[aria-label="Unpin myFT Enterprises from my F T"]')).toBeTruthy();
46
- expect(container.querySelector('button[title="Unpin myFT Enterprises"]')).toBeTruthy();
47
- expect(container.querySelector('button[data-prioritised=true]')).toBeTruthy();
48
- expect(container.querySelector(`button[data-concept-id="${fixtures[0].id}"]`)).toBeTruthy();
49
- });
50
-
51
- test('It renders the form element', () => {
52
- const { container } = render(<PinButton flags={flags} {...fixtures[0]} />);
53
- expect(container.querySelector('form[method="post"]')).toBeTruthy();
54
- expect(container.querySelector(`form[action="/__myft/api/core/prioritised/concept/${fixtures[0].id}?method=put"]`)).toBeTruthy();
55
- });
56
-
57
- });
@@ -1,101 +0,0 @@
1
- import React, { Fragment } from 'react';
2
- import CsrfToken from '../csrf-token/input';
3
-
4
- const ButtonContent = ({ saveButtonWithIcon, buttonText, isSaved, appIsStreamPage }) => {
5
-
6
- const DefaultButtonText = () => {
7
- if (appIsStreamPage !== true) {
8
- return <Fragment>
9
- <span className="save-button-longer-copy" data-variant-label>
10
- {isSaved ? 'Saved ' : 'Save '}
11
- </span>
12
- <span className="n-myft-ui__button--viewport-large" aria-hidden="true">to myFT</span>
13
- </Fragment>
14
- }
15
-
16
- return <span>{isSaved ? 'Saved' : 'Save'}</span>;
17
- }
18
-
19
- return (<Fragment>
20
- {
21
- saveButtonWithIcon &&
22
- <span className="save-button-with-icon-copy" data-variant-label>
23
- {buttonText && buttonText}
24
- {!buttonText && (isSaved ? 'Saved' : 'Save')}
25
- </span>
26
- }
27
-
28
- {
29
- !saveButtonWithIcon &&
30
- <Fragment>
31
- {buttonText && buttonText}
32
- {!buttonText && <DefaultButtonText />
33
- }
34
- </Fragment>
35
- }
36
- </Fragment>);
37
- }
38
- export default function SaveForLater({ flags, contentId, title, variant, trackableId, isSaved, appIsStreamPage, alternateText, saveButtonWithIcon, buttonText, csrfToken, cacheablePersonalisedUrl }) {
39
-
40
- const { myFtApiWrite } = flags;
41
-
42
- const generateSubmitButtonProps = () => {
43
- let props = {
44
- type: 'submit',
45
- 'data-trackable': trackableId ? trackableId : 'save-for-later',
46
- 'data-text-variant': appIsStreamPage !== true ? 'save-button-with-icon-copy' : 'save-button-longer-copy',
47
- 'data-content-id': contentId,
48
- className: saveButtonWithIcon ? 'n-myft-ui__save-button-with-icon' : `n-myft-ui__button ${variant ? `n-myft-ui__button--${variant}` : ''}`
49
- };
50
-
51
- if (isSaved) {
52
- let titleText = `${title ? `${title} is` : ''} saved to myFT`;
53
- props['title'] = title;
54
- props['aria-label'] = titleText;
55
- props['data-alternate-label'] = title ? `Save ${title} to myFT for later` : 'Save this article to myFT for later';
56
- props['aria-pressed'] = true;
57
- } else {
58
- let titleText = title ? `Save ${title} to myFT for later` : 'Save this article to myFT for later';
59
- props['title'] = titleText;
60
- props['aria-label'] = titleText;
61
- props['data-alternate-label'] = `${title ? `${title} is` : ''} saved to myFT`;
62
- props['aria-pressed'] = false;
63
- }
64
-
65
- if (alternateText) {
66
- props['data-alternate-text'] = alternateText;
67
- } else if (isSaved) {
68
- props['data-alternate-text'] = 'Save ';
69
- } else {
70
- props['data-alternate-text'] = 'Saved ';
71
- }
72
-
73
- return props;
74
- }
75
-
76
-
77
- return (
78
- <Fragment>
79
- {myFtApiWrite &&
80
- <form className="n-myft-ui n-myft-ui--save" method="GET"
81
- data-content-id={contentId}
82
- data-myft-ui="saved"
83
- action={`/myft/save/${contentId}`}
84
- data-js-action={`/__myft/api/core/saved/content/${contentId}?method=put`}>
85
- <CsrfToken csrfToken={csrfToken} cacheablePersonalisedUrl={cacheablePersonalisedUrl} />
86
-
87
- <div
88
- className="n-myft-ui__announcement o-normalise-visually-hidden"
89
- aria-live="assertive"
90
- data-pressed-text="Article saved in My FT."
91
- data-unpressed-text="Removed article from My FT."
92
- ></div>
93
- <button {...generateSubmitButtonProps()}>
94
- <ButtonContent buttonText={buttonText} saveButtonWithIcon={saveButtonWithIcon} isSaved={isSaved} appIsStreamPage={appIsStreamPage} />
95
- </button>
96
- </form>
97
- }
98
- </Fragment>
99
-
100
- )
101
- }
@@ -1,59 +0,0 @@
1
- import React from 'react';
2
- import SaveForLater from './save-for-later';
3
- import { render, screen } from '@testing-library/react';
4
- import '@testing-library/jest-dom';
5
-
6
- const flags = {
7
- myFtApi: true,
8
- myFtApiWrite: true
9
- };
10
-
11
- const fixture = {
12
- contentId: '00000000-0000-0000-0000-000000000033',
13
- title: 'myFT Enterprises'
14
- };
15
-
16
- describe('SaveForLater component', () => {
17
-
18
- test('It renders', async () => {
19
- render(<SaveForLater flags={flags} {...fixture}/>);
20
- expect(await screen.findByText('Save')).toBeTruthy();
21
- });
22
-
23
- test('It renders button text wen provided', async () => {
24
- render(<SaveForLater flags={flags} {...fixture} buttonText={'Globetrotter'}/>);
25
- expect(await screen.findByText('Globetrotter')).toBeTruthy();
26
- });
27
-
28
- test('It renders the correct form action attribute', () => {
29
- const { container } = render(<SaveForLater flags={flags} {...fixture} buttonText={'Globetrotter'}/>);
30
- const formElement = container.querySelector(`form[action="/myft/save/${fixture.contentId}"]`);
31
- expect(formElement).toBeTruthy();
32
- });
33
-
34
- test('It renders the correct form data-js-action attribute', () => {
35
- const { container } = render(<SaveForLater flags={flags} {...fixture} buttonText={'Globetrotter'}/>);
36
- const formElement = container.querySelector(`form[data-js-action="/__myft/api/core/saved/content/${fixture.contentId}?method=put"]`);
37
- expect(formElement).toBeTruthy();
38
- });
39
-
40
- test('It renders the correct form method attribute', () => {
41
- const { container } = render(<SaveForLater flags={flags} {...fixture} buttonText={'Globetrotter'}/>);
42
- const formElement = container.querySelector('form[method="GET"]');
43
- expect(formElement).toBeTruthy();
44
- });
45
-
46
- test('It renders the correct button data-text-variant attribute when appIsStreamPage=true', () => {
47
- const { container } = render(<SaveForLater appIsStreamPage={true} flags={flags} {...fixture} buttonText={'Globetrotter'}/>);
48
- const buttonElement = container.querySelector('button[data-text-variant="save-button-longer-copy"]');
49
- expect(buttonElement).toBeTruthy();
50
- });
51
-
52
- describe('Saved', () => {
53
- test('It renders saved item', async () => {
54
- render(<SaveForLater isSaved={true} flags={flags} {...fixture}/>);
55
- expect(await screen.findByText('Saved')).toBeTruthy();
56
- });
57
- });
58
-
59
- });
@@ -1,25 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="utf-8">
6
- <title>{{title}}</title>
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <link rel="stylesheet" href="/public/main.css">
9
- </head>
10
-
11
- <body>
12
-
13
- <div class="o-grid-container o-grid-container--snappy">
14
- <div class="o-grid-row">
15
- <ul>
16
- <li><a href="/">Basic</a></li>
17
- <li><a href="/demo-jsx">JSX demo</a></li>
18
- </ul>
19
- </div>
20
- </div>
21
-
22
- {{{body}}}
23
- </body>
24
-
25
- </html>
@@ -1,125 +0,0 @@
1
- import React from 'react';
2
- import FollowButton from '../../components/follow-button/follow-button';
3
- import ConceptList from '../../components/concept-list/concept-list';
4
- import Collections from '../../components/collections/collections';
5
- import { SaveForLater } from '../../components';
6
- import { PinButton } from '../../components';
7
-
8
- export default function Demo (props) {
9
-
10
- const {
11
- title,
12
- flags,
13
- followButton,
14
- conceptList,
15
- collections,
16
- saveButton,
17
- pinButton
18
- } = props;
19
-
20
- const followButtonProps = { ...followButton, flags };
21
-
22
- return (
23
- <div className="o-grid-container o-grid-container--snappy demo-container">
24
- <h1>{title}</h1>
25
-
26
- <section
27
- id="follow-button"
28
- className="demo-section">
29
- <div className="o-grid-row">
30
- <div data-o-grid-colspan="12">
31
- <h2
32
- className="demo-section__title">
33
- Follow button
34
- </h2>
35
- <FollowButton {...followButtonProps} />
36
-
37
-
38
- <h2
39
- className="demo-section__title">
40
- x-dash follow button
41
- </h2>
42
-
43
- <FollowButton {...followButtonProps} buttonText={followButton.name} />
44
-
45
-
46
- <h2 className="demo-section__title">
47
- Save button
48
- </h2>
49
- <SaveForLater flags={flags} {...saveButton} />
50
-
51
- <h2 className="demo-section__title">
52
- Unsave button
53
- </h2>
54
- <SaveForLater flags={flags} {...saveButton} isSaved={true} />
55
-
56
- <h2 className="demo-section__title">
57
- Unsave button with icon
58
- </h2>
59
- <SaveForLater flags={flags} {...saveButton} saveButtonWithIcon={true} />
60
-
61
- <h2 className="demo-section__title">
62
- Save button with icon
63
- </h2>
64
- <SaveForLater flags={flags} {...saveButton} isSaved={true} saveButtonWithIcon={true} />
65
-
66
- <h2 className="demo-section__title">
67
- Pin button
68
- </h2>
69
-
70
- {pinButton.map((item, index) => <PinButton key={index} {...item}/>)}
71
-
72
- </div>
73
- </div>
74
- </section>
75
-
76
- <section
77
- id="topic-list"
78
- className="demo-section">
79
- <div className="o-grid-row">
80
- <div data-o-grid-colspan="12">
81
- <h2 className="demo-section__title">
82
- Topic list
83
- </h2>
84
-
85
- <p className="demo-section__description">
86
- A list of topics to follow
87
- </p>
88
- </div>
89
-
90
- {
91
- conceptList && conceptList.map((list, index) =>
92
- <div key={index} data-o-grid-colspan="3">
93
- <ConceptList {...list} flags={flags} />
94
- </div>)
95
- }
96
-
97
- </div>
98
- </section>
99
-
100
- <section
101
- id="collections"
102
- className="demo-section">
103
- <div className="o-grid-row">
104
- <div data-o-grid-colspan="12">
105
- <h2 className="demo-section__title">
106
- Collections
107
- </h2>
108
-
109
- <p className="demo-section__description">
110
- Curated collections of topics to follow.
111
- </p>
112
- </div>
113
-
114
- {collections.map((collection, index) => (
115
- <div key={index} data-o-grid-colspan="3">
116
- <Collections {...collection} flags={flags} />
117
- </div>
118
- ))}
119
-
120
- </div>
121
- </section>
122
-
123
- </div>
124
- )
125
- }