├── public ├── favicon.ico ├── manifest.json └── index.html ├── src ├── utils │ ├── __mocks__ │ │ └── terms.js │ ├── GoogleAnalytics.js │ └── terms.js ├── components │ ├── Highlight │ │ ├── Highlight.css │ │ ├── __snapshots__ │ │ │ └── Highlight.test.js.snap │ │ ├── Highlight.test.js │ │ └── Highlight.js │ ├── Header │ │ ├── __snapshots__ │ │ │ └── Header.test.js.snap │ │ ├── Header.test.js │ │ ├── logo.svg │ │ ├── Header.js │ │ └── Header.css │ ├── ScrollToTop │ │ ├── __snapshots__ │ │ │ └── ScrollToTop.test.js.snap │ │ ├── ScrollToTop.test.js │ │ └── ScrollToTop.js │ ├── TermList │ │ ├── __snapshots__ │ │ │ └── TermList.test.js.snap │ │ ├── TermList.test.js │ │ ├── TermList.js │ │ └── TermList.css │ ├── TermCard │ │ ├── __snapshots__ │ │ │ └── TermCard.test.js.snap │ │ ├── TermCard.test.js │ │ ├── TermCard.js │ │ └── TermCard.css │ └── Search │ │ ├── Search.test.js │ │ ├── Search.css │ │ ├── __snapshots__ │ │ └── Search.test.js.snap │ │ └── Search.js ├── index.js ├── shared.css ├── App.css ├── App.test.js ├── screens │ ├── Term │ │ ├── Term.test.js │ │ ├── __snapshots__ │ │ │ └── Term.test.js.snap │ │ ├── Term.css │ │ └── Term.js │ └── Home │ │ ├── __snapshots__ │ │ └── Home.test.js.snap │ │ ├── Home.test.js │ │ ├── Home.css │ │ └── Home.js ├── __snapshots__ │ └── App.test.js.snap ├── App.js ├── sampleTerms.json └── logo.svg ├── .travis.yml ├── app.json ├── config ├── jest │ ├── fileTransform.js │ └── cssTransform.js ├── data.js ├── polyfills.js ├── eslint.js ├── paths.js ├── env.js ├── webpackDevServer.config.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── .gitignore ├── scripts ├── test.js ├── start.js └── build.js ├── LICENSE ├── README.md └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/__mocks__/terms.js: -------------------------------------------------------------------------------- 1 | export const departments = []; 2 | 3 | export default []; 4 | -------------------------------------------------------------------------------- /src/components/Highlight/Highlight.css: -------------------------------------------------------------------------------- 1 | .mark { 2 | color: var(--color-white); 3 | background-color: var(--color-gold); 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | cache: yarn 5 | jobs: 6 | include: 7 | - stage: test 8 | script: eslint 9 | -------------------------------------------------------------------------------- /src/components/Header/__snapshots__/Header.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Header component renders correctly 1`] = `null`; 4 | -------------------------------------------------------------------------------- /src/components/Highlight/__snapshots__/Highlight.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Highlight component renders correctly 1`] = `
`; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /src/components/ScrollToTop/__snapshots__/ScrollToTop.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ScrollToTop component renders correctly 1`] = `
`; 4 | -------------------------------------------------------------------------------- /src/shared.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: block; 3 | padding: 2rem; 4 | position: relative; 5 | min-height: 100vh; 6 | } 7 | 8 | @media(min-width: 40rem) { 9 | .container { 10 | padding: 4rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/GoogleAnalytics.js: -------------------------------------------------------------------------------- 1 | import GA from 'react-ga'; 2 | 3 | export default (props) => { 4 | GA.set({ page: props.location.pathname + props.location.search }); 5 | GA.pageview(props.location.pathname + props.location.search); 6 | return null; 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/TermList/__snapshots__/TermList.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TermList component renders correctly 1`] = ` 4 |
5 |
8 |
9 | `; 10 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lingo", 3 | "description": "Appear Here's Data Dictionary", 4 | "repository": "https://github.com/appearhere/lingo", 5 | "logo": "https://logos.appearhere.co.uk/brackets/64x64/FFFFFF.png", 6 | "keywords": ["node", "react", "data-dictonary"] 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/terms.js: -------------------------------------------------------------------------------- 1 | /* global __TERMS__:true */ 2 | 3 | export const departments = __TERMS__ 4 | .reduce((acc, { department }) => { 5 | if (acc.indexOf(department) === -1) acc.push(department); 6 | return acc; 7 | }, []) 8 | .sort(); 9 | 10 | export default __TERMS__; 11 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .app { 6 | box-sizing: border-box; 7 | -webkit-font-smoothing: antialiased; 8 | margin: 0; 9 | font-family: 'Avenir Next W01', 'Helvetica Neue', 'Helvetica', 'sans-serif'; 10 | color: var(--color-black); 11 | height: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /src/components/TermCard/__snapshots__/TermCard.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TermCard component renders correctly 1`] = ` 4 | 9 |
12 |
15 | 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/TermList/TermList.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import TermList from './TermList'; 4 | 5 | describe('TermList component', () => { 6 | it('renders correctly', () => { 7 | const tree = renderer 8 | .create() 9 | .toJSON(); 10 | expect(tree).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return `module.exports = ${JSON.stringify(path.basename(filename))};`; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Highlight/Highlight.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import Highlight from './Highlight'; 5 | 6 | describe('Highlight component', () => { 7 | it('renders correctly', () => { 8 | const tree = renderer 9 | .create() 10 | .toJSON(); 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | src/terms.json 23 | -------------------------------------------------------------------------------- /config/data.js: -------------------------------------------------------------------------------- 1 | const paths = require('./paths'); 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | 6 | const terms = path.resolve(paths.appSrc, 'terms.json'); 7 | const sampleTerms = path.resolve(paths.appSrc, 'sampleTerms.json'); 8 | 9 | const termsFile = fs.existsSync(terms) ? terms : sampleTerms; 10 | 11 | module.exports = { 12 | terms: fs.readFileSync(termsFile).toString(), 13 | }; 14 | -------------------------------------------------------------------------------- /src/components/Search/Search.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter } from 'react-router'; 4 | 5 | import Search from './Search'; 6 | 7 | describe('Search component', () => { 8 | it('renders correctly', () => { 9 | const tree = renderer 10 | .create() 11 | .toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | import App from './App'; 5 | 6 | jest.mock('@appearhere/react-stickynode'); 7 | jest.mock('react-ga'); 8 | jest.mock('./utils/terms'); 9 | 10 | describe('App component', () => { 11 | it('renders correctly', () => { 12 | const tree = renderer 13 | .create() 14 | .toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/TermCard/TermCard.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter } from 'react-router'; 4 | 5 | import TermCard from './TermCard'; 6 | 7 | describe('TermCard component', () => { 8 | it('renders correctly', () => { 9 | const tree = renderer 10 | .create() 11 | .toJSON(); 12 | expect(tree).toMatchSnapshot(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/screens/Term/Term.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter, Route } from 'react-router'; 4 | 5 | import Term from './Term'; 6 | 7 | jest.mock('../../utils/terms'); 8 | 9 | describe('Term component', () => { 10 | it('renders correctly', () => { 11 | const tree = renderer 12 | .create() 13 | .toJSON(); 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/screens/Home/__snapshots__/Home.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Home component renders correctly 1`] = ` 4 |
7 |

10 | Here's the Lingo 11 |

12 |

15 | Lingo is here to help you understand the terms we all use. 16 |

17 |
18 |
21 |
22 |
23 | `; 24 | -------------------------------------------------------------------------------- /src/screens/Home/Home.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter, Route } from 'react-router'; 4 | 5 | import Home from './Home'; 6 | 7 | jest.mock('../../utils/terms', () => ([])); 8 | 9 | describe('Home component', () => { 10 | it('renders correctly', () => { 11 | const tree = renderer 12 | .create() 13 | .toJSON(); 14 | expect(tree).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/components/Header/Header.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter, Route } from 'react-router'; 4 | 5 | import Header from './Header'; 6 | 7 | jest.mock('@appearhere/react-stickynode'); 8 | jest.mock('../../utils/terms'); 9 | 10 | describe('Header component', () => { 11 | it('renders correctly', () => { 12 | const tree = renderer 13 | .create() 14 | .toJSON(); 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Search/Search.css: -------------------------------------------------------------------------------- 1 | .root { 2 | justify-self: end; 3 | grid-area: search; 4 | overflow: hidden; 5 | min-width: 0; 6 | min-height: 0; 7 | width: 100%; 8 | } 9 | 10 | .inputRoot { 11 | width: 100%; 12 | } 13 | 14 | input[type="text"].input { 15 | padding-top: var(--size-regular); 16 | padding-bottom: var(--size-regular); 17 | background-color: var(--color-greyLightest); 18 | } 19 | 20 | input[type="text"].input:focus { 21 | outline: none; 22 | background-color: var(--color-greyLightest); 23 | border-color: var(--color-greyLighter); 24 | } 25 | -------------------------------------------------------------------------------- /src/__snapshots__/App.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App component renders correctly 1`] = ` 4 |
7 |
10 |

13 | Here's the Lingo 14 |

15 |

18 | Lingo is here to help you understand the terms we all use. 19 |

20 |
21 |
24 |
25 |
26 |
27 | `; 28 | -------------------------------------------------------------------------------- /src/components/ScrollToTop/ScrollToTop.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { StaticRouter } from 'react-router'; 4 | 5 | import ScrollToTop from './ScrollToTop'; 6 | 7 | describe('ScrollToTop component', () => { 8 | it('renders correctly', () => { 9 | const tree = renderer 10 | .create( 11 | 12 | 13 |
14 | 15 | , 16 | ) 17 | .toJSON(); 18 | expect(tree).toMatchSnapshot(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/ScrollToTop/ScrollToTop.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { withRouter } from 'react-router'; 5 | 6 | class ScrollToTop extends Component { 7 | static propTypes = { 8 | location: PropTypes.shape({ }), 9 | children: PropTypes.node, 10 | }; 11 | 12 | componentDidUpdate(prevProps) { 13 | if (this.props.location !== prevProps.location) { 14 | window.scrollTo(0, 0); 15 | } 16 | } 17 | 18 | render() { 19 | return this.props.children; 20 | } 21 | } 22 | 23 | export default withRouter(ScrollToTop); 24 | -------------------------------------------------------------------------------- /src/components/TermList/TermList.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import TermCard from '../TermCard/TermCard'; 5 | 6 | import css from './TermList.css'; 7 | 8 | const TermList = ({ terms, highlight }) => ( 9 |
10 |
11 | { terms.map(term => ( 12 | 18 | )) } 19 |
20 |
21 | ); 22 | 23 | TermList.propTypes = { 24 | terms: PropTypes.arrayOf(PropTypes.shape({})), 25 | highlight: PropTypes.string, 26 | }; 27 | 28 | TermList.defaultProps = { 29 | terms: [], 30 | }; 31 | 32 | export default TermList; 33 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Do this as the first thing so that any code reading it knows the right env. 4 | process.env.BABEL_ENV = 'test'; 5 | process.env.NODE_ENV = 'test'; 6 | process.env.PUBLIC_URL = ''; 7 | 8 | // Makes the script crash on unhandled rejections instead of silently 9 | // ignoring them. In the future, promise rejections that are not handled will 10 | // terminate the Node.js process with a non-zero exit code. 11 | process.on('unhandledRejection', err => { 12 | throw err; 13 | }); 14 | 15 | // Ensure environment variables are read. 16 | require('../config/env'); 17 | 18 | const jest = require('jest'); 19 | const argv = process.argv.slice(2); 20 | 21 | // Watch unless on CI or in coverage mode 22 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 23 | argv.push('--watch'); 24 | } 25 | 26 | 27 | jest.run(argv); 28 | -------------------------------------------------------------------------------- /src/screens/Home/Home.css: -------------------------------------------------------------------------------- 1 | .root { 2 | height: 100%; 3 | background-color: var(--color-greyLightest); 4 | } 5 | 6 | .iconContainer { 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | font-size: var(--fontsize-large-iii); 11 | width: 100%; 12 | height: 100%; 13 | flex-direction: column; 14 | } 15 | 16 | .icon { 17 | font-size: 4rem; 18 | } 19 | 20 | .welcome { 21 | margin-top: 0; 22 | text-align: center; 23 | font-size: var(--fontsize-large-iv); 24 | margin-bottom: var(--size-regular); 25 | } 26 | 27 | .strapline { 28 | text-align: center; 29 | font-size: var(--fontsize-regular); 30 | margin-top: 0; 31 | margin-bottom: 2rem; 32 | } 33 | 34 | @media(min-width: 40rem) { 35 | .welcome { 36 | font-size: var(--fontsize-large-v); 37 | } 38 | 39 | .strapline { 40 | margin-bottom: 4rem; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Highlight/Highlight.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import replace from 'string-replace-to-array'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import css from './Highlight.css'; 6 | 7 | const Highlight = ({ highlight, caseSensitive, text, ...rest }) => ( 8 |
9 | { highlight 10 | ? replace( 11 | text, 12 | new RegExp( 13 | (highlight || '').replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'), 14 | caseSensitive ? 'g' : 'gi', 15 | ), 16 | (tag, index) => { tag }, 17 | ) 18 | : text } 19 |
20 | ); 21 | 22 | 23 | Highlight.propTypes = { 24 | highlight: PropTypes.string, 25 | text: PropTypes.string, 26 | caseSensitive: PropTypes.bool, 27 | }; 28 | 29 | Highlight.defaultProps = { 30 | caseSensitive: false, 31 | }; 32 | 33 | export default Highlight; 34 | -------------------------------------------------------------------------------- /src/components/TermCard/TermCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | import cx from 'classnames'; 5 | 6 | import cardCss from '@appearhere/bloom/components/Cards/Card/Card.css'; 7 | 8 | import Highlight from '../Highlight/Highlight'; 9 | 10 | import css from './TermCard.css'; 11 | 12 | const TermCard = ({ term, highlight, className }) => ( 13 | 17 | 18 |
19 | { term.department } 20 |
21 | 22 | ); 23 | 24 | TermCard.propTypes = { 25 | term: PropTypes.shape({}).isRequired, 26 | highlight: PropTypes.string, 27 | className: PropTypes.string, 28 | }; 29 | 30 | export default TermCard; 31 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | 18 | // In tests, polyfill requestAnimationFrame since jsdom doesn't provide it yet. 19 | // We don't polyfill it in the browser--this is user's responsibility. 20 | if (process.env.NODE_ENV === 'test') { 21 | require('raf').polyfill(global); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Header/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Combined Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/Search/__snapshots__/Search.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Search component renders correctly 1`] = ` 4 |
8 |
11 | 20 |
23 | 35 | 38 |
39 |
40 |
41 | `; 42 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Route } from 'react-router-dom'; 3 | import GA from 'react-ga'; 4 | 5 | import ScrollToTop from './components/ScrollToTop/ScrollToTop'; 6 | import Header from './components/Header/Header'; 7 | 8 | import Home from './screens/Home/Home'; 9 | import Term from './screens/Term/Term'; 10 | 11 | import GoogleAnalytics from './utils/GoogleAnalytics'; 12 | 13 | import css from './App.css'; 14 | 15 | GA.initialize(process.env.REACT_APP_GA_TRACKING_ID, { 16 | debug: process.env.NODE_ENV === 'development', 17 | }); 18 | 19 | const App = () => ( 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 |
30 | ); 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /src/sampleTerms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Aardvark", 4 | "definition": "A large, nocturnal, burrowing mammal, Orycteropus afer, of central and southern Africa, feeding on ants and termites and having a long, extensile tongue, strong claws, and long ears.", 5 | "department": "Animals", 6 | "where": "The Southern part of Africa is where the Aardvark is found in the wild. They are found South of the Sahara where it is very hot and dry. They dig tunnels and spend most of the daylight hours under the ground.", 7 | "what": "A nocturnal feeder, it subsists on ants and termites, which it will dig out of their hills using its sharp claws and powerful legs.", 8 | "when": "Based on fossils, Bryan Patterson has concluded that early relatives of the aardvark appeared in Africa around the end of the Paleocene.", 9 | "looker": "", 10 | "expected_boundary": "The aardvark is known to live in small family groups or as a solitary creature.", 11 | "hint": "If food is scarce though they will become aggressive to protect what they do find." 12 | } 13 | ] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2013-present, Facebook, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/TermList/TermList.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: grid; 3 | grid-gap: var(--size-regular); 4 | min-height: 0; 5 | min-width: 0; 6 | margin-bottom: var(--size-regular); 7 | grid-template-columns: repeat(1, 1fr); 8 | max-width: 90rem; 9 | margin-left: auto; 10 | margin-right: auto; 11 | } 12 | 13 | .h2 { 14 | display: none 15 | } 16 | 17 | .item { 18 | overflow: hidden; 19 | min-width: 0; 20 | min-height: 0; 21 | } 22 | 23 | @media(min-width: 22rem) { 24 | .container { 25 | grid-template-columns: repeat(2, 1fr); 26 | } 27 | } 28 | 29 | @media(min-width: 61rem) { 30 | .container { 31 | grid-template-columns: repeat(4, 1fr); 32 | } 33 | 34 | .h2 { 35 | margin: 0; 36 | display: block; 37 | font-size: var(--fontsize-large-i); 38 | font-weight: var(--fontweight-demi); 39 | } 40 | 41 | .h2:after { 42 | margin-top: var(--size-regular); 43 | margin-bottom: var(--size-regular); 44 | background-color: var(--color-black); 45 | height: 1px; 46 | bottom: 0; 47 | content: ''; 48 | display: block; 49 | left: 0; 50 | vertical-align: middle; 51 | width: 100%; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/screens/Term/__snapshots__/Term.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Term component renders correctly 1`] = ` 4 |
7 |
10 |

13 |

14 |
17 |
20 |

23 | What 24 |

25 |
26 |
29 |

32 | When 33 |

34 |
35 |
38 |

41 | Where 42 |

43 |
44 |
47 |

50 | Expected Boundary 51 |

52 |
53 |
56 |

59 | Hint 60 |

61 |
62 |
63 |
64 | `; 65 | -------------------------------------------------------------------------------- /config/eslint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'airbnb', 3 | parser: 'babel-eslint', 4 | ecmaFeatures: { 5 | classes: true, 6 | jsx: true, 7 | }, 8 | env: { 9 | jest: true, 10 | }, 11 | plugins: [ 12 | 'flowtype', 13 | 'jsx-a11y', 14 | 'react', 15 | ], 16 | parserOptions: { 17 | ecmaFeatures: { 18 | experimentalObjectRestSpread: true, 19 | }, 20 | }, 21 | globals: { 22 | document: true, 23 | window: true, 24 | navigator: true, 25 | }, 26 | rules: { 27 | 'no-unused-vars': [2, { 28 | vars: 'all', 29 | args: 'after-used', 30 | varsIgnorePattern: '^_', 31 | argsIgnorePattern: '^_', 32 | }], 33 | // https://github.com/yannickcr/eslint-plugin-react/issues/621 34 | 'react/prop-types': ['warn', { 35 | ignore: ['children'], 36 | }], 37 | 'react/require-extension': 'off', 38 | 'react/jsx-filename-extension': 'off', 39 | 'import/no-extraneous-dependencies': ['error', { 40 | devDependencies: true, 41 | optionalDependencies: false, 42 | peerDependencies: false, 43 | }], 44 | 'react/no-unused-prop-types': ['error', { 45 | skipShapeProps: true, 46 | }], 47 | 'react/forbid-prop-types': 'off', 48 | 'jsx-a11y/no-static-element-interactions': 'warn', 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /src/components/TermCard/TermCard.css: -------------------------------------------------------------------------------- 1 | .root { 2 | padding: var(--size-lg-i); 3 | position: relative; 4 | background-color: var(--color-white); 5 | transition-duration: 200ms; 6 | transition-property: transform; 7 | transition-timing-function: cubic-bezier(0.65, 0.05, 0.36,1); 8 | } 9 | 10 | .root:before { 11 | content: ""; 12 | display: block; 13 | padding-top: 50%; 14 | float: left; 15 | } 16 | 17 | .root > a { 18 | text-decoration: none; 19 | } 20 | 21 | .root:hover { 22 | transform: scale(0.9); 23 | } 24 | 25 | .name { 26 | font-size: var(--fontsize-regular); 27 | font-weight: var(--fontweight-demi); 28 | color: var(--color-black); 29 | } 30 | 31 | .department { 32 | position: absolute; 33 | bottom: var(--size-lg-i); 34 | font-size: var(--fontsize-small-ii); 35 | font-weight: var(--fontweight-regular); 36 | color: var(--color-black); 37 | text-transform: uppercase; 38 | } 39 | 40 | @media(min-width: 22rem) { 41 | .root:before { 42 | padding-top: 100%; 43 | } 44 | } 45 | 46 | @media(min-width: 40rem) { 47 | .name { 48 | font-size: var(--fontsize-large-ii); 49 | } 50 | 51 | .department { 52 | font-size: var(--fontsize-small-i); 53 | } 54 | 55 | .root { 56 | padding: calc(var(--size-lg-i) * 2); 57 | } 58 | 59 | .root:before { 60 | padding-top: 100%; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Search/Search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withRouter } from 'react-router'; 4 | 5 | import IconInput from '@appearhere/bloom/components/Form/IconInput/IconInput'; 6 | 7 | import css from './Search.css'; 8 | 9 | class Search extends Component { 10 | static propTypes = { 11 | match: PropTypes.shape({}), 12 | location: PropTypes.shape({}), 13 | history: PropTypes.shape({}), 14 | }; 15 | 16 | handleInputChange = (e) => { 17 | const { value } = e.target; 18 | 19 | this.updateURL(value); 20 | }; 21 | 22 | checkLocation = () => { 23 | const { match, history } = this.props; 24 | 25 | if (match.params.department) history.push('/'); 26 | }; 27 | 28 | updateURL = (query) => { 29 | const { location, history } = this.props; 30 | 31 | if (query) { 32 | history.replace(`${location.pathname}?q=${query}`); 33 | } else { 34 | history.replace(`${location.pathname}`); 35 | } 36 | }; 37 | 38 | render() { 39 | const { location } = this.props; 40 | const value = location.search.substr(3); 41 | 42 | return ( 43 | // eslint-disable-next-line 44 |
45 | 54 |
55 | ); 56 | } 57 | } 58 | 59 | export default withRouter(Search); 60 | -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | import cx from 'classnames'; 5 | 6 | import StickyNode from '@appearhere/bloom/components/StickyNode/StickyNode'; 7 | import BtnContainer from '@appearhere/bloom/components/BtnContainer/BtnContainer'; 8 | import Icon from '@appearhere/bloom/components/Icon/Icon'; 9 | 10 | import Search from '../Search/Search'; 11 | import { departments } from '../../utils/terms'; 12 | 13 | import css from './Header.css'; 14 | 15 | export default class Header extends PureComponent { 16 | static propTypes = { 17 | match: PropTypes.shape({}).isRequired, 18 | history: PropTypes.shape({}).isRequired, 19 | }; 20 | 21 | handleItemClick = (e) => { 22 | const { history } = this.props; 23 | const { name } = e.target; 24 | 25 | history.push(`/${name}`); 26 | }; 27 | 28 | render() { 29 | const { match } = this.props; 30 | 31 | return ( 32 | 33 |
34 | 35 | 36 | 37 | 38 |
39 | { departments.map(department => ( 40 | 48 | { department } 49 | 50 | )) } 51 |
52 | 53 |
54 |
55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | Lingo 24 | 25 | 26 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/screens/Term/Term.css: -------------------------------------------------------------------------------- 1 | .root { 2 | background-color: var(--color-white); 3 | } 4 | 5 | .top:after { 6 | margin-top: calc(var(--size-lg-i) + var(--size-sm-ii)); 7 | margin-bottom: calc(var(--size-lg-i) + var(--size-sm-ii)); 8 | background-color: var(--color-greyLighter); 9 | height: 1px; 10 | bottom: 0; 11 | content: ""; 12 | display: block; 13 | left: 0; 14 | vertical-align: middle; 15 | width: 100%; 16 | } 17 | 18 | .bottom { 19 | display: grid; 20 | grid-gap: calc(var(--size-lg-i) + var(--size-sm-ii)); 21 | min-height: 0; 22 | min-width: 0; 23 | } 24 | 25 | .section { 26 | overflow: hidden; 27 | min-width: 0; 28 | min-height: 0; 29 | } 30 | 31 | .h1 { 32 | margin-top: 0; 33 | margin-bottom: 0; 34 | font-weight: var(--fontweight-demi); 35 | font-size: var(--fontsize-large-iv); 36 | } 37 | 38 | .h2 { 39 | text-transform: uppercase; 40 | margin-top: 0; 41 | margin-bottom: 0; 42 | font-weight: var(--fontweight-demi); 43 | font-size: var(--fontsize-large-i); 44 | white-space: nowrap; 45 | } 46 | 47 | .definition p { 48 | font-size: var(--fontsize-large-i); 49 | margin: 0; 50 | margin-top: calc(var(--size-lg-i) + var(--size-sm-ii)); 51 | font-weight: var(--fontweight-demi); 52 | max-width: 43.75rem; 53 | } 54 | 55 | .body p { 56 | font-weight: var(--fontweight-regular); 57 | font-size: var(--fontsize-regular); 58 | } 59 | 60 | .linkContainer { 61 | margin-top: 1em; 62 | } 63 | 64 | .link { 65 | word-wrap: break-word; 66 | color: var(--color-gold); 67 | text-decoration: underline; 68 | } 69 | 70 | @media(min-width: 36.25rem) { 71 | .top:after { 72 | margin-top: var(--size-lg-iii); 73 | margin-bottom: var(--size-lg-iii); 74 | } 75 | 76 | .bottom { 77 | grid-template-columns: repeat(2, 1fr); 78 | grid-gap: var(--size-lg-iii); 79 | } 80 | 81 | .definition p { 82 | margin-top: var(--size-lg-iii); 83 | } 84 | } 85 | 86 | @media(min-width: 48.75rem) { 87 | .bottom { 88 | grid-template-columns: repeat(3, 1fr); 89 | } 90 | } 91 | 92 | @media(min-width: 70rem) { 93 | .root { 94 | padding-left: 12rem; 95 | padding-right: 12rem; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/screens/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Fuse from 'fuse.js'; 4 | import qs from 'query-string'; 5 | import flow from 'lodash/fp/flow'; 6 | import sortBy from 'lodash/fp/sortBy'; 7 | import groupBy from 'lodash/fp/groupBy'; 8 | import cx from 'classnames'; 9 | 10 | import ValueIconNoBull from '@appearhere/bloom/components/ValueIcons/ValueIconNoBull'; 11 | 12 | import TermList from '../../components/TermList/TermList'; 13 | 14 | import terms from '../../utils/terms'; 15 | 16 | import sharedCss from '../../shared.css'; 17 | import css from './Home.css'; 18 | 19 | const fuse = new Fuse(terms, { 20 | shouldSort: true, 21 | threshold: 0.2, 22 | location: 0, 23 | distance: 100, 24 | maxPatternLength: 32, 25 | minMatchCharLength: 1, 26 | keys: ['name'], 27 | }); 28 | 29 | const formatResults = flow(sortBy('name'), groupBy('department')); 30 | 31 | const Home = ({ match, location }) => { 32 | const { q: query } = qs.parse(location.search); 33 | const currentDepartment = match.params.department; 34 | const searchResults = query ? fuse.search(query) : terms; 35 | const results = currentDepartment ? formatResults(searchResults) : searchResults; 36 | 37 | return ( 38 |
39 |

Here's the Lingo

40 | 41 |

42 | Lingo is here to help you understand the terms we all use. 43 |

44 | 45 | { currentDepartment 46 | ? 50 | : 54 | } 55 | { query && !results.length && 56 |
57 | We couldn't find what you were looking for. 58 | 59 |
60 | } 61 |
62 | ); 63 | }; 64 | 65 | 66 | Home.propTypes = { 67 | match: PropTypes.shape({}), 68 | location: PropTypes.shape({}), 69 | }; 70 | 71 | export default Home; 72 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const url = require('url'); 6 | 7 | // Make sure any symlinks in the project folder are resolved: 8 | // https://github.com/facebookincubator/create-react-app/issues/637 9 | const appDirectory = fs.realpathSync(process.cwd()); 10 | const resolveApp = relativePath => path.resolve(appDirectory, relativePath); 11 | 12 | const envPublicUrl = process.env.PUBLIC_URL; 13 | 14 | function ensureSlash(path, needsSlash) { 15 | const hasSlash = path.endsWith('/'); 16 | if (hasSlash && !needsSlash) { 17 | return path.substr(path, path.length - 1); 18 | } else if (!hasSlash && needsSlash) { 19 | return `${path}/`; 20 | } else { 21 | return path; 22 | } 23 | } 24 | 25 | const getPublicUrl = appPackageJson => 26 | envPublicUrl || require(appPackageJson).homepage; 27 | 28 | // We use `PUBLIC_URL` environment variable or "homepage" field to infer 29 | // "public path" at which the app is served. 30 | // Webpack needs to know it to put the right